Merge pull request #394 from AdenKoperczak/update_tdwr_products

Update TDWR products
This commit is contained in:
Dan Paulat 2025-03-25 17:25:39 -05:00 committed by GitHub
commit e25f56fcae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 210 additions and 33 deletions

View file

@ -230,6 +230,7 @@ public:
const std::string radarId_;
bool initialized_;
bool level3ProductsInitialized_;
bool level3AvailabilityReady_ {false};
std::shared_ptr<config::RadarSite> radarSite_;
std::size_t cacheLimit_ {6u};
@ -428,9 +429,16 @@ const scwx::util::time_zone* RadarProductManager::default_time_zone() const
}
}
bool RadarProductManager::is_tdwr() const
{
return p->radarSite_->type() == "tdwr";
}
float RadarProductManager::gate_size() const
{
return (p->radarSite_->type() == "tdwr") ? 150.0f : 250.0f;
// tdwr is 150 meter per gate, wsr88d is 250 meter per gate
// NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers)
return (is_tdwr()) ? 150.0f : 250.0f;
}
std::string RadarProductManager::radar_id() const
@ -454,6 +462,12 @@ void RadarProductManager::Initialize()
logger_->debug("Initialize()");
if (is_tdwr())
{
p->initialized_ = true;
return;
}
boost::timer::cpu_timer timer;
// Calculate half degree azimuth coordinates
@ -1572,6 +1586,12 @@ void RadarProductManager::UpdateAvailableProducts()
if (p->level3ProductsInitialized_)
{
if (p->level3AvailabilityReady_)
{
// Multiple maps may use the same manager, so this ensures that all get
// notified of the change
Q_EMIT Level3ProductsChanged();
}
return;
}
@ -1647,6 +1667,7 @@ void RadarProductManagerImpl::UpdateAvailableProductsSync()
}
}
level3AvailabilityReady_ = true;
Q_EMIT self_->Level3ProductsChanged();
}

View file

@ -44,6 +44,7 @@ public:
[[nodiscard]] const std::vector<float>&
coordinates(common::RadialSize radialSize, bool smoothingEnabled) const;
[[nodiscard]] const scwx::util::time_zone* default_time_zone() const;
[[nodiscard]] bool is_tdwr() const;
[[nodiscard]] float gate_size() const;
[[nodiscard]] std::string radar_id() const;
[[nodiscard]] std::shared_ptr<config::RadarSite> radar_site() const;

View file

@ -31,6 +31,7 @@
#include <scwx/util/logger.hpp>
#include <scwx/util/time.hpp>
#include <algorithm>
#include <set>
#include <backends/imgui_impl_opengl3.h>
@ -178,9 +179,11 @@ public:
void SelectNearestRadarSite(double latitude,
double longitude,
std::optional<std::string> type);
void SetRadarSite(const std::string& radarSite);
void SetRadarSite(const std::string& radarSite,
bool checkProductAvailability = false);
void UpdateLoadedStyle();
bool UpdateStoredMapParameters();
void CheckLevel3Availability();
std::string FindMapSymbologyLayer();
@ -268,6 +271,10 @@ public:
std::set<types::Hotkey> activeHotkeys_ {};
std::chrono::system_clock::time_point prevHotkeyTime_ {};
bool productAvailabilityCheckNeeded_ {false};
bool productAvailabilityUpdated_ {false};
bool productAvailabilityProductSelected_ {false};
public slots:
void Update();
};
@ -429,6 +436,14 @@ void MapWidgetImpl::ConnectSignals()
&manager::HotkeyManager::HotkeyReleased,
this,
&MapWidgetImpl::HandleHotkeyReleased);
connect(widget_,
&MapWidget::RadarSiteUpdated,
widget_,
[this](const std::shared_ptr<config::RadarSite>&)
{
productAvailabilityProductSelected_ = true;
CheckLevel3Availability();
});
}
void MapWidgetImpl::HandleHotkeyPressed(types::Hotkey hotkey, bool isAutoRepeat)
@ -913,7 +928,7 @@ void MapWidget::SelectRadarSite(std::shared_ptr<config::RadarSite> radarSite,
p->map_->setCoordinate(
{radarSite->latitude(), radarSite->longitude()});
}
p->SetRadarSite(radarSite->id());
p->SetRadarSite(radarSite->id(), true);
p->Update();
// Select products from new site
@ -1772,7 +1787,12 @@ void MapWidgetImpl::RadarProductManagerConnect()
connect(radarProductManager_.get(),
&manager::RadarProductManager::Level3ProductsChanged,
this,
[this]() { Q_EMIT widget_->Level3ProductsChanged(); });
[this]()
{
productAvailabilityUpdated_ = true;
CheckLevel3Availability();
Q_EMIT widget_->Level3ProductsChanged();
});
connect(
radarProductManager_.get(),
@ -1990,7 +2010,8 @@ void MapWidgetImpl::SelectNearestRadarSite(double latitude,
}
}
void MapWidgetImpl::SetRadarSite(const std::string& radarSite)
void MapWidgetImpl::SetRadarSite(const std::string& radarSite,
bool checkProductAvailability)
{
// Check if radar site has changed
if (radarProductManager_ == nullptr ||
@ -2009,6 +2030,12 @@ void MapWidgetImpl::SetRadarSite(const std::string& radarSite)
// Connect signals to new RadarProductManager
RadarProductManagerConnect();
// Once the available products are loaded, check to make sure the current
// one is available
productAvailabilityCheckNeeded_ = checkProductAvailability;
productAvailabilityUpdated_ = false;
productAvailabilityProductSelected_ = false;
radarProductManager_->UpdateAvailableProducts();
}
}
@ -2053,6 +2080,83 @@ bool MapWidgetImpl::UpdateStoredMapParameters()
return changed;
}
void MapWidgetImpl::CheckLevel3Availability()
{
/*
* productAvailabilityCheckNeeded_ Only do this when it is indicated that it
* is needed (mostly on radar site change). This is mainly to avoid potential
* recursion with SelectRadarProduct calls.
*
* productAvailabilityUpdated_ Only update once the product availability
* has been updated
*
* productAvailabilityProductSelected_ Only update once the radar site is
* fully selected, including the current product
*/
if (!(productAvailabilityCheckNeeded_ && productAvailabilityUpdated_ &&
productAvailabilityProductSelected_))
{
return;
}
productAvailabilityCheckNeeded_ = false;
// Only do this for level3 products
if (widget_->GetRadarProductGroup() != common::RadarProductGroup::Level3)
{
return;
}
const common::Level3ProductCategoryMap& categoryMap =
widget_->GetAvailableLevel3Categories();
const std::string& productTilt = context_->radar_product();
const std::string& productName =
common::GetLevel3ProductByAwipsId(productTilt);
const common::Level3ProductCategory productCategory =
common::GetLevel3CategoryByProduct(productName);
if (productCategory == common::Level3ProductCategory::Unknown)
{
return;
}
const auto& availableProductsIt = categoryMap.find(productCategory);
// Has no products in this category, do not change categories
if (availableProductsIt == categoryMap.cend())
{
return;
}
const auto& availableProducts = availableProductsIt->second;
const auto& availableTiltsIt = availableProducts.find(productName);
// Does not have the same product, but has others in the same category.
// Switch to the default product and tilt in this category.
if (availableTiltsIt == availableProducts.cend())
{
widget_->SelectRadarProduct(
common::RadarProductGroup::Level3,
common::GetLevel3CategoryDefaultProduct(productCategory, categoryMap),
0,
widget_->GetSelectedTime());
return;
}
const auto& availableTilts = availableTiltsIt->second;
const auto& tilt = std::ranges::find_if(
availableTilts,
[productTilt](const std::string& tilt) { return productTilt == tilt; });
// Tilt is not available, set it to first tilt
if (tilt == availableTilts.cend() && availableTilts.size() > 0)
{
widget_->SelectRadarProduct(common::RadarProductGroup::Level3,
availableTilts[0],
0,
widget_->GetSelectedTime());
return;
}
// Tilt is available, no change needed
}
} // namespace map
} // namespace qt
} // namespace scwx

View file

@ -60,7 +60,9 @@ public:
categoryButtons_ {},
productTiltMap_ {},
awipsProductMap_ {},
awipsProductMutex_ {}
awipsProductMutex_ {},
categoryMap_ {},
categoryMapMutex_ {}
{
layout_->setContentsMargins(0, 0, 0, 0);
layout_->addWidget(productsWidget_);
@ -183,6 +185,9 @@ public:
std::unordered_map<QAction*, std::string> awipsProductMap_;
std::shared_mutex awipsProductMutex_;
common::Level3ProductCategoryMap categoryMap_;
std::shared_mutex categoryMapMutex_;
std::string currentAwipsId_ {};
QAction* currentProductTiltAction_ {nullptr};
@ -322,9 +327,11 @@ void Level3ProductsWidgetImpl::SelectProductCategory(
{
UpdateCategorySelection(category);
const std::shared_lock lock {categoryMapMutex_};
Q_EMIT self_->RadarProductSelected(
common::RadarProductGroup::Level3,
common::GetLevel3CategoryDefaultProduct(category),
common::GetLevel3CategoryDefaultProduct(category, categoryMap_),
0);
}
@ -333,6 +340,12 @@ void Level3ProductsWidget::UpdateAvailableProducts(
{
logger_->trace("UpdateAvailableProducts()");
// Save the category map
{
const std::unique_lock lock {p->categoryMapMutex_};
p->categoryMap_ = updatedCategoryMap;
}
// Iterate through each category tool button
std::for_each(
p->categoryButtons_.cbegin(),

View file

@ -39,13 +39,13 @@ public:
vcp_ {},
sweepTime_ {}
{
coordinates_.resize(kMaxCoordinates_);
}
~Impl() { threadPool_.join(); };
void ComputeCoordinates(
const std::shared_ptr<wsr88d::rpg::GenericRadialDataPacket>& radialData,
bool smoothingEnabled);
bool smoothingEnabled,
float gateSize);
[[nodiscard]] inline std::uint8_t
RemapDataMoment(std::uint8_t dataMoment) const;
@ -269,17 +269,24 @@ void Level3RadialView::ComputeSweep()
}
common::RadialSize radialSize;
if (radials == common::MAX_0_5_DEGREE_RADIALS)
if (radarProductManager->is_tdwr())
{
radialSize = common::RadialSize::_0_5Degree;
}
else if (radials == common::MAX_1_DEGREE_RADIALS)
{
radialSize = common::RadialSize::_1Degree;
radialSize = common::RadialSize::NonStandard;
}
else
{
radialSize = common::RadialSize::NonStandard;
if (radials == common::MAX_0_5_DEGREE_RADIALS)
{
radialSize = common::RadialSize::_0_5Degree;
}
else if (radials == common::MAX_1_DEGREE_RADIALS)
{
radialSize = common::RadialSize::_1Degree;
}
else
{
radialSize = common::RadialSize::NonStandard;
}
}
const std::vector<float>& coordinates =
@ -323,11 +330,21 @@ void Level3RadialView::ComputeSweep()
// Compute threshold at which to display an individual bin
const uint16_t snrThreshold = descriptionBlock->threshold();
// Compute gate interval
const std::uint16_t dataMomentInterval =
descriptionBlock->x_resolution_raw();
// Get the gate length in meters. Use dataMomentInterval for NonStandard to
// avoid generating >1 base gates per bin.
const float gateLength = radialSize == common::RadialSize::NonStandard ?
static_cast<float>(dataMomentInterval) :
radarProductManager->gate_size();
// Determine which radial to start at
std::uint16_t startRadial;
if (radialSize == common::RadialSize::NonStandard)
{
p->ComputeCoordinates(radialData, smoothingEnabled);
p->ComputeCoordinates(radialData, smoothingEnabled, gateLength);
startRadial = 0;
}
else
@ -337,15 +354,9 @@ void Level3RadialView::ComputeSweep()
startRadial = std::lroundf(startAngle * radialMultiplier);
}
// Compute gate interval
const std::uint16_t dataMomentInterval =
descriptionBlock->x_resolution_raw();
// Compute gate size (number of base gates per bin)
const std::uint16_t gateSize = std::max<std::uint16_t>(
1,
dataMomentInterval /
static_cast<std::uint16_t>(radarProductManager->gate_size()));
1, dataMomentInterval / static_cast<std::uint16_t>(gateLength));
// Compute gate range [startGate, endGate)
std::uint16_t startGate = 0;
@ -526,7 +537,8 @@ Level3RadialView::Impl::RemapDataMoment(std::uint8_t dataMoment) const
void Level3RadialView::Impl::ComputeCoordinates(
const std::shared_ptr<wsr88d::rpg::GenericRadialDataPacket>& radialData,
bool smoothingEnabled)
bool smoothingEnabled,
float gateSize)
{
logger_->debug("ComputeCoordinates()");
@ -537,13 +549,14 @@ void Level3RadialView::Impl::ComputeCoordinates(
auto radarProductManager = self_->radar_product_manager();
auto radarSite = radarProductManager->radar_site();
const float gateSize = radarProductManager->gate_size();
const double radarLatitude = radarSite->latitude();
const double radarLongitude = radarSite->longitude();
// Calculate azimuth coordinates
timer.start();
coordinates_.resize(kMaxCoordinates_);
const std::uint16_t numRadials = radialData->number_of_radials();
const std::uint16_t numRangeBins = radialData->number_of_range_bins();
@ -583,6 +596,10 @@ void Level3RadialView::Impl::ComputeCoordinates(
const float range =
(static_cast<float>(gate) + gateRangeOffset) * gateSize;
const std::size_t offset = static_cast<size_t>(radialGate) * 2;
if (offset + 1 >= coordinates_.size())
{
return;
}
double latitude = 0.0;
double longitude = 0.0;

View file

@ -73,8 +73,9 @@ Level2Product GetLevel2Product(const std::string& name);
const std::string& GetLevel3CategoryName(Level3ProductCategory category);
const std::string& GetLevel3CategoryDescription(Level3ProductCategory category);
const std::string&
GetLevel3CategoryDefaultProduct(Level3ProductCategory category);
std::string
GetLevel3CategoryDefaultProduct(Level3ProductCategory category,
const Level3ProductCategoryMap& categoryMap);
Level3ProductCategory GetLevel3Category(const std::string& categoryName);
Level3ProductCategory
GetLevel3CategoryByProduct(const std::string& productName);

View file

@ -49,7 +49,7 @@ static const std::unordered_map<int, std::string> level3ProductCodeMap_ {
{153, "SDR"}, {154, "SDV"}, {159, "DZD"}, {161, "DCC"}, {163, "DKD"},
{165, "DHC"}, {166, "ML"}, {169, "OHA"}, {170, "DAA"}, {172, "DTA"},
{173, "DUA"}, {174, "DOD"}, {175, "DSD"}, {177, "HHC"}, {180, "TDR"},
{182, "TDV"}};
{182, "TDV"}, {186, "TZL"}};
static const std::unordered_map<std::string, std::string>
level3ProductDescription_ {
@ -84,6 +84,7 @@ static const std::unordered_map<std::string, std::string>
{"HHC", "Hybrid Hydrometeor Classification"},
{"TDR", "Digital Reflectivity"},
{"TDV", "Digital Velocity"},
{"TZL", "Long Range Reflectivity"},
{"?", "Unknown"}};
static const std::unordered_map<std::string, std::vector<std::string>>
@ -91,6 +92,7 @@ static const std::unordered_map<std::string, std::vector<std::string>>
// Reflectivity
{"SDR", {"NXB", "NYB", "NZB", "N0B", "NAB", "N1B", "NBB", "N2B", "N3B"}},
{"DR", {"NXQ", "NYQ", "NZQ", "N0Q", "NAQ", "N1Q", "NBQ", "N2Q", "N3Q"}},
{"TZL", {"TZL"}},
{"TDR", {"TZ0", "TZ1", "TZ2"}},
{"NCR", {"NCR"}},
@ -184,7 +186,7 @@ static const std::unordered_map<Level3ProductCategory, std::string>
static const std::unordered_map<Level3ProductCategory, std::vector<std::string>>
level3CategoryProductList_ {
{Level3ProductCategory::Reflectivity, {"SDR", "DR", "TDR", "NCR"}},
{Level3ProductCategory::Reflectivity, {"SDR", "DR", "TZL", "TDR", "NCR"}},
{Level3ProductCategory::Velocity, {"SDV", "DV", "TDV"}},
{Level3ProductCategory::StormRelativeVelocity, {"SRM"}},
{Level3ProductCategory::SpectrumWidth, {"SW"}},
@ -296,9 +298,27 @@ const std::string& GetLevel3CategoryDescription(Level3ProductCategory category)
return level3CategoryDescription_.at(category);
}
const std::string&
GetLevel3CategoryDefaultProduct(Level3ProductCategory category)
std::string
GetLevel3CategoryDefaultProduct(Level3ProductCategory category,
const Level3ProductCategoryMap& categoryMap)
{
const auto& productsIt = categoryMap.find(category);
if (productsIt == categoryMap.cend())
{
return level3CategoryDefaultAwipsId_.at(category);
}
const auto& productsSiteHas = productsIt->second;
const auto& productList = level3CategoryProductList_.at(category);
for (auto& product : productList)
{
const auto& tiltsIt = productsSiteHas.find(product);
if (tiltsIt != productsSiteHas.cend() && tiltsIt->second.size() > 0)
{
return tiltsIt->second[0];
}
}
return level3CategoryDefaultAwipsId_.at(category);
}

View file

@ -57,7 +57,7 @@ static const std::unordered_map<int, unsigned int> rangeMap_ {
{163, 300}, {165, 300}, {166, 230}, {167, 300}, {168, 300}, {169, 230},
{170, 230}, {171, 230}, {172, 230}, {173, 230}, {174, 230}, {175, 230},
{176, 230}, {177, 230}, {178, 300}, {179, 300}, {180, 89}, {181, 89},
{182, 89}, {184, 89}, {186, 412}, {193, 460}, {195, 460}, {196, 50}};
{182, 89}, {184, 89}, {186, 417}, {193, 460}, {195, 460}, {196, 50}};
static const std::unordered_map<int, unsigned int> xResolutionMap_ {
{19, 1000}, {20, 2000}, {27, 1000}, {30, 1000}, {31, 2000}, {32, 1000},