diff --git a/wxdata/include/scwx/util/strings.hpp b/wxdata/include/scwx/util/strings.hpp index 66a1b7c6..ad0d1f9c 100644 --- a/wxdata/include/scwx/util/strings.hpp +++ b/wxdata/include/scwx/util/strings.hpp @@ -30,10 +30,14 @@ 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); +std::optional TryParseNumeric(const std::string& str); + +#if defined(STRINGS_IMPLEMENTATION) +template std::optional TryParseNumeric(const std::string& str); +template std::optional TryParseNumeric(const std::string& str); +template std::optional TryParseNumeric(const std::string& str); +#endif } // namespace util } // namespace scwx diff --git a/wxdata/source/scwx/util/strings.cpp b/wxdata/source/scwx/util/strings.cpp index dfc26177..7067d821 100644 --- a/wxdata/source/scwx/util/strings.cpp +++ b/wxdata/source/scwx/util/strings.cpp @@ -1,6 +1,9 @@ +#define STRINGS_IMPLEMENTATION + #include #include +#include namespace scwx { @@ -88,29 +91,15 @@ 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) +std::optional TryParseNumeric(const std::string& str) { std::optional value = std::nullopt; try { - value = static_cast(std::stoul(str)); + auto trimmed = boost::algorithm::trim_copy(str); + value = boost::lexical_cast(trimmed); } catch (const std::exception&) { @@ -119,8 +108,5 @@ std::optional TryParseUnsignedLong(const std::string& str) return value; } -template std::optional -TryParseUnsignedLong(const std::string& str); - } // namespace util } // namespace scwx 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 2d598539..08d2e44e 100644 --- a/wxdata/source/scwx/wsr88d/rpg/storm_tracking_information_message.cpp +++ b/wxdata/source/scwx/wsr88d/rpg/storm_tracking_information_message.cpp @@ -35,6 +35,24 @@ public: std::optional> dateTime_ {}; std::optional numStormCells_ {}; + // STORM CELL TRACKING/FORECAST ADAPTATION DATA + std::optional> defaultDirection_ {}; + std::optional> minimumSpeed_ {}; + std::optional> defaultSpeed_ {}; + std::optional> allowableError_ {}; + std::optional maximumTime_ {}; + std::optional forecastInterval_ {}; + std::optional numberOfPastVolumes_ {}; + std::optional numberOfIntervals_ {}; + std::optional> + correlationSpeed_ {}; + std::optional errorInterval_ {}; + + // SCIT REFLECTIVITY MEDIAN FILTER + std::optional> filterKernelSize_ {}; + std::optional filterFraction_ {}; + std::optional reflectivityFiltered_ {}; + std::unordered_map stiRecords_ {}; }; @@ -122,8 +140,8 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage( { if (radarId_ == std::nullopt) { - radarId_ = - util::TryParseUnsignedLong(line.substr(14, 3)); + // Radar ID (I3) + radarId_ = util::TryParseNumeric(line.substr(14, 3)); } if (dateTime_ == std::nullopt) { @@ -134,8 +152,9 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage( } if (numStormCells_ == std::nullopt) { + // Number of Storm Cells (I3) numStormCells_ = - util::TryParseUnsignedLong(line.substr(71, 3)); + util::TryParseNumeric(line.substr(71, 3)); } } // clang-format off @@ -151,9 +170,9 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage( if (record.currentPosition_.azimuth_ == std::nullopt) { - // Current Position: Azimuth (Degrees) + // Current Position: Azimuth (Degrees) (I3) auto azimuth = - util::TryParseUnsignedLong(line.substr(9, 3)); + util::TryParseNumeric(line.substr(9, 3)); if (azimuth.has_value()) { record.currentPosition_.azimuth_ = @@ -162,9 +181,9 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage( } if (record.currentPosition_.range_ == std::nullopt) { - // Current Position: Range (Nautical Miles) + // Current Position: Range (Nautical Miles) (I3) auto range = - util::TryParseUnsignedLong(line.substr(13, 3)); + util::TryParseNumeric(line.substr(13, 3)); if (range.has_value()) { record.currentPosition_.range_ = @@ -174,9 +193,9 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage( } if (record.direction_ == std::nullopt) { - // Movement: Direction (Degrees) + // Movement: Direction (Degrees) (I3) auto direction = - util::TryParseUnsignedLong(line.substr(19, 3)); + util::TryParseNumeric(line.substr(19, 3)); if (direction.has_value()) { record.direction_ = @@ -185,9 +204,9 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage( } if (record.speed_ == std::nullopt) { - // Movement: Speed (Knots) + // Movement: Speed (Knots) (I3) auto speed = - util::TryParseUnsignedLong(line.substr(23, 3)); + util::TryParseNumeric(line.substr(23, 3)); if (speed.has_value()) { record.speed_ = @@ -200,10 +219,10 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage( if (record.forecastPosition_[j].azimuth_ == std::nullopt) { - // Forecast Position: Azimuth (Degrees) + // Forecast Position: Azimuth (Degrees) (I3) std::size_t offset = 31 + positionOffset; - auto azimuth = util::TryParseUnsignedLong( + auto azimuth = util::TryParseNumeric( line.substr(offset, 3)); if (azimuth.has_value()) { @@ -213,10 +232,10 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage( } if (record.forecastPosition_[j].range_ == std::nullopt) { - // Forecast Position: Range (Nautical Miles) + // Forecast Position: Range (Nautical Miles) (I3) std::size_t offset = 35 + positionOffset; - auto range = util::TryParseUnsignedLong( + auto range = util::TryParseNumeric( line.substr(offset, 3)); if (range.has_value()) { @@ -228,8 +247,9 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage( } if (record.forecastError_ == std::nullopt) { - // Forecast Error (Nautical Miles) - auto forecastError = util::TryParseFloat(line.substr(71, 4)); + // Forecast Error (Nautical Miles) (F4.1) + auto forecastError = + util::TryParseNumeric(line.substr(71, 4)); if (forecastError.has_value()) { record.forecastError_ = units::length::nautical_miles { @@ -238,8 +258,9 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage( } if (record.meanError_ == std::nullopt) { - // Mean Error (Nautical Miles) - auto meanError = util::TryParseFloat(line.substr(76, 4)); + // Mean Error (Nautical Miles) (F4.1) + auto meanError = + util::TryParseNumeric(line.substr(76, 4)); if (meanError.has_value()) { record.meanError_ = @@ -254,8 +275,137 @@ void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage( void StormTrackingInformationMessage::Impl::ParseStormCellTrackingDataPage( const std::vector& page) { - // TODO - (void) (page); + for (std::size_t i = 1; i < page.size(); ++i) + { + const std::string& line = page[i]; + + // clang-format off + // " 260 (DEG) DEFAULT (DIRECTION) 2.5 (M/S) THRESH (MINIMUM SPEED)" + // clang-format on + if (i == 2 && line.size() >= 75) + { + // Default Direction (Degrees) (I3) + auto direction = + util::TryParseNumeric(line.substr(4, 3)); + if (direction.has_value()) + { + defaultDirection_ = + units::angle::degrees {direction.value()}; + } + + // Minimum Speed (Threshold) (m/s) (F4.1) + auto threshold = util::TryParseNumeric(line.substr(40, 4)); + if (threshold.has_value()) + { + minimumSpeed_ = + units::velocity::meters_per_second {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) + auto speed = util::TryParseNumeric(line.substr(3, 4)); + if (speed.has_value()) + { + defaultSpeed_ = units::velocity::knots {speed.value()}; + } + + // Allowable Error (Kilometers) (I2) + auto error = util::TryParseNumeric(line.substr(42, 2)); + if (error.has_value()) + { + allowableError_ = + units::length::kilometers {error.value()}; + } + } + // clang-format off + // " 20 (MIN) TIME (MAXIMUM) 15 (MIN) FORECAST INTERVAL" + // clang-format on + if (i == 4 && line.size() >= 70) + { + // Maximum Time (Minutes) (I5) + auto time = util::TryParseNumeric(line.substr(2, 5)); + if (time.has_value()) + { + maximumTime_ = std::chrono::minutes {time.value()}; + } + + // Forecast Interval (Minutes) (I2) + auto interval = + util::TryParseNumeric(line.substr(42, 2)); + if (interval.has_value()) + { + forecastInterval_ = std::chrono::minutes {interval.value()}; + } + } + // clang-format off + // " 10 NUMBER OF PAST VOLUMES 4 NUMBER OF INTERVALS" + // clang-format on + if (i == 5 && line.size() >= 72) + { + // Number of Past Volumes (I2) + numberOfPastVolumes_ = + util::TryParseNumeric(line.substr(5, 2)); + + // Number of Intervals (I2) + numberOfIntervals_ = + util::TryParseNumeric(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) + auto speed = util::TryParseNumeric(line.substr(3, 4)); + if (speed.has_value()) + { + correlationSpeed_ = + units::velocity::meters_per_second {speed.value()}; + } + + // Error Interval (Minutes) (I2) + auto interval = + util::TryParseNumeric(line.substr(42, 2)); + if (interval.has_value()) + { + errorInterval_ = std::chrono::minutes {interval.value()}; + } + } + // clang-format off + // " 7.0 (KM) FILTER KERNEL SIZE 0.5 THRESH (FILTER FRACTION)" + // clang-format on + if (i == 11 && line.size() >= 77) + { + // Filter Kernel Size (Kilometers) (F4.1) + auto kernelSize = util::TryParseNumeric(line.substr(3, 4)); + if (kernelSize.has_value()) + { + filterKernelSize_ = + units::length::kilometers {kernelSize.value()}; + } + + // Minimum Speed (Threshold) (m/s) (F4.1) + filterFraction_ = util::TryParseNumeric(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") + { + reflectivityFiltered_ = true; + } + else if (line.substr(5, 2) == "No") + { + reflectivityFiltered_ = false; + } + } + } } std::shared_ptr