From 044e6d68859e0ccebaba6b78154146fd6999ee38 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 30 Jun 2024 00:21:10 -0500 Subject: [PATCH] Rename existing AlertLayer to AlertLayerOld, prepare AlertLayer skeleton --- scwx-qt/scwx-qt.cmake | 2 + scwx-qt/source/scwx/qt/map/alert_layer.cpp | 483 ++--------------- scwx-qt/source/scwx/qt/map/alert_layer.hpp | 27 +- .../source/scwx/qt/map/alert_layer_old.cpp | 497 ++++++++++++++++++ .../source/scwx/qt/map/alert_layer_old.hpp | 34 ++ scwx-qt/source/scwx/qt/map/map_widget.cpp | 6 +- 6 files changed, 587 insertions(+), 462 deletions(-) create mode 100644 scwx-qt/source/scwx/qt/map/alert_layer_old.cpp create mode 100644 scwx-qt/source/scwx/qt/map/alert_layer_old.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index b6e2c44e..d399f4e0 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -120,6 +120,7 @@ set(SRC_MANAGER source/scwx/qt/manager/alert_manager.cpp source/scwx/qt/manager/timeline_manager.cpp source/scwx/qt/manager/update_manager.cpp) set(HDR_MAP source/scwx/qt/map/alert_layer.hpp + source/scwx/qt/map/alert_layer_old.hpp source/scwx/qt/map/color_table_layer.hpp source/scwx/qt/map/draw_layer.hpp source/scwx/qt/map/generic_layer.hpp @@ -135,6 +136,7 @@ set(HDR_MAP source/scwx/qt/map/alert_layer.hpp source/scwx/qt/map/radar_range_layer.hpp source/scwx/qt/map/radar_site_layer.hpp) set(SRC_MAP source/scwx/qt/map/alert_layer.cpp + source/scwx/qt/map/alert_layer_old.cpp source/scwx/qt/map/color_table_layer.cpp source/scwx/qt/map/draw_layer.cpp source/scwx/qt/map/generic_layer.cpp diff --git a/scwx-qt/source/scwx/qt/map/alert_layer.cpp b/scwx-qt/source/scwx/qt/map/alert_layer.cpp index a7f727f0..6996333c 100644 --- a/scwx-qt/source/scwx/qt/map/alert_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/alert_layer.cpp @@ -1,17 +1,5 @@ #include -#include -#include -#include -#include #include -#include - -#include -#include -#include - -#include -#include namespace scwx { @@ -23,473 +11,66 @@ namespace map static const std::string logPrefix_ = "scwx::qt::map::alert_layer"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); -static std::vector -AddAlertLayer(std::shared_ptr map, - awips::Phenomenon phenomenon, - bool alertActive, - const QString& beforeLayer); -static QMapLibre::Feature -CreateFeature(const awips::CodedLocation& codedLocation); -static QMapLibre::Coordinate -GetMapboxCoordinate(const common::Coordinate& coordinate); -static QMapLibre::Coordinates - GetMapboxCoordinates(const awips::CodedLocation& codedLocation); -static QString GetSourceId(awips::Phenomenon phenomenon, bool alertActive); -static QString GetSuffix(awips::Phenomenon phenomenon, bool alertActive); - -static const QVariantMap kEmptyFeatureCollection_ { - {"type", "geojson"}, - {"data", QVariant::fromValue(std::list {})}}; -static const std::vector kAlertPhenomena_ { - awips::Phenomenon::Marine, - awips::Phenomenon::FlashFlood, - awips::Phenomenon::SevereThunderstorm, - awips::Phenomenon::SnowSquall, - awips::Phenomenon::Tornado}; - -template -struct AlertTypeHash; - -template<> -struct AlertTypeHash> +class AlertLayer::Impl { - size_t operator()(const std::pair& x) const; -}; - -class AlertLayerHandler : public QObject -{ - Q_OBJECT public : - explicit AlertLayerHandler() : - textEventManager_ {manager::TextEventManager::Instance()}, - alertUpdateTimer_ {scwx::util::io_context()}, - alertSourceMap_ {}, - featureMap_ {} - { - for (auto& phenomenon : kAlertPhenomena_) - { - for (bool alertActive : {false, true}) - { - alertSourceMap_.emplace(std::make_pair(phenomenon, alertActive), - kEmptyFeatureCollection_); - } - } - - connect(textEventManager_.get(), - &manager::TextEventManager::AlertUpdated, - this, - &AlertLayerHandler::HandleAlert); - } - ~AlertLayerHandler() - { - std::unique_lock lock(alertMutex_); - alertUpdateTimer_.cancel(); - } - - static std::shared_ptr Instance(); - - std::list* FeatureList(awips::Phenomenon phenomenon, - bool alertActive); - - void HandleAlert(const types::TextEventKey& key, size_t messageIndex); - void UpdateAlerts(); - - std::shared_ptr textEventManager_; - - boost::asio::steady_timer alertUpdateTimer_; - std::unordered_map, - QVariantMap, - AlertTypeHash>> - alertSourceMap_; - std::unordered_multimap::iterator, - std::chrono::system_clock::time_point>, - types::TextEventHash> - featureMap_; - std::shared_mutex alertMutex_; - -signals: - void AlertsUpdated(awips::Phenomenon phenomenon, bool alertActive); -}; - -class AlertLayerImpl : public QObject -{ - Q_OBJECT public: - explicit AlertLayerImpl(std::shared_ptr context) : - context_ {context}, alertLayerHandler_ {AlertLayerHandler::Instance()} + explicit Impl([[maybe_unused]] std::shared_ptr context, + awips::Phenomenon phenomenon) : + phenomenon_ {phenomenon} { - connect(alertLayerHandler_.get(), - &AlertLayerHandler::AlertsUpdated, - this, - &AlertLayerImpl::UpdateSource); } - ~AlertLayerImpl() {}; + ~Impl() {}; - void UpdateSource(awips::Phenomenon phenomenon, bool alertActive); - - std::shared_ptr context_; - std::shared_ptr alertLayerHandler_; + awips::Phenomenon phenomenon_; }; -AlertLayer::AlertLayer(std::shared_ptr context) : - p(std::make_unique(context)) +AlertLayer::AlertLayer(std::shared_ptr context, + awips::Phenomenon phenomenon) : + DrawLayer(context), p(std::make_unique(context, phenomenon)) { } AlertLayer::~AlertLayer() = default; -std::vector AlertLayer::AddLayers(awips::Phenomenon phenomenon, - const std::string& before) +void AlertLayer::Initialize() { - logger_->debug("AddLayers(): {}", awips::GetPhenomenonCode(phenomenon)); + logger_->debug("Initialize: {}", awips::GetPhenomenonText(p->phenomenon_)); - std::vector layers {}; - - auto map = p->context_->map().lock(); - if (map == nullptr) - { - return layers; - } - - const QString beforeLayer {QString::fromStdString(before)}; - - // Add/update GeoJSON sources and create layers - for (bool alertActive : {false, true}) - { - p->UpdateSource(phenomenon, alertActive); - auto newLayers = AddAlertLayer(map, phenomenon, alertActive, beforeLayer); - layers.insert(layers.end(), newLayers.cbegin(), newLayers.cend()); - } - - return layers; + DrawLayer::Initialize(); } -std::list* -AlertLayerHandler::FeatureList(awips::Phenomenon phenomenon, bool alertActive) +void AlertLayer::Render(const QMapLibre::CustomLayerRenderParameters& params) { - std::list* featureList = nullptr; + gl::OpenGLFunctions& gl = context()->gl(); - auto key = std::make_pair(phenomenon, alertActive); - auto it = alertSourceMap_.find(key); - if (it != alertSourceMap_.cend()) - { - featureList = reinterpret_cast*>( - it->second["data"].data()); - } + DrawLayer::Render(params); - return featureList; + SCWX_GL_CHECK_ERROR(); } -void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, - size_t messageIndex) +void AlertLayer::Deinitialize() { - // Skip alert if there are more messages to be processed - if (messageIndex + 1 < textEventManager_->message_count(key)) - { - return; - } + logger_->debug("Deinitialize: {}", awips::GetPhenomenonText(p->phenomenon_)); - auto message = textEventManager_->message_list(key).at(messageIndex); - std::unordered_set, - AlertTypeHash>> - alertsUpdated {}; - - // Take a unique lock before modifying feature lists - std::unique_lock lock(alertMutex_); - - // Remove existing features for key - auto existingFeatures = featureMap_.equal_range(key); - for (auto it = existingFeatures.first; it != existingFeatures.second; ++it) - { - auto& [phenomenon, alertActive, featureIt, eventEnd] = it->second; - auto featureList = FeatureList(phenomenon, alertActive); - if (featureList != nullptr) - { - // Remove existing feature for key - featureList->erase(featureIt); - - // Mark alert type as updated - alertsUpdated.emplace(phenomenon, alertActive); - } - } - featureMap_.erase(existingFeatures.first, existingFeatures.second); - - for (auto segment : message->segments()) - { - if (!segment->codedLocation_.has_value()) - { - continue; - } - - auto& vtec = segment->header_->vtecString_.front(); - auto action = vtec.pVtec_.action(); - awips::Phenomenon phenomenon = vtec.pVtec_.phenomenon(); - auto eventEnd = vtec.pVtec_.event_end(); - bool alertActive = (action != awips::PVtec::Action::Canceled); - - // If the event has ended, skip it - if (eventEnd < std::chrono::system_clock::now()) - { - continue; - } - - auto featureList = FeatureList(phenomenon, alertActive); - if (featureList != nullptr) - { - // Add alert location to polygon list - auto featureIt = featureList->emplace( - featureList->cend(), - CreateFeature(segment->codedLocation_.value())); - - // Store iterator for created feature in feature map - featureMap_.emplace(std::piecewise_construct, - std::forward_as_tuple(key), - std::forward_as_tuple( - phenomenon, alertActive, featureIt, eventEnd)); - - // Mark alert type as updated - alertsUpdated.emplace(phenomenon, alertActive); - } - } - - // Release the lock after completing feature list updates - lock.unlock(); - - for (auto& alert : alertsUpdated) - { - // Emit signal for each updated alert type - Q_EMIT AlertsUpdated(alert.first, alert.second); - } + DrawLayer::Deinitialize(); } -void AlertLayerHandler::UpdateAlerts() +bool AlertLayer::RunMousePicking( + const QMapLibre::CustomLayerRenderParameters& params, + const QPointF& mouseLocalPos, + const QPointF& mouseGlobalPos, + const glm::vec2& mouseCoords, + const common::Coordinate& mouseGeoCoords, + std::shared_ptr& eventHandler) { - logger_->trace("UpdateAlerts"); - - // Take a unique lock before modifying feature lists - std::unique_lock lock(alertMutex_); - - std::unordered_set, - AlertTypeHash>> - alertsUpdated {}; - - // Evaluate each rendered feature for expiration - for (auto it = featureMap_.begin(); it != featureMap_.end();) - { - auto& [phenomenon, alertActive, featureIt, eventEnd] = it->second; - - // If the event has ended, remove it from the feature list - if (eventEnd < std::chrono::system_clock::now()) - { - logger_->debug("Alert expired: {}", it->first.ToString()); - - auto featureList = FeatureList(phenomenon, alertActive); - if (featureList != nullptr) - { - // Remove existing feature for key - featureList->erase(featureIt); - - // Mark alert type as updated - alertsUpdated.emplace(phenomenon, alertActive); - } - - // Erase current item and increment iterator - it = featureMap_.erase(it); - } - else - { - // Current item is not expired, continue - ++it; - } - } - - for (auto& alert : alertsUpdated) - { - // Emit signal for each updated alert type - Q_EMIT AlertsUpdated(alert.first, alert.second); - } - - using namespace std::chrono; - alertUpdateTimer_.expires_after(15s); - alertUpdateTimer_.async_wait( - [this](const boost::system::error_code& e) - { - if (e == boost::asio::error::operation_aborted) - { - logger_->debug("Alert update timer cancelled"); - } - else if (e != boost::system::errc::success) - { - logger_->warn("Alert update timer error: {}", e.message()); - } - else - { - try - { - UpdateAlerts(); - } - catch (const std::exception& ex) - { - logger_->error(ex.what()); - } - } - }); -} - -void AlertLayerImpl::UpdateSource(awips::Phenomenon phenomenon, - bool alertActive) -{ - auto map = context_->map().lock(); - if (map == nullptr) - { - return; - } - - // Take a shared lock before using feature lists - std::shared_lock lock(alertLayerHandler_->alertMutex_); - - // Update source, relies on alert source being defined - map->updateSource(GetSourceId(phenomenon, alertActive), - alertLayerHandler_->alertSourceMap_.at( - std::make_pair(phenomenon, alertActive))); -} - -std::shared_ptr AlertLayerHandler::Instance() -{ - static std::weak_ptr alertLayerHandlerReference {}; - static std::mutex instanceMutex {}; - - std::unique_lock lock(instanceMutex); - - std::shared_ptr alertLayerHandler = - alertLayerHandlerReference.lock(); - - if (alertLayerHandler == nullptr) - { - alertLayerHandler = std::make_shared(); - alertLayerHandlerReference = alertLayerHandler; - - alertLayerHandler->UpdateAlerts(); - } - - return alertLayerHandler; -} - -static std::vector -AddAlertLayer(std::shared_ptr map, - awips::Phenomenon phenomenon, - bool alertActive, - const QString& beforeLayer) -{ - settings::PaletteSettings& paletteSettings = - settings::PaletteSettings::Instance(); - - QString layerPrefix = QString::fromStdString( - types::GetLayerName(types::LayerType::Alert, phenomenon)); - - QString sourceId = GetSourceId(phenomenon, alertActive); - QString idSuffix = GetSuffix(phenomenon, alertActive); - auto outlineColor = util::color::ToRgba8PixelT( - paletteSettings.alert_color(phenomenon, alertActive).GetValue()); - - QString bgLayerId = QString("%1::bg-%2").arg(layerPrefix).arg(idSuffix); - QString fgLayerId = QString("%1::fg-%2").arg(layerPrefix).arg(idSuffix); - - if (map->layerExists(bgLayerId)) - { - map->removeLayer(bgLayerId); - } - if (map->layerExists(fgLayerId)) - { - map->removeLayer(fgLayerId); - } - - const float opacity = outlineColor[3] / 255.0f; - - map->addLayer( - bgLayerId, {{"type", "line"}, {"source", sourceId}}, beforeLayer); - map->setLayoutProperty(bgLayerId, "line-join", "round"); - map->setLayoutProperty(bgLayerId, "line-cap", "round"); - map->setPaintProperty(bgLayerId, "line-color", "rgba(0, 0, 0, 255)"); - map->setPaintProperty(bgLayerId, "line-opacity", QString("%1").arg(opacity)); - map->setPaintProperty(bgLayerId, "line-width", "5"); - - map->addLayer( - fgLayerId, {{"type", "line"}, {"source", sourceId}}, beforeLayer); - map->setLayoutProperty(fgLayerId, "line-join", "round"); - map->setLayoutProperty(fgLayerId, "line-cap", "round"); - map->setPaintProperty(fgLayerId, - "line-color", - QString("rgba(%1, %2, %3, %4)") - .arg(outlineColor[0]) - .arg(outlineColor[1]) - .arg(outlineColor[2]) - .arg(outlineColor[3])); - map->setPaintProperty(fgLayerId, "line-opacity", QString("%1").arg(opacity)); - map->setPaintProperty(fgLayerId, "line-width", "3"); - - return {bgLayerId.toStdString(), fgLayerId.toStdString()}; -} - -static QMapLibre::Feature -CreateFeature(const awips::CodedLocation& codedLocation) -{ - auto mapboxCoordinates = GetMapboxCoordinates(codedLocation); - - return QMapLibre::Feature { - QMapLibre::Feature::PolygonType, - std::initializer_list { - std::initializer_list {{mapboxCoordinates}}}}; -} - -static QMapLibre::Coordinate -GetMapboxCoordinate(const common::Coordinate& coordinate) -{ - return {coordinate.latitude_, coordinate.longitude_}; -} - -static QMapLibre::Coordinates -GetMapboxCoordinates(const awips::CodedLocation& codedLocation) -{ - auto scwxCoordinates = codedLocation.coordinates(); - QMapLibre::Coordinates mapboxCoordinates(scwxCoordinates.size() + 1u); - - std::transform(scwxCoordinates.cbegin(), - scwxCoordinates.cend(), - mapboxCoordinates.begin(), - [](auto& coordinate) -> QMapLibre::Coordinate - { return GetMapboxCoordinate(coordinate); }); - - mapboxCoordinates.back() = GetMapboxCoordinate(scwxCoordinates.front()); - - return mapboxCoordinates; -} - -static QString GetSourceId(awips::Phenomenon phenomenon, bool alertActive) -{ - return QString("alertPolygon-%1").arg(GetSuffix(phenomenon, alertActive)); -} - -static QString GetSuffix(awips::Phenomenon phenomenon, bool alertActive) -{ - return QString("-%1.%2") - .arg(QString::fromStdString(awips::GetPhenomenonCode(phenomenon))) - .arg(alertActive); -} - -size_t AlertTypeHash>::operator()( - const std::pair& x) const -{ - size_t seed = 0; - boost::hash_combine(seed, x.first); - boost::hash_combine(seed, x.second); - return seed; + return DrawLayer::RunMousePicking(params, + mouseLocalPos, + mouseGlobalPos, + mouseCoords, + mouseGeoCoords, + eventHandler); } } // namespace map } // namespace qt } // namespace scwx - -#include "alert_layer.moc" diff --git a/scwx-qt/source/scwx/qt/map/alert_layer.hpp b/scwx-qt/source/scwx/qt/map/alert_layer.hpp index 6ce681a9..e93c4bb7 100644 --- a/scwx-qt/source/scwx/qt/map/alert_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/alert_layer.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include #include @@ -14,19 +14,30 @@ namespace qt namespace map { -class AlertLayerImpl; - -class AlertLayer +class AlertLayer : public DrawLayer { + Q_DISABLE_COPY_MOVE(AlertLayer) + public: - explicit AlertLayer(std::shared_ptr context); + explicit AlertLayer(std::shared_ptr context, + scwx::awips::Phenomenon phenomenon); ~AlertLayer(); - std::vector AddLayers(awips::Phenomenon phenomenon, - const std::string& before = {}); + void Initialize() override final; + void Render(const QMapLibre::CustomLayerRenderParameters&) override final; + void Deinitialize() override final; + + bool RunMousePicking( + const QMapLibre::CustomLayerRenderParameters& params, + const QPointF& mouseLocalPos, + const QPointF& mouseGlobalPos, + const glm::vec2& mouseCoords, + const common::Coordinate& mouseGeoCoords, + std::shared_ptr& eventHandler) override final; private: - std::unique_ptr p; + class Impl; + std::unique_ptr p; }; } // namespace map diff --git a/scwx-qt/source/scwx/qt/map/alert_layer_old.cpp b/scwx-qt/source/scwx/qt/map/alert_layer_old.cpp new file mode 100644 index 00000000..d1e73d78 --- /dev/null +++ b/scwx-qt/source/scwx/qt/map/alert_layer_old.cpp @@ -0,0 +1,497 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace map +{ + +static const std::string logPrefix_ = "scwx::qt::map::alert_layer_old"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +static std::vector +AddAlertLayer(std::shared_ptr map, + awips::Phenomenon phenomenon, + bool alertActive, + const QString& beforeLayer); +static QMapLibre::Feature +CreateFeature(const awips::CodedLocation& codedLocation); +static QMapLibre::Coordinate +GetMapboxCoordinate(const common::Coordinate& coordinate); +static QMapLibre::Coordinates + GetMapboxCoordinates(const awips::CodedLocation& codedLocation); +static QString GetSourceId(awips::Phenomenon phenomenon, bool alertActive); +static QString GetSuffix(awips::Phenomenon phenomenon, bool alertActive); + +static const QVariantMap kEmptyFeatureCollection_ { + {"type", "geojson"}, + {"data", QVariant::fromValue(std::list {})}}; +static const std::vector kAlertPhenomena_ { + awips::Phenomenon::Marine, + awips::Phenomenon::FlashFlood, + awips::Phenomenon::SevereThunderstorm, + awips::Phenomenon::SnowSquall, + awips::Phenomenon::Tornado}; + +template +struct AlertTypeHash; + +template<> +struct AlertTypeHash> +{ + size_t operator()(const std::pair& x) const; +}; + +class AlertLayerOldHandler : public QObject +{ + Q_OBJECT public : + explicit AlertLayerOldHandler() : + textEventManager_ {manager::TextEventManager::Instance()}, + alertUpdateTimer_ {scwx::util::io_context()}, + alertSourceMap_ {}, + featureMap_ {} + { + for (auto& phenomenon : kAlertPhenomena_) + { + for (bool alertActive : {false, true}) + { + alertSourceMap_.emplace(std::make_pair(phenomenon, alertActive), + kEmptyFeatureCollection_); + } + } + + connect(textEventManager_.get(), + &manager::TextEventManager::AlertUpdated, + this, + &AlertLayerOldHandler::HandleAlert); + } + ~AlertLayerOldHandler() + { + std::unique_lock lock(alertMutex_); + alertUpdateTimer_.cancel(); + } + + static std::shared_ptr Instance(); + + std::list* FeatureList(awips::Phenomenon phenomenon, + bool alertActive); + + void HandleAlert(const types::TextEventKey& key, size_t messageIndex); + void UpdateAlerts(); + + std::shared_ptr textEventManager_; + + boost::asio::steady_timer alertUpdateTimer_; + std::unordered_map, + QVariantMap, + AlertTypeHash>> + alertSourceMap_; + std::unordered_multimap::iterator, + std::chrono::system_clock::time_point>, + types::TextEventHash> + featureMap_; + std::shared_mutex alertMutex_; + +signals: + void AlertsUpdated(awips::Phenomenon phenomenon, bool alertActive); +}; + +class AlertLayerOldImpl : public QObject +{ + Q_OBJECT +public: + explicit AlertLayerOldImpl(std::shared_ptr context) : + context_ {context}, + alertLayerOldHandler_ {AlertLayerOldHandler::Instance()} + { + connect(alertLayerOldHandler_.get(), + &AlertLayerOldHandler::AlertsUpdated, + this, + &AlertLayerOldImpl::UpdateSource); + } + ~AlertLayerOldImpl() {}; + + void UpdateSource(awips::Phenomenon phenomenon, bool alertActive); + + std::shared_ptr context_; + std::shared_ptr alertLayerOldHandler_; +}; + +AlertLayerOld::AlertLayerOld(std::shared_ptr context) : + p(std::make_unique(context)) +{ +} + +AlertLayerOld::~AlertLayerOld() = default; + +std::vector AlertLayerOld::AddLayers(awips::Phenomenon phenomenon, + const std::string& before) +{ + logger_->debug("AddLayers(): {}", awips::GetPhenomenonCode(phenomenon)); + + std::vector layers {}; + + auto map = p->context_->map().lock(); + if (map == nullptr) + { + return layers; + } + + const QString beforeLayer {QString::fromStdString(before)}; + + // Add/update GeoJSON sources and create layers + for (bool alertActive : {false, true}) + { + p->UpdateSource(phenomenon, alertActive); + auto newLayers = AddAlertLayer(map, phenomenon, alertActive, beforeLayer); + layers.insert(layers.end(), newLayers.cbegin(), newLayers.cend()); + } + + return layers; +} + +std::list* +AlertLayerOldHandler::FeatureList(awips::Phenomenon phenomenon, + bool alertActive) +{ + std::list* featureList = nullptr; + + auto key = std::make_pair(phenomenon, alertActive); + auto it = alertSourceMap_.find(key); + if (it != alertSourceMap_.cend()) + { + featureList = reinterpret_cast*>( + it->second["data"].data()); + } + + return featureList; +} + +void AlertLayerOldHandler::HandleAlert(const types::TextEventKey& key, + size_t messageIndex) +{ + // Skip alert if there are more messages to be processed + if (messageIndex + 1 < textEventManager_->message_count(key)) + { + return; + } + + auto message = textEventManager_->message_list(key).at(messageIndex); + std::unordered_set, + AlertTypeHash>> + alertsUpdated {}; + + // Take a unique lock before modifying feature lists + std::unique_lock lock(alertMutex_); + + // Remove existing features for key + auto existingFeatures = featureMap_.equal_range(key); + for (auto it = existingFeatures.first; it != existingFeatures.second; ++it) + { + auto& [phenomenon, alertActive, featureIt, eventEnd] = it->second; + auto featureList = FeatureList(phenomenon, alertActive); + if (featureList != nullptr) + { + // Remove existing feature for key + featureList->erase(featureIt); + + // Mark alert type as updated + alertsUpdated.emplace(phenomenon, alertActive); + } + } + featureMap_.erase(existingFeatures.first, existingFeatures.second); + + for (auto segment : message->segments()) + { + if (!segment->codedLocation_.has_value()) + { + continue; + } + + auto& vtec = segment->header_->vtecString_.front(); + auto action = vtec.pVtec_.action(); + awips::Phenomenon phenomenon = vtec.pVtec_.phenomenon(); + auto eventEnd = vtec.pVtec_.event_end(); + bool alertActive = (action != awips::PVtec::Action::Canceled); + + // If the event has ended, skip it + if (eventEnd < std::chrono::system_clock::now()) + { + continue; + } + + auto featureList = FeatureList(phenomenon, alertActive); + if (featureList != nullptr) + { + // Add alert location to polygon list + auto featureIt = featureList->emplace( + featureList->cend(), + CreateFeature(segment->codedLocation_.value())); + + // Store iterator for created feature in feature map + featureMap_.emplace(std::piecewise_construct, + std::forward_as_tuple(key), + std::forward_as_tuple( + phenomenon, alertActive, featureIt, eventEnd)); + + // Mark alert type as updated + alertsUpdated.emplace(phenomenon, alertActive); + } + } + + // Release the lock after completing feature list updates + lock.unlock(); + + for (auto& alert : alertsUpdated) + { + // Emit signal for each updated alert type + Q_EMIT AlertsUpdated(alert.first, alert.second); + } +} + +void AlertLayerOldHandler::UpdateAlerts() +{ + logger_->trace("UpdateAlerts"); + + // Take a unique lock before modifying feature lists + std::unique_lock lock(alertMutex_); + + std::unordered_set, + AlertTypeHash>> + alertsUpdated {}; + + // Evaluate each rendered feature for expiration + for (auto it = featureMap_.begin(); it != featureMap_.end();) + { + auto& [phenomenon, alertActive, featureIt, eventEnd] = it->second; + + // If the event has ended, remove it from the feature list + if (eventEnd < std::chrono::system_clock::now()) + { + logger_->debug("Alert expired: {}", it->first.ToString()); + + auto featureList = FeatureList(phenomenon, alertActive); + if (featureList != nullptr) + { + // Remove existing feature for key + featureList->erase(featureIt); + + // Mark alert type as updated + alertsUpdated.emplace(phenomenon, alertActive); + } + + // Erase current item and increment iterator + it = featureMap_.erase(it); + } + else + { + // Current item is not expired, continue + ++it; + } + } + + for (auto& alert : alertsUpdated) + { + // Emit signal for each updated alert type + Q_EMIT AlertsUpdated(alert.first, alert.second); + } + + using namespace std::chrono; + alertUpdateTimer_.expires_after(15s); + alertUpdateTimer_.async_wait( + [this](const boost::system::error_code& e) + { + if (e == boost::asio::error::operation_aborted) + { + logger_->debug("Alert update timer cancelled"); + } + else if (e != boost::system::errc::success) + { + logger_->warn("Alert update timer error: {}", e.message()); + } + else + { + try + { + UpdateAlerts(); + } + catch (const std::exception& ex) + { + logger_->error(ex.what()); + } + } + }); +} + +void AlertLayerOldImpl::UpdateSource(awips::Phenomenon phenomenon, + bool alertActive) +{ + auto map = context_->map().lock(); + if (map == nullptr) + { + return; + } + + // Take a shared lock before using feature lists + std::shared_lock lock(alertLayerOldHandler_->alertMutex_); + + // Update source, relies on alert source being defined + map->updateSource(GetSourceId(phenomenon, alertActive), + alertLayerOldHandler_->alertSourceMap_.at( + std::make_pair(phenomenon, alertActive))); +} + +std::shared_ptr AlertLayerOldHandler::Instance() +{ + static std::weak_ptr alertLayerOldHandlerReference {}; + static std::mutex instanceMutex {}; + + std::unique_lock lock(instanceMutex); + + std::shared_ptr alertLayerOldHandler = + alertLayerOldHandlerReference.lock(); + + if (alertLayerOldHandler == nullptr) + { + alertLayerOldHandler = std::make_shared(); + alertLayerOldHandlerReference = alertLayerOldHandler; + + alertLayerOldHandler->UpdateAlerts(); + } + + return alertLayerOldHandler; +} + +static std::vector +AddAlertLayer(std::shared_ptr map, + awips::Phenomenon phenomenon, + bool alertActive, + const QString& beforeLayer) +{ + settings::PaletteSettings& paletteSettings = + settings::PaletteSettings::Instance(); + + QString layerPrefix = QString::fromStdString( + types::GetLayerName(types::LayerType::Alert, phenomenon)); + + QString sourceId = GetSourceId(phenomenon, alertActive); + QString idSuffix = GetSuffix(phenomenon, alertActive); + auto outlineColor = util::color::ToRgba8PixelT( + paletteSettings.alert_color(phenomenon, alertActive).GetValue()); + + QString bgLayerId = QString("%1::bg-%2").arg(layerPrefix).arg(idSuffix); + QString fgLayerId = QString("%1::fg-%2").arg(layerPrefix).arg(idSuffix); + + if (map->layerExists(bgLayerId)) + { + map->removeLayer(bgLayerId); + } + if (map->layerExists(fgLayerId)) + { + map->removeLayer(fgLayerId); + } + + const float opacity = outlineColor[3] / 255.0f; + + map->addLayer( + bgLayerId, {{"type", "line"}, {"source", sourceId}}, beforeLayer); + map->setLayoutProperty(bgLayerId, "line-join", "round"); + map->setLayoutProperty(bgLayerId, "line-cap", "round"); + map->setPaintProperty(bgLayerId, "line-color", "rgba(0, 0, 0, 255)"); + map->setPaintProperty(bgLayerId, "line-opacity", QString("%1").arg(opacity)); + map->setPaintProperty(bgLayerId, "line-width", "5"); + + map->addLayer( + fgLayerId, {{"type", "line"}, {"source", sourceId}}, beforeLayer); + map->setLayoutProperty(fgLayerId, "line-join", "round"); + map->setLayoutProperty(fgLayerId, "line-cap", "round"); + map->setPaintProperty(fgLayerId, + "line-color", + QString("rgba(%1, %2, %3, %4)") + .arg(outlineColor[0]) + .arg(outlineColor[1]) + .arg(outlineColor[2]) + .arg(outlineColor[3])); + map->setPaintProperty(fgLayerId, "line-opacity", QString("%1").arg(opacity)); + map->setPaintProperty(fgLayerId, "line-width", "3"); + + return {bgLayerId.toStdString(), fgLayerId.toStdString()}; +} + +static QMapLibre::Feature +CreateFeature(const awips::CodedLocation& codedLocation) +{ + auto mapboxCoordinates = GetMapboxCoordinates(codedLocation); + + return QMapLibre::Feature { + QMapLibre::Feature::PolygonType, + std::initializer_list { + std::initializer_list {{mapboxCoordinates}}}}; +} + +static QMapLibre::Coordinate +GetMapboxCoordinate(const common::Coordinate& coordinate) +{ + return {coordinate.latitude_, coordinate.longitude_}; +} + +static QMapLibre::Coordinates +GetMapboxCoordinates(const awips::CodedLocation& codedLocation) +{ + auto scwxCoordinates = codedLocation.coordinates(); + QMapLibre::Coordinates mapboxCoordinates(scwxCoordinates.size() + 1u); + + std::transform(scwxCoordinates.cbegin(), + scwxCoordinates.cend(), + mapboxCoordinates.begin(), + [](auto& coordinate) -> QMapLibre::Coordinate + { return GetMapboxCoordinate(coordinate); }); + + mapboxCoordinates.back() = GetMapboxCoordinate(scwxCoordinates.front()); + + return mapboxCoordinates; +} + +static QString GetSourceId(awips::Phenomenon phenomenon, bool alertActive) +{ + return QString("alertPolygon-%1").arg(GetSuffix(phenomenon, alertActive)); +} + +static QString GetSuffix(awips::Phenomenon phenomenon, bool alertActive) +{ + return QString("-%1.%2") + .arg(QString::fromStdString(awips::GetPhenomenonCode(phenomenon))) + .arg(alertActive); +} + +size_t AlertTypeHash>::operator()( + const std::pair& x) const +{ + size_t seed = 0; + boost::hash_combine(seed, x.first); + boost::hash_combine(seed, x.second); + return seed; +} + +} // namespace map +} // namespace qt +} // namespace scwx + +#include "alert_layer_old.moc" diff --git a/scwx-qt/source/scwx/qt/map/alert_layer_old.hpp b/scwx-qt/source/scwx/qt/map/alert_layer_old.hpp new file mode 100644 index 00000000..dd06a4ed --- /dev/null +++ b/scwx-qt/source/scwx/qt/map/alert_layer_old.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace map +{ + +class AlertLayerOldImpl; + +class AlertLayerOld +{ +public: + explicit AlertLayerOld(std::shared_ptr context); + ~AlertLayerOld(); + + std::vector AddLayers(awips::Phenomenon phenomenon, + const std::string& before = {}); + +private: + std::unique_ptr p; +}; + +} // namespace map +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 3b0358d5..a3f50c8f 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include #include @@ -79,7 +79,7 @@ public: imGuiRendererInitialized_ {false}, radarProductManager_ {nullptr}, radarProductLayer_ {nullptr}, - alertLayer_ {std::make_shared(context_)}, + alertLayer_ {std::make_shared(context_)}, overlayLayer_ {nullptr}, placefileLayer_ {nullptr}, colorTableLayer_ {nullptr}, @@ -218,7 +218,7 @@ public: std::shared_ptr radarProductManager_; std::shared_ptr radarProductLayer_; - std::shared_ptr alertLayer_; + std::shared_ptr alertLayer_; std::shared_ptr overlayLayer_; std::shared_ptr overlayProductLayer_ {nullptr}; std::shared_ptr placefileLayer_;