Merge pull request #257 from dpaulat/feature/enhanced-alerts-2

Alert Animation, Hover and Selection
This commit is contained in:
Dan Paulat 2024-08-06 22:07:41 -05:00 committed by GitHub
commit 822d0762a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 937 additions and 465 deletions

View file

@ -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_ {};
@ -137,7 +149,6 @@ 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)
{ {
if (di->latitude1_ != latitude1 || di->longitude1_ != longitude1 ||
di->latitude2_ != latitude1 || di->longitude2_ != longitude1)
{
di->latitude1_ = latitude1; di->latitude1_ = latitude1;
di->longitude1_ = longitude1; di->longitude1_ = longitude1;
di->latitude2_ = latitude2; di->latitude2_ = latitude2;
di->longitude2_ = longitude2; 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)
{ {
if (di->modulate_ != modulate)
{
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)
{ {
if (di->width_ != width)
{
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)
{ {
if (di->visible_ != visible)
{
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)
{ {
if (di->hoverText_ != text)
{
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,9 +567,11 @@ 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_);
{
// Initiailize line data
const auto lineData = {
// Line // Line
lat1, lon1, lx, by, mc0, mc1, mc2, mc3, a, // BL lat1, lon1, lx, by, mc0, mc1, mc2, mc3, a, // BL
lat2, lon2, lx, ty, mc0, mc1, mc2, mc3, a, // TL lat2, lon2, lx, ty, mc0, mc1, mc2, mc3, a, // TL
@ -448,28 +579,54 @@ void GeoLines::Impl::BufferLine(
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, rx, ty, mc0, mc1, mc2, mc3, a, // TR
lat2, lon2, lx, ty, mc0, mc1, mc2, mc3, a // TL lat2, lon2, lx, ty, mc0, mc1, mc2, mc3, a // TL
}); };
newIntegerBuffer_.insert(newIntegerBuffer_.end(), const auto integerData = {thresholdValue, startTime, endTime, v,
{thresholdValue, thresholdValue, startTime, endTime, v,
startTime, thresholdValue, startTime, endTime, v,
endTime, thresholdValue, startTime, endTime, v,
thresholdValue, thresholdValue, startTime, endTime, v,
startTime, thresholdValue, startTime, endTime, v};
endTime,
thresholdValue,
startTime,
endTime,
thresholdValue,
startTime,
endTime,
thresholdValue,
startTime,
endTime,
thresholdValue,
startTime,
endTime});
if (!di->hoverText_.empty()) // 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())
{
hoverLines.emplace_back(
LineHoverEntry {di, sc1, sc2, otl, otr, obl, obr}); 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;
if (!it->di_->hoverText_.empty())
{
// Show tooltip
util::tooltip::Show(it->di_->hoverText_, mouseGlobalPos); 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

View file

@ -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,7 +79,7 @@ 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,
@ -87,7 +91,7 @@ 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);
/** /**
@ -96,7 +100,7 @@ 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);
/** /**
@ -104,31 +108,67 @@ public:
* *
* @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.
* *
* @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;

View file

@ -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_;

View file

@ -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,

View file

@ -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()));
} }
~AlertLayerImpl() {};
void UpdateSource(awips::Phenomenon phenomenon, bool alertActive); ConnectSignals();
ScheduleRefresh();
}
~Impl()
{
std::unique_lock refreshLock(refreshMutex_);
refreshTimer_.cancel();
refreshLock.unlock();
std::shared_ptr<MapContext> context_; threadPool_.join();
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) : 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);
}
} }
return featureList; geoLines->FinishLines();
}
p->ConnectAlertHandlerSignals();
}
void AlertLayer::Render(const QMapLibre::CustomLayerRenderParameters& params)
{
gl::OpenGLFunctions& gl = context()->gl();
for (auto alertActive : {false, true})
{
p->geoLines_.at(alertActive)->set_selected_time(p->selectedTime_);
}
DrawLayer::Render(params);
SCWX_GL_CHECK_ERROR();
}
void AlertLayer::Deinitialize()
{
logger_->debug("Deinitialize: {}", awips::GetPhenomenonText(p->phenomenon_));
DrawLayer::Deinitialize();
} }
void AlertLayerHandler::HandleAlert(const types::TextEventKey& key, 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( Q_EMIT AlertAdded(segmentRecord, phenomenon);
phenomenon, alertActive, featureIt, eventEnd));
// Mark alert type as updated
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 (phenomenon == phenomenon_)
// 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()); AddAlert(segmentRecord);
auto featureList = FeatureList(phenomenon, alertActive);
if (featureList != nullptr)
{
// Remove existing feature for key
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 UpdateAlert(segmentRecord);
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");
}
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)
{ {
return; if (e == boost::asio::error::operation_aborted)
{
logger_->debug("Refresh timer cancelled");
}
else if (e != boost::system::errc::success)
{
logger_->warn("Refresh timer error: {}", e.message());
}
else
{
Q_EMIT self_->NeedsRendering();
ScheduleRefresh();
}
});
}
void AlertLayer::Impl::AddAlert(
const std::shared_ptr<AlertLayerHandler::SegmentRecord>& segmentRecord)
{
auto& segment = segmentRecord->segment_;
auto& vtec = segment->header_->vtecString_.front();
auto action = vtec.pVtec_.action();
bool alertActive = (action != awips::PVtec::Action::Canceled);
auto& startTime = segmentRecord->segmentBegin_;
auto& endTime = segmentRecord->segmentEnd_;
auto& lineColor = lineColor_.at(alertActive);
auto& geoLines = geoLines_.at(alertActive);
const auto& coordinates = segment->codedLocation_->coordinates();
// Take a mutex before modifying lines by segment
std::unique_lock lock {linesMutex_};
// Add draw items only if the segment has not already been added
auto drawItems = linesBySegment_.try_emplace(
segmentRecord,
boost::container::stable_vector<
std::shared_ptr<gl::draw::GeoLineDrawItem>> {});
// If draw items were added
if (drawItems.second)
{
// Add border
AddLines(geoLines,
coordinates,
kBlack_,
5.0f,
startTime,
endTime,
true,
drawItems.first->second);
// Add only border to segmentsByLine_
for (auto& di : drawItems.first->second)
{
segmentsByLine_.insert({di, segmentRecord});
} }
// Take a shared lock before using feature lists // Add line
std::shared_lock lock(alertLayerHandler_->alertMutex_); AddLines(geoLines,
coordinates,
// Update source, relies on alert source being defined lineColor,
map->updateSource(GetSourceId(phenomenon, alertActive), 3.0f,
alertLayerHandler_->alertSourceMap_.at( startTime,
std::make_pair(phenomenon, alertActive))); endTime,
false,
drawItems.first->second);
}
} }
std::shared_ptr<AlertLayerHandler> AlertLayerHandler::Instance() void AlertLayer::Impl::UpdateAlert(
const std::shared_ptr<AlertLayerHandler::SegmentRecord>& segmentRecord)
{ {
static std::weak_ptr<AlertLayerHandler> alertLayerHandlerReference {}; // Take a mutex before referencing lines iterators and stable vector
static std::mutex instanceMutex {}; std::unique_lock lock {linesMutex_};
std::unique_lock lock(instanceMutex); auto it = linesBySegment_.find(segmentRecord);
if (it != linesBySegment_.cend())
std::shared_ptr<AlertLayerHandler> alertLayerHandler =
alertLayerHandlerReference.lock();
if (alertLayerHandler == nullptr)
{ {
alertLayerHandler = std::make_shared<AlertLayerHandler>(); auto& segment = segmentRecord->segment_;
alertLayerHandlerReference = alertLayerHandler;
alertLayerHandler->UpdateAlerts(); 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;
}
} }
return alertLayerHandler; auto di = geoLines->AddLine();
AddLine(geoLines,
di,
coordinates[i],
coordinates[j],
color,
width,
startTime,
endTime,
enableHover);
drawItems.push_back(di);
}
} }
static std::vector<std::string> void AlertLayer::Impl::AddLine(std::shared_ptr<gl::draw::GeoLines>& geoLines,
AddAlertLayer(std::shared_ptr<QMapLibre::Map> map, std::shared_ptr<gl::draw::GeoLineDrawItem>& di,
awips::Phenomenon phenomenon, const common::Coordinate& p1,
bool alertActive, const common::Coordinate& p2,
const QString& beforeLayer) boost::gil::rgba32f_pixel_t color,
float width,
std::chrono::system_clock::time_point startTime,
std::chrono::system_clock::time_point endTime,
bool enableHover)
{ {
settings::PaletteSettings& paletteSettings = geoLines->SetLineLocation(
settings::PaletteSettings::Instance(); 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);
QString layerPrefix = QString::fromStdString( if (enableHover)
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); 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));
} }
if (map->layerExists(fgLayerId)) }
void AlertLayer::Impl::HandleGeoLinesEvent(
std::shared_ptr<gl::draw::GeoLineDrawItem>& di, QEvent* ev)
{
switch (ev->type())
{ {
map->removeLayer(fgLayerId); 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;
} }
const float opacity = outlineColor[3] / 255.0f; default:
break;
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 void AlertLayer::Impl::HandleGeoLinesHover(
CreateFeature(const awips::CodedLocation& codedLocation) std::shared_ptr<gl::draw::GeoLineDrawItem>& di,
const QPointF& mouseGlobalPos)
{ {
auto mapboxCoordinates = GetMapboxCoordinates(codedLocation); if (di != lastHoverDi_)
{
auto it = segmentsByLine_.find(di);
if (it != segmentsByLine_.cend())
{
tooltip_ =
boost::algorithm::join(it->second->segment_->productContent_, "\n");
}
else
{
tooltip_.clear();
}
return QMapLibre::Feature { lastHoverDi_ = di;
QMapLibre::Feature::PolygonType, }
std::initializer_list<QMapLibre::CoordinatesCollection> {
std::initializer_list<QMapLibre::Coordinates> {{mapboxCoordinates}}}}; if (!tooltip_.empty())
{
util::tooltip::Show(tooltip_, mouseGlobalPos);
}
} }
static QMapLibre::Coordinate AlertLayerHandler& AlertLayerHandler::Instance()
GetMapboxCoordinate(const common::Coordinate& coordinate)
{ {
return {coordinate.latitude_, coordinate.longitude_}; static AlertLayerHandler alertLayerHandler_ {};
} return alertLayerHandler_;
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()(

View file

@ -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

View file

@ -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_);

View file

@ -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)
{ {

View file

@ -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,

View file

@ -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)

View file

@ -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();

View file

@ -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();

View file

@ -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,

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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)
{ {