mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 20:20:05 +00:00 
			
		
		
		
	Merge pull request #257 from dpaulat/feature/enhanced-alerts-2
Alert Animation, Hover and Selection
This commit is contained in:
		
						commit
						822d0762a5
					
				
					 18 changed files with 937 additions and 465 deletions
				
			
		|  | @ -6,6 +6,7 @@ | ||||||
| 
 | 
 | ||||||
| #include <execution> | #include <execution> | ||||||
| 
 | 
 | ||||||
|  | #include <boost/unordered/unordered_flat_set.hpp> | ||||||
| #include <units/angle.h> | #include <units/angle.h> | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx | ||||||
|  | @ -25,13 +26,15 @@ static constexpr size_t kNumTriangles         = kNumRectangles * 2; | ||||||
| static constexpr size_t kVerticesPerTriangle  = 3; | static constexpr size_t kVerticesPerTriangle  = 3; | ||||||
| static constexpr size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; | static constexpr size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; | ||||||
| static constexpr size_t kPointsPerVertex      = 9; | static constexpr size_t kPointsPerVertex      = 9; | ||||||
| static constexpr size_t kBufferLength = | static constexpr size_t kLineBufferLength_ = | ||||||
|    kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; |    kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; | ||||||
| 
 | 
 | ||||||
| // Threshold, start time, end time
 | // Threshold, start time, end time, displayed
 | ||||||
| static constexpr std::size_t kIntegersPerVertex_ = 3; | static constexpr std::size_t kIntegersPerVertex_ = 4; | ||||||
|  | static constexpr std::size_t kIntegerBufferLength_ = | ||||||
|  |    kNumTriangles * kVerticesPerTriangle * kIntegersPerVertex_; | ||||||
| 
 | 
 | ||||||
| struct GeoLineDrawItem | struct GeoLineDrawItem : types::EventHandler | ||||||
| { | { | ||||||
|    bool                                        visible_ {true}; |    bool                                        visible_ {true}; | ||||||
|    units::length::nautical_miles<double>       threshold_ {}; |    units::length::nautical_miles<double>       threshold_ {}; | ||||||
|  | @ -46,6 +49,7 @@ struct GeoLineDrawItem | ||||||
|    float                        width_ {5.0}; |    float                        width_ {5.0}; | ||||||
|    units::angle::degrees<float> angle_ {}; |    units::angle::degrees<float> angle_ {}; | ||||||
|    std::string                  hoverText_ {}; |    std::string                  hoverText_ {}; | ||||||
|  |    GeoLines::HoverCallback      hoverCallback_ {nullptr}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class GeoLines::Impl | class GeoLines::Impl | ||||||
|  | @ -53,7 +57,7 @@ class GeoLines::Impl | ||||||
| public: | public: | ||||||
|    struct LineHoverEntry |    struct LineHoverEntry | ||||||
|    { |    { | ||||||
|       std::shared_ptr<const GeoLineDrawItem> di_; |       std::shared_ptr<GeoLineDrawItem> di_; | ||||||
| 
 | 
 | ||||||
|       glm::vec2 p1_; |       glm::vec2 p1_; | ||||||
|       glm::vec2 p2_; |       glm::vec2 p2_; | ||||||
|  | @ -81,6 +85,12 @@ public: | ||||||
|    void BufferLine(const std::shared_ptr<const GeoLineDrawItem>& di); |    void BufferLine(const std::shared_ptr<const GeoLineDrawItem>& di); | ||||||
|    void Update(); |    void Update(); | ||||||
|    void UpdateBuffers(); |    void UpdateBuffers(); | ||||||
|  |    void UpdateModifiedLineBuffers(); | ||||||
|  |    void UpdateSingleBuffer(const std::shared_ptr<GeoLineDrawItem>& di, | ||||||
|  |                            std::size_t                             lineIndex, | ||||||
|  |                            std::vector<float>&                     linesBuffer, | ||||||
|  |                            std::vector<GLint>&          integerBuffer, | ||||||
|  |                            std::vector<LineHoverEntry>& hoverLines); | ||||||
| 
 | 
 | ||||||
|    std::shared_ptr<GlContext> context_; |    std::shared_ptr<GlContext> context_; | ||||||
| 
 | 
 | ||||||
|  | @ -88,6 +98,8 @@ public: | ||||||
|    bool dirty_ {false}; |    bool dirty_ {false}; | ||||||
|    bool thresholded_ {false}; |    bool thresholded_ {false}; | ||||||
| 
 | 
 | ||||||
|  |    boost::unordered_flat_set<std::shared_ptr<GeoLineDrawItem>> dirtyLines_ {}; | ||||||
|  | 
 | ||||||
|    std::chrono::system_clock::time_point selectedTime_ {}; |    std::chrono::system_clock::time_point selectedTime_ {}; | ||||||
| 
 | 
 | ||||||
|    std::mutex lineMutex_ {}; |    std::mutex lineMutex_ {}; | ||||||
|  | @ -136,8 +148,7 @@ void GeoLines::set_thresholded(bool thresholded) | ||||||
| 
 | 
 | ||||||
| void GeoLines::Initialize() | void GeoLines::Initialize() | ||||||
| { | { | ||||||
|    gl::OpenGLFunctions& gl   = p->context_->gl(); |    gl::OpenGLFunctions& gl = p->context_->gl(); | ||||||
|    auto&                gl30 = p->context_->gl30(); |  | ||||||
| 
 | 
 | ||||||
|    p->shaderProgram_ = p->context_->GetShaderProgram( |    p->shaderProgram_ = p->context_->GetShaderProgram( | ||||||
|       {{GL_VERTEX_SHADER, ":/gl/geo_texture2d.vert"}, |       {{GL_VERTEX_SHADER, ":/gl/geo_texture2d.vert"}, | ||||||
|  | @ -158,8 +169,10 @@ void GeoLines::Initialize() | ||||||
| 
 | 
 | ||||||
|    gl.glBindVertexArray(p->vao_); |    gl.glBindVertexArray(p->vao_); | ||||||
|    gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); |    gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); | ||||||
|    gl.glBufferData( |    gl.glBufferData(GL_ARRAY_BUFFER, | ||||||
|       GL_ARRAY_BUFFER, sizeof(float) * kBufferLength, nullptr, GL_DYNAMIC_DRAW); |                    sizeof(float) * kLineBufferLength_, | ||||||
|  |                    nullptr, | ||||||
|  |                    GL_DYNAMIC_DRAW); | ||||||
| 
 | 
 | ||||||
|    // aLatLong
 |    // aLatLong
 | ||||||
|    gl.glVertexAttribPointer(0, |    gl.glVertexAttribPointer(0, | ||||||
|  | @ -217,7 +230,13 @@ void GeoLines::Initialize() | ||||||
|    gl.glEnableVertexAttribArray(6); |    gl.glEnableVertexAttribArray(6); | ||||||
| 
 | 
 | ||||||
|    // aDisplayed
 |    // aDisplayed
 | ||||||
|    gl30.glVertexAttribI1i(7, 1); |    gl.glVertexAttribPointer(7, | ||||||
|  |                             1, | ||||||
|  |                             GL_INT, | ||||||
|  |                             GL_FALSE, | ||||||
|  |                             kIntegersPerVertex_ * sizeof(GLint), | ||||||
|  |                             reinterpret_cast<void*>(3 * sizeof(float))); | ||||||
|  |    gl.glEnableVertexAttribArray(7); | ||||||
| 
 | 
 | ||||||
|    p->dirty_ = true; |    p->dirty_ = true; | ||||||
| } | } | ||||||
|  | @ -231,7 +250,7 @@ void GeoLines::Render(const QMapLibre::CustomLayerRenderParameters& params) | ||||||
| 
 | 
 | ||||||
|    std::unique_lock lock {p->lineMutex_}; |    std::unique_lock lock {p->lineMutex_}; | ||||||
| 
 | 
 | ||||||
|    if (p->currentLineList_.size() > 0) |    if (p->newLineList_.size() > 0) | ||||||
|    { |    { | ||||||
|       gl::OpenGLFunctions& gl = p->context_->gl(); |       gl::OpenGLFunctions& gl = p->context_->gl(); | ||||||
| 
 | 
 | ||||||
|  | @ -280,7 +299,7 @@ void GeoLines::Deinitialize() | ||||||
|    gl::OpenGLFunctions& gl = p->context_->gl(); |    gl::OpenGLFunctions& gl = p->context_->gl(); | ||||||
| 
 | 
 | ||||||
|    gl.glDeleteVertexArrays(1, &p->vao_); |    gl.glDeleteVertexArrays(1, &p->vao_); | ||||||
|    gl.glDeleteBuffers(2, p->vbo_.data()); |    gl.glDeleteBuffers(static_cast<GLsizei>(p->vbo_.size()), p->vbo_.data()); | ||||||
| 
 | 
 | ||||||
|    std::unique_lock lock {p->lineMutex_}; |    std::unique_lock lock {p->lineMutex_}; | ||||||
| 
 | 
 | ||||||
|  | @ -314,43 +333,104 @@ void GeoLines::SetLineLocation(const std::shared_ptr<GeoLineDrawItem>& di, | ||||||
|                                float latitude2, |                                float latitude2, | ||||||
|                                float longitude2) |                                float longitude2) | ||||||
| { | { | ||||||
|    di->latitude1_  = latitude1; |    if (di->latitude1_ != latitude1 || di->longitude1_ != longitude1 || | ||||||
|    di->longitude1_ = longitude1; |        di->latitude2_ != latitude1 || di->longitude2_ != longitude1) | ||||||
|    di->latitude2_  = latitude2; |    { | ||||||
|    di->longitude2_ = longitude2; |       di->latitude1_  = latitude1; | ||||||
|  |       di->longitude1_ = longitude1; | ||||||
|  |       di->latitude2_  = latitude2; | ||||||
|  |       di->longitude2_ = longitude2; | ||||||
|  |       p->dirtyLines_.insert(di); | ||||||
|  |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void GeoLines::SetLineModulate(const std::shared_ptr<GeoLineDrawItem>& di, | void GeoLines::SetLineModulate(const std::shared_ptr<GeoLineDrawItem>& di, | ||||||
|                                boost::gil::rgba8_pixel_t               modulate) |                                boost::gil::rgba8_pixel_t               modulate) | ||||||
| { | { | ||||||
|    di->modulate_ = {modulate[0] / 255.0f, |    boost::gil::rgba32f_pixel_t newModulate = {modulate[0] / 255.0f, | ||||||
|                     modulate[1] / 255.0f, |                                               modulate[1] / 255.0f, | ||||||
|                     modulate[2] / 255.0f, |                                               modulate[2] / 255.0f, | ||||||
|                     modulate[3] / 255.0f}; |                                               modulate[3] / 255.0f}; | ||||||
|  | 
 | ||||||
|  |    if (di->modulate_ != newModulate) | ||||||
|  |    { | ||||||
|  |       di->modulate_ = newModulate; | ||||||
|  |       p->dirtyLines_.insert(di); | ||||||
|  |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void GeoLines::SetLineModulate(const std::shared_ptr<GeoLineDrawItem>& di, | void GeoLines::SetLineModulate(const std::shared_ptr<GeoLineDrawItem>& di, | ||||||
|                                boost::gil::rgba32f_pixel_t             modulate) |                                boost::gil::rgba32f_pixel_t             modulate) | ||||||
| { | { | ||||||
|    di->modulate_ = modulate; |    if (di->modulate_ != modulate) | ||||||
|  |    { | ||||||
|  |       di->modulate_ = modulate; | ||||||
|  |       p->dirtyLines_.insert(di); | ||||||
|  |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void GeoLines::SetLineWidth(const std::shared_ptr<GeoLineDrawItem>& di, | void GeoLines::SetLineWidth(const std::shared_ptr<GeoLineDrawItem>& di, | ||||||
|                             float                                   width) |                             float                                   width) | ||||||
| { | { | ||||||
|    di->width_ = width; |    if (di->width_ != width) | ||||||
|  |    { | ||||||
|  |       di->width_ = width; | ||||||
|  |       p->dirtyLines_.insert(di); | ||||||
|  |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void GeoLines::SetLineVisible(const std::shared_ptr<GeoLineDrawItem>& di, | void GeoLines::SetLineVisible(const std::shared_ptr<GeoLineDrawItem>& di, | ||||||
|                               bool                                    visible) |                               bool                                    visible) | ||||||
| { | { | ||||||
|    di->visible_ = visible; |    if (di->visible_ != visible) | ||||||
|  |    { | ||||||
|  |       di->visible_ = visible; | ||||||
|  |       p->dirtyLines_.insert(di); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void GeoLines::SetLineHoverCallback(const std::shared_ptr<GeoLineDrawItem>& di, | ||||||
|  |                                     const HoverCallback& callback) | ||||||
|  | { | ||||||
|  |    if (di->hoverCallback_ != nullptr || callback != nullptr) | ||||||
|  |    { | ||||||
|  |       di->hoverCallback_ = callback; | ||||||
|  |       p->dirtyLines_.insert(di); | ||||||
|  |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void GeoLines::SetLineHoverText(const std::shared_ptr<GeoLineDrawItem>& di, | void GeoLines::SetLineHoverText(const std::shared_ptr<GeoLineDrawItem>& di, | ||||||
|                                 const std::string&                      text) |                                 const std::string&                      text) | ||||||
| { | { | ||||||
|    di->hoverText_ = text; |    if (di->hoverText_ != text) | ||||||
|  |    { | ||||||
|  |       di->hoverText_ = text; | ||||||
|  |       p->dirtyLines_.insert(di); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void GeoLines::SetLineStartTime(const std::shared_ptr<GeoLineDrawItem>& di, | ||||||
|  |                                 std::chrono::system_clock::time_point startTime) | ||||||
|  | { | ||||||
|  |    if (di->startTime_ != startTime) | ||||||
|  |    { | ||||||
|  |       di->startTime_ = | ||||||
|  |          std::chrono::time_point_cast<std::chrono::seconds, | ||||||
|  |                                       std::chrono::system_clock>(startTime); | ||||||
|  |       p->dirtyLines_.insert(di); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void GeoLines::SetLineEndTime(const std::shared_ptr<GeoLineDrawItem>& di, | ||||||
|  |                               std::chrono::system_clock::time_point   endTime) | ||||||
|  | { | ||||||
|  |    if (di->endTime_ != endTime) | ||||||
|  |    { | ||||||
|  |       di->endTime_ = | ||||||
|  |          std::chrono::time_point_cast<std::chrono::seconds, | ||||||
|  |                                       std::chrono::system_clock>(endTime); | ||||||
|  |       p->dirtyLines_.insert(di); | ||||||
|  |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void GeoLines::FinishLines() | void GeoLines::FinishLines() | ||||||
|  | @ -361,7 +441,7 @@ void GeoLines::FinishLines() | ||||||
|    std::unique_lock lock {p->lineMutex_}; |    std::unique_lock lock {p->lineMutex_}; | ||||||
| 
 | 
 | ||||||
|    // Swap buffers
 |    // Swap buffers
 | ||||||
|    p->currentLineList_.swap(p->newLineList_); |    p->currentLineList_ = p->newLineList_; | ||||||
|    p->currentLinesBuffer_.swap(p->newLinesBuffer_); |    p->currentLinesBuffer_.swap(p->newLinesBuffer_); | ||||||
|    p->currentIntegerBuffer_.swap(p->newIntegerBuffer_); |    p->currentIntegerBuffer_.swap(p->newIntegerBuffer_); | ||||||
|    p->currentHoverLines_.swap(p->newHoverLines_); |    p->currentHoverLines_.swap(p->newHoverLines_); | ||||||
|  | @ -379,20 +459,69 @@ void GeoLines::FinishLines() | ||||||
| void GeoLines::Impl::UpdateBuffers() | void GeoLines::Impl::UpdateBuffers() | ||||||
| { | { | ||||||
|    newLinesBuffer_.clear(); |    newLinesBuffer_.clear(); | ||||||
|    newLinesBuffer_.reserve(newLineList_.size() * kBufferLength); |    newLinesBuffer_.reserve(newLineList_.size() * kLineBufferLength_); | ||||||
|    newIntegerBuffer_.clear(); |    newIntegerBuffer_.clear(); | ||||||
|    newIntegerBuffer_.reserve(newLineList_.size() * kVerticesPerRectangle * |    newIntegerBuffer_.reserve(newLineList_.size() * kVerticesPerRectangle * | ||||||
|                              kIntegersPerVertex_); |                              kIntegersPerVertex_); | ||||||
|    newHoverLines_.clear(); |    newHoverLines_.clear(); | ||||||
| 
 | 
 | ||||||
|    for (auto& di : newLineList_) |    for (std::size_t i = 0; i < newLineList_.size(); ++i) | ||||||
|    { |    { | ||||||
|       BufferLine(di); |       auto& di = newLineList_[i]; | ||||||
|  | 
 | ||||||
|  |       // Update line buffer
 | ||||||
|  |       UpdateSingleBuffer( | ||||||
|  |          di, i, newLinesBuffer_, newIntegerBuffer_, newHoverLines_); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // All lines have been updated
 | ||||||
|  |    dirtyLines_.clear(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void GeoLines::Impl::UpdateModifiedLineBuffers() | ||||||
|  | { | ||||||
|  |    // Synchronize line list
 | ||||||
|  |    currentLineList_ = newLineList_; | ||||||
|  |    currentLinesBuffer_.resize(currentLineList_.size() * kLineBufferLength_); | ||||||
|  |    currentIntegerBuffer_.resize(currentLineList_.size() * | ||||||
|  |                                 kVerticesPerRectangle * kIntegersPerVertex_); | ||||||
|  | 
 | ||||||
|  |    // Update buffers for modified lines
 | ||||||
|  |    for (auto& di : dirtyLines_) | ||||||
|  |    { | ||||||
|  |       // Find modified line in the current list
 | ||||||
|  |       auto it = | ||||||
|  |          std::find(currentLineList_.cbegin(), currentLineList_.cend(), di); | ||||||
|  | 
 | ||||||
|  |       // Ignore invalid lines
 | ||||||
|  |       if (it == currentLineList_.cend()) | ||||||
|  |       { | ||||||
|  |          continue; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       auto lineIndex = std::distance(currentLineList_.cbegin(), it); | ||||||
|  | 
 | ||||||
|  |       UpdateSingleBuffer(di, | ||||||
|  |                          lineIndex, | ||||||
|  |                          currentLinesBuffer_, | ||||||
|  |                          currentIntegerBuffer_, | ||||||
|  |                          currentHoverLines_); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Clear list of modified lines
 | ||||||
|  |    if (!dirtyLines_.empty()) | ||||||
|  |    { | ||||||
|  |       dirtyLines_.clear(); | ||||||
|  |       dirty_ = true; | ||||||
|    } |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void GeoLines::Impl::BufferLine( | void GeoLines::Impl::UpdateSingleBuffer( | ||||||
|    const std::shared_ptr<const GeoLineDrawItem>& di) |    const std::shared_ptr<GeoLineDrawItem>& di, | ||||||
|  |    std::size_t                             lineIndex, | ||||||
|  |    std::vector<float>&                     lineBuffer, | ||||||
|  |    std::vector<GLint>&                     integerBuffer, | ||||||
|  |    std::vector<LineHoverEntry>&            hoverLines) | ||||||
| { | { | ||||||
|    // Threshold value
 |    // Threshold value
 | ||||||
|    units::length::nautical_miles<double> threshold = di->threshold_; |    units::length::nautical_miles<double> threshold = di->threshold_; | ||||||
|  | @ -438,38 +567,66 @@ void GeoLines::Impl::BufferLine( | ||||||
|    const float mc2 = di->modulate_[2]; |    const float mc2 = di->modulate_[2]; | ||||||
|    const float mc3 = di->modulate_[3]; |    const float mc3 = di->modulate_[3]; | ||||||
| 
 | 
 | ||||||
|    // Update buffers
 |    // Visibility
 | ||||||
|    newLinesBuffer_.insert(newLinesBuffer_.end(), |    const GLint v = static_cast<GLint>(di->visible_); | ||||||
|                           { |  | ||||||
|                              // Line
 |  | ||||||
|                              lat1, lon1, lx, by, mc0, mc1, mc2, mc3, a, // BL
 |  | ||||||
|                              lat2, lon2, lx, ty, mc0, mc1, mc2, mc3, a, // TL
 |  | ||||||
|                              lat1, lon1, rx, by, mc0, mc1, mc2, mc3, a, // BR
 |  | ||||||
|                              lat1, lon1, rx, by, mc0, mc1, mc2, mc3, a, // BR
 |  | ||||||
|                              lat2, lon2, rx, ty, mc0, mc1, mc2, mc3, a, // TR
 |  | ||||||
|                              lat2, lon2, lx, ty, mc0, mc1, mc2, mc3, a  // TL
 |  | ||||||
|                           }); |  | ||||||
|    newIntegerBuffer_.insert(newIntegerBuffer_.end(), |  | ||||||
|                             {thresholdValue, |  | ||||||
|                              startTime, |  | ||||||
|                              endTime, |  | ||||||
|                              thresholdValue, |  | ||||||
|                              startTime, |  | ||||||
|                              endTime, |  | ||||||
|                              thresholdValue, |  | ||||||
|                              startTime, |  | ||||||
|                              endTime, |  | ||||||
|                              thresholdValue, |  | ||||||
|                              startTime, |  | ||||||
|                              endTime, |  | ||||||
|                              thresholdValue, |  | ||||||
|                              startTime, |  | ||||||
|                              endTime, |  | ||||||
|                              thresholdValue, |  | ||||||
|                              startTime, |  | ||||||
|                              endTime}); |  | ||||||
| 
 | 
 | ||||||
|    if (!di->hoverText_.empty()) |    // Initiailize line data
 | ||||||
|  |    const auto lineData = { | ||||||
|  |       // Line
 | ||||||
|  |       lat1, lon1, lx, by, mc0, mc1, mc2, mc3, a, // BL
 | ||||||
|  |       lat2, lon2, lx, ty, mc0, mc1, mc2, mc3, a, // TL
 | ||||||
|  |       lat1, lon1, rx, by, mc0, mc1, mc2, mc3, a, // BR
 | ||||||
|  |       lat1, lon1, rx, by, mc0, mc1, mc2, mc3, a, // BR
 | ||||||
|  |       lat2, lon2, rx, ty, mc0, mc1, mc2, mc3, a, // TR
 | ||||||
|  |       lat2, lon2, lx, ty, mc0, mc1, mc2, mc3, a  // TL
 | ||||||
|  |    }; | ||||||
|  |    const auto integerData = {thresholdValue, startTime, endTime, v, | ||||||
|  |                              thresholdValue, startTime, endTime, v, | ||||||
|  |                              thresholdValue, startTime, endTime, v, | ||||||
|  |                              thresholdValue, startTime, endTime, v, | ||||||
|  |                              thresholdValue, startTime, endTime, v, | ||||||
|  |                              thresholdValue, startTime, endTime, v}; | ||||||
|  | 
 | ||||||
|  |    // Buffer position data
 | ||||||
|  |    auto lineBufferPosition = lineBuffer.end(); | ||||||
|  |    auto lineBufferOffset   = lineIndex * kLineBufferLength_; | ||||||
|  | 
 | ||||||
|  |    auto integerBufferPosition = integerBuffer.end(); | ||||||
|  |    auto integerBufferOffset   = lineIndex * kIntegerBufferLength_; | ||||||
|  | 
 | ||||||
|  |    if (lineBufferOffset < lineBuffer.size()) | ||||||
|  |    { | ||||||
|  |       lineBufferPosition = lineBuffer.begin() + lineBufferOffset; | ||||||
|  |    } | ||||||
|  |    if (integerBufferOffset < integerBuffer.size()) | ||||||
|  |    { | ||||||
|  |       integerBufferPosition = integerBuffer.begin() + integerBufferOffset; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    if (lineBufferPosition == lineBuffer.cend()) | ||||||
|  |    { | ||||||
|  |       lineBuffer.insert(lineBufferPosition, lineData); | ||||||
|  |    } | ||||||
|  |    else | ||||||
|  |    { | ||||||
|  |       std::copy(lineData.begin(), lineData.end(), lineBufferPosition); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    if (integerBufferPosition == integerBuffer.cend()) | ||||||
|  |    { | ||||||
|  |       integerBuffer.insert(integerBufferPosition, integerData); | ||||||
|  |    } | ||||||
|  |    else | ||||||
|  |    { | ||||||
|  |       std::copy(integerData.begin(), integerData.end(), integerBufferPosition); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    auto hoverIt = std::find_if(hoverLines.begin(), | ||||||
|  |                                hoverLines.end(), | ||||||
|  |                                [&di](auto& entry) { return entry.di_ == di; }); | ||||||
|  | 
 | ||||||
|  |    if (di->visible_ && (!di->hoverText_.empty() || | ||||||
|  |                         di->hoverCallback_ != nullptr || di->event_ != nullptr)) | ||||||
|    { |    { | ||||||
|       const units::angle::radians<double> radians = angle; |       const units::angle::radians<double> radians = angle; | ||||||
| 
 | 
 | ||||||
|  | @ -486,13 +643,31 @@ void GeoLines::Impl::BufferLine( | ||||||
|       const glm::vec2 obl = rotate * glm::vec2 {-hw, -hw}; |       const glm::vec2 obl = rotate * glm::vec2 {-hw, -hw}; | ||||||
|       const glm::vec2 obr = rotate * glm::vec2 {+hw, -hw}; |       const glm::vec2 obr = rotate * glm::vec2 {+hw, -hw}; | ||||||
| 
 | 
 | ||||||
|       newHoverLines_.emplace_back( |       if (hoverIt == hoverLines.end()) | ||||||
|          LineHoverEntry {di, sc1, sc2, otl, otr, obl, obr}); |       { | ||||||
|  |          hoverLines.emplace_back( | ||||||
|  |             LineHoverEntry {di, sc1, sc2, otl, otr, obl, obr}); | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |          hoverIt->p1_  = sc1; | ||||||
|  |          hoverIt->p2_  = sc2; | ||||||
|  |          hoverIt->otl_ = otl; | ||||||
|  |          hoverIt->otr_ = otr; | ||||||
|  |          hoverIt->obl_ = obl; | ||||||
|  |          hoverIt->obr_ = obr; | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  |    else if (hoverIt != hoverLines.end()) | ||||||
|  |    { | ||||||
|  |       hoverLines.erase(hoverIt); | ||||||
|    } |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void GeoLines::Impl::Update() | void GeoLines::Impl::Update() | ||||||
| { | { | ||||||
|  |    UpdateModifiedLineBuffers(); | ||||||
|  | 
 | ||||||
|    // If the lines have been updated
 |    // If the lines have been updated
 | ||||||
|    if (dirty_) |    if (dirty_) | ||||||
|    { |    { | ||||||
|  | @ -522,7 +697,7 @@ bool GeoLines::RunMousePicking( | ||||||
|    const QPointF&   mouseGlobalPos, |    const QPointF&   mouseGlobalPos, | ||||||
|    const glm::vec2& mouseCoords, |    const glm::vec2& mouseCoords, | ||||||
|    const common::Coordinate& /* mouseGeoCoords */, |    const common::Coordinate& /* mouseGeoCoords */, | ||||||
|    std::shared_ptr<types::EventHandler>& /* eventHandler */) |    std::shared_ptr<types::EventHandler>& eventHandler) | ||||||
| { | { | ||||||
|    std::unique_lock lock {p->lineMutex_}; |    std::unique_lock lock {p->lineMutex_}; | ||||||
| 
 | 
 | ||||||
|  | @ -552,8 +727,8 @@ bool GeoLines::RunMousePicking( | ||||||
|    // For each pickable line
 |    // For each pickable line
 | ||||||
|    auto it = std::find_if( |    auto it = std::find_if( | ||||||
|       std::execution::par_unseq, |       std::execution::par_unseq, | ||||||
|       p->currentHoverLines_.crbegin(), |       p->currentHoverLines_.rbegin(), | ||||||
|       p->currentHoverLines_.crend(), |       p->currentHoverLines_.rend(), | ||||||
|       [&mapDistance, &selectedTime, &mapMatrix, &mouseCoords](const auto& line) |       [&mapDistance, &selectedTime, &mapMatrix, &mouseCoords](const auto& line) | ||||||
|       { |       { | ||||||
|          if (( |          if (( | ||||||
|  | @ -612,12 +787,34 @@ bool GeoLines::RunMousePicking( | ||||||
|    if (it != p->currentHoverLines_.crend()) |    if (it != p->currentHoverLines_.crend()) | ||||||
|    { |    { | ||||||
|       itemPicked = true; |       itemPicked = true; | ||||||
|       util::tooltip::Show(it->di_->hoverText_, mouseGlobalPos); | 
 | ||||||
|  |       if (!it->di_->hoverText_.empty()) | ||||||
|  |       { | ||||||
|  |          // Show tooltip
 | ||||||
|  |          util::tooltip::Show(it->di_->hoverText_, mouseGlobalPos); | ||||||
|  |       } | ||||||
|  |       else if (it->di_->hoverCallback_ != nullptr) | ||||||
|  |       { | ||||||
|  |          it->di_->hoverCallback_(it->di_, mouseGlobalPos); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (it->di_->event_ != nullptr) | ||||||
|  |       { | ||||||
|  |          // Register event handler
 | ||||||
|  |          eventHandler = it->di_; | ||||||
|  |       } | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    return itemPicked; |    return itemPicked; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void GeoLines::RegisterEventHandler( | ||||||
|  |    const std::shared_ptr<GeoLineDrawItem>& di, | ||||||
|  |    const std::function<void(QEvent*)>&     eventHandler) | ||||||
|  | { | ||||||
|  |    di->event_ = eventHandler; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| } // namespace draw
 | } // namespace draw
 | ||||||
| } // namespace gl
 | } // namespace gl
 | ||||||
| } // namespace qt
 | } // namespace qt
 | ||||||
|  |  | ||||||
|  | @ -19,6 +19,10 @@ struct GeoLineDrawItem; | ||||||
| class GeoLines : public DrawItem | class GeoLines : public DrawItem | ||||||
| { | { | ||||||
| public: | public: | ||||||
|  |    typedef std::function<void(std::shared_ptr<GeoLineDrawItem>&, | ||||||
|  |                               const QPointF&)> | ||||||
|  |       HoverCallback; | ||||||
|  | 
 | ||||||
|    explicit GeoLines(std::shared_ptr<GlContext> context); |    explicit GeoLines(std::shared_ptr<GlContext> context); | ||||||
|    ~GeoLines(); |    ~GeoLines(); | ||||||
| 
 | 
 | ||||||
|  | @ -75,11 +79,11 @@ public: | ||||||
|     * @param [in] longitude2 The longitude of the second endpoint of the geo |     * @param [in] longitude2 The longitude of the second endpoint of the geo | ||||||
|     * line in degrees. |     * line in degrees. | ||||||
|     */ |     */ | ||||||
|    static void SetLineLocation(const std::shared_ptr<GeoLineDrawItem>& di, |    void SetLineLocation(const std::shared_ptr<GeoLineDrawItem>& di, | ||||||
|                                float latitude1, |                         float                                   latitude1, | ||||||
|                                float longitude1, |                         float                                   longitude1, | ||||||
|                                float latitude2, |                         float                                   latitude2, | ||||||
|                                float longitude2); |                         float                                   longitude2); | ||||||
| 
 | 
 | ||||||
|    /**
 |    /**
 | ||||||
|     * Sets the modulate color of a geo line. |     * Sets the modulate color of a geo line. | ||||||
|  | @ -87,8 +91,8 @@ public: | ||||||
|     * @param [in] di Geo line draw item |     * @param [in] di Geo line draw item | ||||||
|     * @param [in] modulate Modulate color |     * @param [in] modulate Modulate color | ||||||
|     */ |     */ | ||||||
|    static void SetLineModulate(const std::shared_ptr<GeoLineDrawItem>& di, |    void SetLineModulate(const std::shared_ptr<GeoLineDrawItem>& di, | ||||||
|                                boost::gil::rgba8_pixel_t               color); |                         boost::gil::rgba8_pixel_t               color); | ||||||
| 
 | 
 | ||||||
|    /**
 |    /**
 | ||||||
|     * Sets the modulate color of a geo line. |     * Sets the modulate color of a geo line. | ||||||
|  | @ -96,24 +100,32 @@ public: | ||||||
|     * @param [in] di Geo line draw item |     * @param [in] di Geo line draw item | ||||||
|     * @param [in] modulate Modulate color |     * @param [in] modulate Modulate color | ||||||
|     */ |     */ | ||||||
|    static void SetLineModulate(const std::shared_ptr<GeoLineDrawItem>& di, |    void SetLineModulate(const std::shared_ptr<GeoLineDrawItem>& di, | ||||||
|                                boost::gil::rgba32f_pixel_t modulate); |                         boost::gil::rgba32f_pixel_t             modulate); | ||||||
| 
 | 
 | ||||||
|    /**
 |    /**
 | ||||||
|     * Sets the width of the geo line. |     * Sets the width of the geo line. | ||||||
|     * |     * | ||||||
|     * @param [in] width Width in pixels |     * @param [in] width Width in pixels | ||||||
|     */ |     */ | ||||||
|    static void SetLineWidth(const std::shared_ptr<GeoLineDrawItem>& di, |    void SetLineWidth(const std::shared_ptr<GeoLineDrawItem>& di, float width); | ||||||
|                             float                                   width); |  | ||||||
| 
 | 
 | ||||||
|    /**
 |    /**
 | ||||||
|     * Sets the visibility of the geo line. |     * Sets the visibility of the geo line. | ||||||
|     * |     * | ||||||
|     * @param [in] visible |     * @param [in] visible | ||||||
|     */ |     */ | ||||||
|    static void SetLineVisible(const std::shared_ptr<GeoLineDrawItem>& di, |    void SetLineVisible(const std::shared_ptr<GeoLineDrawItem>& di, | ||||||
|                               bool                                    visible); |                        bool                                    visible); | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Sets the hover callback enable of a geo line. | ||||||
|  |     * | ||||||
|  |     * @param [in] di Geo line draw item | ||||||
|  |     * @param [in] enabled Hover enabled | ||||||
|  |     */ | ||||||
|  |    void SetLineHoverCallback(const std::shared_ptr<GeoLineDrawItem>& di, | ||||||
|  |                              const HoverCallback&                    callback); | ||||||
| 
 | 
 | ||||||
|    /**
 |    /**
 | ||||||
|     * Sets the hover text of a geo line. |     * Sets the hover text of a geo line. | ||||||
|  | @ -121,14 +133,42 @@ public: | ||||||
|     * @param [in] di Geo line draw item |     * @param [in] di Geo line draw item | ||||||
|     * @param [in] text Hover text |     * @param [in] text Hover text | ||||||
|     */ |     */ | ||||||
|    static void SetLineHoverText(const std::shared_ptr<GeoLineDrawItem>& di, |    void SetLineHoverText(const std::shared_ptr<GeoLineDrawItem>& di, | ||||||
|                                 const std::string&                      text); |                          const std::string&                      text); | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Sets the start time of a geo line. | ||||||
|  |     * | ||||||
|  |     * @param [in] di Geo line draw item | ||||||
|  |     * @param [in] startTime Start time | ||||||
|  |     */ | ||||||
|  |    void SetLineStartTime(const std::shared_ptr<GeoLineDrawItem>& di, | ||||||
|  |                          std::chrono::system_clock::time_point   startTime); | ||||||
|  | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Sets the end time of a geo line. | ||||||
|  |     * | ||||||
|  |     * @param [in] di Geo line draw item | ||||||
|  |     * @param [in] endTime End time | ||||||
|  |     */ | ||||||
|  |    void SetLineEndTime(const std::shared_ptr<GeoLineDrawItem>& di, | ||||||
|  |                        std::chrono::system_clock::time_point   endTime); | ||||||
| 
 | 
 | ||||||
|    /**
 |    /**
 | ||||||
|     * Finalizes the draw item after adding new lines. |     * Finalizes the draw item after adding new lines. | ||||||
|     */ |     */ | ||||||
|    void FinishLines(); |    void FinishLines(); | ||||||
| 
 | 
 | ||||||
|  |    /**
 | ||||||
|  |     * Registers an event handler for a geo line. | ||||||
|  |     * | ||||||
|  |     * @param [in] di Geo line draw item | ||||||
|  |     * @param [in] eventHandler Event handler function | ||||||
|  |     */ | ||||||
|  |    static void | ||||||
|  |    RegisterEventHandler(const std::shared_ptr<GeoLineDrawItem>& di, | ||||||
|  |                         const std::function<void(QEvent*)>&     eventHandler); | ||||||
|  | 
 | ||||||
| private: | private: | ||||||
|    class Impl; |    class Impl; | ||||||
|    std::unique_ptr<Impl> p; |    std::unique_ptr<Impl> p; | ||||||
|  |  | ||||||
|  | @ -219,13 +219,13 @@ void LinkedVectors::FinishVectors() | ||||||
|             const double& latitude2  = coordinate2.latitude_; |             const double& latitude2  = coordinate2.latitude_; | ||||||
|             const double& longitude2 = coordinate2.longitude_; |             const double& longitude2 = coordinate2.longitude_; | ||||||
| 
 | 
 | ||||||
|             GeoLines::SetLineLocation( |             p->geoLines_->SetLineLocation( | ||||||
|                borderLine, latitude1, longitude1, latitude2, longitude2); |                borderLine, latitude1, longitude1, latitude2, longitude2); | ||||||
| 
 | 
 | ||||||
|             GeoLines::SetLineModulate(borderLine, kBlack); |             p->geoLines_->SetLineModulate(borderLine, kBlack); | ||||||
|             GeoLines::SetLineWidth(borderLine, di->width_ + 2.0f); |             p->geoLines_->SetLineWidth(borderLine, di->width_ + 2.0f); | ||||||
|             GeoLines::SetLineVisible(borderLine, di->visible_); |             p->geoLines_->SetLineVisible(borderLine, di->visible_); | ||||||
|             GeoLines::SetLineHoverText(borderLine, di->hoverText_); |             p->geoLines_->SetLineHoverText(borderLine, di->hoverText_); | ||||||
| 
 | 
 | ||||||
|             di->borderDrawItems_.emplace_back(std::move(borderLine)); |             di->borderDrawItems_.emplace_back(std::move(borderLine)); | ||||||
| 
 | 
 | ||||||
|  | @ -248,13 +248,13 @@ void LinkedVectors::FinishVectors() | ||||||
| 
 | 
 | ||||||
|                auto tickBorderLine = p->geoLines_->AddLine(); |                auto tickBorderLine = p->geoLines_->AddLine(); | ||||||
| 
 | 
 | ||||||
|                GeoLines::SetLineLocation( |                p->geoLines_->SetLineLocation( | ||||||
|                   tickBorderLine, tickLat1, tickLon1, tickLat2, tickLon2); |                   tickBorderLine, tickLat1, tickLon1, tickLat2, tickLon2); | ||||||
| 
 | 
 | ||||||
|                GeoLines::SetLineModulate(tickBorderLine, kBlack); |                p->geoLines_->SetLineModulate(tickBorderLine, kBlack); | ||||||
|                GeoLines::SetLineWidth(tickBorderLine, di->width_ + 2.0f); |                p->geoLines_->SetLineWidth(tickBorderLine, di->width_ + 2.0f); | ||||||
|                GeoLines::SetLineVisible(tickBorderLine, di->visible_); |                p->geoLines_->SetLineVisible(tickBorderLine, di->visible_); | ||||||
|                GeoLines::SetLineHoverText(tickBorderLine, di->hoverText_); |                p->geoLines_->SetLineHoverText(tickBorderLine, di->hoverText_); | ||||||
| 
 | 
 | ||||||
|                tickRadius += di->tickRadiusIncrement_; |                tickRadius += di->tickRadiusIncrement_; | ||||||
|             } |             } | ||||||
|  | @ -279,17 +279,17 @@ void LinkedVectors::FinishVectors() | ||||||
|          const double& latitude2  = coordinate2.latitude_; |          const double& latitude2  = coordinate2.latitude_; | ||||||
|          const double& longitude2 = coordinate2.longitude_; |          const double& longitude2 = coordinate2.longitude_; | ||||||
| 
 | 
 | ||||||
|          GeoLines::SetLineLocation( |          p->geoLines_->SetLineLocation( | ||||||
|             geoLine, latitude1, longitude1, latitude2, longitude2); |             geoLine, latitude1, longitude1, latitude2, longitude2); | ||||||
| 
 | 
 | ||||||
|          GeoLines::SetLineModulate(geoLine, di->modulate_); |          p->geoLines_->SetLineModulate(geoLine, di->modulate_); | ||||||
|          GeoLines::SetLineWidth(geoLine, di->width_); |          p->geoLines_->SetLineWidth(geoLine, di->width_); | ||||||
|          GeoLines::SetLineVisible(geoLine, di->visible_); |          p->geoLines_->SetLineVisible(geoLine, di->visible_); | ||||||
| 
 | 
 | ||||||
|          // If the border is not enabled, this line must have hover text instead
 |          // If the border is not enabled, this line must have hover text instead
 | ||||||
|          if (!p->borderEnabled_) |          if (!p->borderEnabled_) | ||||||
|          { |          { | ||||||
|             GeoLines::SetLineHoverText(geoLine, di->hoverText_); |             p->geoLines_->SetLineHoverText(geoLine, di->hoverText_); | ||||||
|          } |          } | ||||||
| 
 | 
 | ||||||
|          di->lineDrawItems_.emplace_back(std::move(geoLine)); |          di->lineDrawItems_.emplace_back(std::move(geoLine)); | ||||||
|  | @ -313,17 +313,17 @@ void LinkedVectors::FinishVectors() | ||||||
| 
 | 
 | ||||||
|             auto tickGeoLine = p->geoLines_->AddLine(); |             auto tickGeoLine = p->geoLines_->AddLine(); | ||||||
| 
 | 
 | ||||||
|             GeoLines::SetLineLocation( |             p->geoLines_->SetLineLocation( | ||||||
|                tickGeoLine, tickLat1, tickLon1, tickLat2, tickLon2); |                tickGeoLine, tickLat1, tickLon1, tickLat2, tickLon2); | ||||||
| 
 | 
 | ||||||
|             GeoLines::SetLineModulate(tickGeoLine, di->modulate_); |             p->geoLines_->SetLineModulate(tickGeoLine, di->modulate_); | ||||||
|             GeoLines::SetLineWidth(tickGeoLine, di->width_); |             p->geoLines_->SetLineWidth(tickGeoLine, di->width_); | ||||||
|             GeoLines::SetLineVisible(tickGeoLine, di->visible_); |             p->geoLines_->SetLineVisible(tickGeoLine, di->visible_); | ||||||
| 
 | 
 | ||||||
|             // If the border is not enabled, this line must have hover text
 |             // If the border is not enabled, this line must have hover text
 | ||||||
|             if (!p->borderEnabled_) |             if (!p->borderEnabled_) | ||||||
|             { |             { | ||||||
|                GeoLines::SetLineHoverText(tickGeoLine, di->hoverText_); |                p->geoLines_->SetLineHoverText(tickGeoLine, di->hoverText_); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             tickRadius += di->tickRadiusIncrement_; |             tickRadius += di->tickRadiusIncrement_; | ||||||
|  |  | ||||||
|  | @ -830,6 +830,10 @@ void MainWindowImpl::ConnectMapSignals() | ||||||
| { | { | ||||||
|    for (const auto& mapWidget : maps_) |    for (const auto& mapWidget : maps_) | ||||||
|    { |    { | ||||||
|  |       connect(mapWidget, | ||||||
|  |               &map::MapWidget::AlertSelected, | ||||||
|  |               alertDockWidget_, | ||||||
|  |               &ui::AlertDockWidget::SelectAlert); | ||||||
|       connect(mapWidget, |       connect(mapWidget, | ||||||
|               &map::MapWidget::MapParametersChanged, |               &map::MapWidget::MapParametersChanged, | ||||||
|               this, |               this, | ||||||
|  |  | ||||||
|  | @ -1,17 +1,23 @@ | ||||||
| #include <scwx/qt/map/alert_layer.hpp> | #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/text_event_manager.hpp> | ||||||
|  | #include <scwx/qt/manager/timeline_manager.hpp> | ||||||
| #include <scwx/qt/settings/palette_settings.hpp> | #include <scwx/qt/settings/palette_settings.hpp> | ||||||
| #include <scwx/qt/types/layer_types.hpp> |  | ||||||
| #include <scwx/qt/util/color.hpp> | #include <scwx/qt/util/color.hpp> | ||||||
|  | #include <scwx/qt/util/tooltip.hpp> | ||||||
| #include <scwx/util/logger.hpp> | #include <scwx/util/logger.hpp> | ||||||
| #include <scwx/util/threads.hpp> |  | ||||||
| 
 | 
 | ||||||
| #include <chrono> | #include <chrono> | ||||||
| #include <shared_mutex> | #include <mutex> | ||||||
|  | #include <unordered_map> | ||||||
| #include <unordered_set> | #include <unordered_set> | ||||||
| 
 | 
 | ||||||
| #include <boost/asio/steady_timer.hpp> | #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 <boost/container_hash/hash.hpp> | ||||||
|  | #include <QEvent> | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx | ||||||
| { | { | ||||||
|  | @ -23,29 +29,7 @@ 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 std::vector<std::string> | static const boost::gil::rgba32f_pixel_t kBlack_ {0.0f, 0.0f, 0.0f, 1.0f}; | ||||||
| AddAlertLayer(std::shared_ptr<QMapLibre::Map> map, |  | ||||||
|               awips::Phenomenon               phenomenon, |  | ||||||
|               bool                            alertActive, |  | ||||||
|               const QString&                  beforeLayer); |  | ||||||
| static QMapLibre::Feature |  | ||||||
| CreateFeature(const awips::CodedLocation& codedLocation); |  | ||||||
| static QMapLibre::Coordinate |  | ||||||
| GetMapboxCoordinate(const common::Coordinate& coordinate); |  | ||||||
| static QMapLibre::Coordinates |  | ||||||
|                GetMapboxCoordinates(const awips::CodedLocation& codedLocation); |  | ||||||
| static QString GetSourceId(awips::Phenomenon phenomenon, bool alertActive); |  | ||||||
| static QString GetSuffix(awips::Phenomenon phenomenon, bool alertActive); |  | ||||||
| 
 |  | ||||||
| static const QVariantMap kEmptyFeatureCollection_ { |  | ||||||
|    {"type", "geojson"}, |  | ||||||
|    {"data", QVariant::fromValue(std::list<QMapLibre::Feature> {})}}; |  | ||||||
| static const std::vector<awips::Phenomenon> kAlertPhenomena_ { |  | ||||||
|    awips::Phenomenon::Marine, |  | ||||||
|    awips::Phenomenon::FlashFlood, |  | ||||||
|    awips::Phenomenon::SevereThunderstorm, |  | ||||||
|    awips::Phenomenon::SnowSquall, |  | ||||||
|    awips::Phenomenon::Tornado}; |  | ||||||
| 
 | 
 | ||||||
| template<class Key> | template<class Key> | ||||||
| struct AlertTypeHash; | struct AlertTypeHash; | ||||||
|  | @ -58,203 +42,315 @@ struct AlertTypeHash<std::pair<awips::Phenomenon, bool>> | ||||||
| 
 | 
 | ||||||
| class AlertLayerHandler : public QObject | class AlertLayerHandler : public QObject | ||||||
| { | { | ||||||
|    Q_OBJECT public : |    Q_OBJECT | ||||||
|        explicit AlertLayerHandler() : | public: | ||||||
|        textEventManager_ {manager::TextEventManager::Instance()}, |    struct SegmentRecord | ||||||
|        alertUpdateTimer_ {scwx::util::io_context()}, |  | ||||||
|        alertSourceMap_ {}, |  | ||||||
|        featureMap_ {} |  | ||||||
|    { |    { | ||||||
|       for (auto& phenomenon : kAlertPhenomena_) |       std::shared_ptr<const awips::Segment>            segment_; | ||||||
|       { |       types::TextEventKey                              key_; | ||||||
|          for (bool alertActive : {false, true}) |       std::shared_ptr<const awips::TextProductMessage> message_; | ||||||
|          { |       std::chrono::system_clock::time_point            segmentBegin_; | ||||||
|             alertSourceMap_.emplace(std::make_pair(phenomenon, alertActive), |       std::chrono::system_clock::time_point            segmentEnd_; | ||||||
|                                     kEmptyFeatureCollection_); |  | ||||||
|          } |  | ||||||
|       } |  | ||||||
| 
 | 
 | ||||||
|  |       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(), |       connect(textEventManager_.get(), | ||||||
|               &manager::TextEventManager::AlertUpdated, |               &manager::TextEventManager::AlertUpdated, | ||||||
|               this, |               this, | ||||||
|               &AlertLayerHandler::HandleAlert); |               [this](const types::TextEventKey& key, std::size_t messageIndex) | ||||||
|  |               { HandleAlert(key, messageIndex); }); | ||||||
|    } |    } | ||||||
|    ~AlertLayerHandler() |    ~AlertLayerHandler() | ||||||
|    { |    { | ||||||
|  |       disconnect(textEventManager_.get(), nullptr, this, nullptr); | ||||||
|  | 
 | ||||||
|       std::unique_lock lock(alertMutex_); |       std::unique_lock lock(alertMutex_); | ||||||
|       alertUpdateTimer_.cancel(); |  | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    static std::shared_ptr<AlertLayerHandler> Instance(); |    std::unordered_map< | ||||||
|  |       std::pair<awips::Phenomenon, bool>, | ||||||
|  |       boost::container::stable_vector<std::shared_ptr<SegmentRecord>>, | ||||||
|  |       AlertTypeHash<std::pair<awips::Phenomenon, bool>>> | ||||||
|  |       segmentsByType_ {}; | ||||||
| 
 | 
 | ||||||
|    std::list<QMapLibre::Feature>* FeatureList(awips::Phenomenon phenomenon, |    std::unordered_map< | ||||||
|                                               bool              alertActive); |       types::TextEventKey, | ||||||
|  |       boost::container::stable_vector<std::shared_ptr<SegmentRecord>>, | ||||||
|  |       types::TextEventHash<types::TextEventKey>> | ||||||
|  |       segmentsByKey_ {}; | ||||||
| 
 | 
 | ||||||
|    void HandleAlert(const types::TextEventKey& key, size_t messageIndex); |    void HandleAlert(const types::TextEventKey& key, size_t messageIndex); | ||||||
|    void UpdateAlerts(); |  | ||||||
| 
 | 
 | ||||||
|    std::shared_ptr<manager::TextEventManager> textEventManager_; |    static AlertLayerHandler& Instance(); | ||||||
| 
 | 
 | ||||||
|    boost::asio::steady_timer alertUpdateTimer_; |    std::shared_ptr<manager::TextEventManager> textEventManager_ { | ||||||
|    std::unordered_map<std::pair<awips::Phenomenon, bool>, |       manager::TextEventManager::Instance()}; | ||||||
|                       QVariantMap, | 
 | ||||||
|                       AlertTypeHash<std::pair<awips::Phenomenon, bool>>> |    std::shared_mutex alertMutex_ {}; | ||||||
|       alertSourceMap_; |  | ||||||
|    std::unordered_multimap<types::TextEventKey, |  | ||||||
|                            std::tuple<awips::Phenomenon, |  | ||||||
|                                       bool, |  | ||||||
|                                       std::list<QMapLibre::Feature>::iterator, |  | ||||||
|                                       std::chrono::system_clock::time_point>, |  | ||||||
|                            types::TextEventHash<types::TextEventKey>> |  | ||||||
|                      featureMap_; |  | ||||||
|    std::shared_mutex alertMutex_; |  | ||||||
| 
 | 
 | ||||||
| signals: | 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); |    void AlertsUpdated(awips::Phenomenon phenomenon, bool alertActive); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class AlertLayerImpl : public QObject | class AlertLayer::Impl | ||||||
| { | { | ||||||
|    Q_OBJECT |  | ||||||
| public: | public: | ||||||
|    explicit AlertLayerImpl(std::shared_ptr<MapContext> context) : |    explicit Impl(AlertLayer*                 self, | ||||||
|        context_ {context}, alertLayerHandler_ {AlertLayerHandler::Instance()} |                  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)}} | ||||||
|    { |    { | ||||||
|       connect(alertLayerHandler_.get(), |       auto& paletteSettings = settings::PaletteSettings::Instance(); | ||||||
|               &AlertLayerHandler::AlertsUpdated, | 
 | ||||||
|               this, |       for (auto alertActive : {false, true}) | ||||||
|               &AlertLayerImpl::UpdateSource); |       { | ||||||
|  |          lineColor_.emplace( | ||||||
|  |             alertActive, | ||||||
|  |             util::color::ToRgba32fPixelT( | ||||||
|  |                paletteSettings.alert_color(phenomenon_, alertActive) | ||||||
|  |                   .GetValue())); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       ConnectSignals(); | ||||||
|  |       ScheduleRefresh(); | ||||||
|    } |    } | ||||||
|    ~AlertLayerImpl() {}; |    ~Impl() | ||||||
|  |    { | ||||||
|  |       std::unique_lock refreshLock(refreshMutex_); | ||||||
|  |       refreshTimer_.cancel(); | ||||||
|  |       refreshLock.unlock(); | ||||||
| 
 | 
 | ||||||
|    void UpdateSource(awips::Phenomenon phenomenon, bool alertActive); |       threadPool_.join(); | ||||||
| 
 | 
 | ||||||
|    std::shared_ptr<MapContext>        context_; |       receiver_ = nullptr; | ||||||
|    std::shared_ptr<AlertLayerHandler> alertLayerHandler_; | 
 | ||||||
|  |       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) : | AlertLayer::AlertLayer(std::shared_ptr<MapContext> context, | ||||||
|     p(std::make_unique<AlertLayerImpl>(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; | AlertLayer::~AlertLayer() = default; | ||||||
| 
 | 
 | ||||||
| std::vector<std::string> AlertLayer::AddLayers(awips::Phenomenon  phenomenon, | void AlertLayer::InitializeHandler() | ||||||
|                                                const std::string& before) |  | ||||||
| { | { | ||||||
|    logger_->debug("AddLayers(): {}", awips::GetPhenomenonCode(phenomenon)); |    static bool ftt = true; | ||||||
| 
 | 
 | ||||||
|    std::vector<std::string> layers {}; |    if (ftt) | ||||||
| 
 |  | ||||||
|    auto map = p->context_->map().lock(); |  | ||||||
|    if (map == nullptr) |  | ||||||
|    { |    { | ||||||
|       return layers; |       logger_->debug("Initializing handler"); | ||||||
|  |       AlertLayerHandler::Instance(); | ||||||
|  |       ftt = false; | ||||||
|    } |    } | ||||||
| 
 |  | ||||||
|    const QString beforeLayer {QString::fromStdString(before)}; |  | ||||||
| 
 |  | ||||||
|    // Add/update GeoJSON sources and create layers
 |  | ||||||
|    for (bool alertActive : {false, true}) |  | ||||||
|    { |  | ||||||
|       p->UpdateSource(phenomenon, alertActive); |  | ||||||
|       auto newLayers = AddAlertLayer(map, phenomenon, alertActive, beforeLayer); |  | ||||||
|       layers.insert(layers.end(), newLayers.cbegin(), newLayers.cend()); |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    return layers; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::list<QMapLibre::Feature>* | void AlertLayer::Initialize() | ||||||
| AlertLayerHandler::FeatureList(awips::Phenomenon phenomenon, bool alertActive) |  | ||||||
| { | { | ||||||
|    std::list<QMapLibre::Feature>* featureList = nullptr; |    logger_->debug("Initialize: {}", awips::GetPhenomenonText(p->phenomenon_)); | ||||||
| 
 | 
 | ||||||
|    auto key = std::make_pair(phenomenon, alertActive); |    DrawLayer::Initialize(); | ||||||
|    auto it  = alertSourceMap_.find(key); | 
 | ||||||
|    if (it != alertSourceMap_.cend()) |    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}) | ||||||
|    { |    { | ||||||
|       featureList = reinterpret_cast<std::list<QMapLibre::Feature>*>( |       auto& geoLines = p->geoLines_.at(alertActive); | ||||||
|          it->second["data"].data()); | 
 | ||||||
|  |       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(); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    return featureList; |    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, | void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, | ||||||
|                                     size_t                     messageIndex) |                                     size_t                     messageIndex) | ||||||
| { | { | ||||||
|    // Skip alert if there are more messages to be processed
 |    logger_->trace("HandleAlert: {}", key.ToString()); | ||||||
|    if (messageIndex + 1 < textEventManager_->message_count(key)) |  | ||||||
|    { |  | ||||||
|       return; |  | ||||||
|    } |  | ||||||
| 
 | 
 | ||||||
|    auto message = textEventManager_->message_list(key).at(messageIndex); |  | ||||||
|    std::unordered_set<std::pair<awips::Phenomenon, bool>, |    std::unordered_set<std::pair<awips::Phenomenon, bool>, | ||||||
|                       AlertTypeHash<std::pair<awips::Phenomenon, bool>>> |                       AlertTypeHash<std::pair<awips::Phenomenon, bool>>> | ||||||
|       alertsUpdated {}; |       alertsUpdated {}; | ||||||
| 
 | 
 | ||||||
|    // Take a unique lock before modifying feature lists
 |    auto message = textEventManager_->message_list(key).at(messageIndex); | ||||||
|    std::unique_lock lock(alertMutex_); |  | ||||||
| 
 | 
 | ||||||
|    // Remove existing features for key
 |    // Determine start time for first segment
 | ||||||
|    auto existingFeatures = featureMap_.equal_range(key); |    std::chrono::system_clock::time_point segmentBegin {}; | ||||||
|    for (auto it = existingFeatures.first; it != existingFeatures.second; ++it) |    if (message->segment_count() > 0) | ||||||
|    { |    { | ||||||
|       auto& [phenomenon, alertActive, featureIt, eventEnd] = it->second; |       segmentBegin = message->segment(0)->event_begin(); | ||||||
|       auto featureList = FeatureList(phenomenon, alertActive); |    } | ||||||
|       if (featureList != nullptr) |  | ||||||
|       { |  | ||||||
|          // Remove existing feature for key
 |  | ||||||
|          featureList->erase(featureIt); |  | ||||||
| 
 | 
 | ||||||
|          // Mark alert type as updated
 |    // Take a unique mutex before modifying segments
 | ||||||
|          alertsUpdated.emplace(phenomenon, alertActive); |    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); | ||||||
|       } |       } | ||||||
|    } |    } | ||||||
|    featureMap_.erase(existingFeatures.first, existingFeatures.second); |  | ||||||
| 
 | 
 | ||||||
|    for (auto segment : message->segments()) |    // Process new segments
 | ||||||
|  |    for (auto& segment : message->segments()) | ||||||
|    { |    { | ||||||
|       if (!segment->codedLocation_.has_value()) |       if (!segment->codedLocation_.has_value()) | ||||||
|       { |       { | ||||||
|  |          // Cannot handle a segment without a location
 | ||||||
|          continue; |          continue; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       auto&             vtec       = segment->header_->vtecString_.front(); |       auto&             vtec       = segment->header_->vtecString_.front(); | ||||||
|       auto              action     = vtec.pVtec_.action(); |       auto              action     = vtec.pVtec_.action(); | ||||||
|       awips::Phenomenon phenomenon = vtec.pVtec_.phenomenon(); |       awips::Phenomenon phenomenon = vtec.pVtec_.phenomenon(); | ||||||
|       auto              eventEnd   = vtec.pVtec_.event_end(); |  | ||||||
|       bool alertActive             = (action != awips::PVtec::Action::Canceled); |       bool alertActive             = (action != awips::PVtec::Action::Canceled); | ||||||
| 
 | 
 | ||||||
|       // If the event has ended, skip it
 |       auto& segmentsForType = segmentsByType_[{key.phenomenon_, alertActive}]; | ||||||
|       if (eventEnd < std::chrono::system_clock::now()) |  | ||||||
|       { |  | ||||||
|          continue; |  | ||||||
|       } |  | ||||||
| 
 | 
 | ||||||
|       auto featureList = FeatureList(phenomenon, alertActive); |       // Insert segment into lists
 | ||||||
|       if (featureList != nullptr) |       std::shared_ptr<SegmentRecord> segmentRecord = | ||||||
|       { |          std::make_shared<SegmentRecord>(segment, key, message); | ||||||
|          // Add alert location to polygon list
 |  | ||||||
|          auto featureIt = featureList->emplace( |  | ||||||
|             featureList->cend(), |  | ||||||
|             CreateFeature(segment->codedLocation_.value())); |  | ||||||
| 
 | 
 | ||||||
|          // Store iterator for created feature in feature map
 |       segmentsForKey.push_back(segmentRecord); | ||||||
|          featureMap_.emplace(std::piecewise_construct, |       segmentsForType.push_back(segmentRecord); | ||||||
|                              std::forward_as_tuple(key), |  | ||||||
|                              std::forward_as_tuple( |  | ||||||
|                                 phenomenon, alertActive, featureIt, eventEnd)); |  | ||||||
| 
 | 
 | ||||||
|          // Mark alert type as updated
 |       Q_EMIT AlertAdded(segmentRecord, phenomenon); | ||||||
|          alertsUpdated.emplace(phenomenon, alertActive); | 
 | ||||||
|       } |       alertsUpdated.emplace(phenomenon, alertActive); | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    // Release the lock after completing feature list updates
 |    // Release the lock after completing segment updates
 | ||||||
|    lock.unlock(); |    lock.unlock(); | ||||||
| 
 | 
 | ||||||
|    for (auto& alert : alertsUpdated) |    for (auto& alert : alertsUpdated) | ||||||
|  | @ -264,219 +360,288 @@ void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, | ||||||
|    } |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void AlertLayerHandler::UpdateAlerts() | void AlertLayer::Impl::ConnectAlertHandlerSignals() | ||||||
| { | { | ||||||
|    logger_->trace("UpdateAlerts"); |    auto& alertLayerHandler = AlertLayerHandler::Instance(); | ||||||
| 
 | 
 | ||||||
|    // Take a unique lock before modifying feature lists
 |    QObject::connect( | ||||||
|    std::unique_lock lock(alertMutex_); |       &alertLayerHandler, | ||||||
| 
 |       &AlertLayerHandler::AlertAdded, | ||||||
|    std::unordered_set<std::pair<awips::Phenomenon, bool>, |       receiver_.get(), | ||||||
|                       AlertTypeHash<std::pair<awips::Phenomenon, bool>>> |       [this]( | ||||||
|       alertsUpdated {}; |          const std::shared_ptr<AlertLayerHandler::SegmentRecord>& segmentRecord, | ||||||
| 
 |          awips::Phenomenon                                        phenomenon) | ||||||
|    // Evaluate each rendered feature for expiration
 |  | ||||||
|    for (auto it = featureMap_.begin(); it != featureMap_.end();) |  | ||||||
|    { |  | ||||||
|       auto& [phenomenon, alertActive, featureIt, eventEnd] = it->second; |  | ||||||
| 
 |  | ||||||
|       // If the event has ended, remove it from the feature list
 |  | ||||||
|       if (eventEnd < std::chrono::system_clock::now()) |  | ||||||
|       { |       { | ||||||
|          logger_->debug("Alert expired: {}", it->first.ToString()); |          if (phenomenon == phenomenon_) | ||||||
| 
 |  | ||||||
|          auto featureList = FeatureList(phenomenon, alertActive); |  | ||||||
|          if (featureList != nullptr) |  | ||||||
|          { |          { | ||||||
|             // Remove existing feature for key
 |             AddAlert(segmentRecord); | ||||||
|             featureList->erase(featureIt); |  | ||||||
| 
 |  | ||||||
|             // Mark alert type as updated
 |  | ||||||
|             alertsUpdated.emplace(phenomenon, alertActive); |  | ||||||
|          } |          } | ||||||
| 
 |       }); | ||||||
|          // Erase current item and increment iterator
 |    QObject::connect( | ||||||
|          it = featureMap_.erase(it); |       &alertLayerHandler, | ||||||
|       } |       &AlertLayerHandler::AlertUpdated, | ||||||
|       else |       receiver_.get(), | ||||||
|  |       [this]( | ||||||
|  |          const std::shared_ptr<AlertLayerHandler::SegmentRecord>& segmentRecord) | ||||||
|       { |       { | ||||||
|          // Current item is not expired, continue
 |          if (segmentRecord->key_.phenomenon_ == phenomenon_) | ||||||
|          ++it; |  | ||||||
|       } |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    for (auto& alert : alertsUpdated) |  | ||||||
|    { |  | ||||||
|       // Emit signal for each updated alert type
 |  | ||||||
|       Q_EMIT AlertsUpdated(alert.first, alert.second); |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    using namespace std::chrono; |  | ||||||
|    alertUpdateTimer_.expires_after(15s); |  | ||||||
|    alertUpdateTimer_.async_wait( |  | ||||||
|       [this](const boost::system::error_code& e) |  | ||||||
|       { |  | ||||||
|          if (e == boost::asio::error::operation_aborted) |  | ||||||
|          { |          { | ||||||
|             logger_->debug("Alert update timer cancelled"); |             UpdateAlert(segmentRecord); | ||||||
|          } |  | ||||||
|          else if (e != boost::system::errc::success) |  | ||||||
|          { |  | ||||||
|             logger_->warn("Alert update timer error: {}", e.message()); |  | ||||||
|          } |  | ||||||
|          else |  | ||||||
|          { |  | ||||||
|             try |  | ||||||
|             { |  | ||||||
|                UpdateAlerts(); |  | ||||||
|             } |  | ||||||
|             catch (const std::exception& ex) |  | ||||||
|             { |  | ||||||
|                logger_->error(ex.what()); |  | ||||||
|             } |  | ||||||
|          } |          } | ||||||
|       }); |       }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void AlertLayerImpl::UpdateSource(awips::Phenomenon phenomenon, | void AlertLayer::Impl::ConnectSignals() | ||||||
|                                   bool              alertActive) |  | ||||||
| { | { | ||||||
|    auto map = context_->map().lock(); |    auto timelineManager = manager::TimelineManager::Instance(); | ||||||
|    if (map == nullptr) | 
 | ||||||
|  |    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) | ||||||
|    { |    { | ||||||
|       return; |       // 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; | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    // Take a shared lock before using feature lists
 |    default: | ||||||
|    std::shared_lock lock(alertLayerHandler_->alertMutex_); |       break; | ||||||
| 
 |    } | ||||||
|    // Update source, relies on alert source being defined
 |  | ||||||
|    map->updateSource(GetSourceId(phenomenon, alertActive), |  | ||||||
|                      alertLayerHandler_->alertSourceMap_.at( |  | ||||||
|                         std::make_pair(phenomenon, alertActive))); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::shared_ptr<AlertLayerHandler> AlertLayerHandler::Instance() | void AlertLayer::Impl::HandleGeoLinesHover( | ||||||
|  |    std::shared_ptr<gl::draw::GeoLineDrawItem>& di, | ||||||
|  |    const QPointF&                              mouseGlobalPos) | ||||||
| { | { | ||||||
|    static std::weak_ptr<AlertLayerHandler> alertLayerHandlerReference {}; |    if (di != lastHoverDi_) | ||||||
|    static std::mutex                       instanceMutex {}; |  | ||||||
| 
 |  | ||||||
|    std::unique_lock lock(instanceMutex); |  | ||||||
| 
 |  | ||||||
|    std::shared_ptr<AlertLayerHandler> alertLayerHandler = |  | ||||||
|       alertLayerHandlerReference.lock(); |  | ||||||
| 
 |  | ||||||
|    if (alertLayerHandler == nullptr) |  | ||||||
|    { |    { | ||||||
|       alertLayerHandler          = std::make_shared<AlertLayerHandler>(); |       auto it = segmentsByLine_.find(di); | ||||||
|       alertLayerHandlerReference = alertLayerHandler; |       if (it != segmentsByLine_.cend()) | ||||||
|  |       { | ||||||
|  |          tooltip_ = | ||||||
|  |             boost::algorithm::join(it->second->segment_->productContent_, "\n"); | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |          tooltip_.clear(); | ||||||
|  |       } | ||||||
| 
 | 
 | ||||||
|       alertLayerHandler->UpdateAlerts(); |       lastHoverDi_ = di; | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    return alertLayerHandler; |    if (!tooltip_.empty()) | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static std::vector<std::string> |  | ||||||
| AddAlertLayer(std::shared_ptr<QMapLibre::Map> map, |  | ||||||
|               awips::Phenomenon               phenomenon, |  | ||||||
|               bool                            alertActive, |  | ||||||
|               const QString&                  beforeLayer) |  | ||||||
| { |  | ||||||
|    settings::PaletteSettings& paletteSettings = |  | ||||||
|       settings::PaletteSettings::Instance(); |  | ||||||
| 
 |  | ||||||
|    QString layerPrefix = QString::fromStdString( |  | ||||||
|       types::GetLayerName(types::LayerType::Alert, phenomenon)); |  | ||||||
| 
 |  | ||||||
|    QString sourceId     = GetSourceId(phenomenon, alertActive); |  | ||||||
|    QString idSuffix     = GetSuffix(phenomenon, alertActive); |  | ||||||
|    auto    outlineColor = util::color::ToRgba8PixelT( |  | ||||||
|       paletteSettings.alert_color(phenomenon, alertActive).GetValue()); |  | ||||||
| 
 |  | ||||||
|    QString bgLayerId = QString("%1::bg-%2").arg(layerPrefix).arg(idSuffix); |  | ||||||
|    QString fgLayerId = QString("%1::fg-%2").arg(layerPrefix).arg(idSuffix); |  | ||||||
| 
 |  | ||||||
|    if (map->layerExists(bgLayerId)) |  | ||||||
|    { |    { | ||||||
|       map->removeLayer(bgLayerId); |       util::tooltip::Show(tooltip_, mouseGlobalPos); | ||||||
|    } |    } | ||||||
|    if (map->layerExists(fgLayerId)) |  | ||||||
|    { |  | ||||||
|       map->removeLayer(fgLayerId); |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    const float opacity = outlineColor[3] / 255.0f; |  | ||||||
| 
 |  | ||||||
|    map->addLayer( |  | ||||||
|       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-opacity", QString("%1").arg(opacity)); |  | ||||||
|    map->setPaintProperty(bgLayerId, "line-width", "5"); |  | ||||||
| 
 |  | ||||||
|    map->addLayer( |  | ||||||
|       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-opacity", QString("%1").arg(opacity)); |  | ||||||
|    map->setPaintProperty(fgLayerId, "line-width", "3"); |  | ||||||
| 
 |  | ||||||
|    return {bgLayerId.toStdString(), fgLayerId.toStdString()}; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static QMapLibre::Feature | AlertLayerHandler& AlertLayerHandler::Instance() | ||||||
| CreateFeature(const awips::CodedLocation& codedLocation) |  | ||||||
| { | { | ||||||
|    auto mapboxCoordinates = GetMapboxCoordinates(codedLocation); |    static AlertLayerHandler alertLayerHandler_ {}; | ||||||
| 
 |    return alertLayerHandler_; | ||||||
|    return QMapLibre::Feature { |  | ||||||
|       QMapLibre::Feature::PolygonType, |  | ||||||
|       std::initializer_list<QMapLibre::CoordinatesCollection> { |  | ||||||
|          std::initializer_list<QMapLibre::Coordinates> {{mapboxCoordinates}}}}; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static QMapLibre::Coordinate |  | ||||||
| GetMapboxCoordinate(const common::Coordinate& coordinate) |  | ||||||
| { |  | ||||||
|    return {coordinate.latitude_, coordinate.longitude_}; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static QMapLibre::Coordinates |  | ||||||
| GetMapboxCoordinates(const awips::CodedLocation& codedLocation) |  | ||||||
| { |  | ||||||
|    auto                   scwxCoordinates = codedLocation.coordinates(); |  | ||||||
|    QMapLibre::Coordinates mapboxCoordinates(scwxCoordinates.size() + 1u); |  | ||||||
| 
 |  | ||||||
|    std::transform(scwxCoordinates.cbegin(), |  | ||||||
|                   scwxCoordinates.cend(), |  | ||||||
|                   mapboxCoordinates.begin(), |  | ||||||
|                   [](auto& coordinate) -> QMapLibre::Coordinate |  | ||||||
|                   { return GetMapboxCoordinate(coordinate); }); |  | ||||||
| 
 |  | ||||||
|    mapboxCoordinates.back() = GetMapboxCoordinate(scwxCoordinates.front()); |  | ||||||
| 
 |  | ||||||
|    return mapboxCoordinates; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static QString GetSourceId(awips::Phenomenon phenomenon, bool alertActive) |  | ||||||
| { |  | ||||||
|    return QString("alertPolygon-%1").arg(GetSuffix(phenomenon, alertActive)); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 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()( | size_t AlertTypeHash<std::pair<awips::Phenomenon, bool>>::operator()( | ||||||
|  |  | ||||||
|  | @ -1,7 +1,8 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <scwx/awips/phenomenon.hpp> | #include <scwx/awips/phenomenon.hpp> | ||||||
| #include <scwx/qt/map/map_context.hpp> | #include <scwx/qt/map/draw_layer.hpp> | ||||||
|  | #include <scwx/qt/types/text_event_key.hpp> | ||||||
| 
 | 
 | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <string> | #include <string> | ||||||
|  | @ -14,19 +15,28 @@ namespace qt | ||||||
| namespace map | namespace map | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| class AlertLayerImpl; | class AlertLayer : public DrawLayer | ||||||
| 
 |  | ||||||
| class AlertLayer |  | ||||||
| { | { | ||||||
|  |    Q_OBJECT | ||||||
|  |    Q_DISABLE_COPY_MOVE(AlertLayer) | ||||||
|  | 
 | ||||||
| public: | public: | ||||||
|    explicit AlertLayer(std::shared_ptr<MapContext> context); |    explicit AlertLayer(std::shared_ptr<MapContext> context, | ||||||
|  |                        scwx::awips::Phenomenon     phenomenon); | ||||||
|    ~AlertLayer(); |    ~AlertLayer(); | ||||||
| 
 | 
 | ||||||
|    std::vector<std::string> AddLayers(awips::Phenomenon  phenomenon, |    void Initialize() override final; | ||||||
|                                       const std::string& before = {}); |    void Render(const QMapLibre::CustomLayerRenderParameters&) override final; | ||||||
|  |    void Deinitialize() override final; | ||||||
|  | 
 | ||||||
|  |    static void InitializeHandler(); | ||||||
|  | 
 | ||||||
|  | signals: | ||||||
|  |    void AlertSelected(const types::TextEventKey& key); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|    std::unique_ptr<AlertLayerImpl> p; |    class Impl; | ||||||
|  |    std::unique_ptr<Impl> p; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace map
 | } // namespace map
 | ||||||
|  |  | ||||||
|  | @ -55,6 +55,9 @@ void DrawLayer::Render(const QMapLibre::CustomLayerRenderParameters& params) | ||||||
|    bool textureAtlasChanged = |    bool textureAtlasChanged = | ||||||
|       newTextureAtlasBuildCount != p->textureAtlasBuildCount_; |       newTextureAtlasBuildCount != p->textureAtlasBuildCount_; | ||||||
| 
 | 
 | ||||||
|  |    // Set OpenGL blend mode for transparency
 | ||||||
|  |    gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | ||||||
|  | 
 | ||||||
|    gl.glActiveTexture(GL_TEXTURE0); |    gl.glActiveTexture(GL_TEXTURE0); | ||||||
|    gl.glBindTexture(GL_TEXTURE_2D_ARRAY, p->textureAtlas_); |    gl.glBindTexture(GL_TEXTURE_2D_ARRAY, p->textureAtlas_); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -79,7 +79,6 @@ public: | ||||||
|        imGuiRendererInitialized_ {false}, |        imGuiRendererInitialized_ {false}, | ||||||
|        radarProductManager_ {nullptr}, |        radarProductManager_ {nullptr}, | ||||||
|        radarProductLayer_ {nullptr}, |        radarProductLayer_ {nullptr}, | ||||||
|        alertLayer_ {std::make_shared<AlertLayer>(context_)}, |  | ||||||
|        overlayLayer_ {nullptr}, |        overlayLayer_ {nullptr}, | ||||||
|        placefileLayer_ {nullptr}, |        placefileLayer_ {nullptr}, | ||||||
|        colorTableLayer_ {nullptr}, |        colorTableLayer_ {nullptr}, | ||||||
|  | @ -100,6 +99,9 @@ public: | ||||||
|       overlayProductView->SetAutoRefresh(autoRefreshEnabled_); |       overlayProductView->SetAutoRefresh(autoRefreshEnabled_); | ||||||
|       overlayProductView->SetAutoUpdate(autoUpdateEnabled_); |       overlayProductView->SetAutoUpdate(autoUpdateEnabled_); | ||||||
| 
 | 
 | ||||||
|  |       // Initialize AlertLayerHandler
 | ||||||
|  |       map::AlertLayer::InitializeHandler(); | ||||||
|  | 
 | ||||||
|       auto& generalSettings = settings::GeneralSettings::Instance(); |       auto& generalSettings = settings::GeneralSettings::Instance(); | ||||||
| 
 | 
 | ||||||
|       // Initialize context
 |       // Initialize context
 | ||||||
|  | @ -218,7 +220,6 @@ public: | ||||||
|    std::shared_ptr<manager::RadarProductManager> radarProductManager_; |    std::shared_ptr<manager::RadarProductManager> radarProductManager_; | ||||||
| 
 | 
 | ||||||
|    std::shared_ptr<RadarProductLayer>   radarProductLayer_; |    std::shared_ptr<RadarProductLayer>   radarProductLayer_; | ||||||
|    std::shared_ptr<AlertLayer>          alertLayer_; |  | ||||||
|    std::shared_ptr<OverlayLayer>        overlayLayer_; |    std::shared_ptr<OverlayLayer>        overlayLayer_; | ||||||
|    std::shared_ptr<OverlayProductLayer> overlayProductLayer_ {nullptr}; |    std::shared_ptr<OverlayProductLayer> overlayProductLayer_ {nullptr}; | ||||||
|    std::shared_ptr<PlacefileLayer>      placefileLayer_; |    std::shared_ptr<PlacefileLayer>      placefileLayer_; | ||||||
|  | @ -1180,10 +1181,17 @@ void MapWidgetImpl::AddLayer(types::LayerType        type, | ||||||
|    } |    } | ||||||
|    else if (type == types::LayerType::Alert) |    else if (type == types::LayerType::Alert) | ||||||
|    { |    { | ||||||
|       // Add the alert layer for the phenomenon
 |       auto phenomenon = std::get<awips::Phenomenon>(description); | ||||||
|       auto newLayers = alertLayer_->AddLayers( | 
 | ||||||
|          std::get<awips::Phenomenon>(description), before); |       std::shared_ptr<AlertLayer> alertLayer = | ||||||
|       layerList_.insert(layerList_.end(), newLayers.cbegin(), newLayers.cend()); |          std::make_shared<AlertLayer>(context_, phenomenon); | ||||||
|  |       AddLayer(fmt::format("alert.{}", awips::GetPhenomenonCode(phenomenon)), | ||||||
|  |                alertLayer, | ||||||
|  |                before); | ||||||
|  |       connect(alertLayer.get(), | ||||||
|  |               &AlertLayer::AlertSelected, | ||||||
|  |               widget_, | ||||||
|  |               &MapWidget::AlertSelected); | ||||||
|    } |    } | ||||||
|    else if (type == types::LayerType::Placefile) |    else if (type == types::LayerType::Placefile) | ||||||
|    { |    { | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ | ||||||
| #include <scwx/qt/config/radar_site.hpp> | #include <scwx/qt/config/radar_site.hpp> | ||||||
| #include <scwx/qt/types/map_types.hpp> | #include <scwx/qt/types/map_types.hpp> | ||||||
| #include <scwx/qt/types/radar_product_record.hpp> | #include <scwx/qt/types/radar_product_record.hpp> | ||||||
|  | #include <scwx/qt/types/text_event_key.hpp> | ||||||
| 
 | 
 | ||||||
| #include <chrono> | #include <chrono> | ||||||
| #include <memory> | #include <memory> | ||||||
|  | @ -150,6 +151,7 @@ private slots: | ||||||
|    void mapChanged(QMapLibre::Map::MapChange); |    void mapChanged(QMapLibre::Map::MapChange); | ||||||
| 
 | 
 | ||||||
| signals: | signals: | ||||||
|  |    void AlertSelected(const types::TextEventKey& key); | ||||||
|    void Level3ProductsChanged(); |    void Level3ProductsChanged(); | ||||||
|    void MapParametersChanged(double latitude, |    void MapParametersChanged(double latitude, | ||||||
|                              double longitude, |                              double longitude, | ||||||
|  |  | ||||||
|  | @ -300,9 +300,6 @@ void OverlayLayer::Render(const QMapLibre::CustomLayerRenderParameters& params) | ||||||
| 
 | 
 | ||||||
|    context()->set_render_parameters(params); |    context()->set_render_parameters(params); | ||||||
| 
 | 
 | ||||||
|    // Set OpenGL blend mode for transparency
 |  | ||||||
|    gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |  | ||||||
| 
 |  | ||||||
|    p->sweepTimePicked_ = false; |    p->sweepTimePicked_ = false; | ||||||
| 
 | 
 | ||||||
|    if (radarProductView != nullptr) |    if (radarProductView != nullptr) | ||||||
|  |  | ||||||
|  | @ -143,9 +143,6 @@ void OverlayProductLayer::Render( | ||||||
| { | { | ||||||
|    gl::OpenGLFunctions& gl = context()->gl(); |    gl::OpenGLFunctions& gl = context()->gl(); | ||||||
| 
 | 
 | ||||||
|    // Set OpenGL blend mode for transparency
 |  | ||||||
|    gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |  | ||||||
| 
 |  | ||||||
|    if (p->stiNeedsUpdate_) |    if (p->stiNeedsUpdate_) | ||||||
|    { |    { | ||||||
|       p->UpdateStormTrackingInformation(); |       p->UpdateStormTrackingInformation(); | ||||||
|  |  | ||||||
|  | @ -129,9 +129,6 @@ void PlacefileLayer::Render( | ||||||
| { | { | ||||||
|    gl::OpenGLFunctions& gl = context()->gl(); |    gl::OpenGLFunctions& gl = context()->gl(); | ||||||
| 
 | 
 | ||||||
|    // Set OpenGL blend mode for transparency
 |  | ||||||
|    gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |  | ||||||
| 
 |  | ||||||
|    std::shared_ptr<manager::PlacefileManager> placefileManager = |    std::shared_ptr<manager::PlacefileManager> placefileManager = | ||||||
|       manager::PlacefileManager::Instance(); |       manager::PlacefileManager::Instance(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -114,6 +114,13 @@ void AlertDockWidget::HandleMapUpdate(double latitude, double longitude) | ||||||
|    } |    } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void AlertDockWidget::SelectAlert(const types::TextEventKey& key) | ||||||
|  | { | ||||||
|  |    // View alert
 | ||||||
|  |    p->alertDialog_->SelectAlert(key); | ||||||
|  |    p->alertDialog_->show(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void AlertDockWidgetImpl::ConnectSignals() | void AlertDockWidgetImpl::ConnectSignals() | ||||||
| { | { | ||||||
|    connect(self_->ui->alertFilter, |    connect(self_->ui->alertFilter, | ||||||
|  |  | ||||||
|  | @ -1,5 +1,7 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include <scwx/qt/types/text_event_key.hpp> | ||||||
|  | 
 | ||||||
| #include <QDockWidget> | #include <QDockWidget> | ||||||
| 
 | 
 | ||||||
| namespace Ui | namespace Ui | ||||||
|  | @ -32,6 +34,7 @@ signals: | ||||||
| 
 | 
 | ||||||
| public slots: | public slots: | ||||||
|    void HandleMapUpdate(double latitude, double longitude); |    void HandleMapUpdate(double latitude, double longitude); | ||||||
|  |    void SelectAlert(const types::TextEventKey& key); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|    friend class AlertDockWidgetImpl; |    friend class AlertDockWidgetImpl; | ||||||
|  |  | ||||||
|  | @ -29,6 +29,15 @@ boost::gil::rgba8_pixel_t ToRgba8PixelT(const std::string& argbString) | ||||||
|                                      static_cast<uint8_t>(qAlpha(color))}; |                                      static_cast<uint8_t>(qAlpha(color))}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | boost::gil::rgba32f_pixel_t ToRgba32fPixelT(const std::string& argbString) | ||||||
|  | { | ||||||
|  |    boost::gil::rgba8_pixel_t rgba8Pixel = ToRgba8PixelT(argbString); | ||||||
|  |    return boost::gil::rgba32f_pixel_t {rgba8Pixel[0] / 255.0f, | ||||||
|  |                                        rgba8Pixel[1] / 255.0f, | ||||||
|  |                                        rgba8Pixel[2] / 255.0f, | ||||||
|  |                                        rgba8Pixel[3] / 255.0f}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| } // namespace color
 | } // namespace color
 | ||||||
| } // namespace util
 | } // namespace util
 | ||||||
| } // namespace qt
 | } // namespace qt
 | ||||||
|  |  | ||||||
|  | @ -29,6 +29,16 @@ std::string ToArgbString(const boost::gil::rgba8_pixel_t& color); | ||||||
|  */ |  */ | ||||||
| boost::gil::rgba8_pixel_t ToRgba8PixelT(const std::string& argbString); | boost::gil::rgba8_pixel_t ToRgba8PixelT(const std::string& argbString); | ||||||
| 
 | 
 | ||||||
|  | /**
 | ||||||
|  |  * Converts an ARGB string used by Qt libraries to a Boost.GIL 32-bit RGBA | ||||||
|  |  * floating point pixel. | ||||||
|  |  * | ||||||
|  |  * @param argbString ARGB string in the format #AARRGGBB | ||||||
|  |  * | ||||||
|  |  * @return RGBA32 floating point pixel | ||||||
|  |  */ | ||||||
|  | boost::gil::rgba32f_pixel_t ToRgba32fPixelT(const std::string& argbString); | ||||||
|  | 
 | ||||||
| } // namespace color
 | } // namespace color
 | ||||||
| } // namespace util
 | } // namespace util
 | ||||||
| } // namespace qt
 | } // namespace qt
 | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ | ||||||
| #include <scwx/awips/ugc.hpp> | #include <scwx/awips/ugc.hpp> | ||||||
| #include <scwx/awips/wmo_header.hpp> | #include <scwx/awips/wmo_header.hpp> | ||||||
| 
 | 
 | ||||||
|  | #include <chrono> | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <string> | #include <string> | ||||||
|  | @ -57,6 +58,7 @@ struct SegmentHeader | ||||||
| 
 | 
 | ||||||
| struct Segment | struct Segment | ||||||
| { | { | ||||||
|  |    std::shared_ptr<WmoHeader>             wmoHeader_ {}; | ||||||
|    std::optional<SegmentHeader>           header_ {}; |    std::optional<SegmentHeader>           header_ {}; | ||||||
|    std::vector<std::string>               productContent_ {}; |    std::vector<std::string>               productContent_ {}; | ||||||
|    std::optional<CodedLocation>           codedLocation_ {}; |    std::optional<CodedLocation>           codedLocation_ {}; | ||||||
|  | @ -73,6 +75,9 @@ struct Segment | ||||||
| 
 | 
 | ||||||
|    Segment(Segment&&) noexcept            = default; |    Segment(Segment&&) noexcept            = default; | ||||||
|    Segment& operator=(Segment&&) noexcept = default; |    Segment& operator=(Segment&&) noexcept = default; | ||||||
|  | 
 | ||||||
|  |    std::chrono::system_clock::time_point event_begin() const; | ||||||
|  |    std::chrono::system_clock::time_point event_end() const; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class TextProductMessageImpl; | class TextProductMessageImpl; | ||||||
|  |  | ||||||
|  | @ -104,16 +104,14 @@ std::shared_ptr<const Segment> TextProductMessage::segment(size_t s) const | ||||||
|    return p->segments_[s]; |    return p->segments_[s]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::chrono::system_clock::time_point | std::chrono::system_clock::time_point Segment::event_begin() const | ||||||
| TextProductMessage::segment_event_begin(std::size_t s) const |  | ||||||
| { | { | ||||||
|    std::chrono::system_clock::time_point eventBegin {}; |    std::chrono::system_clock::time_point eventBegin {}; | ||||||
| 
 | 
 | ||||||
|    auto& header = segment(s)->header_; |    if (header_.has_value() && !header_->vtecString_.empty()) | ||||||
|    if (header.has_value() && !header->vtecString_.empty()) |  | ||||||
|    { |    { | ||||||
|       // Determine event begin from P-VTEC string
 |       // Determine event begin from P-VTEC string
 | ||||||
|       eventBegin = header->vtecString_[0].pVtec_.event_begin(); |       eventBegin = header_->vtecString_[0].pVtec_.event_begin(); | ||||||
| 
 | 
 | ||||||
|       // If event begin is 000000T0000Z
 |       // If event begin is 000000T0000Z
 | ||||||
|       if (eventBegin == std::chrono::system_clock::time_point {}) |       if (eventBegin == std::chrono::system_clock::time_point {}) | ||||||
|  | @ -122,13 +120,13 @@ TextProductMessage::segment_event_begin(std::size_t s) const | ||||||
| 
 | 
 | ||||||
|          // Determine event end from P-VTEC string
 |          // Determine event end from P-VTEC string
 | ||||||
|          system_clock::time_point eventEnd = |          system_clock::time_point eventEnd = | ||||||
|             header->vtecString_[0].pVtec_.event_end(); |             header_->vtecString_[0].pVtec_.event_end(); | ||||||
| 
 | 
 | ||||||
|          auto           endDays = floor<days>(eventEnd); |          auto           endDays = floor<days>(eventEnd); | ||||||
|          year_month_day endDate {endDays}; |          year_month_day endDate {endDays}; | ||||||
| 
 | 
 | ||||||
|          // Determine WMO date/time
 |          // Determine WMO date/time
 | ||||||
|          std::string wmoDateTime = wmo_header()->date_time(); |          std::string wmoDateTime = wmoHeader_->date_time(); | ||||||
| 
 | 
 | ||||||
|          bool          wmoDateTimeValid = false; |          bool          wmoDateTimeValid = false; | ||||||
|          unsigned int  dayOfMonth       = 0; |          unsigned int  dayOfMonth       = 0; | ||||||
|  | @ -189,6 +187,25 @@ TextProductMessage::segment_event_begin(std::size_t s) const | ||||||
|    return eventBegin; |    return eventBegin; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | std::chrono::system_clock::time_point Segment::event_end() const | ||||||
|  | { | ||||||
|  |    std::chrono::system_clock::time_point eventEnd {}; | ||||||
|  | 
 | ||||||
|  |    if (header_.has_value() && !header_->vtecString_.empty()) | ||||||
|  |    { | ||||||
|  |       // Determine event begin from P-VTEC string
 | ||||||
|  |       eventEnd = header_->vtecString_[0].pVtec_.event_end(); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return eventEnd; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::chrono::system_clock::time_point | ||||||
|  | TextProductMessage::segment_event_begin(std::size_t s) const | ||||||
|  | { | ||||||
|  |    return segment(s)->event_begin(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| size_t TextProductMessage::data_size() const | size_t TextProductMessage::data_size() const | ||||||
| { | { | ||||||
|    return 0; |    return 0; | ||||||
|  | @ -211,6 +228,7 @@ bool TextProductMessage::Parse(std::istream& is) | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       std::shared_ptr<Segment> segment = std::make_shared<Segment>(); |       std::shared_ptr<Segment> segment = std::make_shared<Segment>(); | ||||||
|  |       segment->wmoHeader_              = p->wmoHeader_; | ||||||
| 
 | 
 | ||||||
|       if (i == 0) |       if (i == 0) | ||||||
|       { |       { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat