Expose Storm Tracking Information in interface

This commit is contained in:
Dan Paulat 2024-02-23 22:49:17 -06:00
parent c7a9aadffa
commit b4b1706587
2 changed files with 195 additions and 45 deletions

View file

@ -51,6 +51,32 @@ public:
StormTrackingInformationMessage&
operator=(StormTrackingInformationMessage&&) noexcept;
std::optional<std::uint16_t> radar_id() const;
std::optional<std::chrono::sys_time<std::chrono::seconds>> date_time() const;
std::optional<std::uint16_t> num_storm_cells() const;
std::optional<units::angle::degrees<std::uint16_t>>
default_direction() const;
std::optional<units::velocity::meters_per_second<float>>
minimum_speed() const;
std::optional<units::velocity::knots<float>> default_speed() const;
std::optional<units::length::kilometers<std::uint16_t>>
allowable_error() const;
std::optional<std::chrono::minutes> maximum_time() const;
std::optional<std::chrono::minutes> forecast_interval() const;
std::optional<std::uint16_t> number_of_past_volumes() const;
std::optional<std::uint16_t> number_of_intervals() const;
std::optional<units::velocity::meters_per_second<float>>
correlation_speed() const;
std::optional<std::chrono::minutes> error_interval() const;
std::optional<units::length::kilometers<float>> filter_kernel_size() const;
std::optional<float> filter_fraction() const;
std::optional<bool> reflectivity_filtered() const;
std::shared_ptr<const StiRecord>
sti_record(const std::string& stormId) const;
bool Parse(std::istream& is) override;
static std::shared_ptr<StormTrackingInformationMessage>

View file

@ -24,6 +24,8 @@ public:
explicit Impl() {}
~Impl() = default;
std::shared_ptr<StiRecord>& GetOrCreateStiRecord(const std::string& stormId);
void ParseGraphicBlock(
const std::shared_ptr<const GraphicAlphanumericBlock>& block);
void ParseTabularBlock(
@ -58,7 +60,7 @@ public:
std::optional<float> filterFraction_ {};
std::optional<bool> reflectivityFiltered_ {};
std::unordered_map<std::string, StiRecord> stiRecords_ {};
std::unordered_map<std::string, std::shared_ptr<StiRecord>> stiRecords_ {};
};
StormTrackingInformationMessage::StormTrackingInformationMessage() :
@ -72,6 +74,126 @@ StormTrackingInformationMessage::StormTrackingInformationMessage(
StormTrackingInformationMessage& StormTrackingInformationMessage::operator=(
StormTrackingInformationMessage&&) noexcept = default;
std::optional<std::uint16_t> StormTrackingInformationMessage::radar_id() const
{
return p->radarId_;
}
std::optional<std::chrono::sys_time<std::chrono::seconds>>
StormTrackingInformationMessage::date_time() const
{
return p->dateTime_;
}
std::optional<std::uint16_t>
StormTrackingInformationMessage::num_storm_cells() const
{
return p->numStormCells_;
}
std::optional<units::angle::degrees<std::uint16_t>>
StormTrackingInformationMessage::default_direction() const
{
return p->defaultDirection_;
}
std::optional<units::velocity::meters_per_second<float>>
StormTrackingInformationMessage::minimum_speed() const
{
return p->minimumSpeed_;
}
std::optional<units::velocity::knots<float>>
StormTrackingInformationMessage::default_speed() const
{
return p->defaultSpeed_;
}
std::optional<units::length::kilometers<std::uint16_t>>
StormTrackingInformationMessage::allowable_error() const
{
return p->allowableError_;
}
std::optional<std::chrono::minutes>
StormTrackingInformationMessage::maximum_time() const
{
return p->maximumTime_;
}
std::optional<std::chrono::minutes>
StormTrackingInformationMessage::forecast_interval() const
{
return p->forecastInterval_;
}
std::optional<std::uint16_t>
StormTrackingInformationMessage::number_of_past_volumes() const
{
return p->numberOfPastVolumes_;
}
std::optional<std::uint16_t>
StormTrackingInformationMessage::number_of_intervals() const
{
return p->numberOfIntervals_;
}
std::optional<units::velocity::meters_per_second<float>>
StormTrackingInformationMessage::correlation_speed() const
{
return p->correlationSpeed_;
}
std::optional<std::chrono::minutes>
StormTrackingInformationMessage::error_interval() const
{
return p->errorInterval_;
}
std::optional<units::length::kilometers<float>>
StormTrackingInformationMessage::filter_kernel_size() const
{
return p->filterKernelSize_;
}
std::optional<float> StormTrackingInformationMessage::filter_fraction() const
{
return p->filterFraction_;
}
std::optional<bool>
StormTrackingInformationMessage::reflectivity_filtered() const
{
return p->reflectivityFiltered_;
}
std::shared_ptr<const StormTrackingInformationMessage::StiRecord>
StormTrackingInformationMessage::sti_record(const std::string& stormId) const
{
std::shared_ptr<const StiRecord> record = nullptr;
auto it = p->stiRecords_.find(stormId);
if (it != p->stiRecords_.cend())
{
record = it->second;
}
return record;
}
std::shared_ptr<StormTrackingInformationMessage::StiRecord>&
StormTrackingInformationMessage::Impl::GetOrCreateStiRecord(
const std::string& stormId)
{
auto& record = stiRecords_[stormId];
if (record == nullptr)
{
record = std::make_shared<StiRecord>();
}
return record;
}
bool StormTrackingInformationMessage::Parse(std::istream& is)
{
bool dataValid = GraphicProductMessage::Parse(is);
@ -167,28 +289,28 @@ void StormTrackingInformationMessage::Impl::HandleTextUniformPacket(
i < stormIds.size();
++i, azOffset += kStride, ranOffset += kStride)
{
auto& record = stiRecords_[stormIds[i]];
auto& record = GetOrCreateStiRecord(stormIds[i]);
if (!record.currentPosition_.azimuth_.has_value())
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_ =
record->currentPosition_.azimuth_ =
units::angle::degrees<std::uint16_t> {azimuth.value()};
}
}
if (!record.currentPosition_.range_.has_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_ =
record->currentPosition_.range_ =
units::length::nautical_miles<std::uint16_t> {
range.value()};
}
@ -209,28 +331,28 @@ void StormTrackingInformationMessage::Impl::HandleTextUniformPacket(
i < stormIds.size();
++i, dirOffset += kStride, speedOffset += kStride)
{
auto& record = stiRecords_[stormIds[i]];
auto& record = GetOrCreateStiRecord(stormIds[i]);
if (!record.direction_.has_value())
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_ =
record->direction_ =
units::angle::degrees<std::uint16_t> {direction.value()};
}
}
if (!record.speed_.has_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_ =
record->speed_ =
units::velocity::knots<std::uint16_t> {speed.value()};
}
}
@ -250,28 +372,29 @@ void StormTrackingInformationMessage::Impl::HandleTextUniformPacket(
i < stormIds.size();
++i, errOffset += kStride, meanOffset += kStride)
{
auto& record = stiRecords_[stormIds[i]];
auto& record = GetOrCreateStiRecord(stormIds[i]);
if (!record.forecastError_.has_value())
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()};
record->forecastError_ =
units::length::nautical_miles<float> {
forecastError.value()};
}
}
if (!record.meanError_.has_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_ =
record->meanError_ =
units::length::nautical_miles<float> {meanError.value()};
}
}
@ -291,10 +414,10 @@ void StormTrackingInformationMessage::Impl::HandleTextUniformPacket(
i < stormIds.size();
++i, dbzmOffset += kStride, hgtOffset += kStride)
{
auto& record = stiRecords_[stormIds[i]];
auto& record = GetOrCreateStiRecord(stormIds[i]);
// Maximum dBZ (I2)
record.maxDbz_ =
record->maxDbz_ =
util::TryParseNumeric<std::uint16_t>(text.substr(dbzmOffset, 2));
// Maximum dBZ Height (Feet) (F4.1)
@ -302,7 +425,7 @@ void StormTrackingInformationMessage::Impl::HandleTextUniformPacket(
util::TryParseNumeric<float>(text.substr(hgtOffset, 4));
if (height.has_value())
{
record.maxDbzHeight_ = units::length::feet<std::uint32_t> {
record->maxDbzHeight_ = units::length::feet<std::uint32_t> {
static_cast<std::uint32_t>(height.value() * 1000.0f)};
}
}
@ -380,58 +503,58 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage(
if (std::isupper(stormId[0]) && std::isdigit(stormId[1]))
{
auto& record = stiRecords_[stormId];
auto& record = GetOrCreateStiRecord(stormId);
if (!record.currentPosition_.azimuth_.has_value())
if (!record->currentPosition_.azimuth_.has_value())
{
// Current Position: Azimuth (Degrees) (I3)
auto azimuth =
util::TryParseNumeric<std::uint16_t>(line.substr(9, 3));
if (azimuth.has_value())
{
record.currentPosition_.azimuth_ =
record->currentPosition_.azimuth_ =
units::angle::degrees<std::uint16_t> {azimuth.value()};
}
}
if (!record.currentPosition_.range_.has_value())
if (!record->currentPosition_.range_.has_value())
{
// Current Position: Range (Nautical Miles) (I3)
auto range =
util::TryParseNumeric<std::uint16_t>(line.substr(13, 3));
if (range.has_value())
{
record.currentPosition_.range_ =
record->currentPosition_.range_ =
units::length::nautical_miles<std::uint16_t> {
range.value()};
}
}
if (!record.direction_.has_value())
if (!record->direction_.has_value())
{
// Movement: Direction (Degrees) (I3)
auto direction =
util::TryParseNumeric<std::uint16_t>(line.substr(19, 3));
if (direction.has_value())
{
record.direction_ =
record->direction_ =
units::angle::degrees<std::uint16_t> {direction.value()};
}
}
if (!record.speed_.has_value())
if (!record->speed_.has_value())
{
// Movement: Speed (Knots) (I3)
auto speed =
util::TryParseNumeric<std::uint16_t>(line.substr(23, 3));
if (speed.has_value())
{
record.speed_ =
record->speed_ =
units::velocity::knots<std::uint16_t> {speed.value()};
}
}
for (std::size_t j = 0; j < record.forecastPosition_.size(); ++j)
for (std::size_t j = 0; j < record->forecastPosition_.size(); ++j)
{
const std::size_t positionOffset = j * 10;
if (!record.forecastPosition_[j].azimuth_.has_value())
if (!record->forecastPosition_[j].azimuth_.has_value())
{
// Forecast Position: Azimuth (Degrees) (I3)
std::size_t offset = 31 + positionOffset;
@ -440,11 +563,11 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage(
line.substr(offset, 3));
if (azimuth.has_value())
{
record.forecastPosition_[j].azimuth_ =
record->forecastPosition_[j].azimuth_ =
units::angle::degrees<std::uint16_t> {azimuth.value()};
}
}
if (!record.forecastPosition_[j].range_.has_value())
if (!record->forecastPosition_[j].range_.has_value())
{
// Forecast Position: Range (Nautical Miles) (I3)
std::size_t offset = 35 + positionOffset;
@ -453,31 +576,32 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage(
line.substr(offset, 3));
if (range.has_value())
{
record.forecastPosition_[j].range_ =
record->forecastPosition_[j].range_ =
units::length::nautical_miles<std::uint16_t> {
range.value()};
}
}
}
if (!record.forecastError_.has_value())
if (!record->forecastError_.has_value())
{
// Forecast Error (Nautical Miles) (F4.1)
auto forecastError =
util::TryParseNumeric<float>(line.substr(71, 4));
if (forecastError.has_value())
{
record.forecastError_ = units::length::nautical_miles<float> {
forecastError.value()};
record->forecastError_ =
units::length::nautical_miles<float> {
forecastError.value()};
}
}
if (!record.meanError_.has_value())
if (!record->meanError_.has_value())
{
// Mean Error (Nautical Miles) (F4.1)
auto meanError =
util::TryParseNumeric<float>(line.substr(76, 4));
if (meanError.has_value())
{
record.meanError_ =
record->meanError_ =
units::length::nautical_miles<float> {meanError.value()};
}
}
@ -515,9 +639,8 @@ void StormTrackingInformationMessage::Impl::ParseStormCellTrackingDataPage(
units::velocity::meters_per_second<float> {threshold.value()};
}
}
// clang-format off
// " 36.0 (KTS) DEFAULT (SPEED) 20 (KM) ALLOWABLE ERROR"
// clang-format on
if (i == 3 && line.size() >= 68)
{
// Default Speed (Knots) (F4.1)
@ -535,6 +658,7 @@ void StormTrackingInformationMessage::Impl::ParseStormCellTrackingDataPage(
units::length::kilometers<std::uint16_t> {error.value()};
}
}
// clang-format off
// " 20 (MIN) TIME (MAXIMUM) 15 (MIN) FORECAST INTERVAL"
// clang-format on
@ -555,6 +679,7 @@ void StormTrackingInformationMessage::Impl::ParseStormCellTrackingDataPage(
forecastInterval_ = std::chrono::minutes {interval.value()};
}
}
// clang-format off
// " 10 NUMBER OF PAST VOLUMES 4 NUMBER OF INTERVALS"
// clang-format on
@ -568,9 +693,8 @@ void StormTrackingInformationMessage::Impl::ParseStormCellTrackingDataPage(
numberOfIntervals_ =
util::TryParseNumeric<std::uint16_t>(line.substr(42, 2));
}
// clang-format off
// " 30.0 (M/S) CORRELATION SPEED 15 (MIN) ERROR INTERVAL"
// clang-format on
if (i == 6 && line.size() >= 67)
{
// Correlation Speed (m/s) (F4.1)
@ -589,6 +713,7 @@ void StormTrackingInformationMessage::Impl::ParseStormCellTrackingDataPage(
errorInterval_ = std::chrono::minutes {interval.value()};
}
}
// clang-format off
// " 7.0 (KM) FILTER KERNEL SIZE 0.5 THRESH (FILTER FRACTION)"
// clang-format on
@ -605,9 +730,8 @@ void StormTrackingInformationMessage::Impl::ParseStormCellTrackingDataPage(
// Minimum Speed (Threshold) (m/s) (F4.1)
filterFraction_ = util::TryParseNumeric<float>(line.substr(40, 4));
}
// clang-format off
// " Yes REFLECTIVITY FILTERED"
// clang-format on
if (i == 12 && line.size() >= 37)
{
if (line.substr(4, 3) == "Yes")