#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace scwx { namespace qt { namespace map { static const std::string logPrefix_ = "scwx::qt::map::alert_layer"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); static const boost::gil::rgba32f_pixel_t kBlack_ {0.0f, 0.0f, 0.0f, 1.0f}; template struct AlertTypeHash; template<> struct AlertTypeHash> { size_t operator()(const std::pair& x) const; }; class AlertLayerHandler : public QObject { Q_OBJECT public: struct SegmentRecord { std::shared_ptr segment_; types::TextEventKey key_; std::shared_ptr message_; std::chrono::system_clock::time_point segmentBegin_; std::chrono::system_clock::time_point segmentEnd_; SegmentRecord( const std::shared_ptr& segment, const types::TextEventKey& key, const std::shared_ptr& message) : segment_ {segment}, key_ {key}, message_ {message}, segmentBegin_ {segment->event_begin()}, segmentEnd_ {segment->event_end()} { } }; explicit AlertLayerHandler() { connect(textEventManager_.get(), &manager::TextEventManager::AlertUpdated, this, [this](const types::TextEventKey& key, std::size_t messageIndex) { HandleAlert(key, messageIndex); }); } ~AlertLayerHandler() { disconnect(textEventManager_.get(), nullptr, this, nullptr); std::unique_lock lock(alertMutex_); } std::unordered_map< std::pair, boost::container::stable_vector>, AlertTypeHash>> segmentsByType_ {}; std::unordered_map< types::TextEventKey, boost::container::stable_vector>, types::TextEventHash> segmentsByKey_ {}; void HandleAlert(const types::TextEventKey& key, size_t messageIndex); static AlertLayerHandler& Instance(); std::shared_ptr textEventManager_ { manager::TextEventManager::Instance()}; std::shared_mutex alertMutex_ {}; signals: void AlertAdded(const std::shared_ptr& segmentRecord, awips::Phenomenon phenomenon); void AlertUpdated(const std::shared_ptr& segmentRecord); void AlertsUpdated(awips::Phenomenon phenomenon, bool alertActive); }; class AlertLayer::Impl { public: explicit Impl(AlertLayer* self, std::shared_ptr context, awips::Phenomenon phenomenon) : self_ {self}, phenomenon_ {phenomenon}, geoLines_ {{false, std::make_shared(context)}, {true, std::make_shared(context)}} { auto& paletteSettings = settings::PaletteSettings::Instance(); for (auto alertActive : {false, true}) { lineColor_.emplace( alertActive, util::color::ToRgba32fPixelT( paletteSettings.alert_color(phenomenon_, alertActive) .GetValue())); } ConnectSignals(); } ~Impl() { receiver_ = nullptr; std::unique_lock lock(linesMutex_); }; void AddAlert( const std::shared_ptr& segmentRecord); void UpdateAlert( const std::shared_ptr& segmentRecord); void ConnectAlertHandlerSignals(); void ConnectSignals(); void HandleGeoLinesEvent(std::shared_ptr& di, QEvent* ev); void HandleGeoLinesHover(std::shared_ptr& di, const QPointF& mouseGlobalPos); void AddLine(std::shared_ptr& geoLines, std::shared_ptr& di, const common::Coordinate& p1, const common::Coordinate& p2, boost::gil::rgba32f_pixel_t color, float width, std::chrono::system_clock::time_point startTime, std::chrono::system_clock::time_point endTime, bool enableHover); void AddLines(std::shared_ptr& geoLines, const std::vector& coordinates, boost::gil::rgba32f_pixel_t color, float width, std::chrono::system_clock::time_point startTime, std::chrono::system_clock::time_point endTime, bool enableHover, boost::container::stable_vector< std::shared_ptr>& drawItems); AlertLayer* self_; const awips::Phenomenon phenomenon_; std::unique_ptr receiver_ {std::make_unique()}; std::unordered_map> geoLines_; std::unordered_map, boost::container::stable_vector< std::shared_ptr>> linesBySegment_ {}; std::unordered_map, std::shared_ptr> segmentsByLine_; std::mutex linesMutex_ {}; std::unordered_map lineColor_; std::chrono::system_clock::time_point selectedTime_ {}; std::shared_ptr lastHoverDi_ {nullptr}; std::string tooltip_ {}; }; AlertLayer::AlertLayer(std::shared_ptr context, awips::Phenomenon phenomenon) : DrawLayer(context), p(std::make_unique(this, context, phenomenon)) { for (auto alertActive : {false, true}) { auto& geoLines = p->geoLines_.at(alertActive); AddDrawItem(geoLines); } } AlertLayer::~AlertLayer() = default; void AlertLayer::InitializeHandler() { static bool ftt = true; if (ftt) { logger_->debug("Initializing handler"); AlertLayerHandler::Instance(); ftt = false; } } void AlertLayer::Initialize() { logger_->debug("Initialize: {}", awips::GetPhenomenonText(p->phenomenon_)); DrawLayer::Initialize(); auto& alertLayerHandler = AlertLayerHandler::Instance(); // Take a shared lock to prevent handling additional alerts while populating // initial lists std::shared_lock lock {alertLayerHandler.alertMutex_}; for (auto alertActive : {false, true}) { auto& geoLines = p->geoLines_.at(alertActive); geoLines->StartLines(); // Populate initial segments auto segmentsIt = alertLayerHandler.segmentsByType_.find({p->phenomenon_, alertActive}); if (segmentsIt != alertLayerHandler.segmentsByType_.cend()) { for (auto& segment : segmentsIt->second) { p->AddAlert(segment); } } geoLines->FinishLines(); } p->ConnectAlertHandlerSignals(); } void AlertLayer::Render(const QMapLibre::CustomLayerRenderParameters& params) { gl::OpenGLFunctions& gl = context()->gl(); for (auto alertActive : {false, true}) { p->geoLines_.at(alertActive)->set_selected_time(p->selectedTime_); } DrawLayer::Render(params); SCWX_GL_CHECK_ERROR(); } void AlertLayer::Deinitialize() { logger_->debug("Deinitialize: {}", awips::GetPhenomenonText(p->phenomenon_)); DrawLayer::Deinitialize(); } void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, size_t messageIndex) { logger_->trace("HandleAlert: {}", key.ToString()); std::unordered_set, AlertTypeHash>> alertsUpdated {}; auto message = textEventManager_->message_list(key).at(messageIndex); // Determine start time for first segment std::chrono::system_clock::time_point segmentBegin {}; if (message->segment_count() > 0) { segmentBegin = message->segment(0)->event_begin(); } // Take a unique mutex before modifying segments std::unique_lock lock {alertMutex_}; // Update any existing segments with new end time auto& segmentsForKey = segmentsByKey_[key]; for (auto& segmentRecord : segmentsForKey) { if (segmentRecord->segmentEnd_ > segmentBegin) { segmentRecord->segmentEnd_ = segmentBegin; Q_EMIT AlertUpdated(segmentRecord); } } // Process new segments for (auto& segment : message->segments()) { if (!segment->codedLocation_.has_value()) { // Cannot handle a segment without a location 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); auto& segmentsForType = segmentsByType_[{key.phenomenon_, alertActive}]; // Insert segment into lists std::shared_ptr segmentRecord = std::make_shared(segment, key, message); segmentsForKey.push_back(segmentRecord); segmentsForType.push_back(segmentRecord); Q_EMIT AlertAdded(segmentRecord, phenomenon); alertsUpdated.emplace(phenomenon, alertActive); } // Release the lock after completing segment updates lock.unlock(); for (auto& alert : alertsUpdated) { // Emit signal for each updated alert type Q_EMIT AlertsUpdated(alert.first, alert.second); } } void AlertLayer::Impl::ConnectAlertHandlerSignals() { auto& alertLayerHandler = AlertLayerHandler::Instance(); QObject::connect( &alertLayerHandler, &AlertLayerHandler::AlertAdded, receiver_.get(), [this]( const std::shared_ptr& segmentRecord, awips::Phenomenon phenomenon) { if (phenomenon == phenomenon_) { AddAlert(segmentRecord); } }); QObject::connect( &alertLayerHandler, &AlertLayerHandler::AlertUpdated, receiver_.get(), [this]( const std::shared_ptr& segmentRecord) { if (segmentRecord->key_.phenomenon_ == phenomenon_) { UpdateAlert(segmentRecord); } }); } void AlertLayer::Impl::ConnectSignals() { auto timelineManager = manager::TimelineManager::Instance(); QObject::connect(timelineManager.get(), &manager::TimelineManager::SelectedTimeUpdated, receiver_.get(), [this](std::chrono::system_clock::time_point dateTime) { selectedTime_ = dateTime; }); } void AlertLayer::Impl::AddAlert( const std::shared_ptr& segmentRecord) { auto& segment = segmentRecord->segment_; auto& vtec = segment->header_->vtecString_.front(); auto action = vtec.pVtec_.action(); bool alertActive = (action != awips::PVtec::Action::Canceled); auto& startTime = segmentRecord->segmentBegin_; auto& endTime = segmentRecord->segmentEnd_; auto& lineColor = lineColor_.at(alertActive); auto& geoLines = geoLines_.at(alertActive); const auto& coordinates = segment->codedLocation_->coordinates(); // Take a mutex before modifying lines by segment std::unique_lock lock {linesMutex_}; // Add draw items only if the segment has not already been added auto drawItems = linesBySegment_.try_emplace( segmentRecord, boost::container::stable_vector< std::shared_ptr> {}); // If draw items were added if (drawItems.second) { // Add border AddLines(geoLines, coordinates, kBlack_, 5.0f, startTime, endTime, true, drawItems.first->second); // Add only border to segmentsByLine_ for (auto& di : drawItems.first->second) { segmentsByLine_.insert({di, segmentRecord}); } // Add line AddLines(geoLines, coordinates, lineColor, 3.0f, startTime, endTime, false, drawItems.first->second); } } void AlertLayer::Impl::UpdateAlert( const std::shared_ptr& segmentRecord) { // Take a mutex before referencing lines iterators and stable vector std::unique_lock lock {linesMutex_}; auto it = linesBySegment_.find(segmentRecord); if (it != linesBySegment_.cend()) { auto& segment = segmentRecord->segment_; auto& vtec = segment->header_->vtecString_.front(); auto action = vtec.pVtec_.action(); bool alertActive = (action != awips::PVtec::Action::Canceled); auto& geoLines = geoLines_.at(alertActive); auto& lines = it->second; for (auto& line : lines) { geoLines->SetLineStartTime(line, segmentRecord->segmentBegin_); geoLines->SetLineEndTime(line, segmentRecord->segmentEnd_); } } } void AlertLayer::Impl::AddLines( std::shared_ptr& geoLines, const std::vector& coordinates, boost::gil::rgba32f_pixel_t color, float width, std::chrono::system_clock::time_point startTime, std::chrono::system_clock::time_point endTime, bool enableHover, boost::container::stable_vector>& drawItems) { for (std::size_t i = 0, j = 1; i < coordinates.size(); ++i, ++j) { if (j >= coordinates.size()) { j = 0; // Ignore repeated coordinates at the end if (coordinates[i] == coordinates[j]) { break; } } auto di = geoLines->AddLine(); AddLine(geoLines, di, coordinates[i], coordinates[j], color, width, startTime, endTime, enableHover); drawItems.push_back(di); } } void AlertLayer::Impl::AddLine(std::shared_ptr& geoLines, std::shared_ptr& di, const common::Coordinate& p1, const common::Coordinate& p2, boost::gil::rgba32f_pixel_t color, float width, std::chrono::system_clock::time_point startTime, std::chrono::system_clock::time_point endTime, bool enableHover) { geoLines->SetLineLocation( di, p1.latitude_, p1.longitude_, p2.latitude_, p2.longitude_); geoLines->SetLineModulate(di, color); geoLines->SetLineWidth(di, width); geoLines->SetLineStartTime(di, startTime); geoLines->SetLineEndTime(di, endTime); if (enableHover) { geoLines->SetLineHoverCallback( di, std::bind(&AlertLayer::Impl::HandleGeoLinesHover, this, std::placeholders::_1, std::placeholders::_2)); gl::draw::GeoLines::RegisterEventHandler( di, std::bind(&AlertLayer::Impl::HandleGeoLinesEvent, this, di, std::placeholders::_1)); } } void AlertLayer::Impl::HandleGeoLinesEvent( std::shared_ptr& di, QEvent* ev) { switch (ev->type()) { case QEvent::Type::MouseButtonPress: auto it = segmentsByLine_.find(di); if (it != segmentsByLine_.cend()) { // Display alert dialog logger_->debug("Selected alert: {}", it->second->key_.ToString()); Q_EMIT self_->AlertSelected(it->second->key_); } break; } } void AlertLayer::Impl::HandleGeoLinesHover( std::shared_ptr& di, const QPointF& mouseGlobalPos) { if (di != lastHoverDi_) { auto it = segmentsByLine_.find(di); if (it != segmentsByLine_.cend()) { tooltip_ = boost::algorithm::join(it->second->segment_->productContent_, "\n"); } else { tooltip_.clear(); } lastHoverDi_ = di; } if (!tooltip_.empty()) { util::tooltip::Show(tooltip_, mouseGlobalPos); } } AlertLayerHandler& AlertLayerHandler::Instance() { static AlertLayerHandler alertLayerHandler_ {}; return alertLayerHandler_; } size_t AlertTypeHash>::operator()( const std::pair& x) const { size_t seed = 0; boost::hash_combine(seed, x.first); boost::hash_combine(seed, x.second); return seed; } } // namespace map } // namespace qt } // namespace scwx #include "alert_layer.moc"