mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 04:30:05 +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/util/logger.hpp> | ||||
| 
 | ||||
| #include <unordered_set> | ||||
| 
 | ||||
| #include <boost/container_hash/hash.hpp> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
| namespace qt | ||||
|  | @ -12,35 +16,71 @@ namespace map | |||
| static const std::string logPrefix_ = "scwx::qt::map::alert_layer"; | ||||
| 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 | ||||
| GetMapboxCoordinate(const common::Coordinate& coordinate); | ||||
| 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 | ||||
| { | ||||
|    Q_OBJECT | ||||
| public: | ||||
|    explicit AlertLayerHandler() : | ||||
|        alertSource_ {{"type", "geojson"}, | ||||
|                      {"data", QVariant::fromValue(QList<QMapbox::Feature> {})}} | ||||
|    Q_OBJECT public : explicit AlertLayerHandler() : alertSourceMap_ {} | ||||
|    { | ||||
|       for (auto& phenomenon : kAlertPhenomena_) | ||||
|       { | ||||
|          for (bool alertActive : {false, true}) | ||||
|          { | ||||
|             alertSourceMap_.emplace(std::make_pair(phenomenon, alertActive), | ||||
|                                     kEmptyFeatureCollection_); | ||||
|          } | ||||
|       } | ||||
| 
 | ||||
|       connect(&manager::TextEventManager::Instance(), | ||||
|               &manager::TextEventManager::AlertUpdated, | ||||
|               this, | ||||
|               &AlertLayerHandler::HandleAlert); | ||||
|               &AlertLayerHandler::HandleAlert, | ||||
|               Qt::QueuedConnection); | ||||
|    } | ||||
|    ~AlertLayerHandler() = default; | ||||
| 
 | ||||
|    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); | ||||
| 
 | ||||
|    QVariantMap alertSource_; | ||||
|    std::unordered_map<std::pair<awips::Phenomenon, bool>, | ||||
|                       QVariantMap, | ||||
|                       AlertTypeHash<std::pair<awips::Phenomenon, bool>>> | ||||
|       alertSourceMap_; | ||||
| 
 | ||||
| signals: | ||||
|    void AlertsUpdated(); | ||||
|    void AlertsUpdated(awips::Phenomenon phenomenon, bool alertActive); | ||||
| }; | ||||
| 
 | ||||
| class AlertLayerImpl : public QObject | ||||
|  | @ -57,7 +97,7 @@ public: | |||
|    } | ||||
|    ~AlertLayerImpl() = default; | ||||
| 
 | ||||
|    void UpdateSource(); | ||||
|    void UpdateSource(awips::Phenomenon phenomenon, bool alertActive); | ||||
| 
 | ||||
|    std::shared_ptr<MapContext> context_; | ||||
| }; | ||||
|  | @ -102,46 +142,88 @@ void AlertLayer::AddLayers(const std::string& before) | |||
|       return; | ||||
|    } | ||||
| 
 | ||||
|    if (map->layerExists("alertPolygonLayerBg")) | ||||
|    // Add/update GeoJSON sources
 | ||||
|    for (auto& phenomenon : kAlertPhenomena_) | ||||
|    { | ||||
|       map->removeLayer("alertPolygonLayerBg"); | ||||
|    } | ||||
|    if (map->layerExists("alertPolygonLayerFg")) | ||||
|    { | ||||
|       map->removeLayer("alertPolygonLayerFg"); | ||||
|    } | ||||
|    if (map->sourceExists("alertPolygon")) | ||||
|    { | ||||
|       map->removeSource("alertPolygon"); | ||||
|       for (bool alertActive : {false, true}) | ||||
|       { | ||||
|          p->UpdateSource(phenomenon, alertActive); | ||||
|       } | ||||
|    } | ||||
| 
 | ||||
|    map->addSource("alertPolygon", AlertLayerHandler::Instance().alertSource_); | ||||
|    const QString beforeLayer {QString::fromStdString(before)}; | ||||
| 
 | ||||
|    map->addLayer({{"id", "alertPolygonLayerBg"}, | ||||
|                   {"type", "line"}, | ||||
|                   {"source", "alertPolygon"}}, | ||||
|                  QString::fromStdString(before)); | ||||
|    map->setLayoutProperty("alertPolygonLayerBg", "line-join", "round"); | ||||
|    map->setLayoutProperty("alertPolygonLayerBg", "line-cap", "round"); | ||||
|    map->setPaintProperty( | ||||
|       "alertPolygonLayerBg", "line-color", "rgba(0, 0, 0, 255)"); | ||||
|    map->setPaintProperty("alertPolygonLayerBg", "line-width", "5"); | ||||
|    // Create alert layers
 | ||||
|    const QString ffActiveSuffix = | ||||
|       GetSuffix(awips::Phenomenon::FlashFlood, true); | ||||
|    const QString ffInactiveSuffix = | ||||
|       GetSuffix(awips::Phenomenon::FlashFlood, false); | ||||
|    const QString maActiveSuffix   = GetSuffix(awips::Phenomenon::Marine, true); | ||||
|    const QString maInactiveSuffix = GetSuffix(awips::Phenomenon::Marine, false); | ||||
|    const QString svActiveSuffix = | ||||
|       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"}, | ||||
|                   {"type", "line"}, | ||||
|                   {"source", "alertPolygon"}}, | ||||
|                  QString::fromStdString(before)); | ||||
|    map->setLayoutProperty("alertPolygonLayerFg", "line-join", "round"); | ||||
|    map->setLayoutProperty("alertPolygonLayerFg", "line-cap", "round"); | ||||
|    map->setPaintProperty( | ||||
|       "alertPolygonLayerFg", "line-color", "rgba(255, 0, 0, 255)"); | ||||
|    map->setPaintProperty("alertPolygonLayerFg", "line-width", "3"); | ||||
|    AddAlertLayer(map, | ||||
|                  maInactiveSuffix, | ||||
|                  QString("alertPolygon-%1").arg(maInactiveSuffix), | ||||
|                  beforeLayer, | ||||
|                  {127, 63, 0, 255}); | ||||
|    AddAlertLayer(map, | ||||
|                  maActiveSuffix, | ||||
|                  QString("alertPolygon-%1").arg(maActiveSuffix), | ||||
|                  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>*>( | ||||
|       alertSource_["data"].data()); | ||||
|    QList<QMapbox::Feature>* featureList = nullptr; | ||||
| 
 | ||||
|    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, | ||||
|  | @ -149,7 +231,9 @@ void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, | |||
| { | ||||
|    auto message = | ||||
|       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
 | ||||
| 
 | ||||
|  | @ -160,26 +244,28 @@ void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, | |||
|          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
 | ||||
|       auto mapboxCoordinates = | ||||
|          GetMapboxCoordinates(segment->codedLocation_.value()); | ||||
| 
 | ||||
|       FeatureList()->push_back( | ||||
|          {QMapbox::Feature::PolygonType, | ||||
|           std::initializer_list<QMapbox::CoordinatesCollection> { | ||||
|              std::initializer_list<QMapbox::Coordinates> { | ||||
|                 {mapboxCoordinates}}}}); | ||||
| 
 | ||||
|       alertUpdated = true; | ||||
|       auto featureList = FeatureList(phenomenon, alertActive); | ||||
|       if (featureList != nullptr) | ||||
|       { | ||||
|          featureList->push_back(CreateFeature(segment->codedLocation_.value())); | ||||
|          alertsUpdated.insert(std::make_pair(phenomenon, alertActive)); | ||||
|       } | ||||
|    } | ||||
| 
 | ||||
|    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(); | ||||
|    if (map == nullptr) | ||||
|  | @ -187,8 +273,11 @@ void AlertLayerImpl::UpdateSource() | |||
|       return; | ||||
|    } | ||||
| 
 | ||||
|    map->updateSource("alertPolygon", | ||||
|                      AlertLayerHandler::Instance().alertSource_); | ||||
|    // Update source, relies on alert source being defined
 | ||||
|    map->updateSource( | ||||
|       QString("alertPolygon-%1").arg(GetSuffix(phenomenon, alertActive)), | ||||
|       AlertLayerHandler::Instance().alertSourceMap_.at( | ||||
|          std::make_pair(phenomenon, alertActive))); | ||||
| } | ||||
| 
 | ||||
| AlertLayerHandler& AlertLayerHandler::Instance() | ||||
|  | @ -197,6 +286,55 @@ AlertLayerHandler& AlertLayerHandler::Instance() | |||
|    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 | ||||
| GetMapboxCoordinate(const common::Coordinate& coordinate) | ||||
| { | ||||
|  | @ -220,6 +358,22 @@ GetMapboxCoordinates(const awips::CodedLocation& codedLocation) | |||
|    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 qt
 | ||||
| } // namespace scwx
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat