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_; const std::string radarId_;
bool initialized_; bool initialized_;
bool level3ProductsInitialized_; bool level3ProductsInitialized_;
bool level3AvailabilityReady_ {false};
std::shared_ptr<config::RadarSite> radarSite_; std::shared_ptr<config::RadarSite> radarSite_;
std::size_t cacheLimit_ {6u}; 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 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 std::string RadarProductManager::radar_id() const
@ -454,6 +462,12 @@ void RadarProductManager::Initialize()
logger_->debug("Initialize()"); logger_->debug("Initialize()");
if (is_tdwr())
{
p->initialized_ = true;
return;
}
boost::timer::cpu_timer timer; boost::timer::cpu_timer timer;
// Calculate half degree azimuth coordinates // Calculate half degree azimuth coordinates
@ -1572,6 +1586,12 @@ void RadarProductManager::UpdateAvailableProducts()
if (p->level3ProductsInitialized_) 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; return;
} }
@ -1647,6 +1667,7 @@ void RadarProductManagerImpl::UpdateAvailableProductsSync()
} }
} }
level3AvailabilityReady_ = true;
Q_EMIT self_->Level3ProductsChanged(); Q_EMIT self_->Level3ProductsChanged();
} }

View file

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

View file

@ -31,6 +31,7 @@
#include <scwx/util/logger.hpp> #include <scwx/util/logger.hpp>
#include <scwx/util/time.hpp> #include <scwx/util/time.hpp>
#include <algorithm>
#include <set> #include <set>
#include <backends/imgui_impl_opengl3.h> #include <backends/imgui_impl_opengl3.h>
@ -178,9 +179,11 @@ public:
void SelectNearestRadarSite(double latitude, void SelectNearestRadarSite(double latitude,
double longitude, double longitude,
std::optional<std::string> type); std::optional<std::string> type);
void SetRadarSite(const std::string& radarSite); void SetRadarSite(const std::string& radarSite,
bool checkProductAvailability = false);
void UpdateLoadedStyle(); void UpdateLoadedStyle();
bool UpdateStoredMapParameters(); bool UpdateStoredMapParameters();
void CheckLevel3Availability();
std::string FindMapSymbologyLayer(); std::string FindMapSymbologyLayer();
@ -268,6 +271,10 @@ public:
std::set<types::Hotkey> activeHotkeys_ {}; std::set<types::Hotkey> activeHotkeys_ {};
std::chrono::system_clock::time_point prevHotkeyTime_ {}; std::chrono::system_clock::time_point prevHotkeyTime_ {};
bool productAvailabilityCheckNeeded_ {false};
bool productAvailabilityUpdated_ {false};
bool productAvailabilityProductSelected_ {false};
public slots: public slots:
void Update(); void Update();
}; };
@ -429,6 +436,14 @@ void MapWidgetImpl::ConnectSignals()
&manager::HotkeyManager::HotkeyReleased, &manager::HotkeyManager::HotkeyReleased,
this, this,
&MapWidgetImpl::HandleHotkeyReleased); &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) void MapWidgetImpl::HandleHotkeyPressed(types::Hotkey hotkey, bool isAutoRepeat)
@ -913,7 +928,7 @@ void MapWidget::SelectRadarSite(std::shared_ptr<config::RadarSite> radarSite,
p->map_->setCoordinate( p->map_->setCoordinate(
{radarSite->latitude(), radarSite->longitude()}); {radarSite->latitude(), radarSite->longitude()});
} }
p->SetRadarSite(radarSite->id()); p->SetRadarSite(radarSite->id(), true);
p->Update(); p->Update();
// Select products from new site // Select products from new site
@ -1772,7 +1787,12 @@ void MapWidgetImpl::RadarProductManagerConnect()
connect(radarProductManager_.get(), connect(radarProductManager_.get(),
&manager::RadarProductManager::Level3ProductsChanged, &manager::RadarProductManager::Level3ProductsChanged,
this, this,
[this]() { Q_EMIT widget_->Level3ProductsChanged(); }); [this]()
{
productAvailabilityUpdated_ = true;
CheckLevel3Availability();
Q_EMIT widget_->Level3ProductsChanged();
});
connect( connect(
radarProductManager_.get(), 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 // Check if radar site has changed
if (radarProductManager_ == nullptr || if (radarProductManager_ == nullptr ||
@ -2009,6 +2030,12 @@ void MapWidgetImpl::SetRadarSite(const std::string& radarSite)
// Connect signals to new RadarProductManager // Connect signals to new RadarProductManager
RadarProductManagerConnect(); RadarProductManagerConnect();
// Once the available products are loaded, check to make sure the current
// one is available
productAvailabilityCheckNeeded_ = checkProductAvailability;
productAvailabilityUpdated_ = false;
productAvailabilityProductSelected_ = false;
radarProductManager_->UpdateAvailableProducts(); radarProductManager_->UpdateAvailableProducts();
} }
} }
@ -2053,6 +2080,83 @@ bool MapWidgetImpl::UpdateStoredMapParameters()
return changed; 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 map
} // namespace qt } // namespace qt
} // namespace scwx } // namespace scwx

View file

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

View file

@ -39,13 +39,13 @@ public:
vcp_ {}, vcp_ {},
sweepTime_ {} sweepTime_ {}
{ {
coordinates_.resize(kMaxCoordinates_);
} }
~Impl() { threadPool_.join(); }; ~Impl() { threadPool_.join(); };
void ComputeCoordinates( void ComputeCoordinates(
const std::shared_ptr<wsr88d::rpg::GenericRadialDataPacket>& radialData, const std::shared_ptr<wsr88d::rpg::GenericRadialDataPacket>& radialData,
bool smoothingEnabled); bool smoothingEnabled,
float gateSize);
[[nodiscard]] inline std::uint8_t [[nodiscard]] inline std::uint8_t
RemapDataMoment(std::uint8_t dataMoment) const; RemapDataMoment(std::uint8_t dataMoment) const;
@ -269,6 +269,12 @@ void Level3RadialView::ComputeSweep()
} }
common::RadialSize radialSize; common::RadialSize radialSize;
if (radarProductManager->is_tdwr())
{
radialSize = common::RadialSize::NonStandard;
}
else
{
if (radials == common::MAX_0_5_DEGREE_RADIALS) if (radials == common::MAX_0_5_DEGREE_RADIALS)
{ {
radialSize = common::RadialSize::_0_5Degree; radialSize = common::RadialSize::_0_5Degree;
@ -281,6 +287,7 @@ void Level3RadialView::ComputeSweep()
{ {
radialSize = common::RadialSize::NonStandard; radialSize = common::RadialSize::NonStandard;
} }
}
const std::vector<float>& coordinates = const std::vector<float>& coordinates =
(radialSize == common::RadialSize::NonStandard) ? (radialSize == common::RadialSize::NonStandard) ?
@ -323,11 +330,21 @@ void Level3RadialView::ComputeSweep()
// Compute threshold at which to display an individual bin // Compute threshold at which to display an individual bin
const uint16_t snrThreshold = descriptionBlock->threshold(); 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 // Determine which radial to start at
std::uint16_t startRadial; std::uint16_t startRadial;
if (radialSize == common::RadialSize::NonStandard) if (radialSize == common::RadialSize::NonStandard)
{ {
p->ComputeCoordinates(radialData, smoothingEnabled); p->ComputeCoordinates(radialData, smoothingEnabled, gateLength);
startRadial = 0; startRadial = 0;
} }
else else
@ -337,15 +354,9 @@ void Level3RadialView::ComputeSweep()
startRadial = std::lroundf(startAngle * radialMultiplier); 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) // Compute gate size (number of base gates per bin)
const std::uint16_t gateSize = std::max<std::uint16_t>( const std::uint16_t gateSize = std::max<std::uint16_t>(
1, 1, dataMomentInterval / static_cast<std::uint16_t>(gateLength));
dataMomentInterval /
static_cast<std::uint16_t>(radarProductManager->gate_size()));
// Compute gate range [startGate, endGate) // Compute gate range [startGate, endGate)
std::uint16_t startGate = 0; std::uint16_t startGate = 0;
@ -526,7 +537,8 @@ Level3RadialView::Impl::RemapDataMoment(std::uint8_t dataMoment) const
void Level3RadialView::Impl::ComputeCoordinates( void Level3RadialView::Impl::ComputeCoordinates(
const std::shared_ptr<wsr88d::rpg::GenericRadialDataPacket>& radialData, const std::shared_ptr<wsr88d::rpg::GenericRadialDataPacket>& radialData,
bool smoothingEnabled) bool smoothingEnabled,
float gateSize)
{ {
logger_->debug("ComputeCoordinates()"); logger_->debug("ComputeCoordinates()");
@ -537,13 +549,14 @@ void Level3RadialView::Impl::ComputeCoordinates(
auto radarProductManager = self_->radar_product_manager(); auto radarProductManager = self_->radar_product_manager();
auto radarSite = radarProductManager->radar_site(); auto radarSite = radarProductManager->radar_site();
const float gateSize = radarProductManager->gate_size();
const double radarLatitude = radarSite->latitude(); const double radarLatitude = radarSite->latitude();
const double radarLongitude = radarSite->longitude(); const double radarLongitude = radarSite->longitude();
// Calculate azimuth coordinates // Calculate azimuth coordinates
timer.start(); timer.start();
coordinates_.resize(kMaxCoordinates_);
const std::uint16_t numRadials = radialData->number_of_radials(); const std::uint16_t numRadials = radialData->number_of_radials();
const std::uint16_t numRangeBins = radialData->number_of_range_bins(); const std::uint16_t numRangeBins = radialData->number_of_range_bins();
@ -583,6 +596,10 @@ void Level3RadialView::Impl::ComputeCoordinates(
const float range = const float range =
(static_cast<float>(gate) + gateRangeOffset) * gateSize; (static_cast<float>(gate) + gateRangeOffset) * gateSize;
const std::size_t offset = static_cast<size_t>(radialGate) * 2; const std::size_t offset = static_cast<size_t>(radialGate) * 2;
if (offset + 1 >= coordinates_.size())
{
return;
}
double latitude = 0.0; double latitude = 0.0;
double longitude = 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& GetLevel3CategoryName(Level3ProductCategory category);
const std::string& GetLevel3CategoryDescription(Level3ProductCategory category); const std::string& GetLevel3CategoryDescription(Level3ProductCategory category);
const std::string& std::string
GetLevel3CategoryDefaultProduct(Level3ProductCategory category); GetLevel3CategoryDefaultProduct(Level3ProductCategory category,
const Level3ProductCategoryMap& categoryMap);
Level3ProductCategory GetLevel3Category(const std::string& categoryName); Level3ProductCategory GetLevel3Category(const std::string& categoryName);
Level3ProductCategory Level3ProductCategory
GetLevel3CategoryByProduct(const std::string& productName); 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"}, {153, "SDR"}, {154, "SDV"}, {159, "DZD"}, {161, "DCC"}, {163, "DKD"},
{165, "DHC"}, {166, "ML"}, {169, "OHA"}, {170, "DAA"}, {172, "DTA"}, {165, "DHC"}, {166, "ML"}, {169, "OHA"}, {170, "DAA"}, {172, "DTA"},
{173, "DUA"}, {174, "DOD"}, {175, "DSD"}, {177, "HHC"}, {180, "TDR"}, {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> static const std::unordered_map<std::string, std::string>
level3ProductDescription_ { level3ProductDescription_ {
@ -84,6 +84,7 @@ static const std::unordered_map<std::string, std::string>
{"HHC", "Hybrid Hydrometeor Classification"}, {"HHC", "Hybrid Hydrometeor Classification"},
{"TDR", "Digital Reflectivity"}, {"TDR", "Digital Reflectivity"},
{"TDV", "Digital Velocity"}, {"TDV", "Digital Velocity"},
{"TZL", "Long Range Reflectivity"},
{"?", "Unknown"}}; {"?", "Unknown"}};
static const std::unordered_map<std::string, std::vector<std::string>> 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 // Reflectivity
{"SDR", {"NXB", "NYB", "NZB", "N0B", "NAB", "N1B", "NBB", "N2B", "N3B"}}, {"SDR", {"NXB", "NYB", "NZB", "N0B", "NAB", "N1B", "NBB", "N2B", "N3B"}},
{"DR", {"NXQ", "NYQ", "NZQ", "N0Q", "NAQ", "N1Q", "NBQ", "N2Q", "N3Q"}}, {"DR", {"NXQ", "NYQ", "NZQ", "N0Q", "NAQ", "N1Q", "NBQ", "N2Q", "N3Q"}},
{"TZL", {"TZL"}},
{"TDR", {"TZ0", "TZ1", "TZ2"}}, {"TDR", {"TZ0", "TZ1", "TZ2"}},
{"NCR", {"NCR"}}, {"NCR", {"NCR"}},
@ -184,7 +186,7 @@ static const std::unordered_map<Level3ProductCategory, std::string>
static const std::unordered_map<Level3ProductCategory, std::vector<std::string>> static const std::unordered_map<Level3ProductCategory, std::vector<std::string>>
level3CategoryProductList_ { level3CategoryProductList_ {
{Level3ProductCategory::Reflectivity, {"SDR", "DR", "TDR", "NCR"}}, {Level3ProductCategory::Reflectivity, {"SDR", "DR", "TZL", "TDR", "NCR"}},
{Level3ProductCategory::Velocity, {"SDV", "DV", "TDV"}}, {Level3ProductCategory::Velocity, {"SDV", "DV", "TDV"}},
{Level3ProductCategory::StormRelativeVelocity, {"SRM"}}, {Level3ProductCategory::StormRelativeVelocity, {"SRM"}},
{Level3ProductCategory::SpectrumWidth, {"SW"}}, {Level3ProductCategory::SpectrumWidth, {"SW"}},
@ -296,9 +298,27 @@ const std::string& GetLevel3CategoryDescription(Level3ProductCategory category)
return level3CategoryDescription_.at(category); return level3CategoryDescription_.at(category);
} }
const std::string& std::string
GetLevel3CategoryDefaultProduct(Level3ProductCategory category) 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); 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}, {163, 300}, {165, 300}, {166, 230}, {167, 300}, {168, 300}, {169, 230},
{170, 230}, {171, 230}, {172, 230}, {173, 230}, {174, 230}, {175, 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}, {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_ { static const std::unordered_map<int, unsigned int> xResolutionMap_ {
{19, 1000}, {20, 2000}, {27, 1000}, {30, 1000}, {31, 2000}, {32, 1000}, {19, 1000}, {20, 2000}, {27, 1000}, {30, 1000}, {31, 2000}, {32, 1000},