// 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