diff --git a/wxdata/include/scwx/util/strings.hpp b/wxdata/include/scwx/util/strings.hpp index 8aa6014d..66a1b7c6 100644 --- a/wxdata/include/scwx/util/strings.hpp +++ b/wxdata/include/scwx/util/strings.hpp @@ -30,6 +30,8 @@ std::vector ParseTokens(const std::string& s, std::string ToString(const std::vector& v); +std::optional TryParseFloat(const std::string& str); + template std::optional TryParseUnsignedLong(const std::string& str); diff --git a/wxdata/include/scwx/wsr88d/rpg/storm_tracking_information_message.hpp b/wxdata/include/scwx/wsr88d/rpg/storm_tracking_information_message.hpp index 7d238464..a37d0626 100644 --- a/wxdata/include/scwx/wsr88d/rpg/storm_tracking_information_message.hpp +++ b/wxdata/include/scwx/wsr88d/rpg/storm_tracking_information_message.hpp @@ -22,21 +22,21 @@ public: { struct Position { - std::optional> azimuth_ {}; - std::optional> range_ {}; + std::optional> azimuth_ {}; + std::optional> range_ {}; }; - Position currentPosition_ {}; - std::optional> direction_; - std::optional> speed_; + Position currentPosition_ {}; + std::optional> direction_; + std::optional> speed_; std::array forecastPosition_ {}; std::optional> forecastError_ {}; std::optional> meanError_ {}; - std::optional maxDbz_ {}; - std::optional> maxDbzHeight_ {}; + std::optional maxDbz_ {}; + std::optional> maxDbzHeight_ {}; }; explicit StormTrackingInformationMessage(); diff --git a/wxdata/source/scwx/util/strings.cpp b/wxdata/source/scwx/util/strings.cpp index c17489da..dfc26177 100644 --- a/wxdata/source/scwx/util/strings.cpp +++ b/wxdata/source/scwx/util/strings.cpp @@ -88,6 +88,21 @@ std::string ToString(const std::vector& v) return value; } +std::optional TryParseFloat(const std::string& str) +{ + std::optional value = std::nullopt; + + try + { + value = static_cast(std::stof(str)); + } + catch (const std::exception&) + { + } + + return value; +} + template std::optional TryParseUnsignedLong(const std::string& str) { 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 cc68a676..2d598539 100644 --- a/wxdata/source/scwx/wsr88d/rpg/storm_tracking_information_message.cpp +++ b/wxdata/source/scwx/wsr88d/rpg/storm_tracking_information_message.cpp @@ -143,9 +143,110 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage( // clang-format on else if (i >= 7 && line.size() >= 80) { - // TODO: STI Record std::string stormId = line.substr(2, 2); - (void) (stormId); + + if (std::isupper(stormId[0]) && std::isdigit(stormId[1])) + { + auto& record = stiRecords_[stormId]; + + if (record.currentPosition_.azimuth_ == std::nullopt) + { + // Current Position: Azimuth (Degrees) + auto azimuth = + util::TryParseUnsignedLong(line.substr(9, 3)); + if (azimuth.has_value()) + { + record.currentPosition_.azimuth_ = + units::angle::degrees {azimuth.value()}; + } + } + if (record.currentPosition_.range_ == std::nullopt) + { + // Current Position: Range (Nautical Miles) + auto range = + util::TryParseUnsignedLong(line.substr(13, 3)); + if (range.has_value()) + { + record.currentPosition_.range_ = + units::length::nautical_miles { + range.value()}; + } + } + if (record.direction_ == std::nullopt) + { + // Movement: Direction (Degrees) + auto direction = + util::TryParseUnsignedLong(line.substr(19, 3)); + if (direction.has_value()) + { + record.direction_ = + units::angle::degrees {direction.value()}; + } + } + if (record.speed_ == std::nullopt) + { + // Movement: Speed (Knots) + auto speed = + util::TryParseUnsignedLong(line.substr(23, 3)); + if (speed.has_value()) + { + record.speed_ = + units::velocity::knots {speed.value()}; + } + } + for (std::size_t j = 0; j < record.forecastPosition_.size(); ++j) + { + const std::size_t positionOffset = j * 10; + + if (record.forecastPosition_[j].azimuth_ == std::nullopt) + { + // Forecast Position: Azimuth (Degrees) + std::size_t offset = 31 + positionOffset; + + auto azimuth = util::TryParseUnsignedLong( + line.substr(offset, 3)); + if (azimuth.has_value()) + { + record.forecastPosition_[j].azimuth_ = + units::angle::degrees {azimuth.value()}; + } + } + if (record.forecastPosition_[j].range_ == std::nullopt) + { + // Forecast Position: Range (Nautical Miles) + std::size_t offset = 35 + positionOffset; + + auto range = util::TryParseUnsignedLong( + line.substr(offset, 3)); + if (range.has_value()) + { + record.forecastPosition_[j].range_ = + units::length::nautical_miles { + range.value()}; + } + } + } + if (record.forecastError_ == std::nullopt) + { + // Forecast Error (Nautical Miles) + auto forecastError = util::TryParseFloat(line.substr(71, 4)); + if (forecastError.has_value()) + { + record.forecastError_ = units::length::nautical_miles { + forecastError.value()}; + } + } + if (record.meanError_ == std::nullopt) + { + // Mean Error (Nautical Miles) + auto meanError = util::TryParseFloat(line.substr(76, 4)); + if (meanError.has_value()) + { + record.meanError_ = + units::length::nautical_miles {meanError.value()}; + } + } + } } } }