From c7a9aadffa045da532841e35d11a2f5dd2cc9599 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 22 Feb 2024 23:32:58 -0600 Subject: [PATCH] Parse storm tracking graphic alphanumeric block --- .../wsr88d/rpg/graphic_alphanumeric_block.hpp | 4 + .../wsr88d/rpg/graphic_alphanumeric_block.cpp | 8 +- .../storm_tracking_information_message.cpp | 240 +++++++++++++++++- 3 files changed, 238 insertions(+), 14 deletions(-) diff --git a/wxdata/include/scwx/wsr88d/rpg/graphic_alphanumeric_block.hpp b/wxdata/include/scwx/wsr88d/rpg/graphic_alphanumeric_block.hpp index b81f5354..9b1f2019 100644 --- a/wxdata/include/scwx/wsr88d/rpg/graphic_alphanumeric_block.hpp +++ b/wxdata/include/scwx/wsr88d/rpg/graphic_alphanumeric_block.hpp @@ -1,9 +1,11 @@ #pragma once #include +#include #include #include +#include namespace scwx { @@ -31,6 +33,8 @@ public: size_t data_size() const override; + const std::vector>>& page_list() const; + bool Parse(std::istream& is); static constexpr size_t SIZE = 102u; diff --git a/wxdata/source/scwx/wsr88d/rpg/graphic_alphanumeric_block.cpp b/wxdata/source/scwx/wsr88d/rpg/graphic_alphanumeric_block.cpp index a5cc2b3a..bc9f1560 100644 --- a/wxdata/source/scwx/wsr88d/rpg/graphic_alphanumeric_block.cpp +++ b/wxdata/source/scwx/wsr88d/rpg/graphic_alphanumeric_block.cpp @@ -44,7 +44,7 @@ GraphicAlphanumericBlock::GraphicAlphanumericBlock() : GraphicAlphanumericBlock::~GraphicAlphanumericBlock() = default; GraphicAlphanumericBlock::GraphicAlphanumericBlock( - GraphicAlphanumericBlock&&) noexcept = default; + GraphicAlphanumericBlock&&) noexcept = default; GraphicAlphanumericBlock& GraphicAlphanumericBlock::operator=( GraphicAlphanumericBlock&&) noexcept = default; @@ -58,6 +58,12 @@ size_t GraphicAlphanumericBlock::data_size() const return p->lengthOfBlock_; } +const std::vector>>& +GraphicAlphanumericBlock::page_list() const +{ + return p->pageList_; +} + bool GraphicAlphanumericBlock::Parse(std::istream& is) { bool blockValid = true; diff --git a/wxdata/source/scwx/wsr88d/rpg/storm_tracking_information_message.cpp b/wxdata/source/scwx/wsr88d/rpg/storm_tracking_information_message.cpp index 08d2e44e..b39035a2 100644 --- a/wxdata/source/scwx/wsr88d/rpg/storm_tracking_information_message.cpp +++ b/wxdata/source/scwx/wsr88d/rpg/storm_tracking_information_message.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -30,6 +32,9 @@ public: void ParseStormPositionForecastPage(const std::vector& page); void ParseStormCellTrackingDataPage(const std::vector& page); + void HandleTextUniformPacket(std::shared_ptr packet, + std::vector& stormIds); + // STORM POSITION/FORECAST std::optional radarId_ {}; std::optional> dateTime_ {}; @@ -96,8 +101,217 @@ bool StormTrackingInformationMessage::Parse(std::istream& is) void StormTrackingInformationMessage::Impl::ParseGraphicBlock( const std::shared_ptr& block) { - // TODO - (void) (block); + for (auto& page : block->page_list()) + { + std::vector stormIds {}; + + for (auto& packet : page) + { + switch (packet->packet_code()) + { + case static_cast(wsr88d::rpg::PacketCode::TextUniform): + HandleTextUniformPacket(packet, stormIds); + break; + + default: + logger_->trace("Ignoring graphic alphanumeric packet type: {}", + packet->packet_code()); + break; + } + } + } +} + +void StormTrackingInformationMessage::Impl::HandleTextUniformPacket( + std::shared_ptr packet, std::vector& stormIds) +{ + auto textPacket = + std::dynamic_pointer_cast( + packet); + + if (textPacket != nullptr && textPacket->text().size() >= 69) + { + auto text = textPacket->text(); + + // " STORM ID D7 H3 K5 N5 U6 E7" + if (text.starts_with(" STORM ID")) + { + static constexpr std::size_t kMaxStormIds = 6; + static constexpr std::size_t kStartOffset = 17; + static constexpr std::size_t kStride = 10; + + stormIds.clear(); + + for (std::size_t i = 0, offset = kStartOffset; i < kMaxStormIds; + ++i, offset += kStride) + { + std::string stormId = text.substr(offset, 2); + + if (std::isupper(stormId[0]) && std::isdigit(stormId[1])) + { + stormIds.push_back(stormId); + } + } + } + + // " AZ/RAN 242/ 77 45/ 36 180/139 175/126 23/110 25/ 83" + else if (text.starts_with(" AZ/RAN")) + { + static constexpr std::size_t kAzStartOffset = 11; + static constexpr std::size_t kRanStartOffset = 15; + static constexpr std::size_t kStride = 10; + + for (std::size_t i = 0, + azOffset = kAzStartOffset, + ranOffset = kRanStartOffset; + i < stormIds.size(); + ++i, azOffset += kStride, ranOffset += kStride) + { + auto& record = stiRecords_[stormIds[i]]; + + if (!record.currentPosition_.azimuth_.has_value()) + { + // Current Position: Azimuth (Degrees) (I3) + auto azimuth = util::TryParseNumeric( + text.substr(azOffset, 3)); + if (azimuth.has_value()) + { + record.currentPosition_.azimuth_ = + units::angle::degrees {azimuth.value()}; + } + } + + if (!record.currentPosition_.range_.has_value()) + { + // Current Position: Range (Nautical Miles) (I3) + auto range = util::TryParseNumeric( + text.substr(ranOffset, 3)); + if (range.has_value()) + { + record.currentPosition_.range_ = + units::length::nautical_miles { + range.value()}; + } + } + } + } + + // " FCST MVT 262/ 56 249/ 48 234/ 46 228/ 48 227/ 66 242/ 48" + else if (text.starts_with(" FCST MVT")) + { + static constexpr std::size_t kDirStartOffset = 11; + static constexpr std::size_t kSpeedStartOffset = 15; + static constexpr std::size_t kStride = 10; + + for (std::size_t i = 0, + dirOffset = kDirStartOffset, + speedOffset = kSpeedStartOffset; + i < stormIds.size(); + ++i, dirOffset += kStride, speedOffset += kStride) + { + auto& record = stiRecords_[stormIds[i]]; + + if (!record.direction_.has_value()) + { + // Movement: Direction (Degrees) (I3) + auto direction = util::TryParseNumeric( + text.substr(dirOffset, 3)); + if (direction.has_value()) + { + record.direction_ = + units::angle::degrees {direction.value()}; + } + } + + if (!record.speed_.has_value()) + { + // Movement: Speed (Knots) (I3) + auto speed = util::TryParseNumeric( + text.substr(speedOffset, 3)); + if (speed.has_value()) + { + record.speed_ = + units::velocity::knots {speed.value()}; + } + } + } + } + + // " ERR/MEAN 4.5/ 2.9 0.8/ 1.7 1.4/ 1.4 1.3/ 1.3 1.4/ 1.7 1.2/ 0.8" + else if (text.starts_with(" ERR/MEAN")) + { + static constexpr std::size_t kErrStartOffset = 10; + static constexpr std::size_t kMeanStartOffset = 15; + static constexpr std::size_t kStride = 10; + + for (std::size_t i = 0, + errOffset = kErrStartOffset, + meanOffset = kMeanStartOffset; + i < stormIds.size(); + ++i, errOffset += kStride, meanOffset += kStride) + { + auto& record = stiRecords_[stormIds[i]]; + + if (!record.forecastError_.has_value()) + { + // Forecast Error (Nautical Miles) (F4.1) + auto forecastError = + util::TryParseNumeric(text.substr(errOffset, 4)); + if (forecastError.has_value()) + { + record.forecastError_ = units::length::nautical_miles { + forecastError.value()}; + } + } + + if (!record.meanError_.has_value()) + { + // Mean Error (Nautical Miles) (F4.1) + auto meanError = + util::TryParseNumeric(text.substr(meanOffset, 4)); + if (meanError.has_value()) + { + record.meanError_ = + units::length::nautical_miles {meanError.value()}; + } + } + } + } + + // " DBZM HGT 55 7.5 56 4.2 48 20.6 51 17.4 51 14.0 54 8.9" + else if (text.starts_with(" DBZM HGT")) + { + static constexpr std::size_t kDbzmStartOffset = 12; + static constexpr std::size_t kHgtStartOffset = 15; + static constexpr std::size_t kStride = 10; + + for (std::size_t i = 0, + dbzmOffset = kDbzmStartOffset, + hgtOffset = kHgtStartOffset; + i < stormIds.size(); + ++i, dbzmOffset += kStride, hgtOffset += kStride) + { + auto& record = stiRecords_[stormIds[i]]; + + // Maximum dBZ (I2) + record.maxDbz_ = + util::TryParseNumeric(text.substr(dbzmOffset, 2)); + + // Maximum dBZ Height (Feet) (F4.1) + auto height = + util::TryParseNumeric(text.substr(hgtOffset, 4)); + if (height.has_value()) + { + record.maxDbzHeight_ = units::length::feet { + static_cast(height.value() * 1000.0f)}; + } + } + } + } + else + { + logger_->warn("Invalid Text Uniform Packet"); + } } void StormTrackingInformationMessage::Impl::ParseTabularBlock( @@ -138,19 +352,19 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage( // clang-format on if (i == 1 && line.size() >= 74) { - if (radarId_ == std::nullopt) + if (!radarId_.has_value()) { // Radar ID (I3) radarId_ = util::TryParseNumeric(line.substr(14, 3)); } - if (dateTime_ == std::nullopt) + if (!dateTime_.has_value()) { static const std::string kDateTimeFormat_ {"%m:%d:%y/%H:%M:%S"}; dateTime_ = util::TryParseDateTime( kDateTimeFormat_, line.substr(29, 17)); } - if (numStormCells_ == std::nullopt) + if (!numStormCells_.has_value()) { // Number of Storm Cells (I3) numStormCells_ = @@ -168,7 +382,7 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage( { auto& record = stiRecords_[stormId]; - if (record.currentPosition_.azimuth_ == std::nullopt) + if (!record.currentPosition_.azimuth_.has_value()) { // Current Position: Azimuth (Degrees) (I3) auto azimuth = @@ -179,7 +393,7 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage( units::angle::degrees {azimuth.value()}; } } - if (record.currentPosition_.range_ == std::nullopt) + if (!record.currentPosition_.range_.has_value()) { // Current Position: Range (Nautical Miles) (I3) auto range = @@ -191,7 +405,7 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage( range.value()}; } } - if (record.direction_ == std::nullopt) + if (!record.direction_.has_value()) { // Movement: Direction (Degrees) (I3) auto direction = @@ -202,7 +416,7 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage( units::angle::degrees {direction.value()}; } } - if (record.speed_ == std::nullopt) + if (!record.speed_.has_value()) { // Movement: Speed (Knots) (I3) auto speed = @@ -217,7 +431,7 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage( { const std::size_t positionOffset = j * 10; - if (record.forecastPosition_[j].azimuth_ == std::nullopt) + if (!record.forecastPosition_[j].azimuth_.has_value()) { // Forecast Position: Azimuth (Degrees) (I3) std::size_t offset = 31 + positionOffset; @@ -230,7 +444,7 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage( units::angle::degrees {azimuth.value()}; } } - if (record.forecastPosition_[j].range_ == std::nullopt) + if (!record.forecastPosition_[j].range_.has_value()) { // Forecast Position: Range (Nautical Miles) (I3) std::size_t offset = 35 + positionOffset; @@ -245,7 +459,7 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage( } } } - if (record.forecastError_ == std::nullopt) + if (!record.forecastError_.has_value()) { // Forecast Error (Nautical Miles) (F4.1) auto forecastError = @@ -256,7 +470,7 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage( forecastError.value()}; } } - if (record.meanError_ == std::nullopt) + if (!record.meanError_.has_value()) { // Mean Error (Nautical Miles) (F4.1) auto meanError =