mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 04:50:06 +00:00 
			
		
		
		
	Alert layers for multiple phenomena
This commit is contained in:
		
							parent
							
								
									80baa8350d
								
							
						
					
					
						commit
						5639c6c328
					
				
					 1 changed files with 213 additions and 59 deletions
				
			
		|  | @ -2,6 +2,10 @@ | ||||||
| #include <scwx/qt/manager/text_event_manager.hpp> | #include <scwx/qt/manager/text_event_manager.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| 
 | 
 | ||||||
|  | #include <unordered_set> | ||||||
|  | 
 | ||||||
|  | #include <boost/container_hash/hash.hpp> | ||||||
|  | 
 | ||||||
| namespace scwx | namespace scwx | ||||||
| { | { | ||||||
| namespace qt | namespace qt | ||||||
|  | @ -12,35 +16,71 @@ 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 void AddAlertLayer(std::shared_ptr<QMapboxGL> map, | ||||||
|  |                           const QString&             idSuffix, | ||||||
|  |                           const QString&             sourceId, | ||||||
|  |                           const QString&             beforeLayer, | ||||||
|  |                           boost::gil::rgba8_pixel_t  outlineColor); | ||||||
|  | static QMapbox::Feature | ||||||
|  | CreateFeature(const awips::CodedLocation& codedLocation); | ||||||
| static QMapbox::Coordinate | static QMapbox::Coordinate | ||||||
| GetMapboxCoordinate(const common::Coordinate& coordinate); | GetMapboxCoordinate(const common::Coordinate& coordinate); | ||||||
| static QMapbox::Coordinates | static QMapbox::Coordinates | ||||||
| GetMapboxCoordinates(const awips::CodedLocation& codedLocation); |                GetMapboxCoordinates(const awips::CodedLocation& codedLocation); | ||||||
|  | static QString GetSuffix(awips::Phenomenon phenomenon, bool alertActive); | ||||||
|  | 
 | ||||||
|  | static const QVariantMap kEmptyFeatureCollection_ { | ||||||
|  |    {"type", "geojson"}, | ||||||
|  |    {"data", QVariant::fromValue(QList<QMapbox::Feature> {})}}; | ||||||
|  | static const std::list<awips::Phenomenon> kAlertPhenomena_ { | ||||||
|  |    awips::Phenomenon::Marine, | ||||||
|  |    awips::Phenomenon::FlashFlood, | ||||||
|  |    awips::Phenomenon::SevereThunderstorm, | ||||||
|  |    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 | class AlertLayerHandler : public QObject | ||||||
| { | { | ||||||
|    Q_OBJECT |    Q_OBJECT public : explicit AlertLayerHandler() : alertSourceMap_ {} | ||||||
| public: |  | ||||||
|    explicit AlertLayerHandler() : |  | ||||||
|        alertSource_ {{"type", "geojson"}, |  | ||||||
|                      {"data", QVariant::fromValue(QList<QMapbox::Feature> {})}} |  | ||||||
|    { |    { | ||||||
|  |       for (auto& phenomenon : kAlertPhenomena_) | ||||||
|  |       { | ||||||
|  |          for (bool alertActive : {false, true}) | ||||||
|  |          { | ||||||
|  |             alertSourceMap_.emplace(std::make_pair(phenomenon, alertActive), | ||||||
|  |                                     kEmptyFeatureCollection_); | ||||||
|  |          } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       connect(&manager::TextEventManager::Instance(), |       connect(&manager::TextEventManager::Instance(), | ||||||
|               &manager::TextEventManager::AlertUpdated, |               &manager::TextEventManager::AlertUpdated, | ||||||
|               this, |               this, | ||||||
|               &AlertLayerHandler::HandleAlert); |               &AlertLayerHandler::HandleAlert, | ||||||
|  |               Qt::QueuedConnection); | ||||||
|    } |    } | ||||||
|    ~AlertLayerHandler() = default; |    ~AlertLayerHandler() = default; | ||||||
| 
 | 
 | ||||||
|    static AlertLayerHandler& Instance(); |    static AlertLayerHandler& Instance(); | ||||||
| 
 | 
 | ||||||
|    QList<QMapbox::Feature>* FeatureList(); |    QList<QMapbox::Feature>* FeatureList(awips::Phenomenon phenomenon, | ||||||
|  |                                         bool              alertActive); | ||||||
|    void HandleAlert(const types::TextEventKey& key, size_t messageIndex); |    void HandleAlert(const types::TextEventKey& key, size_t messageIndex); | ||||||
| 
 | 
 | ||||||
|    QVariantMap alertSource_; |    std::unordered_map<std::pair<awips::Phenomenon, bool>, | ||||||
|  |                       QVariantMap, | ||||||
|  |                       AlertTypeHash<std::pair<awips::Phenomenon, bool>>> | ||||||
|  |       alertSourceMap_; | ||||||
| 
 | 
 | ||||||
| signals: | signals: | ||||||
|    void AlertsUpdated(); |    void AlertsUpdated(awips::Phenomenon phenomenon, bool alertActive); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class AlertLayerImpl : public QObject | class AlertLayerImpl : public QObject | ||||||
|  | @ -57,7 +97,7 @@ public: | ||||||
|    } |    } | ||||||
|    ~AlertLayerImpl() = default; |    ~AlertLayerImpl() = default; | ||||||
| 
 | 
 | ||||||
|    void UpdateSource(); |    void UpdateSource(awips::Phenomenon phenomenon, bool alertActive); | ||||||
| 
 | 
 | ||||||
|    std::shared_ptr<MapContext> context_; |    std::shared_ptr<MapContext> context_; | ||||||
| }; | }; | ||||||
|  | @ -102,46 +142,88 @@ void AlertLayer::AddLayers(const std::string& before) | ||||||
|       return; |       return; | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    if (map->layerExists("alertPolygonLayerBg")) |    // Add/update GeoJSON sources
 | ||||||
|  |    for (auto& phenomenon : kAlertPhenomena_) | ||||||
|    { |    { | ||||||
|       map->removeLayer("alertPolygonLayerBg"); |       for (bool alertActive : {false, true}) | ||||||
|    } |       { | ||||||
|    if (map->layerExists("alertPolygonLayerFg")) |          p->UpdateSource(phenomenon, alertActive); | ||||||
|    { |       } | ||||||
|       map->removeLayer("alertPolygonLayerFg"); |  | ||||||
|    } |  | ||||||
|    if (map->sourceExists("alertPolygon")) |  | ||||||
|    { |  | ||||||
|       map->removeSource("alertPolygon"); |  | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    map->addSource("alertPolygon", AlertLayerHandler::Instance().alertSource_); |    const QString beforeLayer {QString::fromStdString(before)}; | ||||||
| 
 | 
 | ||||||
|    map->addLayer({{"id", "alertPolygonLayerBg"}, |    // Create alert layers
 | ||||||
|                   {"type", "line"}, |    const QString ffActiveSuffix = | ||||||
|                   {"source", "alertPolygon"}}, |       GetSuffix(awips::Phenomenon::FlashFlood, true); | ||||||
|                  QString::fromStdString(before)); |    const QString ffInactiveSuffix = | ||||||
|    map->setLayoutProperty("alertPolygonLayerBg", "line-join", "round"); |       GetSuffix(awips::Phenomenon::FlashFlood, false); | ||||||
|    map->setLayoutProperty("alertPolygonLayerBg", "line-cap", "round"); |    const QString maActiveSuffix   = GetSuffix(awips::Phenomenon::Marine, true); | ||||||
|    map->setPaintProperty( |    const QString maInactiveSuffix = GetSuffix(awips::Phenomenon::Marine, false); | ||||||
|       "alertPolygonLayerBg", "line-color", "rgba(0, 0, 0, 255)"); |    const QString svActiveSuffix = | ||||||
|    map->setPaintProperty("alertPolygonLayerBg", "line-width", "5"); |       GetSuffix(awips::Phenomenon::SevereThunderstorm, true); | ||||||
|  |    const QString svInactiveSuffix = | ||||||
|  |       GetSuffix(awips::Phenomenon::SevereThunderstorm, false); | ||||||
|  |    const QString toActiveSuffix = GetSuffix(awips::Phenomenon::Tornado, true); | ||||||
|  |    const QString toInactiveSuffix = | ||||||
|  |       GetSuffix(awips::Phenomenon::Tornado, false); | ||||||
| 
 | 
 | ||||||
|    map->addLayer({{"id", "alertPolygonLayerFg"}, |    AddAlertLayer(map, | ||||||
|                   {"type", "line"}, |                  maInactiveSuffix, | ||||||
|                   {"source", "alertPolygon"}}, |                  QString("alertPolygon-%1").arg(maInactiveSuffix), | ||||||
|                  QString::fromStdString(before)); |                  beforeLayer, | ||||||
|    map->setLayoutProperty("alertPolygonLayerFg", "line-join", "round"); |                  {127, 63, 0, 255}); | ||||||
|    map->setLayoutProperty("alertPolygonLayerFg", "line-cap", "round"); |    AddAlertLayer(map, | ||||||
|    map->setPaintProperty( |                  maActiveSuffix, | ||||||
|       "alertPolygonLayerFg", "line-color", "rgba(255, 0, 0, 255)"); |                  QString("alertPolygon-%1").arg(maActiveSuffix), | ||||||
|    map->setPaintProperty("alertPolygonLayerFg", "line-width", "3"); |                  beforeLayer, | ||||||
|  |                  {255, 127, 0, 255}); | ||||||
|  |    AddAlertLayer(map, | ||||||
|  |                  ffInactiveSuffix, | ||||||
|  |                  QString("alertPolygon-%1").arg(ffInactiveSuffix), | ||||||
|  |                  beforeLayer, | ||||||
|  |                  {0, 127, 0, 255}); | ||||||
|  |    AddAlertLayer(map, | ||||||
|  |                  ffActiveSuffix, | ||||||
|  |                  QString("alertPolygon-%1").arg(ffActiveSuffix), | ||||||
|  |                  beforeLayer, | ||||||
|  |                  {0, 255, 0, 255}); | ||||||
|  |    AddAlertLayer(map, | ||||||
|  |                  svInactiveSuffix, | ||||||
|  |                  QString("alertPolygon-%1").arg(svInactiveSuffix), | ||||||
|  |                  beforeLayer, | ||||||
|  |                  {127, 127, 0, 255}); | ||||||
|  |    AddAlertLayer(map, | ||||||
|  |                  svActiveSuffix, | ||||||
|  |                  QString("alertPolygon-%1").arg(svActiveSuffix), | ||||||
|  |                  beforeLayer, | ||||||
|  |                  {255, 255, 0, 255}); | ||||||
|  |    AddAlertLayer(map, | ||||||
|  |                  toInactiveSuffix, | ||||||
|  |                  QString("alertPolygon-%1").arg(toInactiveSuffix), | ||||||
|  |                  beforeLayer, | ||||||
|  |                  {127, 0, 0, 255}); | ||||||
|  |    AddAlertLayer(map, | ||||||
|  |                  toActiveSuffix, | ||||||
|  |                  QString("alertPolygon-%1").arg(toActiveSuffix), | ||||||
|  |                  beforeLayer, | ||||||
|  |                  {255, 0, 0, 255}); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| QList<QMapbox::Feature>* AlertLayerHandler::FeatureList() | QList<QMapbox::Feature>* | ||||||
|  | AlertLayerHandler::FeatureList(awips::Phenomenon phenomenon, bool alertActive) | ||||||
| { | { | ||||||
|    return reinterpret_cast<QList<QMapbox::Feature>*>( |    QList<QMapbox::Feature>* featureList = nullptr; | ||||||
|       alertSource_["data"].data()); | 
 | ||||||
|  |    auto key = std::make_pair(phenomenon, alertActive); | ||||||
|  |    auto it  = alertSourceMap_.find(key); | ||||||
|  |    if (it != alertSourceMap_.cend()) | ||||||
|  |    { | ||||||
|  |       featureList = | ||||||
|  |          reinterpret_cast<QList<QMapbox::Feature>*>(it->second["data"].data()); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return featureList; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, | void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, | ||||||
|  | @ -149,7 +231,9 @@ void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, | ||||||
| { | { | ||||||
|    auto message = |    auto message = | ||||||
|       manager::TextEventManager::Instance().message_list(key).at(messageIndex); |       manager::TextEventManager::Instance().message_list(key).at(messageIndex); | ||||||
|    bool alertUpdated = false; |    std::unordered_set<std::pair<awips::Phenomenon, bool>, | ||||||
|  |                       AlertTypeHash<std::pair<awips::Phenomenon, bool>>> | ||||||
|  |       alertsUpdated {}; | ||||||
| 
 | 
 | ||||||
|    // TODO: Remove previous items
 |    // TODO: Remove previous items
 | ||||||
| 
 | 
 | ||||||
|  | @ -160,26 +244,28 @@ void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, | ||||||
|          continue; |          continue; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|  |       auto&             vtec       = segment->header_->vtecString_.front(); | ||||||
|  |       auto              action     = vtec.pVtec_.action(); | ||||||
|  |       awips::Phenomenon phenomenon = vtec.pVtec_.phenomenon(); | ||||||
|  |       bool alertActive             = (action != awips::PVtec::Action::Canceled); | ||||||
|  | 
 | ||||||
|       // Add alert location to polygon list
 |       // Add alert location to polygon list
 | ||||||
|       auto mapboxCoordinates = |       auto featureList = FeatureList(phenomenon, alertActive); | ||||||
|          GetMapboxCoordinates(segment->codedLocation_.value()); |       if (featureList != nullptr) | ||||||
| 
 |       { | ||||||
|       FeatureList()->push_back( |          featureList->push_back(CreateFeature(segment->codedLocation_.value())); | ||||||
|          {QMapbox::Feature::PolygonType, |          alertsUpdated.insert(std::make_pair(phenomenon, alertActive)); | ||||||
|           std::initializer_list<QMapbox::CoordinatesCollection> { |       } | ||||||
|              std::initializer_list<QMapbox::Coordinates> { |  | ||||||
|                 {mapboxCoordinates}}}}); |  | ||||||
| 
 |  | ||||||
|       alertUpdated = true; |  | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    if (alertUpdated) |    for (auto& alert : alertsUpdated) | ||||||
|    { |    { | ||||||
|       emit AlertsUpdated(); |       emit AlertsUpdated(alert.first, alert.second); | ||||||
|    } |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void AlertLayerImpl::UpdateSource() | void AlertLayerImpl::UpdateSource(awips::Phenomenon phenomenon, | ||||||
|  |                                   bool              alertActive) | ||||||
| { | { | ||||||
|    auto map = context_->map().lock(); |    auto map = context_->map().lock(); | ||||||
|    if (map == nullptr) |    if (map == nullptr) | ||||||
|  | @ -187,8 +273,11 @@ void AlertLayerImpl::UpdateSource() | ||||||
|       return; |       return; | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    map->updateSource("alertPolygon", |    // Update source, relies on alert source being defined
 | ||||||
|                      AlertLayerHandler::Instance().alertSource_); |    map->updateSource( | ||||||
|  |       QString("alertPolygon-%1").arg(GetSuffix(phenomenon, alertActive)), | ||||||
|  |       AlertLayerHandler::Instance().alertSourceMap_.at( | ||||||
|  |          std::make_pair(phenomenon, alertActive))); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| AlertLayerHandler& AlertLayerHandler::Instance() | AlertLayerHandler& AlertLayerHandler::Instance() | ||||||
|  | @ -197,6 +286,55 @@ AlertLayerHandler& AlertLayerHandler::Instance() | ||||||
|    return alertLayerHandler; |    return alertLayerHandler; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void AddAlertLayer(std::shared_ptr<QMapboxGL> map, | ||||||
|  |                           const QString&             idSuffix, | ||||||
|  |                           const QString&             sourceId, | ||||||
|  |                           const QString&             beforeLayer, | ||||||
|  |                           boost::gil::rgba8_pixel_t  outlineColor) | ||||||
|  | { | ||||||
|  |    QString bgLayerId = QString("alertPolygonLayerBg-%1").arg(idSuffix); | ||||||
|  |    QString fgLayerId = QString("alertPolygonLayerFg-%1").arg(idSuffix); | ||||||
|  | 
 | ||||||
|  |    if (map->layerExists(bgLayerId)) | ||||||
|  |    { | ||||||
|  |       map->removeLayer(bgLayerId); | ||||||
|  |    } | ||||||
|  |    if (map->layerExists(fgLayerId)) | ||||||
|  |    { | ||||||
|  |       map->removeLayer(fgLayerId); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    map->addLayer({{"id", 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-width", "5"); | ||||||
|  | 
 | ||||||
|  |    map->addLayer({{"id", 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-width", "3"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static QMapbox::Feature CreateFeature(const awips::CodedLocation& codedLocation) | ||||||
|  | { | ||||||
|  |    auto mapboxCoordinates = GetMapboxCoordinates(codedLocation); | ||||||
|  | 
 | ||||||
|  |    return { | ||||||
|  |       QMapbox::Feature::PolygonType, | ||||||
|  |       std::initializer_list<QMapbox::CoordinatesCollection> { | ||||||
|  |          std::initializer_list<QMapbox::Coordinates> {{mapboxCoordinates}}}}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static QMapbox::Coordinate | static QMapbox::Coordinate | ||||||
| GetMapboxCoordinate(const common::Coordinate& coordinate) | GetMapboxCoordinate(const common::Coordinate& coordinate) | ||||||
| { | { | ||||||
|  | @ -220,6 +358,22 @@ GetMapboxCoordinates(const awips::CodedLocation& codedLocation) | ||||||
|    return mapboxCoordinates; |    return mapboxCoordinates; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 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
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat