mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-30 21:30: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 <boost/unordered/unordered_flat_set.hpp>
|
||||
#include <units/angle.h>
|
||||
|
||||
namespace scwx
|
||||
|
|
@ -25,13 +26,15 @@ static constexpr size_t kNumTriangles = kNumRectangles * 2;
|
|||
static constexpr size_t kVerticesPerTriangle = 3;
|
||||
static constexpr size_t kVerticesPerRectangle = kVerticesPerTriangle * 2;
|
||||
static constexpr size_t kPointsPerVertex = 9;
|
||||
static constexpr size_t kBufferLength =
|
||||
static constexpr size_t kLineBufferLength_ =
|
||||
kNumTriangles * kVerticesPerTriangle * kPointsPerVertex;
|
||||
|
||||
// Threshold, start time, end time
|
||||
static constexpr std::size_t kIntegersPerVertex_ = 3;
|
||||
// Threshold, start time, end time, displayed
|
||||
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};
|
||||
units::length::nautical_miles<double> threshold_ {};
|
||||
|
|
@ -46,6 +49,7 @@ struct GeoLineDrawItem
|
|||
float width_ {5.0};
|
||||
units::angle::degrees<float> angle_ {};
|
||||
std::string hoverText_ {};
|
||||
GeoLines::HoverCallback hoverCallback_ {nullptr};
|
||||
};
|
||||
|
||||
class GeoLines::Impl
|
||||
|
|
@ -53,7 +57,7 @@ class GeoLines::Impl
|
|||
public:
|
||||
struct LineHoverEntry
|
||||
{
|
||||
std::shared_ptr<const GeoLineDrawItem> di_;
|
||||
std::shared_ptr<GeoLineDrawItem> di_;
|
||||
|
||||
glm::vec2 p1_;
|
||||
glm::vec2 p2_;
|
||||
|
|
@ -81,6 +85,12 @@ public:
|
|||
void BufferLine(const std::shared_ptr<const GeoLineDrawItem>& di);
|
||||
void Update();
|
||||
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_;
|
||||
|
||||
|
|
@ -88,6 +98,8 @@ public:
|
|||
bool dirty_ {false};
|
||||
bool thresholded_ {false};
|
||||
|
||||
boost::unordered_flat_set<std::shared_ptr<GeoLineDrawItem>> dirtyLines_ {};
|
||||
|
||||
std::chrono::system_clock::time_point selectedTime_ {};
|
||||
|
||||
std::mutex lineMutex_ {};
|
||||
|
|
@ -136,8 +148,7 @@ void GeoLines::set_thresholded(bool thresholded)
|
|||
|
||||
void GeoLines::Initialize()
|
||||
{
|
||||
gl::OpenGLFunctions& gl = p->context_->gl();
|
||||
auto& gl30 = p->context_->gl30();
|
||||
gl::OpenGLFunctions& gl = p->context_->gl();
|
||||
|
||||
p->shaderProgram_ = p->context_->GetShaderProgram(
|
||||
{{GL_VERTEX_SHADER, ":/gl/geo_texture2d.vert"},
|
||||
|
|
@ -158,8 +169,10 @@ void GeoLines::Initialize()
|
|||
|
||||
gl.glBindVertexArray(p->vao_);
|
||||
gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]);
|
||||
gl.glBufferData(
|
||||
GL_ARRAY_BUFFER, sizeof(float) * kBufferLength, nullptr, GL_DYNAMIC_DRAW);
|
||||
gl.glBufferData(GL_ARRAY_BUFFER,
|
||||
sizeof(float) * kLineBufferLength_,
|
||||
nullptr,
|
||||
GL_DYNAMIC_DRAW);
|
||||
|
||||
// aLatLong
|
||||
gl.glVertexAttribPointer(0,
|
||||
|
|
@ -217,7 +230,13 @@ void GeoLines::Initialize()
|
|||
gl.glEnableVertexAttribArray(6);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
|
@ -231,7 +250,7 @@ void GeoLines::Render(const QMapLibre::CustomLayerRenderParameters& params)
|
|||
|
||||
std::unique_lock lock {p->lineMutex_};
|
||||
|
||||
if (p->currentLineList_.size() > 0)
|
||||
if (p->newLineList_.size() > 0)
|
||||
{
|
||||
gl::OpenGLFunctions& gl = p->context_->gl();
|
||||
|
||||
|
|
@ -280,7 +299,7 @@ void GeoLines::Deinitialize()
|
|||
gl::OpenGLFunctions& gl = p->context_->gl();
|
||||
|
||||
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_};
|
||||
|
||||
|
|
@ -314,43 +333,104 @@ void GeoLines::SetLineLocation(const std::shared_ptr<GeoLineDrawItem>& di,
|
|||
float latitude2,
|
||||
float longitude2)
|
||||
{
|
||||
di->latitude1_ = latitude1;
|
||||
di->longitude1_ = longitude1;
|
||||
di->latitude2_ = latitude2;
|
||||
di->longitude2_ = longitude2;
|
||||
if (di->latitude1_ != latitude1 || di->longitude1_ != longitude1 ||
|
||||
di->latitude2_ != latitude1 || di->longitude2_ != longitude1)
|
||||
{
|
||||
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,
|
||||
boost::gil::rgba8_pixel_t modulate)
|
||||
{
|
||||
di->modulate_ = {modulate[0] / 255.0f,
|
||||
modulate[1] / 255.0f,
|
||||
modulate[2] / 255.0f,
|
||||
modulate[3] / 255.0f};
|
||||
boost::gil::rgba32f_pixel_t newModulate = {modulate[0] / 255.0f,
|
||||
modulate[1] / 255.0f,
|
||||
modulate[2] / 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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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()
|
||||
|
|
@ -361,7 +441,7 @@ void GeoLines::FinishLines()
|
|||
std::unique_lock lock {p->lineMutex_};
|
||||
|
||||
// Swap buffers
|
||||
p->currentLineList_.swap(p->newLineList_);
|
||||
p->currentLineList_ = p->newLineList_;
|
||||
p->currentLinesBuffer_.swap(p->newLinesBuffer_);
|
||||
p->currentIntegerBuffer_.swap(p->newIntegerBuffer_);
|
||||
p->currentHoverLines_.swap(p->newHoverLines_);
|
||||
|
|
@ -379,20 +459,69 @@ void GeoLines::FinishLines()
|
|||
void GeoLines::Impl::UpdateBuffers()
|
||||
{
|
||||
newLinesBuffer_.clear();
|
||||
newLinesBuffer_.reserve(newLineList_.size() * kBufferLength);
|
||||
newLinesBuffer_.reserve(newLineList_.size() * kLineBufferLength_);
|
||||
newIntegerBuffer_.clear();
|
||||
newIntegerBuffer_.reserve(newLineList_.size() * kVerticesPerRectangle *
|
||||
kIntegersPerVertex_);
|
||||
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(
|
||||
const std::shared_ptr<const GeoLineDrawItem>& di)
|
||||
void GeoLines::Impl::UpdateSingleBuffer(
|
||||
const std::shared_ptr<GeoLineDrawItem>& di,
|
||||
std::size_t lineIndex,
|
||||
std::vector<float>& lineBuffer,
|
||||
std::vector<GLint>& integerBuffer,
|
||||
std::vector<LineHoverEntry>& hoverLines)
|
||||
{
|
||||
// Threshold value
|
||||
units::length::nautical_miles<double> threshold = di->threshold_;
|
||||
|
|
@ -438,38 +567,66 @@ void GeoLines::Impl::BufferLine(
|
|||
const float mc2 = di->modulate_[2];
|
||||
const float mc3 = di->modulate_[3];
|
||||
|
||||
// Update buffers
|
||||
newLinesBuffer_.insert(newLinesBuffer_.end(),
|
||||
{
|
||||
// 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});
|
||||
// Visibility
|
||||
const GLint v = static_cast<GLint>(di->visible_);
|
||||
|
||||
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;
|
||||
|
||||
|
|
@ -486,13 +643,31 @@ void GeoLines::Impl::BufferLine(
|
|||
const glm::vec2 obl = rotate * glm::vec2 {-hw, -hw};
|
||||
const glm::vec2 obr = rotate * glm::vec2 {+hw, -hw};
|
||||
|
||||
newHoverLines_.emplace_back(
|
||||
LineHoverEntry {di, sc1, sc2, otl, otr, obl, obr});
|
||||
if (hoverIt == hoverLines.end())
|
||||
{
|
||||
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()
|
||||
{
|
||||
UpdateModifiedLineBuffers();
|
||||
|
||||
// If the lines have been updated
|
||||
if (dirty_)
|
||||
{
|
||||
|
|
@ -522,7 +697,7 @@ bool GeoLines::RunMousePicking(
|
|||
const QPointF& mouseGlobalPos,
|
||||
const glm::vec2& mouseCoords,
|
||||
const common::Coordinate& /* mouseGeoCoords */,
|
||||
std::shared_ptr<types::EventHandler>& /* eventHandler */)
|
||||
std::shared_ptr<types::EventHandler>& eventHandler)
|
||||
{
|
||||
std::unique_lock lock {p->lineMutex_};
|
||||
|
||||
|
|
@ -552,8 +727,8 @@ bool GeoLines::RunMousePicking(
|
|||
// For each pickable line
|
||||
auto it = std::find_if(
|
||||
std::execution::par_unseq,
|
||||
p->currentHoverLines_.crbegin(),
|
||||
p->currentHoverLines_.crend(),
|
||||
p->currentHoverLines_.rbegin(),
|
||||
p->currentHoverLines_.rend(),
|
||||
[&mapDistance, &selectedTime, &mapMatrix, &mouseCoords](const auto& line)
|
||||
{
|
||||
if ((
|
||||
|
|
@ -612,12 +787,34 @@ bool GeoLines::RunMousePicking(
|
|||
if (it != p->currentHoverLines_.crend())
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
void GeoLines::RegisterEventHandler(
|
||||
const std::shared_ptr<GeoLineDrawItem>& di,
|
||||
const std::function<void(QEvent*)>& eventHandler)
|
||||
{
|
||||
di->event_ = eventHandler;
|
||||
}
|
||||
|
||||
} // namespace draw
|
||||
} // namespace gl
|
||||
} // namespace qt
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ struct GeoLineDrawItem;
|
|||
class GeoLines : public DrawItem
|
||||
{
|
||||
public:
|
||||
typedef std::function<void(std::shared_ptr<GeoLineDrawItem>&,
|
||||
const QPointF&)>
|
||||
HoverCallback;
|
||||
|
||||
explicit GeoLines(std::shared_ptr<GlContext> context);
|
||||
~GeoLines();
|
||||
|
||||
|
|
@ -75,11 +79,11 @@ public:
|
|||
* @param [in] longitude2 The longitude of the second endpoint of the geo
|
||||
* line in degrees.
|
||||
*/
|
||||
static void SetLineLocation(const std::shared_ptr<GeoLineDrawItem>& di,
|
||||
float latitude1,
|
||||
float longitude1,
|
||||
float latitude2,
|
||||
float longitude2);
|
||||
void SetLineLocation(const std::shared_ptr<GeoLineDrawItem>& di,
|
||||
float latitude1,
|
||||
float longitude1,
|
||||
float latitude2,
|
||||
float longitude2);
|
||||
|
||||
/**
|
||||
* Sets the modulate color of a geo line.
|
||||
|
|
@ -87,8 +91,8 @@ public:
|
|||
* @param [in] di Geo line draw item
|
||||
* @param [in] modulate Modulate color
|
||||
*/
|
||||
static void SetLineModulate(const std::shared_ptr<GeoLineDrawItem>& di,
|
||||
boost::gil::rgba8_pixel_t color);
|
||||
void SetLineModulate(const std::shared_ptr<GeoLineDrawItem>& di,
|
||||
boost::gil::rgba8_pixel_t color);
|
||||
|
||||
/**
|
||||
* Sets the modulate color of a geo line.
|
||||
|
|
@ -96,24 +100,32 @@ public:
|
|||
* @param [in] di Geo line draw item
|
||||
* @param [in] modulate Modulate color
|
||||
*/
|
||||
static void SetLineModulate(const std::shared_ptr<GeoLineDrawItem>& di,
|
||||
boost::gil::rgba32f_pixel_t modulate);
|
||||
void SetLineModulate(const std::shared_ptr<GeoLineDrawItem>& di,
|
||||
boost::gil::rgba32f_pixel_t modulate);
|
||||
|
||||
/**
|
||||
* Sets the width of the geo line.
|
||||
*
|
||||
* @param [in] width Width in pixels
|
||||
*/
|
||||
static void SetLineWidth(const std::shared_ptr<GeoLineDrawItem>& di,
|
||||
float width);
|
||||
void SetLineWidth(const std::shared_ptr<GeoLineDrawItem>& di, float width);
|
||||
|
||||
/**
|
||||
* Sets the visibility of the geo line.
|
||||
*
|
||||
* @param [in] visible
|
||||
*/
|
||||
static void SetLineVisible(const std::shared_ptr<GeoLineDrawItem>& di,
|
||||
bool visible);
|
||||
void SetLineVisible(const std::shared_ptr<GeoLineDrawItem>& di,
|
||||
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.
|
||||
|
|
@ -121,14 +133,42 @@ public:
|
|||
* @param [in] di Geo line draw item
|
||||
* @param [in] text Hover text
|
||||
*/
|
||||
static void SetLineHoverText(const std::shared_ptr<GeoLineDrawItem>& di,
|
||||
const std::string& text);
|
||||
void SetLineHoverText(const std::shared_ptr<GeoLineDrawItem>& di,
|
||||
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.
|
||||
*/
|
||||
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:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
|
|
|
|||
|
|
@ -219,13 +219,13 @@ void LinkedVectors::FinishVectors()
|
|||
const double& latitude2 = coordinate2.latitude_;
|
||||
const double& longitude2 = coordinate2.longitude_;
|
||||
|
||||
GeoLines::SetLineLocation(
|
||||
p->geoLines_->SetLineLocation(
|
||||
borderLine, latitude1, longitude1, latitude2, longitude2);
|
||||
|
||||
GeoLines::SetLineModulate(borderLine, kBlack);
|
||||
GeoLines::SetLineWidth(borderLine, di->width_ + 2.0f);
|
||||
GeoLines::SetLineVisible(borderLine, di->visible_);
|
||||
GeoLines::SetLineHoverText(borderLine, di->hoverText_);
|
||||
p->geoLines_->SetLineModulate(borderLine, kBlack);
|
||||
p->geoLines_->SetLineWidth(borderLine, di->width_ + 2.0f);
|
||||
p->geoLines_->SetLineVisible(borderLine, di->visible_);
|
||||
p->geoLines_->SetLineHoverText(borderLine, di->hoverText_);
|
||||
|
||||
di->borderDrawItems_.emplace_back(std::move(borderLine));
|
||||
|
||||
|
|
@ -248,13 +248,13 @@ void LinkedVectors::FinishVectors()
|
|||
|
||||
auto tickBorderLine = p->geoLines_->AddLine();
|
||||
|
||||
GeoLines::SetLineLocation(
|
||||
p->geoLines_->SetLineLocation(
|
||||
tickBorderLine, tickLat1, tickLon1, tickLat2, tickLon2);
|
||||
|
||||
GeoLines::SetLineModulate(tickBorderLine, kBlack);
|
||||
GeoLines::SetLineWidth(tickBorderLine, di->width_ + 2.0f);
|
||||
GeoLines::SetLineVisible(tickBorderLine, di->visible_);
|
||||
GeoLines::SetLineHoverText(tickBorderLine, di->hoverText_);
|
||||
p->geoLines_->SetLineModulate(tickBorderLine, kBlack);
|
||||
p->geoLines_->SetLineWidth(tickBorderLine, di->width_ + 2.0f);
|
||||
p->geoLines_->SetLineVisible(tickBorderLine, di->visible_);
|
||||
p->geoLines_->SetLineHoverText(tickBorderLine, di->hoverText_);
|
||||
|
||||
tickRadius += di->tickRadiusIncrement_;
|
||||
}
|
||||
|
|
@ -279,17 +279,17 @@ void LinkedVectors::FinishVectors()
|
|||
const double& latitude2 = coordinate2.latitude_;
|
||||
const double& longitude2 = coordinate2.longitude_;
|
||||
|
||||
GeoLines::SetLineLocation(
|
||||
p->geoLines_->SetLineLocation(
|
||||
geoLine, latitude1, longitude1, latitude2, longitude2);
|
||||
|
||||
GeoLines::SetLineModulate(geoLine, di->modulate_);
|
||||
GeoLines::SetLineWidth(geoLine, di->width_);
|
||||
GeoLines::SetLineVisible(geoLine, di->visible_);
|
||||
p->geoLines_->SetLineModulate(geoLine, di->modulate_);
|
||||
p->geoLines_->SetLineWidth(geoLine, di->width_);
|
||||
p->geoLines_->SetLineVisible(geoLine, di->visible_);
|
||||
|
||||
// If the border is not enabled, this line must have hover text instead
|
||||
if (!p->borderEnabled_)
|
||||
{
|
||||
GeoLines::SetLineHoverText(geoLine, di->hoverText_);
|
||||
p->geoLines_->SetLineHoverText(geoLine, di->hoverText_);
|
||||
}
|
||||
|
||||
di->lineDrawItems_.emplace_back(std::move(geoLine));
|
||||
|
|
@ -313,17 +313,17 @@ void LinkedVectors::FinishVectors()
|
|||
|
||||
auto tickGeoLine = p->geoLines_->AddLine();
|
||||
|
||||
GeoLines::SetLineLocation(
|
||||
p->geoLines_->SetLineLocation(
|
||||
tickGeoLine, tickLat1, tickLon1, tickLat2, tickLon2);
|
||||
|
||||
GeoLines::SetLineModulate(tickGeoLine, di->modulate_);
|
||||
GeoLines::SetLineWidth(tickGeoLine, di->width_);
|
||||
GeoLines::SetLineVisible(tickGeoLine, di->visible_);
|
||||
p->geoLines_->SetLineModulate(tickGeoLine, di->modulate_);
|
||||
p->geoLines_->SetLineWidth(tickGeoLine, di->width_);
|
||||
p->geoLines_->SetLineVisible(tickGeoLine, di->visible_);
|
||||
|
||||
// If the border is not enabled, this line must have hover text
|
||||
if (!p->borderEnabled_)
|
||||
{
|
||||
GeoLines::SetLineHoverText(tickGeoLine, di->hoverText_);
|
||||
p->geoLines_->SetLineHoverText(tickGeoLine, di->hoverText_);
|
||||
}
|
||||
|
||||
tickRadius += di->tickRadiusIncrement_;
|
||||
|
|
|
|||
|
|
@ -830,6 +830,10 @@ void MainWindowImpl::ConnectMapSignals()
|
|||
{
|
||||
for (const auto& mapWidget : maps_)
|
||||
{
|
||||
connect(mapWidget,
|
||||
&map::MapWidget::AlertSelected,
|
||||
alertDockWidget_,
|
||||
&ui::AlertDockWidget::SelectAlert);
|
||||
connect(mapWidget,
|
||||
&map::MapWidget::MapParametersChanged,
|
||||
this,
|
||||
|
|
|
|||
|
|
@ -1,17 +1,23 @@
|
|||
#include <scwx/qt/map/alert_layer.hpp>
|
||||
#include <scwx/qt/gl/draw/geo_lines.hpp>
|
||||
#include <scwx/qt/manager/text_event_manager.hpp>
|
||||
#include <scwx/qt/manager/timeline_manager.hpp>
|
||||
#include <scwx/qt/settings/palette_settings.hpp>
|
||||
#include <scwx/qt/types/layer_types.hpp>
|
||||
#include <scwx/qt/util/color.hpp>
|
||||
#include <scwx/qt/util/tooltip.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/threads.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <shared_mutex>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#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 <QEvent>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
|
|
@ -23,29 +29,7 @@ namespace map
|
|||
static const std::string logPrefix_ = "scwx::qt::map::alert_layer";
|
||||
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
|
||||
|
||||
static std::vector<std::string>
|
||||
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};
|
||||
static const boost::gil::rgba32f_pixel_t kBlack_ {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
|
||||
template<class Key>
|
||||
struct AlertTypeHash;
|
||||
|
|
@ -58,203 +42,315 @@ struct AlertTypeHash<std::pair<awips::Phenomenon, bool>>
|
|||
|
||||
class AlertLayerHandler : public QObject
|
||||
{
|
||||
Q_OBJECT public :
|
||||
explicit AlertLayerHandler() :
|
||||
textEventManager_ {manager::TextEventManager::Instance()},
|
||||
alertUpdateTimer_ {scwx::util::io_context()},
|
||||
alertSourceMap_ {},
|
||||
featureMap_ {}
|
||||
Q_OBJECT
|
||||
public:
|
||||
struct SegmentRecord
|
||||
{
|
||||
for (auto& phenomenon : kAlertPhenomena_)
|
||||
{
|
||||
for (bool alertActive : {false, true})
|
||||
{
|
||||
alertSourceMap_.emplace(std::make_pair(phenomenon, alertActive),
|
||||
kEmptyFeatureCollection_);
|
||||
}
|
||||
}
|
||||
std::shared_ptr<const awips::Segment> segment_;
|
||||
types::TextEventKey key_;
|
||||
std::shared_ptr<const awips::TextProductMessage> message_;
|
||||
std::chrono::system_clock::time_point segmentBegin_;
|
||||
std::chrono::system_clock::time_point segmentEnd_;
|
||||
|
||||
SegmentRecord(
|
||||
const std::shared_ptr<const awips::Segment>& segment,
|
||||
const types::TextEventKey& key,
|
||||
const std::shared_ptr<const awips::TextProductMessage>& message) :
|
||||
segment_ {segment},
|
||||
key_ {key},
|
||||
message_ {message},
|
||||
segmentBegin_ {segment->event_begin()},
|
||||
segmentEnd_ {segment->event_end()}
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
explicit AlertLayerHandler()
|
||||
{
|
||||
connect(textEventManager_.get(),
|
||||
&manager::TextEventManager::AlertUpdated,
|
||||
this,
|
||||
&AlertLayerHandler::HandleAlert);
|
||||
[this](const types::TextEventKey& key, std::size_t messageIndex)
|
||||
{ HandleAlert(key, messageIndex); });
|
||||
}
|
||||
~AlertLayerHandler()
|
||||
{
|
||||
disconnect(textEventManager_.get(), nullptr, this, nullptr);
|
||||
|
||||
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,
|
||||
bool alertActive);
|
||||
std::unordered_map<
|
||||
types::TextEventKey,
|
||||
boost::container::stable_vector<std::shared_ptr<SegmentRecord>>,
|
||||
types::TextEventHash<types::TextEventKey>>
|
||||
segmentsByKey_ {};
|
||||
|
||||
void HandleAlert(const types::TextEventKey& key, size_t messageIndex);
|
||||
void UpdateAlerts();
|
||||
|
||||
std::shared_ptr<manager::TextEventManager> textEventManager_;
|
||||
static AlertLayerHandler& Instance();
|
||||
|
||||
boost::asio::steady_timer alertUpdateTimer_;
|
||||
std::unordered_map<std::pair<awips::Phenomenon, bool>,
|
||||
QVariantMap,
|
||||
AlertTypeHash<std::pair<awips::Phenomenon, bool>>>
|
||||
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_;
|
||||
std::shared_ptr<manager::TextEventManager> textEventManager_ {
|
||||
manager::TextEventManager::Instance()};
|
||||
|
||||
std::shared_mutex alertMutex_ {};
|
||||
|
||||
signals:
|
||||
void AlertAdded(const std::shared_ptr<SegmentRecord>& segmentRecord,
|
||||
awips::Phenomenon phenomenon);
|
||||
void AlertUpdated(const std::shared_ptr<SegmentRecord>& segmentRecord);
|
||||
void AlertsUpdated(awips::Phenomenon phenomenon, bool alertActive);
|
||||
};
|
||||
|
||||
class AlertLayerImpl : public QObject
|
||||
class AlertLayer::Impl
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AlertLayerImpl(std::shared_ptr<MapContext> context) :
|
||||
context_ {context}, alertLayerHandler_ {AlertLayerHandler::Instance()}
|
||||
explicit Impl(AlertLayer* self,
|
||||
std::shared_ptr<MapContext> context,
|
||||
awips::Phenomenon phenomenon) :
|
||||
self_ {self},
|
||||
phenomenon_ {phenomenon},
|
||||
geoLines_ {{false, std::make_shared<gl::draw::GeoLines>(context)},
|
||||
{true, std::make_shared<gl::draw::GeoLines>(context)}}
|
||||
{
|
||||
connect(alertLayerHandler_.get(),
|
||||
&AlertLayerHandler::AlertsUpdated,
|
||||
this,
|
||||
&AlertLayerImpl::UpdateSource);
|
||||
auto& paletteSettings = settings::PaletteSettings::Instance();
|
||||
|
||||
for (auto alertActive : {false, true})
|
||||
{
|
||||
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_;
|
||||
std::shared_ptr<AlertLayerHandler> alertLayerHandler_;
|
||||
receiver_ = nullptr;
|
||||
|
||||
std::unique_lock lock(linesMutex_);
|
||||
};
|
||||
|
||||
void AddAlert(
|
||||
const std::shared_ptr<AlertLayerHandler::SegmentRecord>& segmentRecord);
|
||||
void UpdateAlert(
|
||||
const std::shared_ptr<AlertLayerHandler::SegmentRecord>& segmentRecord);
|
||||
void ConnectAlertHandlerSignals();
|
||||
void ConnectSignals();
|
||||
void HandleGeoLinesEvent(std::shared_ptr<gl::draw::GeoLineDrawItem>& di,
|
||||
QEvent* ev);
|
||||
void HandleGeoLinesHover(std::shared_ptr<gl::draw::GeoLineDrawItem>& di,
|
||||
const QPointF& mouseGlobalPos);
|
||||
void ScheduleRefresh();
|
||||
|
||||
void AddLine(std::shared_ptr<gl::draw::GeoLines>& geoLines,
|
||||
std::shared_ptr<gl::draw::GeoLineDrawItem>& di,
|
||||
const common::Coordinate& p1,
|
||||
const common::Coordinate& p2,
|
||||
boost::gil::rgba32f_pixel_t color,
|
||||
float width,
|
||||
std::chrono::system_clock::time_point startTime,
|
||||
std::chrono::system_clock::time_point endTime,
|
||||
bool enableHover);
|
||||
void AddLines(std::shared_ptr<gl::draw::GeoLines>& geoLines,
|
||||
const std::vector<common::Coordinate>& coordinates,
|
||||
boost::gil::rgba32f_pixel_t color,
|
||||
float width,
|
||||
std::chrono::system_clock::time_point startTime,
|
||||
std::chrono::system_clock::time_point endTime,
|
||||
bool enableHover,
|
||||
boost::container::stable_vector<
|
||||
std::shared_ptr<gl::draw::GeoLineDrawItem>>& drawItems);
|
||||
|
||||
boost::asio::thread_pool threadPool_ {1u};
|
||||
|
||||
AlertLayer* self_;
|
||||
|
||||
boost::asio::system_timer refreshTimer_ {threadPool_};
|
||||
std::mutex refreshMutex_;
|
||||
|
||||
const awips::Phenomenon phenomenon_;
|
||||
|
||||
std::unique_ptr<QObject> receiver_ {std::make_unique<QObject>()};
|
||||
|
||||
std::unordered_map<bool, std::shared_ptr<gl::draw::GeoLines>> geoLines_;
|
||||
|
||||
std::unordered_map<std::shared_ptr<const AlertLayerHandler::SegmentRecord>,
|
||||
boost::container::stable_vector<
|
||||
std::shared_ptr<gl::draw::GeoLineDrawItem>>>
|
||||
linesBySegment_ {};
|
||||
std::unordered_map<std::shared_ptr<const gl::draw::GeoLineDrawItem>,
|
||||
std::shared_ptr<const AlertLayerHandler::SegmentRecord>>
|
||||
segmentsByLine_;
|
||||
std::mutex linesMutex_ {};
|
||||
|
||||
std::unordered_map<bool, boost::gil::rgba32f_pixel_t> lineColor_;
|
||||
|
||||
std::chrono::system_clock::time_point selectedTime_ {};
|
||||
|
||||
std::shared_ptr<const gl::draw::GeoLineDrawItem> lastHoverDi_ {nullptr};
|
||||
std::string tooltip_ {};
|
||||
};
|
||||
|
||||
AlertLayer::AlertLayer(std::shared_ptr<MapContext> context) :
|
||||
p(std::make_unique<AlertLayerImpl>(context))
|
||||
AlertLayer::AlertLayer(std::shared_ptr<MapContext> context,
|
||||
awips::Phenomenon phenomenon) :
|
||||
DrawLayer(context), p(std::make_unique<Impl>(this, context, phenomenon))
|
||||
{
|
||||
for (auto alertActive : {false, true})
|
||||
{
|
||||
auto& geoLines = p->geoLines_.at(alertActive);
|
||||
|
||||
AddDrawItem(geoLines);
|
||||
}
|
||||
}
|
||||
|
||||
AlertLayer::~AlertLayer() = default;
|
||||
|
||||
std::vector<std::string> AlertLayer::AddLayers(awips::Phenomenon phenomenon,
|
||||
const std::string& before)
|
||||
void AlertLayer::InitializeHandler()
|
||||
{
|
||||
logger_->debug("AddLayers(): {}", awips::GetPhenomenonCode(phenomenon));
|
||||
static bool ftt = true;
|
||||
|
||||
std::vector<std::string> layers {};
|
||||
|
||||
auto map = p->context_->map().lock();
|
||||
if (map == nullptr)
|
||||
if (ftt)
|
||||
{
|
||||
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>*
|
||||
AlertLayerHandler::FeatureList(awips::Phenomenon phenomenon, bool alertActive)
|
||||
void AlertLayer::Initialize()
|
||||
{
|
||||
std::list<QMapLibre::Feature>* featureList = nullptr;
|
||||
logger_->debug("Initialize: {}", awips::GetPhenomenonText(p->phenomenon_));
|
||||
|
||||
auto key = std::make_pair(phenomenon, alertActive);
|
||||
auto it = alertSourceMap_.find(key);
|
||||
if (it != alertSourceMap_.cend())
|
||||
DrawLayer::Initialize();
|
||||
|
||||
auto& alertLayerHandler = AlertLayerHandler::Instance();
|
||||
|
||||
// Take a shared lock to prevent handling additional alerts while populating
|
||||
// initial lists
|
||||
std::shared_lock lock {alertLayerHandler.alertMutex_};
|
||||
|
||||
for (auto alertActive : {false, true})
|
||||
{
|
||||
featureList = reinterpret_cast<std::list<QMapLibre::Feature>*>(
|
||||
it->second["data"].data());
|
||||
auto& geoLines = p->geoLines_.at(alertActive);
|
||||
|
||||
geoLines->StartLines();
|
||||
|
||||
// Populate initial segments
|
||||
auto segmentsIt =
|
||||
alertLayerHandler.segmentsByType_.find({p->phenomenon_, alertActive});
|
||||
if (segmentsIt != alertLayerHandler.segmentsByType_.cend())
|
||||
{
|
||||
for (auto& segment : segmentsIt->second)
|
||||
{
|
||||
p->AddAlert(segment);
|
||||
}
|
||||
}
|
||||
|
||||
geoLines->FinishLines();
|
||||
}
|
||||
|
||||
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,
|
||||
size_t messageIndex)
|
||||
{
|
||||
// Skip alert if there are more messages to be processed
|
||||
if (messageIndex + 1 < textEventManager_->message_count(key))
|
||||
{
|
||||
return;
|
||||
}
|
||||
logger_->trace("HandleAlert: {}", key.ToString());
|
||||
|
||||
auto message = textEventManager_->message_list(key).at(messageIndex);
|
||||
std::unordered_set<std::pair<awips::Phenomenon, bool>,
|
||||
AlertTypeHash<std::pair<awips::Phenomenon, bool>>>
|
||||
alertsUpdated {};
|
||||
|
||||
// Take a unique lock before modifying feature lists
|
||||
std::unique_lock lock(alertMutex_);
|
||||
auto message = textEventManager_->message_list(key).at(messageIndex);
|
||||
|
||||
// Remove existing features for key
|
||||
auto existingFeatures = featureMap_.equal_range(key);
|
||||
for (auto it = existingFeatures.first; it != existingFeatures.second; ++it)
|
||||
// Determine start time for first segment
|
||||
std::chrono::system_clock::time_point segmentBegin {};
|
||||
if (message->segment_count() > 0)
|
||||
{
|
||||
auto& [phenomenon, alertActive, featureIt, eventEnd] = it->second;
|
||||
auto featureList = FeatureList(phenomenon, alertActive);
|
||||
if (featureList != nullptr)
|
||||
{
|
||||
// Remove existing feature for key
|
||||
featureList->erase(featureIt);
|
||||
segmentBegin = message->segment(0)->event_begin();
|
||||
}
|
||||
|
||||
// Mark alert type as updated
|
||||
alertsUpdated.emplace(phenomenon, alertActive);
|
||||
// Take a unique mutex before modifying segments
|
||||
std::unique_lock lock {alertMutex_};
|
||||
|
||||
// Update any existing segments with new end time
|
||||
auto& segmentsForKey = segmentsByKey_[key];
|
||||
for (auto& segmentRecord : segmentsForKey)
|
||||
{
|
||||
if (segmentRecord->segmentEnd_ > segmentBegin)
|
||||
{
|
||||
segmentRecord->segmentEnd_ = segmentBegin;
|
||||
|
||||
Q_EMIT AlertUpdated(segmentRecord);
|
||||
}
|
||||
}
|
||||
featureMap_.erase(existingFeatures.first, existingFeatures.second);
|
||||
|
||||
for (auto segment : message->segments())
|
||||
// Process new segments
|
||||
for (auto& segment : message->segments())
|
||||
{
|
||||
if (!segment->codedLocation_.has_value())
|
||||
{
|
||||
// Cannot handle a segment without a location
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& vtec = segment->header_->vtecString_.front();
|
||||
auto action = vtec.pVtec_.action();
|
||||
awips::Phenomenon phenomenon = vtec.pVtec_.phenomenon();
|
||||
auto eventEnd = vtec.pVtec_.event_end();
|
||||
bool alertActive = (action != awips::PVtec::Action::Canceled);
|
||||
|
||||
// If the event has ended, skip it
|
||||
if (eventEnd < std::chrono::system_clock::now())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto& segmentsForType = segmentsByType_[{key.phenomenon_, alertActive}];
|
||||
|
||||
auto featureList = FeatureList(phenomenon, alertActive);
|
||||
if (featureList != nullptr)
|
||||
{
|
||||
// Add alert location to polygon list
|
||||
auto featureIt = featureList->emplace(
|
||||
featureList->cend(),
|
||||
CreateFeature(segment->codedLocation_.value()));
|
||||
// Insert segment into lists
|
||||
std::shared_ptr<SegmentRecord> segmentRecord =
|
||||
std::make_shared<SegmentRecord>(segment, key, message);
|
||||
|
||||
// Store iterator for created feature in feature map
|
||||
featureMap_.emplace(std::piecewise_construct,
|
||||
std::forward_as_tuple(key),
|
||||
std::forward_as_tuple(
|
||||
phenomenon, alertActive, featureIt, eventEnd));
|
||||
segmentsForKey.push_back(segmentRecord);
|
||||
segmentsForType.push_back(segmentRecord);
|
||||
|
||||
// Mark alert type as updated
|
||||
alertsUpdated.emplace(phenomenon, alertActive);
|
||||
}
|
||||
Q_EMIT AlertAdded(segmentRecord, phenomenon);
|
||||
|
||||
alertsUpdated.emplace(phenomenon, alertActive);
|
||||
}
|
||||
|
||||
// Release the lock after completing feature list updates
|
||||
// Release the lock after completing segment updates
|
||||
lock.unlock();
|
||||
|
||||
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
|
||||
std::unique_lock lock(alertMutex_);
|
||||
|
||||
std::unordered_set<std::pair<awips::Phenomenon, bool>,
|
||||
AlertTypeHash<std::pair<awips::Phenomenon, bool>>>
|
||||
alertsUpdated {};
|
||||
|
||||
// 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())
|
||||
QObject::connect(
|
||||
&alertLayerHandler,
|
||||
&AlertLayerHandler::AlertAdded,
|
||||
receiver_.get(),
|
||||
[this](
|
||||
const std::shared_ptr<AlertLayerHandler::SegmentRecord>& segmentRecord,
|
||||
awips::Phenomenon phenomenon)
|
||||
{
|
||||
logger_->debug("Alert expired: {}", it->first.ToString());
|
||||
|
||||
auto featureList = FeatureList(phenomenon, alertActive);
|
||||
if (featureList != nullptr)
|
||||
if (phenomenon == phenomenon_)
|
||||
{
|
||||
// Remove existing feature for key
|
||||
featureList->erase(featureIt);
|
||||
|
||||
// Mark alert type as updated
|
||||
alertsUpdated.emplace(phenomenon, alertActive);
|
||||
AddAlert(segmentRecord);
|
||||
}
|
||||
|
||||
// Erase current item and increment iterator
|
||||
it = featureMap_.erase(it);
|
||||
}
|
||||
else
|
||||
});
|
||||
QObject::connect(
|
||||
&alertLayerHandler,
|
||||
&AlertLayerHandler::AlertUpdated,
|
||||
receiver_.get(),
|
||||
[this](
|
||||
const std::shared_ptr<AlertLayerHandler::SegmentRecord>& segmentRecord)
|
||||
{
|
||||
// Current item is not expired, continue
|
||||
++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)
|
||||
if (segmentRecord->key_.phenomenon_ == phenomenon_)
|
||||
{
|
||||
logger_->debug("Alert update timer cancelled");
|
||||
}
|
||||
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());
|
||||
}
|
||||
UpdateAlert(segmentRecord);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void AlertLayerImpl::UpdateSource(awips::Phenomenon phenomenon,
|
||||
bool alertActive)
|
||||
void AlertLayer::Impl::ConnectSignals()
|
||||
{
|
||||
auto map = context_->map().lock();
|
||||
if (map == nullptr)
|
||||
auto timelineManager = manager::TimelineManager::Instance();
|
||||
|
||||
QObject::connect(timelineManager.get(),
|
||||
&manager::TimelineManager::SelectedTimeUpdated,
|
||||
receiver_.get(),
|
||||
[this](std::chrono::system_clock::time_point dateTime)
|
||||
{ selectedTime_ = dateTime; });
|
||||
}
|
||||
|
||||
void AlertLayer::Impl::ScheduleRefresh()
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
// Take a unique lock before refreshing
|
||||
std::unique_lock lock(refreshMutex_);
|
||||
|
||||
// Expires at the top of the next minute
|
||||
std::chrono::system_clock::time_point now =
|
||||
std::chrono::floor<std::chrono::minutes>(
|
||||
std::chrono::system_clock::now());
|
||||
refreshTimer_.expires_at(now + 1min);
|
||||
|
||||
refreshTimer_.async_wait(
|
||||
[this](const boost::system::error_code& e)
|
||||
{
|
||||
if (e == boost::asio::error::operation_aborted)
|
||||
{
|
||||
logger_->debug("Refresh timer cancelled");
|
||||
}
|
||||
else if (e != boost::system::errc::success)
|
||||
{
|
||||
logger_->warn("Refresh timer error: {}", e.message());
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_EMIT self_->NeedsRendering();
|
||||
ScheduleRefresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void AlertLayer::Impl::AddAlert(
|
||||
const std::shared_ptr<AlertLayerHandler::SegmentRecord>& segmentRecord)
|
||||
{
|
||||
auto& segment = segmentRecord->segment_;
|
||||
|
||||
auto& vtec = segment->header_->vtecString_.front();
|
||||
auto action = vtec.pVtec_.action();
|
||||
bool alertActive = (action != awips::PVtec::Action::Canceled);
|
||||
auto& startTime = segmentRecord->segmentBegin_;
|
||||
auto& endTime = segmentRecord->segmentEnd_;
|
||||
|
||||
auto& lineColor = lineColor_.at(alertActive);
|
||||
auto& geoLines = geoLines_.at(alertActive);
|
||||
|
||||
const auto& coordinates = segment->codedLocation_->coordinates();
|
||||
|
||||
// Take a mutex before modifying lines by segment
|
||||
std::unique_lock lock {linesMutex_};
|
||||
|
||||
// Add draw items only if the segment has not already been added
|
||||
auto drawItems = linesBySegment_.try_emplace(
|
||||
segmentRecord,
|
||||
boost::container::stable_vector<
|
||||
std::shared_ptr<gl::draw::GeoLineDrawItem>> {});
|
||||
|
||||
// If draw items were added
|
||||
if (drawItems.second)
|
||||
{
|
||||
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
|
||||
std::shared_lock lock(alertLayerHandler_->alertMutex_);
|
||||
|
||||
// Update source, relies on alert source being defined
|
||||
map->updateSource(GetSourceId(phenomenon, alertActive),
|
||||
alertLayerHandler_->alertSourceMap_.at(
|
||||
std::make_pair(phenomenon, alertActive)));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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 {};
|
||||
static std::mutex instanceMutex {};
|
||||
|
||||
std::unique_lock lock(instanceMutex);
|
||||
|
||||
std::shared_ptr<AlertLayerHandler> alertLayerHandler =
|
||||
alertLayerHandlerReference.lock();
|
||||
|
||||
if (alertLayerHandler == nullptr)
|
||||
if (di != lastHoverDi_)
|
||||
{
|
||||
alertLayerHandler = std::make_shared<AlertLayerHandler>();
|
||||
alertLayerHandlerReference = alertLayerHandler;
|
||||
auto it = segmentsByLine_.find(di);
|
||||
if (it != segmentsByLine_.cend())
|
||||
{
|
||||
tooltip_ =
|
||||
boost::algorithm::join(it->second->segment_->productContent_, "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
tooltip_.clear();
|
||||
}
|
||||
|
||||
alertLayerHandler->UpdateAlerts();
|
||||
lastHoverDi_ = di;
|
||||
}
|
||||
|
||||
return alertLayerHandler;
|
||||
}
|
||||
|
||||
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))
|
||||
if (!tooltip_.empty())
|
||||
{
|
||||
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
|
||||
CreateFeature(const awips::CodedLocation& codedLocation)
|
||||
AlertLayerHandler& AlertLayerHandler::Instance()
|
||||
{
|
||||
auto mapboxCoordinates = GetMapboxCoordinates(codedLocation);
|
||||
|
||||
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);
|
||||
static AlertLayerHandler alertLayerHandler_ {};
|
||||
return alertLayerHandler_;
|
||||
}
|
||||
|
||||
size_t AlertTypeHash<std::pair<awips::Phenomenon, bool>>::operator()(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#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 <string>
|
||||
|
|
@ -14,19 +15,28 @@ namespace qt
|
|||
namespace map
|
||||
{
|
||||
|
||||
class AlertLayerImpl;
|
||||
|
||||
class AlertLayer
|
||||
class AlertLayer : public DrawLayer
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(AlertLayer)
|
||||
|
||||
public:
|
||||
explicit AlertLayer(std::shared_ptr<MapContext> context);
|
||||
explicit AlertLayer(std::shared_ptr<MapContext> context,
|
||||
scwx::awips::Phenomenon phenomenon);
|
||||
~AlertLayer();
|
||||
|
||||
std::vector<std::string> AddLayers(awips::Phenomenon phenomenon,
|
||||
const std::string& before = {});
|
||||
void Initialize() override final;
|
||||
void Render(const QMapLibre::CustomLayerRenderParameters&) override final;
|
||||
void Deinitialize() override final;
|
||||
|
||||
static void InitializeHandler();
|
||||
|
||||
signals:
|
||||
void AlertSelected(const types::TextEventKey& key);
|
||||
|
||||
private:
|
||||
std::unique_ptr<AlertLayerImpl> p;
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace map
|
||||
|
|
|
|||
|
|
@ -55,6 +55,9 @@ void DrawLayer::Render(const QMapLibre::CustomLayerRenderParameters& params)
|
|||
bool textureAtlasChanged =
|
||||
newTextureAtlasBuildCount != p->textureAtlasBuildCount_;
|
||||
|
||||
// Set OpenGL blend mode for transparency
|
||||
gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
gl.glActiveTexture(GL_TEXTURE0);
|
||||
gl.glBindTexture(GL_TEXTURE_2D_ARRAY, p->textureAtlas_);
|
||||
|
||||
|
|
|
|||
|
|
@ -79,7 +79,6 @@ public:
|
|||
imGuiRendererInitialized_ {false},
|
||||
radarProductManager_ {nullptr},
|
||||
radarProductLayer_ {nullptr},
|
||||
alertLayer_ {std::make_shared<AlertLayer>(context_)},
|
||||
overlayLayer_ {nullptr},
|
||||
placefileLayer_ {nullptr},
|
||||
colorTableLayer_ {nullptr},
|
||||
|
|
@ -100,6 +99,9 @@ public:
|
|||
overlayProductView->SetAutoRefresh(autoRefreshEnabled_);
|
||||
overlayProductView->SetAutoUpdate(autoUpdateEnabled_);
|
||||
|
||||
// Initialize AlertLayerHandler
|
||||
map::AlertLayer::InitializeHandler();
|
||||
|
||||
auto& generalSettings = settings::GeneralSettings::Instance();
|
||||
|
||||
// Initialize context
|
||||
|
|
@ -218,7 +220,6 @@ public:
|
|||
std::shared_ptr<manager::RadarProductManager> radarProductManager_;
|
||||
|
||||
std::shared_ptr<RadarProductLayer> radarProductLayer_;
|
||||
std::shared_ptr<AlertLayer> alertLayer_;
|
||||
std::shared_ptr<OverlayLayer> overlayLayer_;
|
||||
std::shared_ptr<OverlayProductLayer> overlayProductLayer_ {nullptr};
|
||||
std::shared_ptr<PlacefileLayer> placefileLayer_;
|
||||
|
|
@ -1180,10 +1181,17 @@ void MapWidgetImpl::AddLayer(types::LayerType type,
|
|||
}
|
||||
else if (type == types::LayerType::Alert)
|
||||
{
|
||||
// Add the alert layer for the phenomenon
|
||||
auto newLayers = alertLayer_->AddLayers(
|
||||
std::get<awips::Phenomenon>(description), before);
|
||||
layerList_.insert(layerList_.end(), newLayers.cbegin(), newLayers.cend());
|
||||
auto phenomenon = std::get<awips::Phenomenon>(description);
|
||||
|
||||
std::shared_ptr<AlertLayer> alertLayer =
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <scwx/qt/config/radar_site.hpp>
|
||||
#include <scwx/qt/types/map_types.hpp>
|
||||
#include <scwx/qt/types/radar_product_record.hpp>
|
||||
#include <scwx/qt/types/text_event_key.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
|
@ -150,6 +151,7 @@ private slots:
|
|||
void mapChanged(QMapLibre::Map::MapChange);
|
||||
|
||||
signals:
|
||||
void AlertSelected(const types::TextEventKey& key);
|
||||
void Level3ProductsChanged();
|
||||
void MapParametersChanged(double latitude,
|
||||
double longitude,
|
||||
|
|
|
|||
|
|
@ -300,9 +300,6 @@ void OverlayLayer::Render(const QMapLibre::CustomLayerRenderParameters& 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;
|
||||
|
||||
if (radarProductView != nullptr)
|
||||
|
|
|
|||
|
|
@ -143,9 +143,6 @@ void OverlayProductLayer::Render(
|
|||
{
|
||||
gl::OpenGLFunctions& gl = context()->gl();
|
||||
|
||||
// Set OpenGL blend mode for transparency
|
||||
gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
if (p->stiNeedsUpdate_)
|
||||
{
|
||||
p->UpdateStormTrackingInformation();
|
||||
|
|
|
|||
|
|
@ -129,9 +129,6 @@ void PlacefileLayer::Render(
|
|||
{
|
||||
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 =
|
||||
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()
|
||||
{
|
||||
connect(self_->ui->alertFilter,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/qt/types/text_event_key.hpp>
|
||||
|
||||
#include <QDockWidget>
|
||||
|
||||
namespace Ui
|
||||
|
|
@ -32,6 +34,7 @@ signals:
|
|||
|
||||
public slots:
|
||||
void HandleMapUpdate(double latitude, double longitude);
|
||||
void SelectAlert(const types::TextEventKey& key);
|
||||
|
||||
private:
|
||||
friend class AlertDockWidgetImpl;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,15 @@ boost::gil::rgba8_pixel_t ToRgba8PixelT(const std::string& argbString)
|
|||
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 util
|
||||
} // 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);
|
||||
|
||||
/**
|
||||
* 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 util
|
||||
} // namespace qt
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include <scwx/awips/ugc.hpp>
|
||||
#include <scwx/awips/wmo_header.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
|
@ -57,6 +58,7 @@ struct SegmentHeader
|
|||
|
||||
struct Segment
|
||||
{
|
||||
std::shared_ptr<WmoHeader> wmoHeader_ {};
|
||||
std::optional<SegmentHeader> header_ {};
|
||||
std::vector<std::string> productContent_ {};
|
||||
std::optional<CodedLocation> codedLocation_ {};
|
||||
|
|
@ -73,6 +75,9 @@ struct Segment
|
|||
|
||||
Segment(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;
|
||||
|
|
|
|||
|
|
@ -104,16 +104,14 @@ std::shared_ptr<const Segment> TextProductMessage::segment(size_t s) const
|
|||
return p->segments_[s];
|
||||
}
|
||||
|
||||
std::chrono::system_clock::time_point
|
||||
TextProductMessage::segment_event_begin(std::size_t s) const
|
||||
std::chrono::system_clock::time_point Segment::event_begin() const
|
||||
{
|
||||
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
|
||||
eventBegin = header->vtecString_[0].pVtec_.event_begin();
|
||||
eventBegin = header_->vtecString_[0].pVtec_.event_begin();
|
||||
|
||||
// If event begin is 000000T0000Z
|
||||
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
|
||||
system_clock::time_point eventEnd =
|
||||
header->vtecString_[0].pVtec_.event_end();
|
||||
header_->vtecString_[0].pVtec_.event_end();
|
||||
|
||||
auto endDays = floor<days>(eventEnd);
|
||||
year_month_day endDate {endDays};
|
||||
|
||||
// Determine WMO date/time
|
||||
std::string wmoDateTime = wmo_header()->date_time();
|
||||
std::string wmoDateTime = wmoHeader_->date_time();
|
||||
|
||||
bool wmoDateTimeValid = false;
|
||||
unsigned int dayOfMonth = 0;
|
||||
|
|
@ -189,6 +187,25 @@ TextProductMessage::segment_event_begin(std::size_t s) const
|
|||
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
|
||||
{
|
||||
return 0;
|
||||
|
|
@ -211,6 +228,7 @@ bool TextProductMessage::Parse(std::istream& is)
|
|||
}
|
||||
|
||||
std::shared_ptr<Segment> segment = std::make_shared<Segment>();
|
||||
segment->wmoHeader_ = p->wmoHeader_;
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue