From 9437b0bfceb8f908229a01722568c1ef6b838889 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 3 Jun 2024 00:09:32 -0500 Subject: [PATCH 1/4] Look for specific impact based warning tags in text products --- .../scwx/awips/impact_based_warnings.hpp | 24 ++++++++++ .../scwx/awips/text_product_message.hpp | 18 ++++---- .../scwx/awips/impact_based_warnings.cpp | 31 +++++++++++++ .../scwx/awips/text_product_message.cpp | 44 ++++++++++++++++++- wxdata/wxdata.cmake | 2 + 5 files changed, 109 insertions(+), 10 deletions(-) create mode 100644 wxdata/include/scwx/awips/impact_based_warnings.hpp create mode 100644 wxdata/source/scwx/awips/impact_based_warnings.cpp 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..ff64bb99 --- /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 +{ + Base, + Significant, + Considerable, + Destructive, + Catastrophic, + 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 From 49ba9b905b29063082ec4b4c894355f3b42b4377 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 18 Jun 2024 00:33:22 -0500 Subject: [PATCH 2/4] Add threat category to alert dock widget --- scwx-qt/source/scwx/qt/model/alert_model.cpp | 41 +++++++++++++++++++ scwx-qt/source/scwx/qt/model/alert_model.hpp | 19 +++++---- .../scwx/awips/impact_based_warnings.hpp | 12 +++--- 3 files changed, 57 insertions(+), 15 deletions(-) diff --git a/scwx-qt/source/scwx/qt/model/alert_model.cpp b/scwx-qt/source/scwx/qt/model/alert_model.cpp index 4cadff42..18ff2055 100644 --- a/scwx-qt/source/scwx/qt/model/alert_model.cpp +++ b/scwx-qt/source/scwx/qt/model/alert_model.cpp @@ -34,6 +34,8 @@ class AlertModelImpl explicit AlertModelImpl(); ~AlertModelImpl() = default; + awips::ThreatCategory GetThreatCategory(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 +51,10 @@ class AlertModelImpl const GeographicLib::Geodesic& geodesic_; + std::unordered_map> + threatCategoryMap_; std::unordered_map> @@ -125,6 +131,17 @@ QVariant AlertModel::data(const QModelIndex& index, int role) const return QString::fromStdString( awips::GetSignificanceText(textEventKey.significance_)); + 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 +221,9 @@ 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::State): return tr("State"); @@ -240,6 +260,10 @@ 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::State): contentsSize = fontMetrics.size(0, "WW, WW"); break; @@ -285,6 +309,9 @@ void AlertModel::HandleAlert(const types::TextEventKey& alertKey, std::shared_ptr alertSegment = alertMessages[messageIndex]->segments().back(); + p->threatCategoryMap_.insert_or_assign(alertKey, + alertSegment->threatCategory_); + if (alertSegment->codedLocation_.has_value()) { // Update centroid and distance @@ -365,6 +392,20 @@ AlertModelImpl::AlertModelImpl() : { } +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; +} + 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..71fb0dfb 100644 --- a/scwx-qt/source/scwx/qt/model/alert_model.hpp +++ b/scwx-qt/source/scwx/qt/model/alert_model.hpp @@ -21,15 +21,16 @@ 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, + State = 5, + Counties = 6, + StartTime = 7, + EndTime = 8, + Distance = 9 }; 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 index ff64bb99..a7b22288 100644 --- a/wxdata/include/scwx/awips/impact_based_warnings.hpp +++ b/wxdata/include/scwx/awips/impact_based_warnings.hpp @@ -7,13 +7,13 @@ namespace scwx namespace awips { -enum class ThreatCategory +enum class ThreatCategory : int { - Base, - Significant, - Considerable, - Destructive, - Catastrophic, + Base = 0, + Significant = 1, + Considerable = 2, + Destructive = 3, + Catastrophic = 4, Unknown }; From f0347da923ce2a5f30821cf403dac42392b12a1f Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 26 Jun 2024 22:46:54 -0500 Subject: [PATCH 3/4] Add tornado possible to alert dock widget --- scwx-qt/source/scwx/qt/model/alert_model.cpp | 34 ++++++++++++++++++++ scwx-qt/source/scwx/qt/model/alert_model.hpp | 21 ++++++------ 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/scwx-qt/source/scwx/qt/model/alert_model.cpp b/scwx-qt/source/scwx/qt/model/alert_model.cpp index 18ff2055..fd316c1d 100644 --- a/scwx-qt/source/scwx/qt/model/alert_model.cpp +++ b/scwx-qt/source/scwx/qt/model/alert_model.cpp @@ -35,6 +35,7 @@ class AlertModelImpl ~AlertModelImpl() = default; 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); @@ -55,6 +56,10 @@ class AlertModelImpl awips::ThreatCategory, types::TextEventHash> threatCategoryMap_; + std::unordered_map> + tornadoPossibleMap_; std::unordered_map> @@ -131,6 +136,13 @@ QVariant AlertModel::data(const QModelIndex& index, int role) const return QString::fromStdString( awips::GetSignificanceText(textEventKey.significance_)); + case static_cast(Column::TornadoPossible): + if (p->GetTornadoPossible(textEventKey)) + { + return tr("Possible"); + } + break; + case static_cast(Column::ThreatCategory): if (role == Qt::DisplayRole) { @@ -224,6 +236,9 @@ AlertModel::headerData(int section, Qt::Orientation orientation, int role) const case static_cast(Column::ThreatCategory): return tr("Category"); + case static_cast(Column::TornadoPossible): + return tr("Tornado"); + case static_cast(Column::State): return tr("State"); @@ -264,6 +279,10 @@ AlertModel::headerData(int section, Qt::Orientation orientation, int role) const contentsSize = fontMetrics.size(0, QString(6, 'W')); break; + case static_cast(Column::TornadoPossible): + contentsSize = fontMetrics.size(0, QString(4, 'W')); + break; + case static_cast(Column::State): contentsSize = fontMetrics.size(0, "WW, WW"); break; @@ -311,6 +330,8 @@ void AlertModel::HandleAlert(const types::TextEventKey& alertKey, p->threatCategoryMap_.insert_or_assign(alertKey, alertSegment->threatCategory_); + p->tornadoPossibleMap_.insert_or_assign(alertKey, + alertSegment->tornadoPossible_); if (alertSegment->codedLocation_.has_value()) { @@ -406,6 +427,19 @@ AlertModelImpl::GetThreatCategory(const types::TextEventKey& key) 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 71fb0dfb..6654dcb1 100644 --- a/scwx-qt/source/scwx/qt/model/alert_model.hpp +++ b/scwx-qt/source/scwx/qt/model/alert_model.hpp @@ -21,16 +21,17 @@ class AlertModel : public QAbstractTableModel public: enum class Column : int { - Etn = 0, - OfficeId = 1, - Phenomenon = 2, - Significance = 3, - ThreatCategory = 4, - State = 5, - Counties = 6, - StartTime = 7, - EndTime = 8, - Distance = 9 + Etn = 0, + OfficeId = 1, + Phenomenon = 2, + Significance = 3, + ThreatCategory = 4, + TornadoPossible = 5, + State = 6, + Counties = 7, + StartTime = 8, + EndTime = 9, + Distance = 10 }; explicit AlertModel(QObject* parent = nullptr); From c80b26be22e330c34a36e77d6f9c648bd7a0817b Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 28 Jun 2024 23:24:31 -0500 Subject: [PATCH 4/4] Add tornado observed to alert dock --- scwx-qt/source/scwx/qt/model/alert_model.cpp | 32 +++++++++++++++++--- scwx-qt/source/scwx/qt/model/alert_model.hpp | 22 +++++++------- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/scwx-qt/source/scwx/qt/model/alert_model.cpp b/scwx-qt/source/scwx/qt/model/alert_model.cpp index fd316c1d..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,7 @@ 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); @@ -52,6 +53,10 @@ class AlertModelImpl const GeographicLib::Geodesic& geodesic_; + std::unordered_map> + observedMap_; std::unordered_map> @@ -136,7 +141,12 @@ QVariant AlertModel::data(const QModelIndex& index, int role) const return QString::fromStdString( awips::GetSignificanceText(textEventKey.significance_)); - case static_cast(Column::TornadoPossible): + case static_cast(Column::Tornado): + if (textEventKey.phenomenon_ == awips::Phenomenon::Tornado && + p->GetObserved(textEventKey)) + { + return tr("Observed"); + } if (p->GetTornadoPossible(textEventKey)) { return tr("Possible"); @@ -236,7 +246,7 @@ AlertModel::headerData(int section, Qt::Orientation orientation, int role) const case static_cast(Column::ThreatCategory): return tr("Category"); - case static_cast(Column::TornadoPossible): + case static_cast(Column::Tornado): return tr("Tornado"); case static_cast(Column::State): @@ -279,8 +289,8 @@ AlertModel::headerData(int section, Qt::Orientation orientation, int role) const contentsSize = fontMetrics.size(0, QString(6, 'W')); break; - case static_cast(Column::TornadoPossible): - contentsSize = fontMetrics.size(0, QString(4, 'W')); + case static_cast(Column::Tornado): + contentsSize = fontMetrics.size(0, QString(5, 'W')); break; case static_cast(Column::State): @@ -328,6 +338,7 @@ 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, @@ -413,6 +424,19 @@ 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) { diff --git a/scwx-qt/source/scwx/qt/model/alert_model.hpp b/scwx-qt/source/scwx/qt/model/alert_model.hpp index 6654dcb1..df6d561e 100644 --- a/scwx-qt/source/scwx/qt/model/alert_model.hpp +++ b/scwx-qt/source/scwx/qt/model/alert_model.hpp @@ -21,17 +21,17 @@ class AlertModel : public QAbstractTableModel public: enum class Column : int { - Etn = 0, - OfficeId = 1, - Phenomenon = 2, - Significance = 3, - ThreatCategory = 4, - TornadoPossible = 5, - State = 6, - Counties = 7, - StartTime = 8, - EndTime = 9, - Distance = 10 + 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);