mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-11-04 04:40:06 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			660 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			660 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#include <scwx/qt/map/alert_layer.hpp>
 | 
						|
#include <scwx/qt/gl/draw/geo_lines.hpp>
 | 
						|
#include <scwx/qt/manager/text_event_manager.hpp>
 | 
						|
#include <scwx/qt/manager/timeline_manager.hpp>
 | 
						|
#include <scwx/qt/settings/palette_settings.hpp>
 | 
						|
#include <scwx/qt/util/color.hpp>
 | 
						|
#include <scwx/qt/util/tooltip.hpp>
 | 
						|
#include <scwx/util/logger.hpp>
 | 
						|
 | 
						|
#include <chrono>
 | 
						|
#include <mutex>
 | 
						|
#include <unordered_map>
 | 
						|
#include <unordered_set>
 | 
						|
 | 
						|
#include <boost/algorithm/string/join.hpp>
 | 
						|
#include <boost/asio/system_timer.hpp>
 | 
						|
#include <boost/asio/thread_pool.hpp>
 | 
						|
#include <boost/container/stable_vector.hpp>
 | 
						|
#include <boost/container_hash/hash.hpp>
 | 
						|
#include <QEvent>
 | 
						|
 | 
						|
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<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:
 | 
						|
   struct SegmentRecord
 | 
						|
   {
 | 
						|
      std::shared_ptr<const awips::Segment>            segment_;
 | 
						|
      types::TextEventKey                              key_;
 | 
						|
      std::shared_ptr<const awips::TextProductMessage> message_;
 | 
						|
      std::chrono::system_clock::time_point            segmentBegin_;
 | 
						|
      std::chrono::system_clock::time_point            segmentEnd_;
 | 
						|
 | 
						|
      SegmentRecord(
 | 
						|
         const std::shared_ptr<const awips::Segment>&            segment,
 | 
						|
         const types::TextEventKey&                              key,
 | 
						|
         const std::shared_ptr<const awips::TextProductMessage>& 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<awips::Phenomenon, bool>,
 | 
						|
      boost::container::stable_vector<std::shared_ptr<SegmentRecord>>,
 | 
						|
      AlertTypeHash<std::pair<awips::Phenomenon, bool>>>
 | 
						|
      segmentsByType_ {};
 | 
						|
 | 
						|
   std::unordered_map<
 | 
						|
      types::TextEventKey,
 | 
						|
      boost::container::stable_vector<std::shared_ptr<SegmentRecord>>,
 | 
						|
      types::TextEventHash<types::TextEventKey>>
 | 
						|
      segmentsByKey_ {};
 | 
						|
 | 
						|
   void HandleAlert(const types::TextEventKey& key, size_t messageIndex);
 | 
						|
 | 
						|
   static AlertLayerHandler& Instance();
 | 
						|
 | 
						|
   std::shared_ptr<manager::TextEventManager> textEventManager_ {
 | 
						|
      manager::TextEventManager::Instance()};
 | 
						|
 | 
						|
   std::shared_mutex alertMutex_ {};
 | 
						|
 | 
						|
signals:
 | 
						|
   void AlertAdded(const std::shared_ptr<SegmentRecord>& segmentRecord,
 | 
						|
                   awips::Phenomenon                     phenomenon);
 | 
						|
   void AlertUpdated(const std::shared_ptr<SegmentRecord>& segmentRecord);
 | 
						|
   void AlertsUpdated(awips::Phenomenon phenomenon, bool alertActive);
 | 
						|
};
 | 
						|
 | 
						|
class AlertLayer::Impl
 | 
						|
{
 | 
						|
public:
 | 
						|
   explicit Impl(AlertLayer*                 self,
 | 
						|
                 std::shared_ptr<MapContext> context,
 | 
						|
                 awips::Phenomenon           phenomenon) :
 | 
						|
       self_ {self},
 | 
						|
       phenomenon_ {phenomenon},
 | 
						|
       geoLines_ {{false, std::make_shared<gl::draw::GeoLines>(context)},
 | 
						|
                  {true, std::make_shared<gl::draw::GeoLines>(context)}}
 | 
						|
   {
 | 
						|
      auto& paletteSettings = settings::PaletteSettings::Instance();
 | 
						|
 | 
						|
      for (auto alertActive : {false, true})
 | 
						|
      {
 | 
						|
         lineColor_.emplace(
 | 
						|
            alertActive,
 | 
						|
            util::color::ToRgba32fPixelT(
 | 
						|
               paletteSettings.alert_color(phenomenon_, alertActive)
 | 
						|
                  .GetValue()));
 | 
						|
      }
 | 
						|
 | 
						|
      ConnectSignals();
 | 
						|
      ScheduleRefresh();
 | 
						|
   }
 | 
						|
   ~Impl()
 | 
						|
   {
 | 
						|
      std::unique_lock refreshLock(refreshMutex_);
 | 
						|
      refreshTimer_.cancel();
 | 
						|
      refreshLock.unlock();
 | 
						|
 | 
						|
      threadPool_.join();
 | 
						|
 | 
						|
      receiver_ = nullptr;
 | 
						|
 | 
						|
      std::unique_lock lock(linesMutex_);
 | 
						|
   };
 | 
						|
 | 
						|
   void AddAlert(
 | 
						|
      const std::shared_ptr<AlertLayerHandler::SegmentRecord>& segmentRecord);
 | 
						|
   void UpdateAlert(
 | 
						|
      const std::shared_ptr<AlertLayerHandler::SegmentRecord>& segmentRecord);
 | 
						|
   void ConnectAlertHandlerSignals();
 | 
						|
   void ConnectSignals();
 | 
						|
   void HandleGeoLinesEvent(std::shared_ptr<gl::draw::GeoLineDrawItem>& di,
 | 
						|
                            QEvent*                                     ev);
 | 
						|
   void HandleGeoLinesHover(std::shared_ptr<gl::draw::GeoLineDrawItem>& di,
 | 
						|
                            const QPointF& mouseGlobalPos);
 | 
						|
   void ScheduleRefresh();
 | 
						|
 | 
						|
   void AddLine(std::shared_ptr<gl::draw::GeoLines>&        geoLines,
 | 
						|
                std::shared_ptr<gl::draw::GeoLineDrawItem>& 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<gl::draw::GeoLines>&   geoLines,
 | 
						|
                 const std::vector<common::Coordinate>& 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<gl::draw::GeoLineDrawItem>>& drawItems);
 | 
						|
 | 
						|
   boost::asio::thread_pool threadPool_ {1u};
 | 
						|
 | 
						|
   AlertLayer* self_;
 | 
						|
 | 
						|
   boost::asio::system_timer refreshTimer_ {threadPool_};
 | 
						|
   std::mutex                refreshMutex_;
 | 
						|
 | 
						|
   const awips::Phenomenon phenomenon_;
 | 
						|
 | 
						|
   std::unique_ptr<QObject> receiver_ {std::make_unique<QObject>()};
 | 
						|
 | 
						|
   std::unordered_map<bool, std::shared_ptr<gl::draw::GeoLines>> geoLines_;
 | 
						|
 | 
						|
   std::unordered_map<std::shared_ptr<const AlertLayerHandler::SegmentRecord>,
 | 
						|
                      boost::container::stable_vector<
 | 
						|
                         std::shared_ptr<gl::draw::GeoLineDrawItem>>>
 | 
						|
      linesBySegment_ {};
 | 
						|
   std::unordered_map<std::shared_ptr<const gl::draw::GeoLineDrawItem>,
 | 
						|
                      std::shared_ptr<const AlertLayerHandler::SegmentRecord>>
 | 
						|
              segmentsByLine_;
 | 
						|
   std::mutex linesMutex_ {};
 | 
						|
 | 
						|
   std::unordered_map<bool, boost::gil::rgba32f_pixel_t> lineColor_;
 | 
						|
 | 
						|
   std::chrono::system_clock::time_point selectedTime_ {};
 | 
						|
 | 
						|
   std::shared_ptr<const gl::draw::GeoLineDrawItem> lastHoverDi_ {nullptr};
 | 
						|
   std::string                                      tooltip_ {};
 | 
						|
};
 | 
						|
 | 
						|
AlertLayer::AlertLayer(std::shared_ptr<MapContext> context,
 | 
						|
                       awips::Phenomenon           phenomenon) :
 | 
						|
    DrawLayer(context), p(std::make_unique<Impl>(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<std::pair<awips::Phenomenon, bool>,
 | 
						|
                      AlertTypeHash<std::pair<awips::Phenomenon, bool>>>
 | 
						|
      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> segmentRecord =
 | 
						|
         std::make_shared<SegmentRecord>(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<AlertLayerHandler::SegmentRecord>& segmentRecord,
 | 
						|
         awips::Phenomenon                                        phenomenon)
 | 
						|
      {
 | 
						|
         if (phenomenon == phenomenon_)
 | 
						|
         {
 | 
						|
            AddAlert(segmentRecord);
 | 
						|
         }
 | 
						|
      });
 | 
						|
   QObject::connect(
 | 
						|
      &alertLayerHandler,
 | 
						|
      &AlertLayerHandler::AlertUpdated,
 | 
						|
      receiver_.get(),
 | 
						|
      [this](
 | 
						|
         const std::shared_ptr<AlertLayerHandler::SegmentRecord>& 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::ScheduleRefresh()
 | 
						|
{
 | 
						|
   using namespace std::chrono_literals;
 | 
						|
 | 
						|
   // Take a unique lock before refreshing
 | 
						|
   std::unique_lock lock(refreshMutex_);
 | 
						|
 | 
						|
   // Expires at the top of the next minute
 | 
						|
   std::chrono::system_clock::time_point now =
 | 
						|
      std::chrono::floor<std::chrono::minutes>(
 | 
						|
         std::chrono::system_clock::now());
 | 
						|
   refreshTimer_.expires_at(now + 1min);
 | 
						|
 | 
						|
   refreshTimer_.async_wait(
 | 
						|
      [this](const boost::system::error_code& e)
 | 
						|
      {
 | 
						|
         if (e == boost::asio::error::operation_aborted)
 | 
						|
         {
 | 
						|
            logger_->debug("Refresh timer cancelled");
 | 
						|
         }
 | 
						|
         else if (e != boost::system::errc::success)
 | 
						|
         {
 | 
						|
            logger_->warn("Refresh timer error: {}", e.message());
 | 
						|
         }
 | 
						|
         else
 | 
						|
         {
 | 
						|
            Q_EMIT self_->NeedsRendering();
 | 
						|
            ScheduleRefresh();
 | 
						|
         }
 | 
						|
      });
 | 
						|
}
 | 
						|
 | 
						|
void AlertLayer::Impl::AddAlert(
 | 
						|
   const std::shared_ptr<AlertLayerHandler::SegmentRecord>& 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<gl::draw::GeoLineDrawItem>> {});
 | 
						|
 | 
						|
   // 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<AlertLayerHandler::SegmentRecord>& 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<gl::draw::GeoLines>&   geoLines,
 | 
						|
   const std::vector<common::Coordinate>& 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<gl::draw::GeoLineDrawItem>>&
 | 
						|
      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<gl::draw::GeoLines>& geoLines,
 | 
						|
                               std::shared_ptr<gl::draw::GeoLineDrawItem>& 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<gl::draw::GeoLineDrawItem>& 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;
 | 
						|
   }
 | 
						|
 | 
						|
   default:
 | 
						|
      break;
 | 
						|
   }
 | 
						|
}
 | 
						|
 | 
						|
void AlertLayer::Impl::HandleGeoLinesHover(
 | 
						|
   std::shared_ptr<gl::draw::GeoLineDrawItem>& 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<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.moc"
 |