Parse storm tracking graphic alphanumeric block

This commit is contained in:
Dan Paulat 2024-02-22 23:32:58 -06:00
parent c03947d604
commit c7a9aadffa
3 changed files with 238 additions and 14 deletions

View file

@ -1,9 +1,11 @@
#pragma once
#include <scwx/awips/message.hpp>
#include <scwx/wsr88d/rpg/packet.hpp>
#include <cstdint>
#include <memory>
#include <vector>
namespace scwx
{
@ -31,6 +33,8 @@ public:
size_t data_size() const override;
const std::vector<std::vector<std::shared_ptr<Packet>>>& page_list() const;
bool Parse(std::istream& is);
static constexpr size_t SIZE = 102u;

View file

@ -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<std::vector<std::shared_ptr<Packet>>>&
GraphicAlphanumericBlock::page_list() const
{
return p->pageList_;
}
bool GraphicAlphanumericBlock::Parse(std::istream& is)
{
bool blockValid = true;

View file

@ -1,4 +1,6 @@
#include <scwx/wsr88d/rpg/storm_tracking_information_message.hpp>
#include <scwx/wsr88d/rpg/text_and_special_symbol_packet.hpp>
#include <scwx/wsr88d/rpg/rpg_types.hpp>
#include <scwx/util/logger.hpp>
#include <scwx/util/strings.hpp>
#include <scwx/util/time.hpp>
@ -30,6 +32,9 @@ public:
void ParseStormPositionForecastPage(const std::vector<std::string>& page);
void ParseStormCellTrackingDataPage(const std::vector<std::string>& page);
void HandleTextUniformPacket(std::shared_ptr<const Packet> packet,
std::vector<std::string>& stormIds);
// STORM POSITION/FORECAST
std::optional<std::uint16_t> radarId_ {};
std::optional<std::chrono::sys_time<std::chrono::seconds>> dateTime_ {};
@ -96,8 +101,217 @@ bool StormTrackingInformationMessage::Parse(std::istream& is)
void StormTrackingInformationMessage::Impl::ParseGraphicBlock(
const std::shared_ptr<const GraphicAlphanumericBlock>& block)
{
// TODO
(void) (block);
for (auto& page : block->page_list())
{
std::vector<std::string> stormIds {};
for (auto& packet : page)
{
switch (packet->packet_code())
{
case static_cast<std::uint16_t>(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<const Packet> packet, std::vector<std::string>& stormIds)
{
auto textPacket =
std::dynamic_pointer_cast<const wsr88d::rpg::TextAndSpecialSymbolPacket>(
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<std::uint16_t>(
text.substr(azOffset, 3));
if (azimuth.has_value())
{
record.currentPosition_.azimuth_ =
units::angle::degrees<std::uint16_t> {azimuth.value()};
}
}
if (!record.currentPosition_.range_.has_value())
{
// Current Position: Range (Nautical Miles) (I3)
auto range = util::TryParseNumeric<std::uint16_t>(
text.substr(ranOffset, 3));
if (range.has_value())
{
record.currentPosition_.range_ =
units::length::nautical_miles<std::uint16_t> {
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<std::uint16_t>(
text.substr(dirOffset, 3));
if (direction.has_value())
{
record.direction_ =
units::angle::degrees<std::uint16_t> {direction.value()};
}
}
if (!record.speed_.has_value())
{
// Movement: Speed (Knots) (I3)
auto speed = util::TryParseNumeric<std::uint16_t>(
text.substr(speedOffset, 3));
if (speed.has_value())
{
record.speed_ =
units::velocity::knots<std::uint16_t> {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<float>(text.substr(errOffset, 4));
if (forecastError.has_value())
{
record.forecastError_ = units::length::nautical_miles<float> {
forecastError.value()};
}
}
if (!record.meanError_.has_value())
{
// Mean Error (Nautical Miles) (F4.1)
auto meanError =
util::TryParseNumeric<float>(text.substr(meanOffset, 4));
if (meanError.has_value())
{
record.meanError_ =
units::length::nautical_miles<float> {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<std::uint16_t>(text.substr(dbzmOffset, 2));
// Maximum dBZ Height (Feet) (F4.1)
auto height =
util::TryParseNumeric<float>(text.substr(hgtOffset, 4));
if (height.has_value())
{
record.maxDbzHeight_ = units::length::feet<std::uint32_t> {
static_cast<std::uint32_t>(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<std::uint16_t>(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<std::chrono::seconds>(
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<std::uint16_t> {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<std::uint16_t> {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<std::uint16_t> {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 =