diff --git a/test/source/scwx/awips/pvtec.test.cpp b/test/source/scwx/awips/pvtec.test.cpp new file mode 100644 index 00000000..23b21b7b --- /dev/null +++ b/test/source/scwx/awips/pvtec.test.cpp @@ -0,0 +1,131 @@ +#include + +#include + +#include + +namespace scwx +{ +namespace awips +{ + +static const std::string logPrefix_ = "[scwx::awips::pvtec.test] "; + +std::pair> +GetDateTime(std::chrono::system_clock::time_point t); + +TEST(PVtec, FlashFloodWarningExtended) +{ + using namespace std::chrono; + + PVtec pvtec; + std::string s = "/O.EXT.KJAN.FF.W.0023.000000T0000Z-210606T1700Z/"; + + pvtec.Parse(s); + + auto eventBegin = GetDateTime(pvtec.event_begin()); + auto eventEnd = GetDateTime(pvtec.event_end()); + + EXPECT_EQ(pvtec.fixed_identifier(), PVtec::ProductType::Operational); + EXPECT_EQ(pvtec.action(), PVtec::Action::ExtendedInTime); + EXPECT_EQ(pvtec.office_id(), "KJAN"); + EXPECT_EQ(pvtec.phenomenon(), Phenomenon::FlashFlood); + EXPECT_EQ(pvtec.significance(), Significance::Warning); + EXPECT_EQ(pvtec.event_tracking_number(), 23); + + EXPECT_EQ(pvtec.event_begin(), std::chrono::system_clock::time_point {}); + EXPECT_EQ(eventBegin.first.year(), 1970y); + EXPECT_EQ(eventBegin.first.month(), month {1}); + EXPECT_EQ(eventBegin.first.day(), 1d); + EXPECT_EQ(eventBegin.second.hours(), 0h); + EXPECT_EQ(eventBegin.second.minutes(), 0min); + + EXPECT_EQ(eventEnd.first.year(), 2021y); + EXPECT_EQ(eventEnd.first.month(), month {6}); + EXPECT_EQ(eventEnd.first.day(), 6d); + EXPECT_EQ(eventEnd.second.hours(), 17h); + EXPECT_EQ(eventEnd.second.minutes(), 0min); +} + +TEST(PVtec, TornadoWarningNew) +{ + using namespace std::chrono; + + PVtec pvtec; + std::string s = "/O.NEW.KLIX.TO.W.0032.210606T1501Z-210606T1600Z/"; + + pvtec.Parse(s); + + auto eventBegin = GetDateTime(pvtec.event_begin()); + auto eventEnd = GetDateTime(pvtec.event_end()); + + EXPECT_EQ(pvtec.fixed_identifier(), PVtec::ProductType::Operational); + EXPECT_EQ(pvtec.action(), PVtec::Action::New); + EXPECT_EQ(pvtec.office_id(), "KLIX"); + EXPECT_EQ(pvtec.phenomenon(), Phenomenon::Tornado); + EXPECT_EQ(pvtec.significance(), Significance::Warning); + EXPECT_EQ(pvtec.event_tracking_number(), 32); + + EXPECT_EQ(eventBegin.first.year(), 2021y); + EXPECT_EQ(eventBegin.first.month(), month {6}); + EXPECT_EQ(eventBegin.first.day(), 6d); + EXPECT_EQ(eventBegin.second.hours(), 15h); + EXPECT_EQ(eventBegin.second.minutes(), 1min); + + EXPECT_EQ(eventEnd.first.year(), 2021y); + EXPECT_EQ(eventEnd.first.month(), month {6}); + EXPECT_EQ(eventEnd.first.day(), 6d); + EXPECT_EQ(eventEnd.second.hours(), 16h); + EXPECT_EQ(eventEnd.second.minutes(), 0min); +} + +TEST(PVtec, TornadoWarningContinued) +{ + using namespace std::chrono; + + PVtec pvtec; + std::string s = "/O.CON.KLIX.TO.W.0032.000000T0000Z-210606T1600Z/"; + + pvtec.Parse(s); + + auto eventBegin = GetDateTime(pvtec.event_begin()); + auto eventEnd = GetDateTime(pvtec.event_end()); + + EXPECT_EQ(pvtec.fixed_identifier(), PVtec::ProductType::Operational); + EXPECT_EQ(pvtec.action(), PVtec::Action::Continued); + EXPECT_EQ(pvtec.office_id(), "KLIX"); + EXPECT_EQ(pvtec.phenomenon(), Phenomenon::Tornado); + EXPECT_EQ(pvtec.significance(), Significance::Warning); + EXPECT_EQ(pvtec.event_tracking_number(), 32); + + EXPECT_EQ(pvtec.event_begin(), std::chrono::system_clock::time_point {}); + EXPECT_EQ(eventBegin.first.year(), 1970y); + EXPECT_EQ(eventBegin.first.month(), month {1}); + EXPECT_EQ(eventBegin.first.day(), 1d); + EXPECT_EQ(eventBegin.second.hours(), 0h); + EXPECT_EQ(eventBegin.second.minutes(), 0min); + + EXPECT_EQ(eventEnd.first.year(), 2021y); + EXPECT_EQ(eventEnd.first.month(), month {6}); + EXPECT_EQ(eventEnd.first.day(), 6d); + EXPECT_EQ(eventEnd.second.hours(), 16h); + EXPECT_EQ(eventEnd.second.minutes(), 0min); +} + +std::pair> +GetDateTime(std::chrono::system_clock::time_point t) +{ + using namespace std::chrono; + + auto tDays = floor(t); + auto tDate = year_month_day {tDays}; + auto tMinutes = floor(t - tDays); + auto tTime = hh_mm_ss {tMinutes}; + + return std::make_pair(tDate, tTime); +} + +} // namespace awips +} // namespace scwx diff --git a/test/test.cmake b/test/test.cmake index 03a62516..e776fcef 100644 --- a/test/test.cmake +++ b/test/test.cmake @@ -8,7 +8,8 @@ find_package(BZip2) find_package(GTest) set(SRC_MAIN source/scwx/wxtest.cpp) -set(SRC_AWIPS_TESTS source/scwx/awips/text_product_file.test.cpp) +set(SRC_AWIPS_TESTS source/scwx/awips/pvtec.test.cpp + source/scwx/awips/text_product_file.test.cpp) set(SRC_COMMON_TESTS source/scwx/common/color_table.test.cpp) set(SRC_QT_MANAGER_TESTS source/scwx/qt/manager/settings_manager.test.cpp) set(SRC_UTIL_TESTS source/scwx/util/rangebuf.test.cpp diff --git a/wxdata/include/scwx/awips/phenomenon.hpp b/wxdata/include/scwx/awips/phenomenon.hpp new file mode 100644 index 00000000..376241cd --- /dev/null +++ b/wxdata/include/scwx/awips/phenomenon.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include + +namespace scwx +{ +namespace awips +{ + +enum class Phenomenon +{ + AshfallLand, + AirStagnation, + BeachHazard, + BriskWind, + Blizzard, + CoastalFlood, + DebrisFlow, + DustStorm, + BlowingDust, + ExtremeCold, + ExcessiveHeat, + ExtremeWind, + Flood, + FlashFlood, + DenseFogLand, + FloodForecastPoints, + Frost, + FireWeather, + Freeze, + Gale, + HurricaneForceWind, + Heat, + Hurricane, + HighWind, + Hydrologic, + HardFreeze, + IceStorm, + LakeEffectSnow, + LowWater, + LakeshoreFlood, + LakeWind, + Marine, + DenseFogMarine, + AshfallMarine, + DenseSmokeMarine, + RipCurrentRisk, + SmallCraft, + HazardousSeas, + DenseSmokeLand, + Storm, + StormSurge, + SnowSquall, + HighSurf, + SevereThunderstorm, + Tornado, + TropicalStorm, + Tsunami, + Typhoon, + HeavyFreezingSpray, + WindChill, + Wind, + WinterStorm, + WinterWeather, + FreezingFog, + FreezingRain, + FreezingSpray, + Unknown +}; + +Phenomenon GetPhenomenon(const std::string& code); +std::string GetPhenomenonCode(Phenomenon phenomenon); +std::string GetPhenomenonText(Phenomenon phenomenon); + +} // namespace awips +} // namespace scwx diff --git a/wxdata/include/scwx/awips/pvtec.hpp b/wxdata/include/scwx/awips/pvtec.hpp new file mode 100644 index 00000000..405fe9ac --- /dev/null +++ b/wxdata/include/scwx/awips/pvtec.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include +#include + +#include +#include + +namespace scwx +{ +namespace awips +{ + +class PVtecImpl; + +class PVtec +{ +public: + enum class ProductType + { + Operational, + Test, + Experimental, + OperationalWithExperimentalVtec, + Unknown + }; + + enum class Action + { + New, + Continued, + ExtendedInArea, + ExtendedInTime, + ExtendedInAreaAndTime, + Upgraded, + Canceled, + Expired, + Routine, + Correction, + Unknown + }; + + explicit PVtec(); + ~PVtec(); + + PVtec(const PVtec&) = delete; + PVtec& operator=(const PVtec&) = delete; + + PVtec(PVtec&&) noexcept; + PVtec& operator=(PVtec&&) noexcept; + + ProductType fixed_identifier() const; + Action action() const; + std::string office_id() const; + Phenomenon phenomenon() const; + Significance significance() const; + int16_t event_tracking_number() const; + + std::chrono::system_clock::time_point event_begin() const; + std::chrono::system_clock::time_point event_end() const; + + bool Parse(const std::string& s); + + static ProductType GetProductType(const std::string& code); + static std::string GetProductTypeCode(ProductType productType); + + static Action GetAction(const std::string& code); + static std::string GetActionCode(Action action); + +private: + std::unique_ptr p; +}; + +} // namespace awips +} // namespace scwx diff --git a/wxdata/include/scwx/awips/significance.hpp b/wxdata/include/scwx/awips/significance.hpp new file mode 100644 index 00000000..f2250300 --- /dev/null +++ b/wxdata/include/scwx/awips/significance.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +namespace scwx +{ +namespace awips +{ + +enum class Significance +{ + Warning, + Watch, + Advisory, + Statement, + Forecast, + Outlook, + Synopsis, + Unknown +}; + +Significance GetSignificance(const std::string& code); +std::string GetSignificanceCode(Significance significance); +std::string GetSignificanceText(Significance significance); + +} // namespace awips +} // namespace scwx diff --git a/wxdata/source/scwx/awips/phenomenon.cpp b/wxdata/source/scwx/awips/phenomenon.cpp new file mode 100644 index 00000000..4e14c061 --- /dev/null +++ b/wxdata/source/scwx/awips/phenomenon.cpp @@ -0,0 +1,168 @@ +#include + +#include +#include +#include +#include + +namespace scwx +{ +namespace awips +{ + +static const std::string logPrefix_ = "[scwx::awips::phenomenon] "; + +typedef boost::bimap, + boost::bimaps::unordered_set_of> + PhenomenonCodesBimap; + +static const PhenomenonCodesBimap phenomenonCodes_ = + boost::assign::list_of // + (Phenomenon::AshfallLand, "AF") // + (Phenomenon::AirStagnation, "AS") // + (Phenomenon::BeachHazard, "BH") // + (Phenomenon::BriskWind, "BW") // + (Phenomenon::Blizzard, "BZ") // + (Phenomenon::CoastalFlood, "CF") // + (Phenomenon::DebrisFlow, "DF") // + (Phenomenon::DustStorm, "DS") // + (Phenomenon::BlowingDust, "DU") // + (Phenomenon::ExtremeCold, "EC") // + (Phenomenon::ExcessiveHeat, "EH") // + (Phenomenon::ExtremeWind, "EW") // + (Phenomenon::Flood, "FA") // + (Phenomenon::FlashFlood, "FF") // + (Phenomenon::DenseFogLand, "FG") // + (Phenomenon::FloodForecastPoints, "FL") // + (Phenomenon::Frost, "FR") // + (Phenomenon::FireWeather, "FW") // + (Phenomenon::Freeze, "FZ") // + (Phenomenon::Gale, "GL") // + (Phenomenon::HurricaneForceWind, "HF") // + (Phenomenon::Heat, "HT") // + (Phenomenon::Hurricane, "HU") // + (Phenomenon::HighWind, "HW") // + (Phenomenon::Hydrologic, "HY") // + (Phenomenon::HardFreeze, "HZ") // + (Phenomenon::IceStorm, "IS") // + (Phenomenon::LakeEffectSnow, "LE") // + (Phenomenon::LowWater, "LO") // + (Phenomenon::LakeshoreFlood, "LS") // + (Phenomenon::LakeWind, "LW") // + (Phenomenon::Marine, "MA") // + (Phenomenon::DenseFogMarine, "MF") // + (Phenomenon::AshfallMarine, "MH") // + (Phenomenon::DenseSmokeMarine, "MS") // + (Phenomenon::RipCurrentRisk, "RP") // + (Phenomenon::SmallCraft, "SC") // + (Phenomenon::HazardousSeas, "SE") // + (Phenomenon::DenseSmokeLand, "SM") // + (Phenomenon::Storm, "SR") // + (Phenomenon::StormSurge, "SS") // + (Phenomenon::SnowSquall, "SQ") // + (Phenomenon::HighSurf, "SU") // + (Phenomenon::SevereThunderstorm, "SV") // + (Phenomenon::Tornado, "TO") // + (Phenomenon::TropicalStorm, "TR") // + (Phenomenon::Tsunami, "TS") // + (Phenomenon::Typhoon, "TY") // + (Phenomenon::HeavyFreezingSpray, "UP") // + (Phenomenon::WindChill, "WC") // + (Phenomenon::Wind, "WI") // + (Phenomenon::WinterStorm, "WS") // + (Phenomenon::WinterWeather, "WW") // + (Phenomenon::FreezingFog, "ZF") // + (Phenomenon::FreezingRain, "ZR") // + (Phenomenon::FreezingSpray, "ZY") // + (Phenomenon::Unknown, "??"); + +static const std::unordered_map phenomenonText_ { + {Phenomenon::AshfallLand, "Ashfall (land)"}, // + {Phenomenon::AirStagnation, "Air Stagnation"}, // + {Phenomenon::BeachHazard, "Beach Hazard"}, // + {Phenomenon::BriskWind, "Brisk Wind"}, // + {Phenomenon::Blizzard, "Blizzard"}, // + {Phenomenon::CoastalFlood, "Coastal Flood"}, // + {Phenomenon::DebrisFlow, "Debris Flow"}, // + {Phenomenon::DustStorm, "Dust Storm"}, // + {Phenomenon::BlowingDust, "Blowing Dust"}, // + {Phenomenon::ExtremeCold, "Extreme Cold"}, // + {Phenomenon::ExcessiveHeat, "Excessive Heat"}, // + {Phenomenon::ExtremeWind, "Extreme Wind"}, // + {Phenomenon::Flood, "Flood"}, // + {Phenomenon::FlashFlood, "Flash Flood"}, // + {Phenomenon::DenseFogLand, "Dense Fog (land)"}, // + {Phenomenon::Flood, "Flood (Forecast Points)"}, // + {Phenomenon::Frost, "Frost"}, // + {Phenomenon::FireWeather, "Fire Weather"}, // + {Phenomenon::Freeze, "Freeze"}, // + {Phenomenon::Gale, "Gale"}, // + {Phenomenon::HurricaneForceWind, "Hurricane Force Wind"}, // + {Phenomenon::Heat, "Heat"}, // + {Phenomenon::Hurricane, "Hurricane"}, // + {Phenomenon::HighWind, "High Wind"}, // + {Phenomenon::Hydrologic, "Hydrologic"}, // + {Phenomenon::HardFreeze, "Hard Freeze"}, // + {Phenomenon::IceStorm, "Ice Storm"}, // + {Phenomenon::LakeEffectSnow, "Lake Effect Snow"}, // + {Phenomenon::LowWater, "Low Water"}, // + {Phenomenon::LakeshoreFlood, "Lakeshore Flood"}, // + {Phenomenon::LakeWind, "Lake Wind"}, // + {Phenomenon::Marine, "Marine"}, // + {Phenomenon::DenseFogMarine, "Dense Fog (marine)"}, // + {Phenomenon::AshfallMarine, "Ashfall (marine)"}, // + {Phenomenon::DenseSmokeMarine, "Dense Smoke (marine)"}, // + {Phenomenon::RipCurrentRisk, "Rip Current Risk"}, // + {Phenomenon::SmallCraft, "Small Craft"}, // + {Phenomenon::HazardousSeas, "Hazardous Seas"}, // + {Phenomenon::DenseSmokeLand, "Dense Smoke (land)"}, // + {Phenomenon::Storm, "Storm"}, // + {Phenomenon::StormSurge, "Storm Surge"}, // + {Phenomenon::SnowSquall, "Snow Squall"}, // + {Phenomenon::HighSurf, "High Surf"}, // + {Phenomenon::SevereThunderstorm, "Severe Thunderstorm"}, // + {Phenomenon::Tornado, "Tornado"}, // + {Phenomenon::TropicalStorm, "Tropical Storm"}, // + {Phenomenon::Tsunami, "Tsunami"}, // + {Phenomenon::Typhoon, "Typhoon"}, // + {Phenomenon::HeavyFreezingSpray, "Heavy Freezing Spray"}, // + {Phenomenon::WindChill, "Wind Chill"}, // + {Phenomenon::Wind, "Wind"}, // + {Phenomenon::WinterStorm, "Winter Storm"}, // + {Phenomenon::WinterWeather, "Winter Weather"}, // + {Phenomenon::FreezingFog, "Freezing Fog"}, // + {Phenomenon::FreezingRain, "Freezing Rain"}, // + {Phenomenon::FreezingSpray, "Freezing Spray"}, // + {Phenomenon::Unknown, "Unknown"}}; + +Phenomenon GetPhenomenon(const std::string& code) +{ + Phenomenon phenomenon; + + if (phenomenonCodes_.right.find(code) != phenomenonCodes_.right.end()) + { + phenomenon = phenomenonCodes_.right.at(code); + } + else + { + phenomenon = Phenomenon::Unknown; + + BOOST_LOG_TRIVIAL(debug) + << logPrefix_ << "Unrecognized code: \"" << code << "\""; + } + + return phenomenon; +} + +std::string GetPhenomenonCode(Phenomenon phenomenon) +{ + return phenomenonCodes_.left.at(phenomenon); +} + +std::string GetPhenomenonText(Phenomenon phenomenon) +{ + return phenomenonText_.at(phenomenon); +} + +} // namespace awips +} // namespace scwx diff --git a/wxdata/source/scwx/awips/pvtec.cpp b/wxdata/source/scwx/awips/pvtec.cpp new file mode 100644 index 00000000..4e5f20e5 --- /dev/null +++ b/wxdata/source/scwx/awips/pvtec.cpp @@ -0,0 +1,275 @@ +// Enable chrono formatters +#ifndef __cpp_lib_format +# define __cpp_lib_format 202110L +#endif + +#include + +#include + +#include +#include +#include +#include + +namespace scwx +{ +namespace awips +{ + +static const std::string logPrefix_ = "[scwx::awips::pvtec] "; + +typedef boost::bimap, + boost::bimaps::unordered_set_of> + ProductTypeCodesBimap; + +static const ProductTypeCodesBimap productTypeCodes_ = + boost::assign::list_of // + (PVtec::ProductType::Operational, "O") // + (PVtec::ProductType::Test, "T") // + (PVtec::ProductType::Experimental, "E") // + (PVtec::ProductType::OperationalWithExperimentalVtec, "X") // + (PVtec::ProductType::Unknown, "??"); + +typedef boost::bimap, + boost::bimaps::unordered_set_of> + ActionCodesBimap; + +static const ActionCodesBimap actionCodes_ = + boost::assign::list_of // + (PVtec::Action::New, "NEW") // + (PVtec::Action::Continued, "CON") // + (PVtec::Action::ExtendedInArea, "EXA") // + (PVtec::Action::ExtendedInTime, "EXT") // + (PVtec::Action::ExtendedInAreaAndTime, "EXB") // + (PVtec::Action::Upgraded, "UPG") // + (PVtec::Action::Canceled, "CAN") // + (PVtec::Action::Expired, "EXP") // + (PVtec::Action::Routine, "ROU") // + (PVtec::Action::Correction, "COR") // + (PVtec::Action::Unknown, "???"); + +class PVtecImpl +{ +public: + explicit PVtecImpl() : + pVtecString_ {}, + fixedIdentifier_ {PVtec::ProductType::Unknown}, + action_ {PVtec::Action::Unknown}, + officeId_ {"????"}, + phenomenon_ {Phenomenon::Unknown}, + significance_ {Significance::Unknown}, + eventTrackingNumber_ {-1}, + eventBegin_ {}, + eventEnd_ {}, + valid_ {false} + { + } + + ~PVtecImpl() {} + + std::string pVtecString_; + + PVtec::ProductType fixedIdentifier_; + PVtec::Action action_; + std::string officeId_; + Phenomenon phenomenon_; + Significance significance_; + int16_t eventTrackingNumber_; + + std::chrono::system_clock::time_point eventBegin_; + std::chrono::system_clock::time_point eventEnd_; + + bool valid_; +}; + +PVtec::PVtec() : p(std::make_unique()) {} +PVtec::~PVtec() = default; + +PVtec::PVtec(PVtec&&) noexcept = default; +PVtec& PVtec::operator=(PVtec&&) noexcept = default; + +PVtec::ProductType PVtec::fixed_identifier() const +{ + return p->fixedIdentifier_; +} + +PVtec::Action PVtec::action() const +{ + return p->action_; +} + +std::string PVtec::office_id() const +{ + return p->officeId_; +} + +Phenomenon PVtec::phenomenon() const +{ + return p->phenomenon_; +} + +Significance PVtec::significance() const +{ + return p->significance_; +} + +int16_t PVtec::event_tracking_number() const +{ + return p->eventTrackingNumber_; +} + +std::chrono::system_clock::time_point PVtec::event_begin() const +{ + return p->eventBegin_; +} + +std::chrono::system_clock::time_point PVtec::event_end() const +{ + return p->eventEnd_; +} + +bool PVtec::Parse(const std::string& s) +{ + using namespace std::chrono; + + // P-VTEC takes the form: + // /k.aaa.cccc.pp.s.####.yymmddThhnnZ-yymmddThhnnZ/ + // 012345678901234567890123456789012345678901234567 + static constexpr size_t pVtecLength_ = 48u; + static constexpr size_t pVtecOffsetStart_ = 0u; + static constexpr size_t pVtecOffsetIdentifier_ = 1u; + static constexpr size_t pVtecOffsetAction_ = 3u; + static constexpr size_t pVtecOffsetOfficeId_ = 7u; + static constexpr size_t pVtecOffsetPhenomenon_ = 12u; + static constexpr size_t pVtecOffsetSignificance_ = 15u; + static constexpr size_t pVtecOffsetEventNumber_ = 17u; + static constexpr size_t pVtecOffsetEventBegin_ = 22u; + static constexpr size_t pVtecOffsetEventEnd_ = 35u; + static constexpr size_t pVtecOffsetEnd_ = 47u; + + bool dataValid = (s.length() >= pVtecLength_ && // + s.at(pVtecOffsetStart_) == '/' && // + s.at(pVtecOffsetEnd_) == '/'); + + if (dataValid) + { + p->pVtecString_ = s.substr(0, pVtecLength_); + + p->fixedIdentifier_ = GetProductType(s.substr(pVtecOffsetIdentifier_, 1)); + p->action_ = GetAction(s.substr(pVtecOffsetAction_, 3)); + p->officeId_ = s.substr(pVtecOffsetOfficeId_, 4); + p->phenomenon_ = GetPhenomenon(s.substr(pVtecOffsetPhenomenon_, 2)); + p->significance_ = GetSignificance(s.substr(pVtecOffsetSignificance_, 1)); + + std::string eventNumberString = s.substr(pVtecOffsetEventNumber_, 4); + + try + { + p->eventTrackingNumber_ = + static_cast(std::stoi(eventNumberString)); + } + catch (const std::exception& ex) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Error parsing event tracking number: \"" + << eventNumberString << "\" (" << ex.what() << ")"; + + p->eventTrackingNumber_ = -1; + } + + static const std::string dateTimeFormat {"%y%m%dT%H%MZ"}; + + std::string sEventBegin = s.substr(pVtecOffsetEventBegin_, 12); + std::string sEventEnd = s.substr(pVtecOffsetEventEnd_, 12); + + std::istringstream ssEventBegin {sEventBegin}; + std::istringstream ssEventEnd {sEventEnd}; + + sys_time eventBegin; + sys_time eventEnd; + + ssEventBegin >> parse(dateTimeFormat, eventBegin); + ssEventEnd >> parse(dateTimeFormat, eventEnd); + + if (!ssEventBegin.fail()) + { + p->eventBegin_ = eventBegin; + } + else + { + // Time parsing expected to fail if time is "000000T0000Z" + p->eventBegin_ = {}; + } + + if (!ssEventEnd.fail()) + { + p->eventEnd_ = eventEnd; + } + else + { + // Time parsing expected to fail if time is "000000T0000Z" + p->eventEnd_ = {}; + } + } + else + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Invalid P-VTEC: \"" << s << "\""; + } + + p->valid_ = dataValid; + + return dataValid; +} + +PVtec::ProductType PVtec::GetProductType(const std::string& code) +{ + ProductType productType; + + if (productTypeCodes_.right.find(code) != productTypeCodes_.right.end()) + { + productType = productTypeCodes_.right.at(code); + } + else + { + productType = ProductType::Unknown; + + BOOST_LOG_TRIVIAL(debug) + << logPrefix_ << "Unrecognized product code: \"" << code << "\""; + } + + return productType; +} + +std::string PVtec::GetProductTypeCode(PVtec::ProductType productType) +{ + return productTypeCodes_.left.at(productType); +} + +PVtec::Action PVtec::GetAction(const std::string& code) +{ + Action action; + + if (actionCodes_.right.find(code) != actionCodes_.right.end()) + { + action = actionCodes_.right.at(code); + } + else + { + action = Action::Unknown; + + BOOST_LOG_TRIVIAL(debug) + << logPrefix_ << "Unrecognized action code: \"" << code << "\""; + } + + return action; +} + +std::string PVtec::GetActionCode(PVtec::Action action) +{ + return actionCodes_.left.at(action); +} + +} // namespace awips +} // namespace scwx diff --git a/wxdata/source/scwx/awips/significance.cpp b/wxdata/source/scwx/awips/significance.cpp new file mode 100644 index 00000000..a4a29e7c --- /dev/null +++ b/wxdata/source/scwx/awips/significance.cpp @@ -0,0 +1,70 @@ +#include + +#include +#include +#include +#include + +namespace scwx +{ +namespace awips +{ + +static const std::string logPrefix_ = "[scwx::awips::significance] "; + +typedef boost::bimap, + boost::bimaps::unordered_set_of> + SignificanceCodesBimap; + +static const SignificanceCodesBimap significanceCodes_ = + boost::assign::list_of // + (Significance::Warning, "W") // + (Significance::Watch, "A") // + (Significance::Advisory, "Y") // + (Significance::Statement, "S") // + (Significance::Forecast, "F") // + (Significance::Outlook, "O") // + (Significance::Synopsis, "N") // + (Significance::Unknown, "?"); + +static const std::unordered_map significanceText_ { + {Significance::Warning, "Warning"}, // + {Significance::Watch, "Watch"}, // + {Significance::Advisory, "Advisory"}, // + {Significance::Statement, "Statement"}, // + {Significance::Forecast, "Forecast"}, // + {Significance::Outlook, "Outlook"}, // + {Significance::Synopsis, "Synopsis"}, // + {Significance::Unknown, "Unknown"}}; + +Significance GetSignificance(const std::string& code) +{ + Significance significance; + + if (significanceCodes_.right.find(code) != significanceCodes_.right.end()) + { + significance = significanceCodes_.right.at(code); + } + else + { + significance = Significance::Unknown; + + BOOST_LOG_TRIVIAL(debug) + << logPrefix_ << "Unrecognized code: \"" << code << "\""; + } + + return significance; +} + +std::string GetSignificanceCode(Significance significance) +{ + return significanceCodes_.left.at(significance); +} + +std::string GetSignificanceText(Significance significance) +{ + return significanceText_.at(significance); +} + +} // namespace awips +} // namespace scwx diff --git a/wxdata/source/scwx/awips/text_product_message.cpp b/wxdata/source/scwx/awips/text_product_message.cpp index 1b7bf749..7a3ab4ae 100644 --- a/wxdata/source/scwx/awips/text_product_message.cpp +++ b/wxdata/source/scwx/awips/text_product_message.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -26,7 +27,7 @@ static const std::regex reDateTimeString {"^[0-9]{3,4} ([AP]M|UTC)"}; struct Vtec { - std::string pVtec_; + PVtec pVtec_; std::string hVtec_; Vtec() : pVtec_ {}, hVtec_ {} {} @@ -330,7 +331,7 @@ std::optional TryParseVtecString(std::istream& is) if (std::regex_search(line, rePVtecString)) { vtec = Vtec(); - vtec->pVtec_.swap(line); + vtec->pVtec_.Parse(line); isBegin = is.tellg(); @@ -342,8 +343,8 @@ std::optional TryParseVtecString(std::istream& is) } else { - // H-VTEC was not found, so reset the istream to the beginning of the - // line + // H-VTEC was not found, so reset the istream to the beginning of + // the line is.seekg(isBegin, std::ios_base::beg); } } diff --git a/wxdata/wxdata.cmake b/wxdata/wxdata.cmake index e67ca419..a529a836 100644 --- a/wxdata/wxdata.cmake +++ b/wxdata/wxdata.cmake @@ -3,10 +3,16 @@ project(scwx-data) find_package(Boost) set(HDR_AWIPS include/scwx/awips/message.hpp + include/scwx/awips/phenomenon.hpp + include/scwx/awips/pvtec.hpp + include/scwx/awips/significance.hpp include/scwx/awips/text_product_file.hpp include/scwx/awips/text_product_message.hpp include/scwx/awips/wmo_header.hpp) set(SRC_AWIPS source/scwx/awips/message.cpp + source/scwx/awips/phenomenon.cpp + source/scwx/awips/pvtec.cpp + source/scwx/awips/significance.cpp source/scwx/awips/text_product_file.cpp source/scwx/awips/text_product_message.cpp source/scwx/awips/wmo_header.cpp)