#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::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(std::shared_ptr context, awips::Phenomenon phenomenon) : 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::ToRgba8PixelT( paletteSettings.alert_color(phenomenon_, alertActive) .GetValue())); } ConnectSignals(); } ~Impl() { receiver_ = nullptr; }; void AddAlert( const std::shared_ptr& segmentRecord); void UpdateAlert( const std::shared_ptr& segmentRecord); void ConnectSignals(); static 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); static void AddLines(std::shared_ptr& geoLines, const std::vector& coordinates, boost::gil::rgba32_pixel_t color, float width, std::chrono::system_clock::time_point startTime, std::chrono::system_clock::time_point endTime, std::vector>& drawItems); const awips::Phenomenon phenomenon_; std::unique_ptr receiver_ {std::make_unique()}; std::unordered_map> geoLines_; std::unordered_map lineColor_; std::chrono::system_clock::time_point selectedTime_ {}; }; AlertLayer::AlertLayer(std::shared_ptr context, awips::Phenomenon phenomenon) : DrawLayer(context), p(std::make_unique(context, phenomenon)) { for (auto alertActive : {false, true}) { auto& geoLines = p->geoLines_.at(alertActive); AddDrawItem(geoLines); } } AlertLayer::~AlertLayer() = default; void AlertLayer::Initialize() { logger_->debug("Initialize: {}", awips::GetPhenomenonText(p->phenomenon_)); DrawLayer::Initialize(); for (auto alertActive : {false, true}) { auto& geoLines = p->geoLines_.at(alertActive); geoLines->StartLines(); geoLines->FinishLines(); } } 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(); } bool AlertLayer::RunMousePicking( const QMapLibre::CustomLayerRenderParameters& params, const QPointF& mouseLocalPos, const QPointF& mouseGlobalPos, const glm::vec2& mouseCoords, const common::Coordinate& mouseGeoCoords, std::shared_ptr& eventHandler) { return DrawLayer::RunMousePicking(params, mouseLocalPos, mouseGlobalPos, mouseCoords, mouseGeoCoords, eventHandler); } 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(); auto eventEnd = vtec.pVtec_.event_end(); 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::ConnectSignals() { auto timelineManager = manager::TimelineManager::Instance(); QObject::connect( &AlertLayerHandler::Instance(), &AlertLayerHandler::AlertAdded, receiver_.get(), [this]( const std::shared_ptr& segmentRecord, awips::Phenomenon phenomenon) { if (phenomenon == phenomenon_) { AddAlert(segmentRecord); } }); 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(); std::vector> drawItems {}; AddLines( geoLines, coordinates, kBlack_, 5.0f, startTime, endTime, drawItems); AddLines( geoLines, coordinates, lineColor, 3.0f, startTime, endTime, drawItems); } void AlertLayer::Impl::UpdateAlert( [[maybe_unused]] const std::shared_ptr& segmentRecord) { // TODO } void AlertLayer::Impl::AddLines( std::shared_ptr& geoLines, const std::vector& coordinates, boost::gil::rgba32_pixel_t color, float width, std::chrono::system_clock::time_point startTime, std::chrono::system_clock::time_point endTime, 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); 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) { 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); } 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"