mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-30 08:00:06 +00:00
Parse storm tracking graphic alphanumeric block
This commit is contained in:
parent
c03947d604
commit
c7a9aadffa
3 changed files with 238 additions and 14 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue