mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 03:20:05 +00:00 
			
		
		
		
	Rename existing AlertLayer to AlertLayerOld, prepare AlertLayer skeleton
This commit is contained in:
		
							parent
							
								
									2e1b537c87
								
							
						
					
					
						commit
						044e6d6885
					
				
					 6 changed files with 587 additions and 462 deletions
				
			
		|  | @ -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/timeline_manager.cpp | ||||||
|                 source/scwx/qt/manager/update_manager.cpp) |                 source/scwx/qt/manager/update_manager.cpp) | ||||||
| set(HDR_MAP source/scwx/qt/map/alert_layer.hpp | 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/color_table_layer.hpp | ||||||
|             source/scwx/qt/map/draw_layer.hpp |             source/scwx/qt/map/draw_layer.hpp | ||||||
|             source/scwx/qt/map/generic_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_range_layer.hpp | ||||||
|             source/scwx/qt/map/radar_site_layer.hpp) |             source/scwx/qt/map/radar_site_layer.hpp) | ||||||
| set(SRC_MAP source/scwx/qt/map/alert_layer.cpp | 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/color_table_layer.cpp | ||||||
|             source/scwx/qt/map/draw_layer.cpp |             source/scwx/qt/map/draw_layer.cpp | ||||||
|             source/scwx/qt/map/generic_layer.cpp |             source/scwx/qt/map/generic_layer.cpp | ||||||
|  |  | ||||||
|  | @ -1,17 +1,5 @@ | ||||||
| #include <scwx/qt/map/alert_layer.hpp> | #include <scwx/qt/map/alert_layer.hpp> | ||||||
| #include <scwx/qt/manager/text_event_manager.hpp> |  | ||||||
| #include <scwx/qt/settings/palette_settings.hpp> |  | ||||||
| #include <scwx/qt/types/layer_types.hpp> |  | ||||||
| #include <scwx/qt/util/color.hpp> |  | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| #include <scwx/util/threads.hpp> |  | ||||||
| 
 |  | ||||||
| #include <chrono> |  | ||||||
| #include <shared_mutex> |  | ||||||
| #include <unordered_set> |  | ||||||
| 
 |  | ||||||
| #include <boost/asio/steady_timer.hpp> |  | ||||||
| #include <boost/container_hash/hash.hpp> |  | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx | ||||||
| { | { | ||||||
|  | @ -23,473 +11,66 @@ namespace map | ||||||
| static const std::string logPrefix_ = "scwx::qt::map::alert_layer"; | static const std::string logPrefix_ = "scwx::qt::map::alert_layer"; | ||||||
| static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
| 
 | 
 | ||||||
| static std::vector<std::string> | class AlertLayer::Impl | ||||||
| AddAlertLayer(std::shared_ptr<QMapLibre::Map> 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<QMapLibre::Feature> {})}}; |  | ||||||
| static const std::vector<awips::Phenomenon> kAlertPhenomena_ { |  | ||||||
|    awips::Phenomenon::Marine, |  | ||||||
|    awips::Phenomenon::FlashFlood, |  | ||||||
|    awips::Phenomenon::SevereThunderstorm, |  | ||||||
|    awips::Phenomenon::SnowSquall, |  | ||||||
|    awips::Phenomenon::Tornado}; |  | ||||||
| 
 |  | ||||||
| template<class Key> |  | ||||||
| struct AlertTypeHash; |  | ||||||
| 
 |  | ||||||
| template<> |  | ||||||
| struct AlertTypeHash<std::pair<awips::Phenomenon, bool>> |  | ||||||
| { | { | ||||||
|    size_t operator()(const std::pair<awips::Phenomenon, bool>& 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<AlertLayerHandler> Instance(); |  | ||||||
| 
 |  | ||||||
|    std::list<QMapLibre::Feature>* FeatureList(awips::Phenomenon phenomenon, |  | ||||||
|                                               bool              alertActive); |  | ||||||
| 
 |  | ||||||
|    void HandleAlert(const types::TextEventKey& key, size_t messageIndex); |  | ||||||
|    void UpdateAlerts(); |  | ||||||
| 
 |  | ||||||
|    std::shared_ptr<manager::TextEventManager> textEventManager_; |  | ||||||
| 
 |  | ||||||
|    boost::asio::steady_timer alertUpdateTimer_; |  | ||||||
|    std::unordered_map<std::pair<awips::Phenomenon, bool>, |  | ||||||
|                       QVariantMap, |  | ||||||
|                       AlertTypeHash<std::pair<awips::Phenomenon, bool>>> |  | ||||||
|       alertSourceMap_; |  | ||||||
|    std::unordered_multimap<types::TextEventKey, |  | ||||||
|                            std::tuple<awips::Phenomenon, |  | ||||||
|                                       bool, |  | ||||||
|                                       std::list<QMapLibre::Feature>::iterator, |  | ||||||
|                                       std::chrono::system_clock::time_point>, |  | ||||||
|                            types::TextEventHash<types::TextEventKey>> |  | ||||||
|                      featureMap_; |  | ||||||
|    std::shared_mutex alertMutex_; |  | ||||||
| 
 |  | ||||||
| signals: |  | ||||||
|    void AlertsUpdated(awips::Phenomenon phenomenon, bool alertActive); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| class AlertLayerImpl : public QObject |  | ||||||
| { |  | ||||||
|    Q_OBJECT |  | ||||||
| public: | public: | ||||||
|    explicit AlertLayerImpl(std::shared_ptr<MapContext> context) : |    explicit Impl([[maybe_unused]] std::shared_ptr<MapContext> context, | ||||||
|        context_ {context}, alertLayerHandler_ {AlertLayerHandler::Instance()} |                  awips::Phenomenon                            phenomenon) : | ||||||
|  |        phenomenon_ {phenomenon} | ||||||
|    { |    { | ||||||
|       connect(alertLayerHandler_.get(), |  | ||||||
|               &AlertLayerHandler::AlertsUpdated, |  | ||||||
|               this, |  | ||||||
|               &AlertLayerImpl::UpdateSource); |  | ||||||
|    } |    } | ||||||
|    ~AlertLayerImpl() {}; |    ~Impl() {}; | ||||||
| 
 | 
 | ||||||
|    void UpdateSource(awips::Phenomenon phenomenon, bool alertActive); |    awips::Phenomenon phenomenon_; | ||||||
| 
 |  | ||||||
|    std::shared_ptr<MapContext>        context_; |  | ||||||
|    std::shared_ptr<AlertLayerHandler> alertLayerHandler_; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| AlertLayer::AlertLayer(std::shared_ptr<MapContext> context) : | AlertLayer::AlertLayer(std::shared_ptr<MapContext> context, | ||||||
|     p(std::make_unique<AlertLayerImpl>(context)) |                        awips::Phenomenon           phenomenon) : | ||||||
|  |     DrawLayer(context), p(std::make_unique<Impl>(context, phenomenon)) | ||||||
| { | { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| AlertLayer::~AlertLayer() = default; | AlertLayer::~AlertLayer() = default; | ||||||
| 
 | 
 | ||||||
| std::vector<std::string> AlertLayer::AddLayers(awips::Phenomenon  phenomenon, | void AlertLayer::Initialize() | ||||||
|                                                const std::string& before) |  | ||||||
| { | { | ||||||
|    logger_->debug("AddLayers(): {}", awips::GetPhenomenonCode(phenomenon)); |    logger_->debug("Initialize: {}", awips::GetPhenomenonText(p->phenomenon_)); | ||||||
| 
 | 
 | ||||||
|    std::vector<std::string> layers {}; |    DrawLayer::Initialize(); | ||||||
| 
 |  | ||||||
|    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<QMapLibre::Feature>* | void AlertLayer::Render(const QMapLibre::CustomLayerRenderParameters& params) | ||||||
| AlertLayerHandler::FeatureList(awips::Phenomenon phenomenon, bool alertActive) |  | ||||||
| { | { | ||||||
|    std::list<QMapLibre::Feature>* featureList = nullptr; |    gl::OpenGLFunctions& gl = context()->gl(); | ||||||
| 
 | 
 | ||||||
|    auto key = std::make_pair(phenomenon, alertActive); |    DrawLayer::Render(params); | ||||||
|    auto it  = alertSourceMap_.find(key); |  | ||||||
|    if (it != alertSourceMap_.cend()) |  | ||||||
|    { |  | ||||||
|       featureList = reinterpret_cast<std::list<QMapLibre::Feature>*>( |  | ||||||
|          it->second["data"].data()); |  | ||||||
|    } |  | ||||||
| 
 | 
 | ||||||
|    return featureList; |    SCWX_GL_CHECK_ERROR(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, | void AlertLayer::Deinitialize() | ||||||
|                                     size_t                     messageIndex) |  | ||||||
| { | { | ||||||
|    // Skip alert if there are more messages to be processed
 |    logger_->debug("Deinitialize: {}", awips::GetPhenomenonText(p->phenomenon_)); | ||||||
|    if (messageIndex + 1 < textEventManager_->message_count(key)) |  | ||||||
|    { |  | ||||||
|       return; |  | ||||||
|    } |  | ||||||
| 
 | 
 | ||||||
|    auto message = textEventManager_->message_list(key).at(messageIndex); |    DrawLayer::Deinitialize(); | ||||||
|    std::unordered_set<std::pair<awips::Phenomenon, bool>, |  | ||||||
|                       AlertTypeHash<std::pair<awips::Phenomenon, bool>>> |  | ||||||
|       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 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<types::EventHandler>&         eventHandler) | ||||||
| { | { | ||||||
|    logger_->trace("UpdateAlerts"); |    return DrawLayer::RunMousePicking(params, | ||||||
| 
 |                                      mouseLocalPos, | ||||||
|    // Take a unique lock before modifying feature lists
 |                                      mouseGlobalPos, | ||||||
|    std::unique_lock lock(alertMutex_); |                                      mouseCoords, | ||||||
| 
 |                                      mouseGeoCoords, | ||||||
|    std::unordered_set<std::pair<awips::Phenomenon, bool>, |                                      eventHandler); | ||||||
|                       AlertTypeHash<std::pair<awips::Phenomenon, bool>>> |  | ||||||
|       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> AlertLayerHandler::Instance() |  | ||||||
| { |  | ||||||
|    static std::weak_ptr<AlertLayerHandler> alertLayerHandlerReference {}; |  | ||||||
|    static std::mutex                       instanceMutex {}; |  | ||||||
| 
 |  | ||||||
|    std::unique_lock lock(instanceMutex); |  | ||||||
| 
 |  | ||||||
|    std::shared_ptr<AlertLayerHandler> alertLayerHandler = |  | ||||||
|       alertLayerHandlerReference.lock(); |  | ||||||
| 
 |  | ||||||
|    if (alertLayerHandler == nullptr) |  | ||||||
|    { |  | ||||||
|       alertLayerHandler          = std::make_shared<AlertLayerHandler>(); |  | ||||||
|       alertLayerHandlerReference = alertLayerHandler; |  | ||||||
| 
 |  | ||||||
|       alertLayerHandler->UpdateAlerts(); |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    return alertLayerHandler; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static std::vector<std::string> |  | ||||||
| AddAlertLayer(std::shared_ptr<QMapLibre::Map> 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<QMapLibre::CoordinatesCollection> { |  | ||||||
|          std::initializer_list<QMapLibre::Coordinates> {{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<std::pair<awips::Phenomenon, bool>>::operator()( |  | ||||||
|    const std::pair<awips::Phenomenon, bool>& x) const |  | ||||||
| { |  | ||||||
|    size_t seed = 0; |  | ||||||
|    boost::hash_combine(seed, x.first); |  | ||||||
|    boost::hash_combine(seed, x.second); |  | ||||||
|    return seed; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace map
 | } // namespace map
 | ||||||
| } // namespace qt
 | } // namespace qt
 | ||||||
| } // namespace scwx
 | } // namespace scwx
 | ||||||
| 
 |  | ||||||
| #include "alert_layer.moc" |  | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <scwx/awips/phenomenon.hpp> | #include <scwx/awips/phenomenon.hpp> | ||||||
| #include <scwx/qt/map/map_context.hpp> | #include <scwx/qt/map/draw_layer.hpp> | ||||||
| 
 | 
 | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <string> | #include <string> | ||||||
|  | @ -14,19 +14,30 @@ namespace qt | ||||||
| namespace map | namespace map | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| class AlertLayerImpl; | class AlertLayer : public DrawLayer | ||||||
| 
 |  | ||||||
| class AlertLayer |  | ||||||
| { | { | ||||||
|  |    Q_DISABLE_COPY_MOVE(AlertLayer) | ||||||
|  | 
 | ||||||
| public: | public: | ||||||
|    explicit AlertLayer(std::shared_ptr<MapContext> context); |    explicit AlertLayer(std::shared_ptr<MapContext> context, | ||||||
|  |                        scwx::awips::Phenomenon     phenomenon); | ||||||
|    ~AlertLayer(); |    ~AlertLayer(); | ||||||
| 
 | 
 | ||||||
|    std::vector<std::string> AddLayers(awips::Phenomenon  phenomenon, |    void Initialize() override final; | ||||||
|                                       const std::string& before = {}); |    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<types::EventHandler>& eventHandler) override final; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|    std::unique_ptr<AlertLayerImpl> p; |    class Impl; | ||||||
|  |    std::unique_ptr<Impl> p; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace map
 | } // namespace map
 | ||||||
|  |  | ||||||
							
								
								
									
										497
									
								
								scwx-qt/source/scwx/qt/map/alert_layer_old.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										497
									
								
								scwx-qt/source/scwx/qt/map/alert_layer_old.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,497 @@ | ||||||
|  | #include <scwx/qt/map/alert_layer_old.hpp> | ||||||
|  | #include <scwx/qt/manager/text_event_manager.hpp> | ||||||
|  | #include <scwx/qt/settings/palette_settings.hpp> | ||||||
|  | #include <scwx/qt/types/layer_types.hpp> | ||||||
|  | #include <scwx/qt/util/color.hpp> | ||||||
|  | #include <scwx/util/logger.hpp> | ||||||
|  | #include <scwx/util/threads.hpp> | ||||||
|  | 
 | ||||||
|  | #include <chrono> | ||||||
|  | #include <shared_mutex> | ||||||
|  | #include <unordered_set> | ||||||
|  | 
 | ||||||
|  | #include <boost/asio/steady_timer.hpp> | ||||||
|  | #include <boost/container_hash/hash.hpp> | ||||||
|  | 
 | ||||||
|  | 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<std::string> | ||||||
|  | AddAlertLayer(std::shared_ptr<QMapLibre::Map> 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<QMapLibre::Feature> {})}}; | ||||||
|  | static const std::vector<awips::Phenomenon> kAlertPhenomena_ { | ||||||
|  |    awips::Phenomenon::Marine, | ||||||
|  |    awips::Phenomenon::FlashFlood, | ||||||
|  |    awips::Phenomenon::SevereThunderstorm, | ||||||
|  |    awips::Phenomenon::SnowSquall, | ||||||
|  |    awips::Phenomenon::Tornado}; | ||||||
|  | 
 | ||||||
|  | template<class Key> | ||||||
|  | struct AlertTypeHash; | ||||||
|  | 
 | ||||||
|  | template<> | ||||||
|  | struct AlertTypeHash<std::pair<awips::Phenomenon, bool>> | ||||||
|  | { | ||||||
|  |    size_t operator()(const std::pair<awips::Phenomenon, bool>& 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<AlertLayerOldHandler> Instance(); | ||||||
|  | 
 | ||||||
|  |    std::list<QMapLibre::Feature>* FeatureList(awips::Phenomenon phenomenon, | ||||||
|  |                                               bool              alertActive); | ||||||
|  | 
 | ||||||
|  |    void HandleAlert(const types::TextEventKey& key, size_t messageIndex); | ||||||
|  |    void UpdateAlerts(); | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<manager::TextEventManager> textEventManager_; | ||||||
|  | 
 | ||||||
|  |    boost::asio::steady_timer alertUpdateTimer_; | ||||||
|  |    std::unordered_map<std::pair<awips::Phenomenon, bool>, | ||||||
|  |                       QVariantMap, | ||||||
|  |                       AlertTypeHash<std::pair<awips::Phenomenon, bool>>> | ||||||
|  |       alertSourceMap_; | ||||||
|  |    std::unordered_multimap<types::TextEventKey, | ||||||
|  |                            std::tuple<awips::Phenomenon, | ||||||
|  |                                       bool, | ||||||
|  |                                       std::list<QMapLibre::Feature>::iterator, | ||||||
|  |                                       std::chrono::system_clock::time_point>, | ||||||
|  |                            types::TextEventHash<types::TextEventKey>> | ||||||
|  |                      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<MapContext> 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<MapContext>           context_; | ||||||
|  |    std::shared_ptr<AlertLayerOldHandler> alertLayerOldHandler_; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | AlertLayerOld::AlertLayerOld(std::shared_ptr<MapContext> context) : | ||||||
|  |     p(std::make_unique<AlertLayerOldImpl>(context)) | ||||||
|  | { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | AlertLayerOld::~AlertLayerOld() = default; | ||||||
|  | 
 | ||||||
|  | std::vector<std::string> AlertLayerOld::AddLayers(awips::Phenomenon  phenomenon, | ||||||
|  |                                                   const std::string& before) | ||||||
|  | { | ||||||
|  |    logger_->debug("AddLayers(): {}", awips::GetPhenomenonCode(phenomenon)); | ||||||
|  | 
 | ||||||
|  |    std::vector<std::string> 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<QMapLibre::Feature>* | ||||||
|  | AlertLayerOldHandler::FeatureList(awips::Phenomenon phenomenon, | ||||||
|  |                                   bool              alertActive) | ||||||
|  | { | ||||||
|  |    std::list<QMapLibre::Feature>* featureList = nullptr; | ||||||
|  | 
 | ||||||
|  |    auto key = std::make_pair(phenomenon, alertActive); | ||||||
|  |    auto it  = alertSourceMap_.find(key); | ||||||
|  |    if (it != alertSourceMap_.cend()) | ||||||
|  |    { | ||||||
|  |       featureList = reinterpret_cast<std::list<QMapLibre::Feature>*>( | ||||||
|  |          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<std::pair<awips::Phenomenon, bool>, | ||||||
|  |                       AlertTypeHash<std::pair<awips::Phenomenon, bool>>> | ||||||
|  |       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<std::pair<awips::Phenomenon, bool>, | ||||||
|  |                       AlertTypeHash<std::pair<awips::Phenomenon, bool>>> | ||||||
|  |       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> AlertLayerOldHandler::Instance() | ||||||
|  | { | ||||||
|  |    static std::weak_ptr<AlertLayerOldHandler> alertLayerOldHandlerReference {}; | ||||||
|  |    static std::mutex                          instanceMutex {}; | ||||||
|  | 
 | ||||||
|  |    std::unique_lock lock(instanceMutex); | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<AlertLayerOldHandler> alertLayerOldHandler = | ||||||
|  |       alertLayerOldHandlerReference.lock(); | ||||||
|  | 
 | ||||||
|  |    if (alertLayerOldHandler == nullptr) | ||||||
|  |    { | ||||||
|  |       alertLayerOldHandler          = std::make_shared<AlertLayerOldHandler>(); | ||||||
|  |       alertLayerOldHandlerReference = alertLayerOldHandler; | ||||||
|  | 
 | ||||||
|  |       alertLayerOldHandler->UpdateAlerts(); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return alertLayerOldHandler; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static std::vector<std::string> | ||||||
|  | AddAlertLayer(std::shared_ptr<QMapLibre::Map> 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<QMapLibre::CoordinatesCollection> { | ||||||
|  |          std::initializer_list<QMapLibre::Coordinates> {{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<std::pair<awips::Phenomenon, bool>>::operator()( | ||||||
|  |    const std::pair<awips::Phenomenon, bool>& 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" | ||||||
							
								
								
									
										34
									
								
								scwx-qt/source/scwx/qt/map/alert_layer_old.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								scwx-qt/source/scwx/qt/map/alert_layer_old.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <scwx/awips/phenomenon.hpp> | ||||||
|  | #include <scwx/qt/map/map_context.hpp> | ||||||
|  | 
 | ||||||
|  | #include <memory> | ||||||
|  | #include <string> | ||||||
|  | #include <vector> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace map | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | class AlertLayerOldImpl; | ||||||
|  | 
 | ||||||
|  | class AlertLayerOld | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit AlertLayerOld(std::shared_ptr<MapContext> context); | ||||||
|  |    ~AlertLayerOld(); | ||||||
|  | 
 | ||||||
|  |    std::vector<std::string> AddLayers(awips::Phenomenon  phenomenon, | ||||||
|  |                                       const std::string& before = {}); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |    std::unique_ptr<AlertLayerOldImpl> p; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace map
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
|  | @ -4,7 +4,7 @@ | ||||||
| #include <scwx/qt/manager/hotkey_manager.hpp> | #include <scwx/qt/manager/hotkey_manager.hpp> | ||||||
| #include <scwx/qt/manager/placefile_manager.hpp> | #include <scwx/qt/manager/placefile_manager.hpp> | ||||||
| #include <scwx/qt/manager/radar_product_manager.hpp> | #include <scwx/qt/manager/radar_product_manager.hpp> | ||||||
| #include <scwx/qt/map/alert_layer.hpp> | #include <scwx/qt/map/alert_layer_old.hpp> | ||||||
| #include <scwx/qt/map/color_table_layer.hpp> | #include <scwx/qt/map/color_table_layer.hpp> | ||||||
| #include <scwx/qt/map/layer_wrapper.hpp> | #include <scwx/qt/map/layer_wrapper.hpp> | ||||||
| #include <scwx/qt/map/map_provider.hpp> | #include <scwx/qt/map/map_provider.hpp> | ||||||
|  | @ -79,7 +79,7 @@ public: | ||||||
|        imGuiRendererInitialized_ {false}, |        imGuiRendererInitialized_ {false}, | ||||||
|        radarProductManager_ {nullptr}, |        radarProductManager_ {nullptr}, | ||||||
|        radarProductLayer_ {nullptr}, |        radarProductLayer_ {nullptr}, | ||||||
|        alertLayer_ {std::make_shared<AlertLayer>(context_)}, |        alertLayer_ {std::make_shared<AlertLayerOld>(context_)}, | ||||||
|        overlayLayer_ {nullptr}, |        overlayLayer_ {nullptr}, | ||||||
|        placefileLayer_ {nullptr}, |        placefileLayer_ {nullptr}, | ||||||
|        colorTableLayer_ {nullptr}, |        colorTableLayer_ {nullptr}, | ||||||
|  | @ -218,7 +218,7 @@ public: | ||||||
|    std::shared_ptr<manager::RadarProductManager> radarProductManager_; |    std::shared_ptr<manager::RadarProductManager> radarProductManager_; | ||||||
| 
 | 
 | ||||||
|    std::shared_ptr<RadarProductLayer>   radarProductLayer_; |    std::shared_ptr<RadarProductLayer>   radarProductLayer_; | ||||||
|    std::shared_ptr<AlertLayer>          alertLayer_; |    std::shared_ptr<AlertLayerOld>       alertLayer_; | ||||||
|    std::shared_ptr<OverlayLayer>        overlayLayer_; |    std::shared_ptr<OverlayLayer>        overlayLayer_; | ||||||
|    std::shared_ptr<OverlayProductLayer> overlayProductLayer_ {nullptr}; |    std::shared_ptr<OverlayProductLayer> overlayProductLayer_ {nullptr}; | ||||||
|    std::shared_ptr<PlacefileLayer>      placefileLayer_; |    std::shared_ptr<PlacefileLayer>      placefileLayer_; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat