From 900267b16ff29898541cd7354a12e7871fdb68c8 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 17 Feb 2024 23:07:25 -0600 Subject: [PATCH] Add Storm Tracking Information display to Overlay Product Layer --- .../scwx/qt/map/overlay_product_layer.cpp | 224 +++++++++++++++++- wxdata/include/scwx/wsr88d/rpg/rpg_types.hpp | 42 ++++ 2 files changed, 264 insertions(+), 2 deletions(-) diff --git a/scwx-qt/source/scwx/qt/map/overlay_product_layer.cpp b/scwx-qt/source/scwx/qt/map/overlay_product_layer.cpp index 90e85372..e8ff43e9 100644 --- a/scwx-qt/source/scwx/qt/map/overlay_product_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/overlay_product_layer.cpp @@ -1,4 +1,12 @@ #include +#include +#include +#include +#include +#include +#include +#include +#include #include namespace scwx @@ -14,13 +22,54 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class OverlayProductLayer::Impl { public: - explicit Impl(std::shared_ptr context) {} + explicit Impl(OverlayProductLayer* self, + const std::shared_ptr& context) : + self_ {self}, + linkedVectors_ {std::make_shared(context)} + { + } ~Impl() = default; + + void UpdateStormTrackingInformation(); + + static void HandleLinkedVectorPacket( + const std::shared_ptr& packet, + const common::Coordinate& center, + const std::string& hoverText, + boost::gil::rgba32f_pixel_t color, + std::shared_ptr& linkedVectors); + static void HandleScitDataPacket( + const std::shared_ptr& packet, + const common::Coordinate& center, + const std::string& stormId, + std::shared_ptr& linkedVectors); + static void + HandleStormIdPacket(const std::shared_ptr& packet, + std::string& stormId); + + OverlayProductLayer* self_; + + bool stiNeedsUpdate_ {false}; + + std::shared_ptr linkedVectors_; }; OverlayProductLayer::OverlayProductLayer(std::shared_ptr context) : - DrawLayer(context), p(std::make_unique(context)) + DrawLayer(context), p(std::make_unique(this, context)) { + auto overlayProductView = context->overlay_product_view(); + connect(overlayProductView.get(), + &view::OverlayProductView::ProductUpdated, + this, + [this](std::string product) + { + if (product == "NST") + { + p->stiNeedsUpdate_ = true; + } + }); + + AddDrawItem(p->linkedVectors_); } OverlayProductLayer::~OverlayProductLayer() = default; @@ -29,6 +78,8 @@ void OverlayProductLayer::Initialize() { logger_->debug("Initialize()"); + p->UpdateStormTrackingInformation(); + DrawLayer::Initialize(); } @@ -40,6 +91,11 @@ void OverlayProductLayer::Render( // Set OpenGL blend mode for transparency gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + if (p->stiNeedsUpdate_) + { + p->UpdateStormTrackingInformation(); + } + DrawLayer::Render(params); SCWX_GL_CHECK_ERROR(); @@ -52,6 +108,170 @@ void OverlayProductLayer::Deinitialize() DrawLayer::Deinitialize(); } +void OverlayProductLayer::Impl::UpdateStormTrackingInformation() +{ + logger_->debug("Update Storm Tracking Information"); + + stiNeedsUpdate_ = false; + + auto overlayProductView = self_->context()->overlay_product_view(); + auto radarProductManager = overlayProductView->radar_product_manager(); + auto record = overlayProductView->radar_product_record("NST"); + + float latitude = 0.0f; + float longitude = 0.0f; + + std::shared_ptr l3File = nullptr; + std::shared_ptr gpm = nullptr; + std::shared_ptr psb = nullptr; + if (record != nullptr) + { + l3File = record->level3_file(); + } + if (l3File != nullptr) + { + gpm = std::dynamic_pointer_cast( + l3File->message()); + } + if (gpm != nullptr) + { + psb = gpm->symbology_block(); + } + + linkedVectors_->StartVectors(); + + if (psb != nullptr) + { + std::shared_ptr radarSite = nullptr; + if (radarProductManager != nullptr) + { + radarSite = radarProductManager->radar_site(); + } + if (radarSite != nullptr) + { + latitude = radarSite->latitude(); + longitude = radarSite->longitude(); + } + + std::string stormId = "?"; + + for (std::size_t i = 0; i < psb->number_of_layers(); ++i) + { + auto packetList = psb->packet_list(static_cast(i)); + for (auto& packet : packetList) + { + switch (packet->packet_code()) + { + case static_cast(wsr88d::rpg::PacketCode::StormId): + HandleStormIdPacket(packet, stormId); + break; + + case static_cast( + wsr88d::rpg::PacketCode::ScitPastData): + case static_cast( + wsr88d::rpg::PacketCode::ScitForecastData): + HandleScitDataPacket( + packet, {latitude, longitude}, stormId, linkedVectors_); + break; + + default: + logger_->trace("Ignoring packet type: {}", + packet->packet_code()); + break; + } + } + } + } + else + { + logger_->trace("No Storm Tracking Information found"); + } + + linkedVectors_->FinishVectors(); +} + +void OverlayProductLayer::Impl::HandleStormIdPacket( + const std::shared_ptr& packet, std::string& stormId) +{ + auto stormIdPacket = + std::dynamic_pointer_cast(packet); + + if (stormIdPacket != nullptr && stormIdPacket->RecordCount() > 0) + { + stormId = stormIdPacket->storm_id(0); + } + else + { + logger_->warn("Invalid Storm ID Packet"); + + stormId = "?"; + } +} + +void OverlayProductLayer::Impl::HandleScitDataPacket( + const std::shared_ptr& packet, + const common::Coordinate& center, + const std::string& stormId, + std::shared_ptr& linkedVectors) +{ + auto scitDataPacket = + std::dynamic_pointer_cast(packet); + + if (scitDataPacket != nullptr) + { + boost::gil::rgba32f_pixel_t color {1.0f, 1.0f, 1.0f, 1.0f}; + if (scitDataPacket->packet_code() == + static_cast(wsr88d::rpg::PacketCode::ScitPastData)) + { + color = {0.5f, 0.5f, 0.5f, 1.0f}; + } + + for (auto& subpacket : scitDataPacket->packet_list()) + { + switch (subpacket->packet_code()) + { + case static_cast( + wsr88d::rpg::PacketCode::LinkedVectorNoValue): + HandleLinkedVectorPacket( + subpacket, center, stormId, color, linkedVectors); + break; + + default: + logger_->trace("Ignoring SCIT subpacket type: {}", + subpacket->packet_code()); + break; + } + } + } + else + { + logger_->warn("Invalid SCIT Data Packet"); + } +} + +void OverlayProductLayer::Impl::HandleLinkedVectorPacket( + const std::shared_ptr& packet, + const common::Coordinate& center, + const std::string& hoverText, + boost::gil::rgba32f_pixel_t color, + std::shared_ptr& linkedVectors) +{ + auto linkedVectorPacket = + std::dynamic_pointer_cast(packet); + + if (linkedVectorPacket != nullptr) + { + auto di = linkedVectors->AddVector(center, linkedVectorPacket); + gl::draw::LinkedVectors::SetVectorWidth(di, 1.0f); + gl::draw::LinkedVectors::SetVectorModulate(di, color); + gl::draw::LinkedVectors::SetVectorHoverText(di, hoverText); + } + else + { + logger_->warn("Invalid Linked Vector Packet"); + } +} + bool OverlayProductLayer::RunMousePicking( const QMapLibreGL::CustomLayerRenderParameters& params, const QPointF& mouseLocalPos, diff --git a/wxdata/include/scwx/wsr88d/rpg/rpg_types.hpp b/wxdata/include/scwx/wsr88d/rpg/rpg_types.hpp index a278017c..9bf2f07d 100644 --- a/wxdata/include/scwx/wsr88d/rpg/rpg_types.hpp +++ b/wxdata/include/scwx/wsr88d/rpg/rpg_types.hpp @@ -7,6 +7,48 @@ namespace wsr88d namespace rpg { +enum class PacketCode : std::uint16_t +{ + TextNoValue = 1, + SpecialSymbol = 2, + MesocycloneSymbol3 = 3, + WindBarbData = 4, + VectorArrowData = 5, + LinkedVectorNoValue = 6, + UnlinkedVectorNoValue = 7, + TextUniform = 8, + LinkedVectorUniform = 9, + UnlinkedVectorUniform = 10, + MesocycloneSymbol11 = 11, + TornadoVortexSignatureSymbol = 12, + HailPositiveSymbol = 13, + HailProbableSymbol = 14, + StormId = 15, + DigitalRadialDataArray = 16, + DigitalPrecipitationDataArray = 17, + PrecipitationRateDataArray = 18, + HdaHailSymbol = 19, + PointFeatureSymbol = 20, + CellTrendData = 21, + CellTrendVolumeScanTimes = 22, + ScitPastData = 23, + ScitForecastData = 24, + StiCircle = 25, + ElevatedTornadoVortexSignatureSymbol = 26, + GenericData28 = 28, + GenericData29 = 29, + SetColorLevel = 0x0802, + LinkedContourVector = 0x0E03, + UnlinkedContourVector = 0x3501, + MapMessage0E23 = 0x0E23, + MapMessage3521 = 0x3521, + MapMessage4E00 = 0x4E00, + MapMessage4E01 = 0x4E01, + RadialData = 0xAF1F, + RasterDataBA07 = 0xBA07, + RasterDataBA0F = 0xBA0F +}; + enum class SpecialSymbol { PastStormCellPosition,