mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-30 20:50: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
|
#pragma once
|
||||||
|
|
||||||
#include <scwx/awips/message.hpp>
|
#include <scwx/awips/message.hpp>
|
||||||
|
#include <scwx/wsr88d/rpg/packet.hpp>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace scwx
|
namespace scwx
|
||||||
{
|
{
|
||||||
|
|
@ -31,6 +33,8 @@ public:
|
||||||
|
|
||||||
size_t data_size() const override;
|
size_t data_size() const override;
|
||||||
|
|
||||||
|
const std::vector<std::vector<std::shared_ptr<Packet>>>& page_list() const;
|
||||||
|
|
||||||
bool Parse(std::istream& is);
|
bool Parse(std::istream& is);
|
||||||
|
|
||||||
static constexpr size_t SIZE = 102u;
|
static constexpr size_t SIZE = 102u;
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ GraphicAlphanumericBlock::GraphicAlphanumericBlock() :
|
||||||
GraphicAlphanumericBlock::~GraphicAlphanumericBlock() = default;
|
GraphicAlphanumericBlock::~GraphicAlphanumericBlock() = default;
|
||||||
|
|
||||||
GraphicAlphanumericBlock::GraphicAlphanumericBlock(
|
GraphicAlphanumericBlock::GraphicAlphanumericBlock(
|
||||||
GraphicAlphanumericBlock&&) noexcept = default;
|
GraphicAlphanumericBlock&&) noexcept = default;
|
||||||
GraphicAlphanumericBlock& GraphicAlphanumericBlock::operator=(
|
GraphicAlphanumericBlock& GraphicAlphanumericBlock::operator=(
|
||||||
GraphicAlphanumericBlock&&) noexcept = default;
|
GraphicAlphanumericBlock&&) noexcept = default;
|
||||||
|
|
||||||
|
|
@ -58,6 +58,12 @@ size_t GraphicAlphanumericBlock::data_size() const
|
||||||
return p->lengthOfBlock_;
|
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 GraphicAlphanumericBlock::Parse(std::istream& is)
|
||||||
{
|
{
|
||||||
bool blockValid = true;
|
bool blockValid = true;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
#include <scwx/wsr88d/rpg/storm_tracking_information_message.hpp>
|
#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/logger.hpp>
|
||||||
#include <scwx/util/strings.hpp>
|
#include <scwx/util/strings.hpp>
|
||||||
#include <scwx/util/time.hpp>
|
#include <scwx/util/time.hpp>
|
||||||
|
|
@ -30,6 +32,9 @@ public:
|
||||||
void ParseStormPositionForecastPage(const std::vector<std::string>& page);
|
void ParseStormPositionForecastPage(const std::vector<std::string>& page);
|
||||||
void ParseStormCellTrackingDataPage(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
|
// STORM POSITION/FORECAST
|
||||||
std::optional<std::uint16_t> radarId_ {};
|
std::optional<std::uint16_t> radarId_ {};
|
||||||
std::optional<std::chrono::sys_time<std::chrono::seconds>> dateTime_ {};
|
std::optional<std::chrono::sys_time<std::chrono::seconds>> dateTime_ {};
|
||||||
|
|
@ -96,8 +101,217 @@ bool StormTrackingInformationMessage::Parse(std::istream& is)
|
||||||
void StormTrackingInformationMessage::Impl::ParseGraphicBlock(
|
void StormTrackingInformationMessage::Impl::ParseGraphicBlock(
|
||||||
const std::shared_ptr<const GraphicAlphanumericBlock>& block)
|
const std::shared_ptr<const GraphicAlphanumericBlock>& block)
|
||||||
{
|
{
|
||||||
// TODO
|
for (auto& page : block->page_list())
|
||||||
(void) (block);
|
{
|
||||||
|
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(
|
void StormTrackingInformationMessage::Impl::ParseTabularBlock(
|
||||||
|
|
@ -138,19 +352,19 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage(
|
||||||
// clang-format on
|
// clang-format on
|
||||||
if (i == 1 && line.size() >= 74)
|
if (i == 1 && line.size() >= 74)
|
||||||
{
|
{
|
||||||
if (radarId_ == std::nullopt)
|
if (!radarId_.has_value())
|
||||||
{
|
{
|
||||||
// Radar ID (I3)
|
// Radar ID (I3)
|
||||||
radarId_ = util::TryParseNumeric<std::uint16_t>(line.substr(14, 3));
|
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"};
|
static const std::string kDateTimeFormat_ {"%m:%d:%y/%H:%M:%S"};
|
||||||
|
|
||||||
dateTime_ = util::TryParseDateTime<std::chrono::seconds>(
|
dateTime_ = util::TryParseDateTime<std::chrono::seconds>(
|
||||||
kDateTimeFormat_, line.substr(29, 17));
|
kDateTimeFormat_, line.substr(29, 17));
|
||||||
}
|
}
|
||||||
if (numStormCells_ == std::nullopt)
|
if (!numStormCells_.has_value())
|
||||||
{
|
{
|
||||||
// Number of Storm Cells (I3)
|
// Number of Storm Cells (I3)
|
||||||
numStormCells_ =
|
numStormCells_ =
|
||||||
|
|
@ -168,7 +382,7 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage(
|
||||||
{
|
{
|
||||||
auto& record = stiRecords_[stormId];
|
auto& record = stiRecords_[stormId];
|
||||||
|
|
||||||
if (record.currentPosition_.azimuth_ == std::nullopt)
|
if (!record.currentPosition_.azimuth_.has_value())
|
||||||
{
|
{
|
||||||
// Current Position: Azimuth (Degrees) (I3)
|
// Current Position: Azimuth (Degrees) (I3)
|
||||||
auto azimuth =
|
auto azimuth =
|
||||||
|
|
@ -179,7 +393,7 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage(
|
||||||
units::angle::degrees<std::uint16_t> {azimuth.value()};
|
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)
|
// Current Position: Range (Nautical Miles) (I3)
|
||||||
auto range =
|
auto range =
|
||||||
|
|
@ -191,7 +405,7 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage(
|
||||||
range.value()};
|
range.value()};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (record.direction_ == std::nullopt)
|
if (!record.direction_.has_value())
|
||||||
{
|
{
|
||||||
// Movement: Direction (Degrees) (I3)
|
// Movement: Direction (Degrees) (I3)
|
||||||
auto direction =
|
auto direction =
|
||||||
|
|
@ -202,7 +416,7 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage(
|
||||||
units::angle::degrees<std::uint16_t> {direction.value()};
|
units::angle::degrees<std::uint16_t> {direction.value()};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (record.speed_ == std::nullopt)
|
if (!record.speed_.has_value())
|
||||||
{
|
{
|
||||||
// Movement: Speed (Knots) (I3)
|
// Movement: Speed (Knots) (I3)
|
||||||
auto speed =
|
auto speed =
|
||||||
|
|
@ -217,7 +431,7 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage(
|
||||||
{
|
{
|
||||||
const std::size_t positionOffset = j * 10;
|
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)
|
// Forecast Position: Azimuth (Degrees) (I3)
|
||||||
std::size_t offset = 31 + positionOffset;
|
std::size_t offset = 31 + positionOffset;
|
||||||
|
|
@ -230,7 +444,7 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage(
|
||||||
units::angle::degrees<std::uint16_t> {azimuth.value()};
|
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)
|
// Forecast Position: Range (Nautical Miles) (I3)
|
||||||
std::size_t offset = 35 + positionOffset;
|
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)
|
// Forecast Error (Nautical Miles) (F4.1)
|
||||||
auto forecastError =
|
auto forecastError =
|
||||||
|
|
@ -256,7 +470,7 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage(
|
||||||
forecastError.value()};
|
forecastError.value()};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (record.meanError_ == std::nullopt)
|
if (!record.meanError_.has_value())
|
||||||
{
|
{
|
||||||
// Mean Error (Nautical Miles) (F4.1)
|
// Mean Error (Nautical Miles) (F4.1)
|
||||||
auto meanError =
|
auto meanError =
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue