diff --git a/scwx-qt/source/scwx/qt/model/alert_model.cpp b/scwx-qt/source/scwx/qt/model/alert_model.cpp index 4cadff42..76cd5a5c 100644 --- a/scwx-qt/source/scwx/qt/model/alert_model.cpp +++ b/scwx-qt/source/scwx/qt/model/alert_model.cpp @@ -34,6 +34,10 @@ class AlertModelImpl explicit AlertModelImpl(); ~AlertModelImpl() = default; + bool GetObserved(const types::TextEventKey& key); + awips::ThreatCategory GetThreatCategory(const types::TextEventKey& key); + bool GetTornadoPossible(const types::TextEventKey& key); + static std::string GetCounties(const types::TextEventKey& key); static std::string GetState(const types::TextEventKey& key); static std::chrono::system_clock::time_point @@ -49,6 +53,18 @@ class AlertModelImpl const GeographicLib::Geodesic& geodesic_; + std::unordered_map> + observedMap_; + std::unordered_map> + threatCategoryMap_; + std::unordered_map> + tornadoPossibleMap_; std::unordered_map> @@ -125,6 +141,29 @@ QVariant AlertModel::data(const QModelIndex& index, int role) const return QString::fromStdString( awips::GetSignificanceText(textEventKey.significance_)); + case static_cast(Column::Tornado): + if (textEventKey.phenomenon_ == awips::Phenomenon::Tornado && + p->GetObserved(textEventKey)) + { + return tr("Observed"); + } + if (p->GetTornadoPossible(textEventKey)) + { + return tr("Possible"); + } + break; + + case static_cast(Column::ThreatCategory): + if (role == Qt::DisplayRole) + { + return QString::fromStdString(awips::GetThreatCategoryName( + p->GetThreatCategory(textEventKey))); + } + else + { + return static_cast(p->GetThreatCategory(textEventKey)); + } + case static_cast(Column::State): return QString::fromStdString(AlertModelImpl::GetState(textEventKey)); @@ -204,6 +243,12 @@ AlertModel::headerData(int section, Qt::Orientation orientation, int role) const case static_cast(Column::Significance): return tr("Significance"); + case static_cast(Column::ThreatCategory): + return tr("Category"); + + case static_cast(Column::Tornado): + return tr("Tornado"); + case static_cast(Column::State): return tr("State"); @@ -240,6 +285,14 @@ AlertModel::headerData(int section, Qt::Orientation orientation, int role) const contentsSize = fontMetrics.size(0, QString(10, 'W')); break; + case static_cast(Column::ThreatCategory): + contentsSize = fontMetrics.size(0, QString(6, 'W')); + break; + + case static_cast(Column::Tornado): + contentsSize = fontMetrics.size(0, QString(5, 'W')); + break; + case static_cast(Column::State): contentsSize = fontMetrics.size(0, "WW, WW"); break; @@ -285,6 +338,12 @@ void AlertModel::HandleAlert(const types::TextEventKey& alertKey, std::shared_ptr alertSegment = alertMessages[messageIndex]->segments().back(); + p->observedMap_.insert_or_assign(alertKey, alertSegment->observed_); + p->threatCategoryMap_.insert_or_assign(alertKey, + alertSegment->threatCategory_); + p->tornadoPossibleMap_.insert_or_assign(alertKey, + alertSegment->tornadoPossible_); + if (alertSegment->codedLocation_.has_value()) { // Update centroid and distance @@ -365,6 +424,46 @@ AlertModelImpl::AlertModelImpl() : { } +bool AlertModelImpl::GetObserved(const types::TextEventKey& key) +{ + bool observed = false; + + auto it = observedMap_.find(key); + if (it != observedMap_.cend()) + { + observed = it->second; + } + + return observed; +} + +awips::ThreatCategory +AlertModelImpl::GetThreatCategory(const types::TextEventKey& key) +{ + awips::ThreatCategory threatCategory = awips::ThreatCategory::Base; + + auto it = threatCategoryMap_.find(key); + if (it != threatCategoryMap_.cend()) + { + threatCategory = it->second; + } + + return threatCategory; +} + +bool AlertModelImpl::GetTornadoPossible(const types::TextEventKey& key) +{ + bool tornadoPossible = false; + + auto it = tornadoPossibleMap_.find(key); + if (it != tornadoPossibleMap_.cend()) + { + tornadoPossible = it->second; + } + + return tornadoPossible; +} + std::string AlertModelImpl::GetCounties(const types::TextEventKey& key) { auto messageList = manager::TextEventManager::Instance()->message_list(key); diff --git a/scwx-qt/source/scwx/qt/model/alert_model.hpp b/scwx-qt/source/scwx/qt/model/alert_model.hpp index 76bddf9d..df6d561e 100644 --- a/scwx-qt/source/scwx/qt/model/alert_model.hpp +++ b/scwx-qt/source/scwx/qt/model/alert_model.hpp @@ -21,15 +21,17 @@ class AlertModel : public QAbstractTableModel public: enum class Column : int { - Etn = 0, - OfficeId = 1, - Phenomenon = 2, - Significance = 3, - State = 4, - Counties = 5, - StartTime = 6, - EndTime = 7, - Distance = 8 + Etn = 0, + OfficeId = 1, + Phenomenon = 2, + Significance = 3, + ThreatCategory = 4, + Tornado = 5, + State = 6, + Counties = 7, + StartTime = 8, + EndTime = 9, + Distance = 10 }; explicit AlertModel(QObject* parent = nullptr); diff --git a/wxdata/include/scwx/awips/impact_based_warnings.hpp b/wxdata/include/scwx/awips/impact_based_warnings.hpp new file mode 100644 index 00000000..a7b22288 --- /dev/null +++ b/wxdata/include/scwx/awips/impact_based_warnings.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace scwx +{ +namespace awips +{ + +enum class ThreatCategory : int +{ + Base = 0, + Significant = 1, + Considerable = 2, + Destructive = 3, + Catastrophic = 4, + Unknown +}; + +ThreatCategory GetThreatCategory(const std::string& name); +const std::string& GetThreatCategoryName(ThreatCategory threatCategory); + +} // namespace awips +} // namespace scwx diff --git a/wxdata/include/scwx/awips/text_product_message.hpp b/wxdata/include/scwx/awips/text_product_message.hpp index 80ba4a16..a83f7b82 100644 --- a/wxdata/include/scwx/awips/text_product_message.hpp +++ b/wxdata/include/scwx/awips/text_product_message.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -56,15 +57,16 @@ struct SegmentHeader struct Segment { - std::optional header_; - std::vector productContent_; - std::optional codedLocation_; - std::optional codedMotion_; + std::optional header_ {}; + std::vector productContent_ {}; + std::optional codedLocation_ {}; + std::optional codedMotion_ {}; - Segment() : - header_ {}, productContent_ {}, codedLocation_ {}, codedMotion_ {} - { - } + bool observed_ {false}; + ThreatCategory threatCategory_ {ThreatCategory::Base}; + bool tornadoPossible_ {false}; + + Segment() = default; Segment(const Segment&) = delete; Segment& operator=(const Segment&) = delete; diff --git a/wxdata/source/scwx/awips/impact_based_warnings.cpp b/wxdata/source/scwx/awips/impact_based_warnings.cpp new file mode 100644 index 00000000..75f04d1e --- /dev/null +++ b/wxdata/source/scwx/awips/impact_based_warnings.cpp @@ -0,0 +1,31 @@ +#include +#include + +#include + +#include + +namespace scwx +{ +namespace awips +{ + +static const std::string logPrefix_ = "scwx::awips::impact_based_warnings"; + +static const std::unordered_map + threatCategoryName_ {{ThreatCategory::Base, "Base"}, + {ThreatCategory::Significant, "Significant"}, + {ThreatCategory::Considerable, "Considerable"}, + {ThreatCategory::Destructive, "Destructive"}, + {ThreatCategory::Catastrophic, "Catastrophic"}, + {ThreatCategory::Unknown, "?"}}; + +SCWX_GET_ENUM(ThreatCategory, GetThreatCategory, threatCategoryName_) + +const std::string& GetThreatCategoryName(ThreatCategory threatCategory) +{ + return threatCategoryName_.at(threatCategory); +} + +} // 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 ffbd41dc..cccd9e07 100644 --- a/wxdata/source/scwx/awips/text_product_message.cpp +++ b/wxdata/source/scwx/awips/text_product_message.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -304,6 +305,14 @@ void ParseCodedInformation(std::shared_ptr segment, { typedef std::vector::const_iterator StringIterator; + static constexpr std::size_t kThreatCategoryTagCount = 4; + static const std::array + kThreatCategoryTags {"FLASH FLOOD DAMAGE THREAT...", + "SNOW SQUALL IMPACT...", + "THUNDERSTORM DAMAGE THREAT...", + "TORNADO DAMAGE THREAT..."}; + std::array::const_iterator threatTagIt; + std::vector& productContent = segment->productContent_; StringIterator codedLocationBegin = productContent.cend(); @@ -325,8 +334,8 @@ void ParseCodedInformation(std::shared_ptr segment, codedLocationEnd = it; } - if (codedMotionBegin == productContent.cend() && - it->starts_with("TIME...MOT...LOC")) + else if (codedMotionBegin == productContent.cend() && + it->starts_with("TIME...MOT...LOC")) { codedMotionBegin = it; } @@ -338,6 +347,37 @@ void ParseCodedInformation(std::shared_ptr segment, { codedMotionEnd = it; } + + else if (!segment->observed_ && + it->find("...OBSERVED") != std::string::npos) + { + segment->observed_ = true; + } + + else if (!segment->tornadoPossible_ && *it == "TORNADO...POSSIBLE") + { + segment->tornadoPossible_ = true; + } + + else if (segment->threatCategory_ == ThreatCategory::Base && + (threatTagIt = std::find_if(kThreatCategoryTags.cbegin(), + kThreatCategoryTags.cend(), + [&it](const std::string& tag) { + return it->starts_with(tag); + })) != kThreatCategoryTags.cend() && + it->length() > threatTagIt->length()) + { + const std::string threatCategoryName = + it->substr(threatTagIt->length()); + + ThreatCategory threatCategory = GetThreatCategory(threatCategoryName); + if (threatCategory == ThreatCategory::Unknown) + { + threatCategory = ThreatCategory::Base; + } + + segment->threatCategory_ = threatCategory; + } } if (codedLocationBegin != productContent.cend()) diff --git a/wxdata/wxdata.cmake b/wxdata/wxdata.cmake index 0ce08cbb..47ada181 100644 --- a/wxdata/wxdata.cmake +++ b/wxdata/wxdata.cmake @@ -14,6 +14,7 @@ endif() set(HDR_AWIPS include/scwx/awips/coded_location.hpp include/scwx/awips/coded_time_motion_location.hpp + include/scwx/awips/impact_based_warnings.hpp include/scwx/awips/message.hpp include/scwx/awips/phenomenon.hpp include/scwx/awips/pvtec.hpp @@ -24,6 +25,7 @@ set(HDR_AWIPS include/scwx/awips/coded_location.hpp include/scwx/awips/wmo_header.hpp) set(SRC_AWIPS source/scwx/awips/coded_location.cpp source/scwx/awips/coded_time_motion_location.cpp + source/scwx/awips/impact_based_warnings.cpp source/scwx/awips/message.cpp source/scwx/awips/phenomenon.cpp source/scwx/awips/pvtec.cpp