mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-30 13:10:05 +00:00
Merge pull request #146 from dpaulat/feature/storm-tracks
Storm Tracking Information
This commit is contained in:
commit
0a1754db84
53 changed files with 3858 additions and 720 deletions
|
|
@ -61,8 +61,9 @@ set(SRC_GL source/scwx/qt/gl/gl_context.cpp
|
|||
source/scwx/qt/gl/shader_program.cpp)
|
||||
set(HDR_GL_DRAW source/scwx/qt/gl/draw/draw_item.hpp
|
||||
source/scwx/qt/gl/draw/geo_icons.hpp
|
||||
source/scwx/qt/gl/draw/geo_line.hpp
|
||||
source/scwx/qt/gl/draw/geo_lines.hpp
|
||||
source/scwx/qt/gl/draw/icons.hpp
|
||||
source/scwx/qt/gl/draw/linked_vectors.hpp
|
||||
source/scwx/qt/gl/draw/placefile_icons.hpp
|
||||
source/scwx/qt/gl/draw/placefile_images.hpp
|
||||
source/scwx/qt/gl/draw/placefile_lines.hpp
|
||||
|
|
@ -72,8 +73,9 @@ set(HDR_GL_DRAW source/scwx/qt/gl/draw/draw_item.hpp
|
|||
source/scwx/qt/gl/draw/rectangle.hpp)
|
||||
set(SRC_GL_DRAW source/scwx/qt/gl/draw/draw_item.cpp
|
||||
source/scwx/qt/gl/draw/geo_icons.cpp
|
||||
source/scwx/qt/gl/draw/geo_line.cpp
|
||||
source/scwx/qt/gl/draw/geo_lines.cpp
|
||||
source/scwx/qt/gl/draw/icons.cpp
|
||||
source/scwx/qt/gl/draw/linked_vectors.cpp
|
||||
source/scwx/qt/gl/draw/placefile_icons.cpp
|
||||
source/scwx/qt/gl/draw/placefile_images.cpp
|
||||
source/scwx/qt/gl/draw/placefile_lines.cpp
|
||||
|
|
@ -115,6 +117,7 @@ set(HDR_MAP source/scwx/qt/map/alert_layer.hpp
|
|||
source/scwx/qt/map/map_settings.hpp
|
||||
source/scwx/qt/map/map_widget.hpp
|
||||
source/scwx/qt/map/overlay_layer.hpp
|
||||
source/scwx/qt/map/overlay_product_layer.hpp
|
||||
source/scwx/qt/map/placefile_layer.hpp
|
||||
source/scwx/qt/map/radar_product_layer.hpp
|
||||
source/scwx/qt/map/radar_range_layer.hpp
|
||||
|
|
@ -128,6 +131,7 @@ set(SRC_MAP source/scwx/qt/map/alert_layer.cpp
|
|||
source/scwx/qt/map/map_provider.cpp
|
||||
source/scwx/qt/map/map_widget.cpp
|
||||
source/scwx/qt/map/overlay_layer.cpp
|
||||
source/scwx/qt/map/overlay_product_layer.cpp
|
||||
source/scwx/qt/map/placefile_layer.cpp
|
||||
source/scwx/qt/map/radar_product_layer.cpp
|
||||
source/scwx/qt/map/radar_range_layer.cpp
|
||||
|
|
@ -154,6 +158,7 @@ set(HDR_SETTINGS source/scwx/qt/settings/audio_settings.hpp
|
|||
source/scwx/qt/settings/general_settings.hpp
|
||||
source/scwx/qt/settings/map_settings.hpp
|
||||
source/scwx/qt/settings/palette_settings.hpp
|
||||
source/scwx/qt/settings/product_settings.hpp
|
||||
source/scwx/qt/settings/settings_category.hpp
|
||||
source/scwx/qt/settings/settings_container.hpp
|
||||
source/scwx/qt/settings/settings_definitions.hpp
|
||||
|
|
@ -167,6 +172,7 @@ set(SRC_SETTINGS source/scwx/qt/settings/audio_settings.cpp
|
|||
source/scwx/qt/settings/general_settings.cpp
|
||||
source/scwx/qt/settings/map_settings.cpp
|
||||
source/scwx/qt/settings/palette_settings.cpp
|
||||
source/scwx/qt/settings/product_settings.cpp
|
||||
source/scwx/qt/settings/settings_category.cpp
|
||||
source/scwx/qt/settings/settings_container.cpp
|
||||
source/scwx/qt/settings/settings_interface.cpp
|
||||
|
|
@ -298,12 +304,14 @@ set(HDR_VIEW source/scwx/qt/view/level2_product_view.hpp
|
|||
source/scwx/qt/view/level3_product_view.hpp
|
||||
source/scwx/qt/view/level3_radial_view.hpp
|
||||
source/scwx/qt/view/level3_raster_view.hpp
|
||||
source/scwx/qt/view/overlay_product_view.hpp
|
||||
source/scwx/qt/view/radar_product_view.hpp
|
||||
source/scwx/qt/view/radar_product_view_factory.hpp)
|
||||
set(SRC_VIEW source/scwx/qt/view/level2_product_view.cpp
|
||||
source/scwx/qt/view/level3_product_view.cpp
|
||||
source/scwx/qt/view/level3_radial_view.cpp
|
||||
source/scwx/qt/view/level3_raster_view.cpp
|
||||
source/scwx/qt/view/overlay_product_view.cpp
|
||||
source/scwx/qt/view/radar_product_view.cpp
|
||||
source/scwx/qt/view/radar_product_view_factory.cpp)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,314 +0,0 @@
|
|||
#include <scwx/qt/gl/draw/geo_line.hpp>
|
||||
#include <scwx/qt/util/geographic_lib.hpp>
|
||||
#include <scwx/qt/util/texture_atlas.hpp>
|
||||
#include <scwx/common/geographic.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
#include <numbers>
|
||||
#include <optional>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace gl
|
||||
{
|
||||
namespace draw
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ = "scwx::qt::gl::draw::geo_line";
|
||||
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
|
||||
|
||||
static constexpr size_t kNumRectangles = 1;
|
||||
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 = 11;
|
||||
static constexpr size_t kBufferLength =
|
||||
kNumTriangles * kVerticesPerTriangle * kPointsPerVertex;
|
||||
|
||||
static const std::string kTextureName = "lines/default-1x7";
|
||||
|
||||
class GeoLine::Impl
|
||||
{
|
||||
public:
|
||||
explicit Impl(std::shared_ptr<GlContext> context) :
|
||||
context_ {context},
|
||||
geodesic_ {util::GeographicLib::DefaultGeodesic()},
|
||||
dirty_ {false},
|
||||
visible_ {true},
|
||||
points_ {},
|
||||
angle_ {},
|
||||
width_ {7.0f},
|
||||
modulateColor_ {std::nullopt},
|
||||
shaderProgram_ {nullptr},
|
||||
uMVPMatrixLocation_(GL_INVALID_INDEX),
|
||||
uMapMatrixLocation_(GL_INVALID_INDEX),
|
||||
uMapScreenCoordLocation_(GL_INVALID_INDEX),
|
||||
texture_ {},
|
||||
vao_ {GL_INVALID_INDEX},
|
||||
vbo_ {GL_INVALID_INDEX}
|
||||
{
|
||||
}
|
||||
|
||||
~Impl() {}
|
||||
|
||||
std::shared_ptr<GlContext> context_;
|
||||
|
||||
const GeographicLib::Geodesic& geodesic_;
|
||||
|
||||
bool dirty_;
|
||||
|
||||
bool visible_;
|
||||
std::array<common::Coordinate, 2> points_;
|
||||
float angle_;
|
||||
float width_;
|
||||
|
||||
std::optional<boost::gil::rgba8_pixel_t> modulateColor_;
|
||||
|
||||
std::shared_ptr<ShaderProgram> shaderProgram_;
|
||||
GLint uMVPMatrixLocation_;
|
||||
GLint uMapMatrixLocation_;
|
||||
GLint uMapScreenCoordLocation_;
|
||||
|
||||
util::TextureAttributes texture_;
|
||||
|
||||
GLuint vao_;
|
||||
GLuint vbo_;
|
||||
|
||||
void Update();
|
||||
};
|
||||
|
||||
GeoLine::GeoLine(std::shared_ptr<GlContext> context) :
|
||||
DrawItem(context->gl()), p(std::make_unique<Impl>(context))
|
||||
{
|
||||
}
|
||||
GeoLine::~GeoLine() = default;
|
||||
|
||||
GeoLine::GeoLine(GeoLine&&) noexcept = default;
|
||||
GeoLine& GeoLine::operator=(GeoLine&&) noexcept = default;
|
||||
|
||||
void GeoLine::Initialize()
|
||||
{
|
||||
gl::OpenGLFunctions& gl = p->context_->gl();
|
||||
|
||||
p->shaderProgram_ = p->context_->GetShaderProgram(
|
||||
":/gl/geo_line.vert", ":/gl/texture2d_array.frag");
|
||||
|
||||
p->uMVPMatrixLocation_ =
|
||||
gl.glGetUniformLocation(p->shaderProgram_->id(), "uMVPMatrix");
|
||||
if (p->uMVPMatrixLocation_ == -1)
|
||||
{
|
||||
logger_->warn("Could not find uMVPMatrix");
|
||||
}
|
||||
|
||||
p->uMapMatrixLocation_ =
|
||||
gl.glGetUniformLocation(p->shaderProgram_->id(), "uMapMatrix");
|
||||
if (p->uMapMatrixLocation_ == -1)
|
||||
{
|
||||
logger_->warn("Could not find uMapMatrix");
|
||||
}
|
||||
|
||||
p->uMapScreenCoordLocation_ =
|
||||
gl.glGetUniformLocation(p->shaderProgram_->id(), "uMapScreenCoord");
|
||||
if (p->uMapScreenCoordLocation_ == -1)
|
||||
{
|
||||
logger_->warn("Could not find uMapScreenCoord");
|
||||
}
|
||||
|
||||
p->texture_ =
|
||||
util::TextureAtlas::Instance().GetTextureAttributes(kTextureName);
|
||||
|
||||
gl.glGenVertexArrays(1, &p->vao_);
|
||||
gl.glGenBuffers(1, &p->vbo_);
|
||||
|
||||
gl.glBindVertexArray(p->vao_);
|
||||
gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_);
|
||||
gl.glBufferData(
|
||||
GL_ARRAY_BUFFER, sizeof(float) * kBufferLength, nullptr, GL_DYNAMIC_DRAW);
|
||||
|
||||
// aLatLong
|
||||
gl.glVertexAttribPointer(0,
|
||||
2,
|
||||
GL_FLOAT,
|
||||
GL_FALSE,
|
||||
kPointsPerVertex * sizeof(float),
|
||||
static_cast<void*>(0));
|
||||
gl.glEnableVertexAttribArray(0);
|
||||
|
||||
// aXYOffset
|
||||
gl.glVertexAttribPointer(1,
|
||||
2,
|
||||
GL_FLOAT,
|
||||
GL_FALSE,
|
||||
kPointsPerVertex * sizeof(float),
|
||||
reinterpret_cast<void*>(2 * sizeof(float)));
|
||||
gl.glEnableVertexAttribArray(1);
|
||||
|
||||
// aTexCoord
|
||||
gl.glVertexAttribPointer(2,
|
||||
3,
|
||||
GL_FLOAT,
|
||||
GL_FALSE,
|
||||
kPointsPerVertex * sizeof(float),
|
||||
reinterpret_cast<void*>(4 * sizeof(float)));
|
||||
gl.glEnableVertexAttribArray(2);
|
||||
|
||||
// aModulate
|
||||
gl.glVertexAttribPointer(3,
|
||||
4,
|
||||
GL_FLOAT,
|
||||
GL_FALSE,
|
||||
kPointsPerVertex * sizeof(float),
|
||||
reinterpret_cast<void*>(7 * sizeof(float)));
|
||||
gl.glEnableVertexAttribArray(3);
|
||||
|
||||
p->dirty_ = true;
|
||||
}
|
||||
|
||||
void GeoLine::Render(const QMapLibreGL::CustomLayerRenderParameters& params)
|
||||
{
|
||||
if (p->visible_)
|
||||
{
|
||||
gl::OpenGLFunctions& gl = p->context_->gl();
|
||||
|
||||
gl.glBindVertexArray(p->vao_);
|
||||
gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_);
|
||||
|
||||
p->Update();
|
||||
p->shaderProgram_->Use();
|
||||
UseDefaultProjection(params, p->uMVPMatrixLocation_);
|
||||
UseMapProjection(
|
||||
params, p->uMapMatrixLocation_, p->uMapScreenCoordLocation_);
|
||||
|
||||
// Draw line
|
||||
gl.glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
}
|
||||
}
|
||||
|
||||
void GeoLine::Deinitialize()
|
||||
{
|
||||
gl::OpenGLFunctions& gl = p->context_->gl();
|
||||
|
||||
gl.glDeleteVertexArrays(1, &p->vao_);
|
||||
gl.glDeleteBuffers(1, &p->vbo_);
|
||||
}
|
||||
|
||||
void GeoLine::SetPoints(float latitude1,
|
||||
float longitude1,
|
||||
float latitude2,
|
||||
float longitude2)
|
||||
{
|
||||
if (p->points_[0].latitude_ != latitude1 ||
|
||||
p->points_[0].longitude_ != longitude1 ||
|
||||
p->points_[1].latitude_ != latitude2 ||
|
||||
p->points_[1].longitude_ != longitude2)
|
||||
{
|
||||
p->points_[0] = {latitude1, longitude1};
|
||||
p->points_[1] = {latitude2, longitude2};
|
||||
|
||||
double azi1; // Azimuth at point 1 (degrees)
|
||||
double azi2; // (Forward) azimuth at point 2 (degrees)
|
||||
p->geodesic_.Inverse(p->points_[0].latitude_,
|
||||
p->points_[0].longitude_,
|
||||
p->points_[1].latitude_,
|
||||
p->points_[1].longitude_,
|
||||
azi1,
|
||||
azi2);
|
||||
p->angle_ = -azi1 * std::numbers::pi / 180.0;
|
||||
|
||||
p->dirty_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void GeoLine::SetModulateColor(boost::gil::rgba8_pixel_t color)
|
||||
{
|
||||
if (p->modulateColor_ != color)
|
||||
{
|
||||
p->modulateColor_ = color;
|
||||
p->dirty_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void GeoLine::SetWidth(float width)
|
||||
{
|
||||
if (p->width_ != width)
|
||||
{
|
||||
p->width_ = width;
|
||||
p->dirty_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void GeoLine::SetVisible(bool visible)
|
||||
{
|
||||
p->visible_ = visible;
|
||||
}
|
||||
|
||||
void GeoLine::Impl::Update()
|
||||
{
|
||||
if (dirty_)
|
||||
{
|
||||
gl::OpenGLFunctions& gl = context_->gl();
|
||||
|
||||
texture_ =
|
||||
util::TextureAtlas::Instance().GetTextureAttributes(kTextureName);
|
||||
|
||||
// Latitude and longitude coordinates in degrees
|
||||
const float lx = points_[0].latitude_;
|
||||
const float rx = points_[1].latitude_;
|
||||
const float by = points_[0].longitude_;
|
||||
const float ty = points_[1].longitude_;
|
||||
|
||||
// Offset x/y in pixels
|
||||
const float ox = width_ * 0.5f * cosf(angle_);
|
||||
const float oy = width_ * 0.5f * sinf(angle_);
|
||||
|
||||
// Texture coordinates
|
||||
static constexpr float r = 0.0f;
|
||||
|
||||
const float ls = texture_.sLeft_;
|
||||
const float rs = texture_.sRight_;
|
||||
const float tt = texture_.tTop_;
|
||||
const float bt = texture_.tBottom_;
|
||||
|
||||
float mc0 = 1.0f;
|
||||
float mc1 = 1.0f;
|
||||
float mc2 = 1.0f;
|
||||
float mc3 = 1.0f;
|
||||
|
||||
if (modulateColor_.has_value())
|
||||
{
|
||||
boost::gil::rgba8_pixel_t& mc = modulateColor_.value();
|
||||
|
||||
mc0 = mc[0] / 255.0f;
|
||||
mc1 = mc[1] / 255.0f;
|
||||
mc2 = mc[2] / 255.0f;
|
||||
mc3 = mc[3] / 255.0f;
|
||||
}
|
||||
|
||||
const float buffer[kNumRectangles][kVerticesPerRectangle]
|
||||
[kPointsPerVertex] = //
|
||||
{ //
|
||||
// Line
|
||||
{
|
||||
{lx, by, -ox, -oy, ls, bt, r, mc0, mc1, mc2, mc3}, // BL
|
||||
{lx, by, +ox, +oy, ls, tt, r, mc0, mc1, mc2, mc3}, // TL
|
||||
{rx, ty, -ox, -oy, rs, bt, r, mc0, mc1, mc2, mc3}, // BR
|
||||
{rx, ty, -ox, -oy, rs, bt, r, mc0, mc1, mc2, mc3}, // BR
|
||||
{rx, ty, +ox, +oy, rs, tt, r, mc0, mc1, mc2, mc3}, // TR
|
||||
{lx, by, +ox, +oy, ls, tt, r, mc0, mc1, mc2, mc3} // TL
|
||||
}};
|
||||
|
||||
gl.glBufferData(GL_ARRAY_BUFFER,
|
||||
sizeof(float) * kBufferLength,
|
||||
buffer,
|
||||
GL_DYNAMIC_DRAW);
|
||||
|
||||
dirty_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace draw
|
||||
} // namespace gl
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/qt/gl/gl_context.hpp>
|
||||
#include <scwx/qt/gl/draw/draw_item.hpp>
|
||||
|
||||
#include <boost/gil.hpp>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace gl
|
||||
{
|
||||
namespace draw
|
||||
{
|
||||
|
||||
class GeoLine : public DrawItem
|
||||
{
|
||||
public:
|
||||
explicit GeoLine(std::shared_ptr<GlContext> context);
|
||||
~GeoLine();
|
||||
|
||||
GeoLine(const GeoLine&) = delete;
|
||||
GeoLine& operator=(const GeoLine&) = delete;
|
||||
|
||||
GeoLine(GeoLine&&) noexcept;
|
||||
GeoLine& operator=(GeoLine&&) noexcept;
|
||||
|
||||
void Initialize() override;
|
||||
void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override;
|
||||
void Deinitialize() override;
|
||||
|
||||
/**
|
||||
* Sets the geographic coordinate endpoints associated with the line.
|
||||
*
|
||||
* @param latitude1 Latitude of the first endpoint in degrees
|
||||
* @param longitude1 Longitude of the first endpoint in degrees
|
||||
* @param latitude2 Latitude of the second endpoint in degrees
|
||||
* @param longitude2 Longitude of the second endpoint in degrees
|
||||
*/
|
||||
void SetPoints(float latitude1,
|
||||
float longitude1,
|
||||
float latitude2,
|
||||
float longitude2);
|
||||
|
||||
/**
|
||||
* Sets the modulate color of the line. If specified, the texture color will
|
||||
* be multiplied by the modulate color to produce the result.
|
||||
*
|
||||
* @param color Modulate color (RGBA)
|
||||
*/
|
||||
void SetModulateColor(boost::gil::rgba8_pixel_t color);
|
||||
|
||||
/**
|
||||
* Sets the width of the line.
|
||||
*
|
||||
* @param width Width in pixels
|
||||
*/
|
||||
void SetWidth(float width);
|
||||
|
||||
/**
|
||||
* Sets the visibility of the line.
|
||||
*
|
||||
* @param visible
|
||||
*/
|
||||
void SetVisible(bool visible);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
|
||||
std::unique_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace draw
|
||||
} // namespace gl
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
620
scwx-qt/source/scwx/qt/gl/draw/geo_lines.cpp
Normal file
620
scwx-qt/source/scwx/qt/gl/draw/geo_lines.cpp
Normal file
|
|
@ -0,0 +1,620 @@
|
|||
#include <scwx/qt/gl/draw/geo_lines.hpp>
|
||||
#include <scwx/qt/util/geographic_lib.hpp>
|
||||
#include <scwx/qt/util/maplibre.hpp>
|
||||
#include <scwx/qt/util/tooltip.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
#include <execution>
|
||||
|
||||
#include <units/angle.h>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace gl
|
||||
{
|
||||
namespace draw
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ = "scwx::qt::gl::draw::geo_lines";
|
||||
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
|
||||
|
||||
static constexpr size_t kNumRectangles = 1;
|
||||
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 =
|
||||
kNumTriangles * kVerticesPerTriangle * kPointsPerVertex;
|
||||
|
||||
// Threshold, start time, end time
|
||||
static constexpr std::size_t kIntegersPerVertex_ = 3;
|
||||
|
||||
struct GeoLineDrawItem
|
||||
{
|
||||
bool visible_ {true};
|
||||
units::length::nautical_miles<double> threshold_ {};
|
||||
std::chrono::sys_time<std::chrono::seconds> startTime_ {};
|
||||
std::chrono::sys_time<std::chrono::seconds> endTime_ {};
|
||||
|
||||
boost::gil::rgba32f_pixel_t modulate_ {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
float latitude1_ {};
|
||||
float longitude1_ {};
|
||||
float latitude2_ {};
|
||||
float longitude2_ {};
|
||||
float width_ {5.0};
|
||||
units::angle::degrees<float> angle_ {};
|
||||
std::string hoverText_ {};
|
||||
};
|
||||
|
||||
class GeoLines::Impl
|
||||
{
|
||||
public:
|
||||
struct LineHoverEntry
|
||||
{
|
||||
std::shared_ptr<const GeoLineDrawItem> di_;
|
||||
|
||||
glm::vec2 p1_;
|
||||
glm::vec2 p2_;
|
||||
glm::vec2 otl_;
|
||||
glm::vec2 otr_;
|
||||
glm::vec2 obl_;
|
||||
glm::vec2 obr_;
|
||||
};
|
||||
|
||||
explicit Impl(std::shared_ptr<GlContext> context) :
|
||||
context_ {context},
|
||||
shaderProgram_ {nullptr},
|
||||
uMVPMatrixLocation_(GL_INVALID_INDEX),
|
||||
uMapMatrixLocation_(GL_INVALID_INDEX),
|
||||
uMapScreenCoordLocation_(GL_INVALID_INDEX),
|
||||
uMapDistanceLocation_(GL_INVALID_INDEX),
|
||||
uSelectedTimeLocation_(GL_INVALID_INDEX),
|
||||
vao_ {GL_INVALID_INDEX},
|
||||
vbo_ {GL_INVALID_INDEX}
|
||||
{
|
||||
}
|
||||
|
||||
~Impl() {}
|
||||
|
||||
void BufferLine(const std::shared_ptr<const GeoLineDrawItem>& di);
|
||||
void Update();
|
||||
void UpdateBuffers();
|
||||
|
||||
std::shared_ptr<GlContext> context_;
|
||||
|
||||
bool visible_ {true};
|
||||
bool dirty_ {false};
|
||||
bool thresholded_ {false};
|
||||
|
||||
std::chrono::system_clock::time_point selectedTime_ {};
|
||||
|
||||
std::mutex lineMutex_ {};
|
||||
|
||||
std::vector<std::shared_ptr<GeoLineDrawItem>> currentLineList_ {};
|
||||
std::vector<std::shared_ptr<GeoLineDrawItem>> newLineList_ {};
|
||||
|
||||
std::vector<float> currentLinesBuffer_ {};
|
||||
std::vector<GLint> currentIntegerBuffer_ {};
|
||||
std::vector<float> newLinesBuffer_ {};
|
||||
std::vector<GLint> newIntegerBuffer_ {};
|
||||
|
||||
std::vector<LineHoverEntry> currentHoverLines_ {};
|
||||
std::vector<LineHoverEntry> newHoverLines_ {};
|
||||
|
||||
std::shared_ptr<ShaderProgram> shaderProgram_;
|
||||
GLint uMVPMatrixLocation_;
|
||||
GLint uMapMatrixLocation_;
|
||||
GLint uMapScreenCoordLocation_;
|
||||
GLint uMapDistanceLocation_;
|
||||
GLint uSelectedTimeLocation_;
|
||||
|
||||
GLuint vao_;
|
||||
std::array<GLuint, 2> vbo_;
|
||||
};
|
||||
|
||||
GeoLines::GeoLines(std::shared_ptr<GlContext> context) :
|
||||
DrawItem(context->gl()), p(std::make_unique<Impl>(context))
|
||||
{
|
||||
}
|
||||
GeoLines::~GeoLines() = default;
|
||||
|
||||
GeoLines::GeoLines(GeoLines&&) noexcept = default;
|
||||
GeoLines& GeoLines::operator=(GeoLines&&) noexcept = default;
|
||||
|
||||
void GeoLines::set_selected_time(
|
||||
std::chrono::system_clock::time_point selectedTime)
|
||||
{
|
||||
p->selectedTime_ = selectedTime;
|
||||
}
|
||||
|
||||
void GeoLines::set_thresholded(bool thresholded)
|
||||
{
|
||||
p->thresholded_ = thresholded;
|
||||
}
|
||||
|
||||
void GeoLines::Initialize()
|
||||
{
|
||||
gl::OpenGLFunctions& gl = p->context_->gl();
|
||||
|
||||
p->shaderProgram_ = p->context_->GetShaderProgram(
|
||||
{{GL_VERTEX_SHADER, ":/gl/geo_texture2d.vert"},
|
||||
{GL_GEOMETRY_SHADER, ":/gl/threshold.geom"},
|
||||
{GL_FRAGMENT_SHADER, ":/gl/color.frag"}});
|
||||
|
||||
p->uMVPMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMVPMatrix");
|
||||
p->uMapMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMapMatrix");
|
||||
p->uMapScreenCoordLocation_ =
|
||||
p->shaderProgram_->GetUniformLocation("uMapScreenCoord");
|
||||
p->uMapDistanceLocation_ =
|
||||
p->shaderProgram_->GetUniformLocation("uMapDistance");
|
||||
p->uSelectedTimeLocation_ =
|
||||
p->shaderProgram_->GetUniformLocation("uSelectedTime");
|
||||
|
||||
gl.glGenVertexArrays(1, &p->vao_);
|
||||
gl.glGenBuffers(static_cast<GLsizei>(p->vbo_.size()), p->vbo_.data());
|
||||
|
||||
gl.glBindVertexArray(p->vao_);
|
||||
gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]);
|
||||
gl.glBufferData(
|
||||
GL_ARRAY_BUFFER, sizeof(float) * kBufferLength, nullptr, GL_DYNAMIC_DRAW);
|
||||
|
||||
// aLatLong
|
||||
gl.glVertexAttribPointer(0,
|
||||
2,
|
||||
GL_FLOAT,
|
||||
GL_FALSE,
|
||||
kPointsPerVertex * sizeof(float),
|
||||
static_cast<void*>(0));
|
||||
gl.glEnableVertexAttribArray(0);
|
||||
|
||||
// aXYOffset
|
||||
gl.glVertexAttribPointer(1,
|
||||
2,
|
||||
GL_FLOAT,
|
||||
GL_FALSE,
|
||||
kPointsPerVertex * sizeof(float),
|
||||
reinterpret_cast<void*>(2 * sizeof(float)));
|
||||
gl.glEnableVertexAttribArray(1);
|
||||
|
||||
// aModulate
|
||||
gl.glVertexAttribPointer(3,
|
||||
4,
|
||||
GL_FLOAT,
|
||||
GL_FALSE,
|
||||
kPointsPerVertex * sizeof(float),
|
||||
reinterpret_cast<void*>(4 * sizeof(float)));
|
||||
gl.glEnableVertexAttribArray(3);
|
||||
|
||||
// aAngle
|
||||
gl.glVertexAttribPointer(4,
|
||||
1,
|
||||
GL_FLOAT,
|
||||
GL_FALSE,
|
||||
kPointsPerVertex * sizeof(float),
|
||||
reinterpret_cast<void*>(8 * sizeof(float)));
|
||||
gl.glEnableVertexAttribArray(4);
|
||||
|
||||
gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]);
|
||||
gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW);
|
||||
|
||||
// aThreshold
|
||||
gl.glVertexAttribIPointer(5, //
|
||||
1,
|
||||
GL_INT,
|
||||
kIntegersPerVertex_ * sizeof(GLint),
|
||||
static_cast<void*>(0));
|
||||
gl.glEnableVertexAttribArray(5);
|
||||
|
||||
// aTimeRange
|
||||
gl.glVertexAttribIPointer(6, //
|
||||
2,
|
||||
GL_INT,
|
||||
kIntegersPerVertex_ * sizeof(GLint),
|
||||
reinterpret_cast<void*>(1 * sizeof(GLint)));
|
||||
gl.glEnableVertexAttribArray(6);
|
||||
|
||||
p->dirty_ = true;
|
||||
}
|
||||
|
||||
void GeoLines::Render(const QMapLibreGL::CustomLayerRenderParameters& params)
|
||||
{
|
||||
if (!p->visible_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock lock {p->lineMutex_};
|
||||
|
||||
if (p->currentLineList_.size() > 0)
|
||||
{
|
||||
gl::OpenGLFunctions& gl = p->context_->gl();
|
||||
|
||||
gl.glBindVertexArray(p->vao_);
|
||||
|
||||
p->Update();
|
||||
p->shaderProgram_->Use();
|
||||
UseRotationProjection(params, p->uMVPMatrixLocation_);
|
||||
UseMapProjection(
|
||||
params, p->uMapMatrixLocation_, p->uMapScreenCoordLocation_);
|
||||
|
||||
if (p->thresholded_)
|
||||
{
|
||||
// If thresholding is enabled, set the map distance
|
||||
units::length::nautical_miles<float> mapDistance =
|
||||
util::maplibre::GetMapDistance(params);
|
||||
gl.glUniform1f(p->uMapDistanceLocation_, mapDistance.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
// If thresholding is disabled, set the map distance to 0
|
||||
gl.glUniform1f(p->uMapDistanceLocation_, 0.0f);
|
||||
}
|
||||
|
||||
// Selected time
|
||||
std::chrono::system_clock::time_point selectedTime =
|
||||
(p->selectedTime_ == std::chrono::system_clock::time_point {}) ?
|
||||
std::chrono::system_clock::now() :
|
||||
p->selectedTime_;
|
||||
gl.glUniform1i(
|
||||
p->uSelectedTimeLocation_,
|
||||
static_cast<GLint>(std::chrono::duration_cast<std::chrono::minutes>(
|
||||
selectedTime.time_since_epoch())
|
||||
.count()));
|
||||
|
||||
// Draw icons
|
||||
gl.glDrawArrays(GL_TRIANGLES,
|
||||
0,
|
||||
static_cast<GLsizei>(p->currentLineList_.size() *
|
||||
kVerticesPerRectangle));
|
||||
}
|
||||
}
|
||||
|
||||
void GeoLines::Deinitialize()
|
||||
{
|
||||
gl::OpenGLFunctions& gl = p->context_->gl();
|
||||
|
||||
gl.glDeleteVertexArrays(1, &p->vao_);
|
||||
gl.glDeleteBuffers(2, p->vbo_.data());
|
||||
|
||||
std::unique_lock lock {p->lineMutex_};
|
||||
|
||||
p->currentLinesBuffer_.clear();
|
||||
p->currentIntegerBuffer_.clear();
|
||||
p->currentHoverLines_.clear();
|
||||
}
|
||||
|
||||
void GeoLines::SetVisible(bool visible)
|
||||
{
|
||||
p->visible_ = visible;
|
||||
}
|
||||
|
||||
void GeoLines::StartLines()
|
||||
{
|
||||
// Clear the new buffers
|
||||
p->newLineList_.clear();
|
||||
p->newLinesBuffer_.clear();
|
||||
p->newIntegerBuffer_.clear();
|
||||
p->newHoverLines_.clear();
|
||||
}
|
||||
|
||||
std::shared_ptr<GeoLineDrawItem> GeoLines::AddLine()
|
||||
{
|
||||
return p->newLineList_.emplace_back(std::make_shared<GeoLineDrawItem>());
|
||||
}
|
||||
|
||||
void GeoLines::SetLineLocation(const std::shared_ptr<GeoLineDrawItem>& di,
|
||||
float latitude1,
|
||||
float longitude1,
|
||||
float latitude2,
|
||||
float longitude2)
|
||||
{
|
||||
di->latitude1_ = latitude1;
|
||||
di->longitude1_ = longitude1;
|
||||
di->latitude2_ = latitude2;
|
||||
di->longitude2_ = longitude2;
|
||||
}
|
||||
|
||||
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};
|
||||
}
|
||||
|
||||
void GeoLines::SetLineModulate(const std::shared_ptr<GeoLineDrawItem>& di,
|
||||
boost::gil::rgba32f_pixel_t modulate)
|
||||
{
|
||||
di->modulate_ = modulate;
|
||||
}
|
||||
|
||||
void GeoLines::SetLineWidth(const std::shared_ptr<GeoLineDrawItem>& di,
|
||||
float width)
|
||||
{
|
||||
di->width_ = width;
|
||||
}
|
||||
|
||||
void GeoLines::SetLineVisible(const std::shared_ptr<GeoLineDrawItem>& di,
|
||||
bool visible)
|
||||
{
|
||||
di->visible_ = visible;
|
||||
}
|
||||
|
||||
void GeoLines::SetLineHoverText(const std::shared_ptr<GeoLineDrawItem>& di,
|
||||
const std::string& text)
|
||||
{
|
||||
di->hoverText_ = text;
|
||||
}
|
||||
|
||||
void GeoLines::FinishLines()
|
||||
{
|
||||
// Update buffers
|
||||
p->UpdateBuffers();
|
||||
|
||||
std::unique_lock lock {p->lineMutex_};
|
||||
|
||||
// Swap buffers
|
||||
p->currentLineList_.swap(p->newLineList_);
|
||||
p->currentLinesBuffer_.swap(p->newLinesBuffer_);
|
||||
p->currentIntegerBuffer_.swap(p->newIntegerBuffer_);
|
||||
p->currentHoverLines_.swap(p->newHoverLines_);
|
||||
|
||||
// Clear the new buffers, except the full line list (used to update buffers
|
||||
// without re-adding lines)
|
||||
p->newLinesBuffer_.clear();
|
||||
p->newIntegerBuffer_.clear();
|
||||
p->newHoverLines_.clear();
|
||||
|
||||
// Mark the draw item dirty
|
||||
p->dirty_ = true;
|
||||
}
|
||||
|
||||
void GeoLines::Impl::UpdateBuffers()
|
||||
{
|
||||
newLinesBuffer_.clear();
|
||||
newLinesBuffer_.reserve(newLineList_.size() * kBufferLength);
|
||||
newIntegerBuffer_.clear();
|
||||
newIntegerBuffer_.reserve(newLineList_.size() * kVerticesPerRectangle *
|
||||
kIntegersPerVertex_);
|
||||
newHoverLines_.clear();
|
||||
|
||||
for (auto& di : newLineList_)
|
||||
{
|
||||
BufferLine(di);
|
||||
}
|
||||
}
|
||||
|
||||
void GeoLines::Impl::BufferLine(
|
||||
const std::shared_ptr<const GeoLineDrawItem>& di)
|
||||
{
|
||||
// Threshold value
|
||||
units::length::nautical_miles<double> threshold = di->threshold_;
|
||||
GLint thresholdValue = static_cast<GLint>(std::round(threshold.value()));
|
||||
|
||||
// Start and end time
|
||||
GLint startTime =
|
||||
static_cast<GLint>(std::chrono::duration_cast<std::chrono::minutes>(
|
||||
di->startTime_.time_since_epoch())
|
||||
.count());
|
||||
GLint endTime =
|
||||
static_cast<GLint>(std::chrono::duration_cast<std::chrono::minutes>(
|
||||
di->endTime_.time_since_epoch())
|
||||
.count());
|
||||
|
||||
// Latitude and longitude coordinates in degrees
|
||||
const float lat1 = di->latitude1_;
|
||||
const float lon1 = di->longitude1_;
|
||||
const float lat2 = di->latitude2_;
|
||||
const float lon2 = di->longitude2_;
|
||||
|
||||
// TODO: Base X/Y offsets in pixels
|
||||
// const float x1 = static_cast<float>(di->x1_);
|
||||
// const float y1 = static_cast<float>(di->y1_);
|
||||
// const float x2 = static_cast<float>(di->x2_);
|
||||
// const float y2 = static_cast<float>(di->y2_);
|
||||
|
||||
// Angle
|
||||
const units::angle::degrees<double> angle =
|
||||
util::GeographicLib::GetAngle(lat1, lon1, lat2, lon2);
|
||||
const float a = static_cast<float>(angle.value());
|
||||
|
||||
// Final X/Y offsets in pixels
|
||||
const float hw = di->width_ * 0.5f;
|
||||
const float lx = -hw;
|
||||
const float rx = +hw;
|
||||
const float ty = +hw;
|
||||
const float by = -hw;
|
||||
|
||||
// Modulate color
|
||||
const float mc0 = di->modulate_[0];
|
||||
const float mc1 = di->modulate_[1];
|
||||
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});
|
||||
|
||||
if (!di->hoverText_.empty())
|
||||
{
|
||||
const units::angle::radians<double> radians = angle;
|
||||
|
||||
const auto sc1 = util::maplibre::LatLongToScreenCoordinate({lat1, lon1});
|
||||
const auto sc2 = util::maplibre::LatLongToScreenCoordinate({lat2, lon2});
|
||||
|
||||
const float cosAngle = cosf(static_cast<float>(radians.value()));
|
||||
const float sinAngle = sinf(static_cast<float>(radians.value()));
|
||||
|
||||
const glm::mat2 rotate {cosAngle, -sinAngle, sinAngle, cosAngle};
|
||||
|
||||
const glm::vec2 otl = rotate * glm::vec2 {-hw, +hw};
|
||||
const glm::vec2 otr = rotate * glm::vec2 {+hw, +hw};
|
||||
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});
|
||||
}
|
||||
}
|
||||
|
||||
void GeoLines::Impl::Update()
|
||||
{
|
||||
// If the lines have been updated
|
||||
if (dirty_)
|
||||
{
|
||||
gl::OpenGLFunctions& gl = context_->gl();
|
||||
|
||||
// Buffer lines data
|
||||
gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]);
|
||||
gl.glBufferData(GL_ARRAY_BUFFER,
|
||||
sizeof(float) * currentLinesBuffer_.size(),
|
||||
currentLinesBuffer_.data(),
|
||||
GL_DYNAMIC_DRAW);
|
||||
|
||||
// Buffer threshold data
|
||||
gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]);
|
||||
gl.glBufferData(GL_ARRAY_BUFFER,
|
||||
sizeof(GLint) * currentIntegerBuffer_.size(),
|
||||
currentIntegerBuffer_.data(),
|
||||
GL_DYNAMIC_DRAW);
|
||||
}
|
||||
|
||||
dirty_ = false;
|
||||
}
|
||||
|
||||
bool GeoLines::RunMousePicking(
|
||||
const QMapLibreGL::CustomLayerRenderParameters& params,
|
||||
const QPointF& /* mouseLocalPos */,
|
||||
const QPointF& mouseGlobalPos,
|
||||
const glm::vec2& mouseCoords,
|
||||
const common::Coordinate& /* mouseGeoCoords */,
|
||||
std::shared_ptr<types::EventHandler>& /* eventHandler */)
|
||||
{
|
||||
std::unique_lock lock {p->lineMutex_};
|
||||
|
||||
bool itemPicked = false;
|
||||
|
||||
// Calculate map scale, remove width and height from original calculation
|
||||
glm::vec2 scale = util::maplibre::GetMapScale(params);
|
||||
scale = 2.0f / glm::vec2 {scale.x * params.width, scale.y * params.height};
|
||||
|
||||
// Scale and rotate the identity matrix to create the map matrix
|
||||
glm::mat4 mapMatrix {1.0f};
|
||||
mapMatrix = glm::scale(mapMatrix, glm::vec3 {scale, 1.0f});
|
||||
mapMatrix = glm::rotate(mapMatrix,
|
||||
glm::radians<float>(params.bearing),
|
||||
glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
|
||||
units::length::meters<double> mapDistance =
|
||||
(p->thresholded_) ? util::maplibre::GetMapDistance(params) :
|
||||
units::length::meters<double> {0.0};
|
||||
|
||||
// If no time has been selected, use the current time
|
||||
std::chrono::system_clock::time_point selectedTime =
|
||||
(p->selectedTime_ == std::chrono::system_clock::time_point {}) ?
|
||||
std::chrono::system_clock::now() :
|
||||
p->selectedTime_;
|
||||
|
||||
// For each pickable line
|
||||
auto it = std::find_if(
|
||||
std::execution::par_unseq,
|
||||
p->currentHoverLines_.crbegin(),
|
||||
p->currentHoverLines_.crend(),
|
||||
[&mapDistance, &selectedTime, &mapMatrix, &mouseCoords](const auto& line)
|
||||
{
|
||||
if ((
|
||||
// Placefile is thresholded
|
||||
mapDistance > units::length::meters<double> {0.0} &&
|
||||
|
||||
// Placefile threshold is < 999 nmi
|
||||
static_cast<int>(std::round(
|
||||
units::length::nautical_miles<double> {line.di_->threshold_}
|
||||
.value())) < 999 &&
|
||||
|
||||
// Map distance is beyond the threshold
|
||||
line.di_->threshold_ < mapDistance) ||
|
||||
|
||||
(
|
||||
// Line has a start time
|
||||
line.di_->startTime_ !=
|
||||
std::chrono::system_clock::time_point {} &&
|
||||
|
||||
// The time range has not yet started
|
||||
(selectedTime < line.di_->startTime_ ||
|
||||
|
||||
// The time range has ended
|
||||
line.di_->endTime_ <= selectedTime)))
|
||||
{
|
||||
// Line is not pickable
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize vertices
|
||||
glm::vec2 bl = line.p1_;
|
||||
glm::vec2 br = bl;
|
||||
glm::vec2 tl = line.p2_;
|
||||
glm::vec2 tr = tl;
|
||||
|
||||
// Calculate offsets
|
||||
// - Rotated offset is half the line width (pixels) in each direction
|
||||
// - Multiply the offset by the scaled and rotated map matrix
|
||||
const glm::vec2 otl = mapMatrix * glm::vec4 {line.otl_, 0.0f, 1.0f};
|
||||
const glm::vec2 obl = mapMatrix * glm::vec4 {line.obl_, 0.0f, 1.0f};
|
||||
const glm::vec2 obr = mapMatrix * glm::vec4 {line.obr_, 0.0f, 1.0f};
|
||||
const glm::vec2 otr = mapMatrix * glm::vec4 {line.otr_, 0.0f, 1.0f};
|
||||
|
||||
// Offset vertices
|
||||
tl += otl;
|
||||
bl += obl;
|
||||
br += obr;
|
||||
tr += otr;
|
||||
|
||||
// TODO: X/Y offsets
|
||||
|
||||
// Test point against polygon bounds
|
||||
return util::maplibre::IsPointInPolygon({tl, bl, br, tr}, mouseCoords);
|
||||
});
|
||||
|
||||
if (it != p->currentHoverLines_.crend())
|
||||
{
|
||||
itemPicked = true;
|
||||
util::tooltip::Show(it->di_->hoverText_, mouseGlobalPos);
|
||||
}
|
||||
|
||||
return itemPicked;
|
||||
}
|
||||
|
||||
} // namespace draw
|
||||
} // namespace gl
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
140
scwx-qt/source/scwx/qt/gl/draw/geo_lines.hpp
Normal file
140
scwx-qt/source/scwx/qt/gl/draw/geo_lines.hpp
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/qt/gl/gl_context.hpp>
|
||||
#include <scwx/qt/gl/draw/draw_item.hpp>
|
||||
|
||||
#include <boost/gil.hpp>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace gl
|
||||
{
|
||||
namespace draw
|
||||
{
|
||||
|
||||
struct GeoLineDrawItem;
|
||||
|
||||
class GeoLines : public DrawItem
|
||||
{
|
||||
public:
|
||||
explicit GeoLines(std::shared_ptr<GlContext> context);
|
||||
~GeoLines();
|
||||
|
||||
GeoLines(const GeoLines&) = delete;
|
||||
GeoLines& operator=(const GeoLines&) = delete;
|
||||
|
||||
GeoLines(GeoLines&&) noexcept;
|
||||
GeoLines& operator=(GeoLines&&) noexcept;
|
||||
|
||||
void set_selected_time(std::chrono::system_clock::time_point selectedTime);
|
||||
void set_thresholded(bool thresholded);
|
||||
|
||||
void Initialize() override;
|
||||
void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override;
|
||||
void Deinitialize() override;
|
||||
|
||||
bool
|
||||
RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params,
|
||||
const QPointF& mouseLocalPos,
|
||||
const QPointF& mouseGlobalPos,
|
||||
const glm::vec2& mouseCoords,
|
||||
const common::Coordinate& mouseGeoCoords,
|
||||
std::shared_ptr<types::EventHandler>& eventHandler) override;
|
||||
|
||||
/**
|
||||
* Sets the visibility of the geo lines.
|
||||
*
|
||||
* @param [in] visible Line visibility
|
||||
*/
|
||||
void SetVisible(bool visible);
|
||||
|
||||
/**
|
||||
* Resets and prepares the draw item for adding a new set of lines.
|
||||
*/
|
||||
void StartLines();
|
||||
|
||||
/**
|
||||
* Adds a geo line to the internal draw list.
|
||||
*
|
||||
* @return Geo line draw item
|
||||
*/
|
||||
std::shared_ptr<GeoLineDrawItem> AddLine();
|
||||
|
||||
/**
|
||||
* Sets the location of a geo line.
|
||||
*
|
||||
* @param [in] di Geo line draw item
|
||||
* @param [in] latitude1 The latitude of the first endpoint of the geo line
|
||||
* in degrees.
|
||||
* @param [in] longitude1 The longitude of the first endpoint of the geo line
|
||||
* in degrees.
|
||||
* @param [in] latitude2 The latitude of the second endpoint of the geo line
|
||||
* in degrees.
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* Sets the modulate color of a geo line.
|
||||
*
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* Sets the modulate color of a geo line.
|
||||
*
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* Sets the width of the geo line.
|
||||
*
|
||||
* @param [in] width Width in pixels
|
||||
*/
|
||||
static 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);
|
||||
|
||||
/**
|
||||
* Sets the hover text of a geo line.
|
||||
*
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* Finalizes the draw item after adding new lines.
|
||||
*/
|
||||
void FinishLines();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace draw
|
||||
} // namespace gl
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
358
scwx-qt/source/scwx/qt/gl/draw/linked_vectors.cpp
Normal file
358
scwx-qt/source/scwx/qt/gl/draw/linked_vectors.cpp
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
#include <scwx/qt/gl/draw/linked_vectors.hpp>
|
||||
#include <scwx/qt/gl/draw/geo_lines.hpp>
|
||||
#include <scwx/qt/util/geographic_lib.hpp>
|
||||
#include <scwx/wsr88d/rpg/linked_vector_packet.hpp>
|
||||
|
||||
#include <boost/iterator/zip_iterator.hpp>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace gl
|
||||
{
|
||||
namespace draw
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ = "scwx::qt::gl::draw::linked_vectors";
|
||||
|
||||
static const boost::gil::rgba32f_pixel_t kBlack {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
|
||||
struct LinkedVectorDrawItem
|
||||
{
|
||||
LinkedVectorDrawItem(
|
||||
const common::Coordinate& center,
|
||||
const std::shared_ptr<const wsr88d::rpg::LinkedVectorPacket>&
|
||||
vectorPacket)
|
||||
{
|
||||
coordinates_.push_back(util::GeographicLib::GetCoordinate(
|
||||
center, vectorPacket->start_i_km(), vectorPacket->start_j_km()));
|
||||
|
||||
const auto endI = vectorPacket->end_i_km();
|
||||
const auto endJ = vectorPacket->end_j_km();
|
||||
|
||||
std::for_each(
|
||||
boost::make_zip_iterator(
|
||||
boost::make_tuple(endI.begin(), endJ.begin())),
|
||||
boost::make_zip_iterator(boost::make_tuple(endI.end(), endJ.end())),
|
||||
[this,
|
||||
¢er](const boost::tuple<units::length::kilometers<double>,
|
||||
units::length::kilometers<double>>& p)
|
||||
{
|
||||
coordinates_.push_back(util::GeographicLib::GetCoordinate(
|
||||
center, p.get<0>(), p.get<1>()));
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<GeoLineDrawItem>> borderDrawItems_ {};
|
||||
std::vector<std::shared_ptr<GeoLineDrawItem>> lineDrawItems_ {};
|
||||
|
||||
std::vector<common::Coordinate> coordinates_ {};
|
||||
|
||||
boost::gil::rgba32f_pixel_t modulate_ {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
float width_ {5.0f};
|
||||
bool visible_ {true};
|
||||
std::string hoverText_ {};
|
||||
|
||||
bool ticksEnabled_ {false};
|
||||
units::length::nautical_miles<double> tickRadius_ {1.0};
|
||||
units::length::nautical_miles<double> tickRadiusIncrement_ {0.0};
|
||||
};
|
||||
|
||||
class LinkedVectors::Impl
|
||||
{
|
||||
public:
|
||||
explicit Impl(std::shared_ptr<GlContext> context) :
|
||||
context_ {context}, geoLines_ {std::make_shared<GeoLines>(context)}
|
||||
{
|
||||
}
|
||||
|
||||
~Impl() {}
|
||||
|
||||
std::shared_ptr<GlContext> context_;
|
||||
|
||||
bool borderEnabled_ {true};
|
||||
bool visible_ {true};
|
||||
|
||||
std::vector<std::shared_ptr<LinkedVectorDrawItem>> vectorList_ {};
|
||||
std::shared_ptr<GeoLines> geoLines_;
|
||||
};
|
||||
|
||||
LinkedVectors::LinkedVectors(std::shared_ptr<GlContext> context) :
|
||||
DrawItem(context->gl()), p(std::make_unique<Impl>(context))
|
||||
{
|
||||
}
|
||||
LinkedVectors::~LinkedVectors() = default;
|
||||
|
||||
LinkedVectors::LinkedVectors(LinkedVectors&&) noexcept = default;
|
||||
LinkedVectors& LinkedVectors::operator=(LinkedVectors&&) noexcept = default;
|
||||
|
||||
void LinkedVectors::set_selected_time(
|
||||
std::chrono::system_clock::time_point selectedTime)
|
||||
{
|
||||
p->geoLines_->set_selected_time(selectedTime);
|
||||
}
|
||||
|
||||
void LinkedVectors::set_thresholded(bool thresholded)
|
||||
{
|
||||
p->geoLines_->set_thresholded(thresholded);
|
||||
}
|
||||
|
||||
void LinkedVectors::Initialize()
|
||||
{
|
||||
p->geoLines_->Initialize();
|
||||
}
|
||||
|
||||
void LinkedVectors::Render(
|
||||
const QMapLibreGL::CustomLayerRenderParameters& params)
|
||||
{
|
||||
if (!p->visible_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
p->geoLines_->Render(params);
|
||||
}
|
||||
|
||||
void LinkedVectors::Deinitialize()
|
||||
{
|
||||
p->geoLines_->Deinitialize();
|
||||
}
|
||||
|
||||
void LinkedVectors::SetBorderEnabled(bool enabled)
|
||||
{
|
||||
p->borderEnabled_ = enabled;
|
||||
}
|
||||
|
||||
void LinkedVectors::SetVisible(bool visible)
|
||||
{
|
||||
p->visible_ = visible;
|
||||
}
|
||||
|
||||
void LinkedVectors::StartVectors()
|
||||
{
|
||||
// Start a new set of geo lines
|
||||
p->geoLines_->StartLines();
|
||||
p->vectorList_.clear();
|
||||
}
|
||||
|
||||
std::shared_ptr<LinkedVectorDrawItem> LinkedVectors::AddVector(
|
||||
const common::Coordinate& center,
|
||||
const std::shared_ptr<const wsr88d::rpg::LinkedVectorPacket>& vectorPacket)
|
||||
{
|
||||
return p->vectorList_.emplace_back(
|
||||
std::make_shared<LinkedVectorDrawItem>(center, vectorPacket));
|
||||
}
|
||||
|
||||
void LinkedVectors::SetVectorModulate(
|
||||
const std::shared_ptr<LinkedVectorDrawItem>& 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};
|
||||
}
|
||||
|
||||
void LinkedVectors::SetVectorModulate(
|
||||
const std::shared_ptr<LinkedVectorDrawItem>& di,
|
||||
boost::gil::rgba32f_pixel_t modulate)
|
||||
{
|
||||
di->modulate_ = modulate;
|
||||
}
|
||||
|
||||
void LinkedVectors::SetVectorWidth(
|
||||
const std::shared_ptr<LinkedVectorDrawItem>& di, float width)
|
||||
{
|
||||
di->width_ = width;
|
||||
}
|
||||
|
||||
void LinkedVectors::SetVectorVisible(
|
||||
const std::shared_ptr<LinkedVectorDrawItem>& di, bool visible)
|
||||
{
|
||||
di->visible_ = visible;
|
||||
}
|
||||
|
||||
void LinkedVectors::SetVectorHoverText(
|
||||
const std::shared_ptr<LinkedVectorDrawItem>& di, const std::string& text)
|
||||
{
|
||||
di->hoverText_ = text;
|
||||
}
|
||||
|
||||
void LinkedVectors::SetVectorTicksEnabled(
|
||||
const std::shared_ptr<LinkedVectorDrawItem>& di, bool enabled)
|
||||
{
|
||||
di->ticksEnabled_ = enabled;
|
||||
}
|
||||
|
||||
void LinkedVectors::SetVectorTickRadius(
|
||||
const std::shared_ptr<LinkedVectorDrawItem>& di,
|
||||
units::length::meters<double> radius)
|
||||
{
|
||||
di->tickRadius_ = radius;
|
||||
}
|
||||
|
||||
void LinkedVectors::SetVectorTickRadiusIncrement(
|
||||
const std::shared_ptr<LinkedVectorDrawItem>& di,
|
||||
units::length::meters<double> radiusIncrement)
|
||||
{
|
||||
di->tickRadiusIncrement_ = radiusIncrement;
|
||||
}
|
||||
|
||||
void LinkedVectors::FinishVectors()
|
||||
{
|
||||
// Generate borders
|
||||
if (p->borderEnabled_)
|
||||
{
|
||||
for (auto& di : p->vectorList_)
|
||||
{
|
||||
auto tickRadius = di->tickRadius_;
|
||||
|
||||
for (std::size_t i = 0; i < di->coordinates_.size() - 1; ++i)
|
||||
{
|
||||
auto borderLine = p->geoLines_->AddLine();
|
||||
|
||||
const common::Coordinate& coordinate1 = di->coordinates_[i];
|
||||
const common::Coordinate& coordinate2 = di->coordinates_[i + 1];
|
||||
|
||||
const double& latitude1 = coordinate1.latitude_;
|
||||
const double& longitude1 = coordinate1.longitude_;
|
||||
const double& latitude2 = coordinate2.latitude_;
|
||||
const double& longitude2 = coordinate2.longitude_;
|
||||
|
||||
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_);
|
||||
|
||||
di->borderDrawItems_.emplace_back(std::move(borderLine));
|
||||
|
||||
if (di->ticksEnabled_)
|
||||
{
|
||||
auto angle = util::GeographicLib::GetAngle(
|
||||
latitude1, longitude1, latitude2, longitude2);
|
||||
auto angle1 = angle + units::angle::degrees<double>(90.0);
|
||||
auto angle2 = angle - units::angle::degrees<double>(90.0);
|
||||
|
||||
auto tickCoord1 = util::GeographicLib::GetCoordinate(
|
||||
coordinate2, angle1, tickRadius);
|
||||
auto tickCoord2 = util::GeographicLib::GetCoordinate(
|
||||
coordinate2, angle2, tickRadius);
|
||||
|
||||
const double& tickLat1 = tickCoord1.latitude_;
|
||||
const double& tickLon1 = tickCoord1.longitude_;
|
||||
const double& tickLat2 = tickCoord2.latitude_;
|
||||
const double& tickLon2 = tickCoord2.longitude_;
|
||||
|
||||
auto tickBorderLine = p->geoLines_->AddLine();
|
||||
|
||||
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_);
|
||||
|
||||
tickRadius += di->tickRadiusIncrement_;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate geo lines
|
||||
for (auto& di : p->vectorList_)
|
||||
{
|
||||
auto tickRadius = di->tickRadius_;
|
||||
|
||||
for (std::size_t i = 0; i < di->coordinates_.size() - 1; ++i)
|
||||
{
|
||||
auto geoLine = p->geoLines_->AddLine();
|
||||
|
||||
const common::Coordinate& coordinate1 = di->coordinates_[i];
|
||||
const common::Coordinate& coordinate2 = di->coordinates_[i + 1];
|
||||
|
||||
const double& latitude1 = coordinate1.latitude_;
|
||||
const double& longitude1 = coordinate1.longitude_;
|
||||
const double& latitude2 = coordinate2.latitude_;
|
||||
const double& longitude2 = coordinate2.longitude_;
|
||||
|
||||
GeoLines::SetLineLocation(
|
||||
geoLine, latitude1, longitude1, latitude2, longitude2);
|
||||
|
||||
GeoLines::SetLineModulate(geoLine, di->modulate_);
|
||||
GeoLines::SetLineWidth(geoLine, di->width_);
|
||||
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_);
|
||||
}
|
||||
|
||||
di->lineDrawItems_.emplace_back(std::move(geoLine));
|
||||
|
||||
if (di->ticksEnabled_)
|
||||
{
|
||||
auto angle = util::GeographicLib::GetAngle(
|
||||
latitude1, longitude1, latitude2, longitude2);
|
||||
auto angle1 = angle + units::angle::degrees<double>(90.0);
|
||||
auto angle2 = angle - units::angle::degrees<double>(90.0);
|
||||
|
||||
auto tickCoord1 = util::GeographicLib::GetCoordinate(
|
||||
coordinate2, angle1, tickRadius);
|
||||
auto tickCoord2 = util::GeographicLib::GetCoordinate(
|
||||
coordinate2, angle2, tickRadius);
|
||||
|
||||
const double& tickLat1 = tickCoord1.latitude_;
|
||||
const double& tickLon1 = tickCoord1.longitude_;
|
||||
const double& tickLat2 = tickCoord2.latitude_;
|
||||
const double& tickLon2 = tickCoord2.longitude_;
|
||||
|
||||
auto tickGeoLine = p->geoLines_->AddLine();
|
||||
|
||||
GeoLines::SetLineLocation(
|
||||
tickGeoLine, tickLat1, tickLon1, tickLat2, tickLon2);
|
||||
|
||||
GeoLines::SetLineModulate(tickGeoLine, di->modulate_);
|
||||
GeoLines::SetLineWidth(tickGeoLine, di->width_);
|
||||
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_);
|
||||
}
|
||||
|
||||
tickRadius += di->tickRadiusIncrement_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finish geo lines
|
||||
p->geoLines_->FinishLines();
|
||||
}
|
||||
|
||||
bool LinkedVectors::RunMousePicking(
|
||||
const QMapLibreGL::CustomLayerRenderParameters& params,
|
||||
const QPointF& mouseLocalPos,
|
||||
const QPointF& mouseGlobalPos,
|
||||
const glm::vec2& mouseCoords,
|
||||
const common::Coordinate& mouseGeoCoords,
|
||||
std::shared_ptr<types::EventHandler>& eventHandler)
|
||||
{
|
||||
return p->geoLines_->RunMousePicking(params,
|
||||
mouseLocalPos,
|
||||
mouseGlobalPos,
|
||||
mouseCoords,
|
||||
mouseGeoCoords,
|
||||
eventHandler);
|
||||
}
|
||||
|
||||
} // namespace draw
|
||||
} // namespace gl
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
179
scwx-qt/source/scwx/qt/gl/draw/linked_vectors.hpp
Normal file
179
scwx-qt/source/scwx/qt/gl/draw/linked_vectors.hpp
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/qt/gl/gl_context.hpp>
|
||||
#include <scwx/qt/gl/draw/draw_item.hpp>
|
||||
|
||||
#include <boost/gil.hpp>
|
||||
#include <units/length.h>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace wsr88d
|
||||
{
|
||||
namespace rpg
|
||||
{
|
||||
|
||||
class LinkedVectorPacket;
|
||||
|
||||
} // namespace rpg
|
||||
} // namespace wsr88d
|
||||
|
||||
namespace qt
|
||||
{
|
||||
namespace gl
|
||||
{
|
||||
namespace draw
|
||||
{
|
||||
|
||||
struct LinkedVectorDrawItem;
|
||||
|
||||
class LinkedVectors : public DrawItem
|
||||
{
|
||||
public:
|
||||
explicit LinkedVectors(std::shared_ptr<GlContext> context);
|
||||
~LinkedVectors();
|
||||
|
||||
LinkedVectors(const LinkedVectors&) = delete;
|
||||
LinkedVectors& operator=(const LinkedVectors&) = delete;
|
||||
|
||||
LinkedVectors(LinkedVectors&&) noexcept;
|
||||
LinkedVectors& operator=(LinkedVectors&&) noexcept;
|
||||
|
||||
void set_selected_time(std::chrono::system_clock::time_point selectedTime);
|
||||
void set_thresholded(bool thresholded);
|
||||
|
||||
void Initialize() override;
|
||||
void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override;
|
||||
void Deinitialize() override;
|
||||
|
||||
bool
|
||||
RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params,
|
||||
const QPointF& mouseLocalPos,
|
||||
const QPointF& mouseGlobalPos,
|
||||
const glm::vec2& mouseCoords,
|
||||
const common::Coordinate& mouseGeoCoords,
|
||||
std::shared_ptr<types::EventHandler>& eventHandler) override;
|
||||
|
||||
/**
|
||||
* Enables or disables the border around each line in a linked vector.
|
||||
*
|
||||
* @param [in] enabled Border visibility
|
||||
*/
|
||||
void SetBorderEnabled(bool enabled);
|
||||
|
||||
/**
|
||||
* Sets the visibility of the linked vectors.
|
||||
*
|
||||
* @param [in] visible Line visibility
|
||||
*/
|
||||
void SetVisible(bool visible);
|
||||
|
||||
/**
|
||||
* Resets and prepares the draw item for adding a new set of linked vectors.
|
||||
*/
|
||||
void StartVectors();
|
||||
|
||||
/**
|
||||
* Adds a linked vector to the internal draw list.
|
||||
*
|
||||
* @param [in] center Center coordinate on which the linked vectors are based
|
||||
* @param [in] vectorPacket Linked vector packet containing start and end
|
||||
* points
|
||||
*
|
||||
* @return Linked vector draw item
|
||||
*/
|
||||
std::shared_ptr<LinkedVectorDrawItem>
|
||||
AddVector(const common::Coordinate& center,
|
||||
const std::shared_ptr<const wsr88d::rpg::LinkedVectorPacket>&
|
||||
vectorPacket);
|
||||
|
||||
/**
|
||||
* Sets the modulate color of a linked vector.
|
||||
*
|
||||
* @param [in] di Linked vector draw item
|
||||
* @param [in] modulate Modulate color
|
||||
*/
|
||||
static void
|
||||
SetVectorModulate(const std::shared_ptr<LinkedVectorDrawItem>& di,
|
||||
boost::gil::rgba8_pixel_t color);
|
||||
|
||||
/**
|
||||
* Sets the modulate color of a linked vector.
|
||||
*
|
||||
* @param [in] di Linked vector draw item
|
||||
* @param [in] modulate Modulate color
|
||||
*/
|
||||
static void
|
||||
SetVectorModulate(const std::shared_ptr<LinkedVectorDrawItem>& di,
|
||||
boost::gil::rgba32f_pixel_t modulate);
|
||||
|
||||
/**
|
||||
* Sets the width of the linked vector.
|
||||
*
|
||||
* @param [in] width Width in pixels
|
||||
*/
|
||||
static void SetVectorWidth(const std::shared_ptr<LinkedVectorDrawItem>& di,
|
||||
float width);
|
||||
|
||||
/**
|
||||
* Sets the visibility of the linked vector.
|
||||
*
|
||||
* @param [in] visible
|
||||
*/
|
||||
static void SetVectorVisible(const std::shared_ptr<LinkedVectorDrawItem>& di,
|
||||
bool visible);
|
||||
|
||||
/**
|
||||
* Sets the hover text of a linked vector.
|
||||
*
|
||||
* @param [in] di Linked vector draw item
|
||||
* @param [in] text Hover text
|
||||
*/
|
||||
static void
|
||||
SetVectorHoverText(const std::shared_ptr<LinkedVectorDrawItem>& di,
|
||||
const std::string& text);
|
||||
|
||||
/**
|
||||
* Sets the presence of ticks on the linked vector.
|
||||
*
|
||||
* @param [in] di Linked vector draw item
|
||||
* @param [in] enabled Ticks enabled
|
||||
*/
|
||||
static void
|
||||
SetVectorTicksEnabled(const std::shared_ptr<LinkedVectorDrawItem>& di,
|
||||
bool enabled);
|
||||
|
||||
/**
|
||||
* Sets the tick radius of the linked vector.
|
||||
*
|
||||
* @param [in] di Linked vector draw item
|
||||
* @param [in] radius Length of the tick extending beyond the linked vector
|
||||
*/
|
||||
static void
|
||||
SetVectorTickRadius(const std::shared_ptr<LinkedVectorDrawItem>& di,
|
||||
units::length::meters<double> radius);
|
||||
|
||||
/**
|
||||
* Sets the tick radius increment of the linked vector.
|
||||
*
|
||||
* @param [in] di Linked vector draw item
|
||||
* @param [in] radiusIncrement Length increment of each tick beyond the first
|
||||
*/
|
||||
static void
|
||||
SetVectorTickRadiusIncrement(const std::shared_ptr<LinkedVectorDrawItem>& di,
|
||||
units::length::meters<double> radiusIncrement);
|
||||
|
||||
/**
|
||||
* Finalizes the draw item after adding new linked vectors.
|
||||
*/
|
||||
void FinishVectors();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace draw
|
||||
} // namespace gl
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
|
|
@ -11,6 +11,7 @@
|
|||
#include <scwx/qt/types/qt_types.hpp>
|
||||
#include <scwx/qt/ui/setup/setup_wizard.hpp>
|
||||
#include <scwx/network/cpr.hpp>
|
||||
#include <scwx/util/environment.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/threads.hpp>
|
||||
|
||||
|
|
@ -19,6 +20,7 @@
|
|||
#include <fmt/format.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <QApplication>
|
||||
#include <QStandardPaths>
|
||||
#include <QTranslator>
|
||||
|
||||
static const std::string logPrefix_ = "scwx::main";
|
||||
|
|
@ -45,6 +47,11 @@ int main(int argc, char* argv[])
|
|||
QCoreApplication::installTranslator(&translator);
|
||||
}
|
||||
|
||||
if (!scwx::util::GetEnvironment("SCWX_TEST").empty())
|
||||
{
|
||||
QStandardPaths::setTestModeEnabled(true);
|
||||
}
|
||||
|
||||
// Start the io_context main loop
|
||||
boost::asio::io_context& ioContext = scwx::util::io_context();
|
||||
auto work = boost::asio::make_work_guard(ioContext);
|
||||
|
|
|
|||
|
|
@ -62,7 +62,8 @@ static constexpr uint32_t NUM_COORIDNATES_1_DEGREE =
|
|||
|
||||
static const std::string kDefaultLevel3Product_ {"N0B"};
|
||||
|
||||
static constexpr std::chrono::seconds kRetryInterval_ {15};
|
||||
static constexpr std::chrono::seconds kFastRetryInterval_ {15};
|
||||
static constexpr std::chrono::seconds kSlowRetryInterval_ {120};
|
||||
|
||||
static std::unordered_map<std::string, std::weak_ptr<RadarProductManager>>
|
||||
instanceMap_;
|
||||
|
|
@ -638,10 +639,12 @@ void RadarProductManagerImpl::RefreshData(
|
|||
threadPool_,
|
||||
[=, this]()
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
auto [newObjects, totalObjects] =
|
||||
providerManager->provider_->Refresh();
|
||||
|
||||
std::chrono::milliseconds interval = kRetryInterval_;
|
||||
std::chrono::milliseconds interval = kFastRetryInterval_;
|
||||
|
||||
if (totalObjects > 0)
|
||||
{
|
||||
|
|
@ -651,12 +654,25 @@ void RadarProductManagerImpl::RefreshData(
|
|||
|
||||
auto updatePeriod = providerManager->provider_->update_period();
|
||||
auto lastModified = providerManager->provider_->last_modified();
|
||||
auto sinceLastModified =
|
||||
std::chrono::system_clock::now() - lastModified;
|
||||
|
||||
// For the default interval, assume products are updated at a
|
||||
// constant rate. Expect the next product at a time based on the
|
||||
// previous two.
|
||||
interval = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
updatePeriod -
|
||||
(std::chrono::system_clock::now() - lastModified));
|
||||
if (interval < std::chrono::milliseconds {kRetryInterval_})
|
||||
updatePeriod - sinceLastModified);
|
||||
|
||||
if (updatePeriod > 0s && sinceLastModified > updatePeriod * 5)
|
||||
{
|
||||
interval = kRetryInterval_;
|
||||
// If it has been at least 5 update periods since the file has
|
||||
// been last modified, slow the retry period
|
||||
interval = kSlowRetryInterval_;
|
||||
}
|
||||
else if (interval < std::chrono::milliseconds {kFastRetryInterval_})
|
||||
{
|
||||
// The interval should be no quicker than the fast retry interval
|
||||
interval = kFastRetryInterval_;
|
||||
}
|
||||
|
||||
if (newObjects > 0)
|
||||
|
|
@ -669,10 +685,10 @@ void RadarProductManagerImpl::RefreshData(
|
|||
}
|
||||
else if (providerManager->refreshEnabled_)
|
||||
{
|
||||
logger_->info("[{}] No data found, disabling refresh",
|
||||
providerManager->name());
|
||||
logger_->info("[{}] No data found", providerManager->name());
|
||||
|
||||
providerManager->refreshEnabled_ = false;
|
||||
// If no data is found, retry at the slow retry interval
|
||||
interval = kSlowRetryInterval_;
|
||||
}
|
||||
|
||||
if (providerManager->refreshEnabled_)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <scwx/qt/settings/general_settings.hpp>
|
||||
#include <scwx/qt/settings/map_settings.hpp>
|
||||
#include <scwx/qt/settings/palette_settings.hpp>
|
||||
#include <scwx/qt/settings/product_settings.hpp>
|
||||
#include <scwx/qt/settings/text_settings.hpp>
|
||||
#include <scwx/qt/settings/ui_settings.hpp>
|
||||
#include <scwx/qt/util/json.hpp>
|
||||
|
|
@ -116,6 +117,7 @@ void SettingsManager::Shutdown()
|
|||
|
||||
dataChanged |= settings::GeneralSettings::Instance().Shutdown();
|
||||
dataChanged |= settings::MapSettings::Instance().Shutdown();
|
||||
dataChanged |= settings::ProductSettings::Instance().Shutdown();
|
||||
dataChanged |= settings::UiSettings::Instance().Shutdown();
|
||||
|
||||
if (dataChanged)
|
||||
|
|
@ -132,6 +134,7 @@ boost::json::value SettingsManager::Impl::ConvertSettingsToJson()
|
|||
settings::AudioSettings::Instance().WriteJson(settingsJson);
|
||||
settings::MapSettings::Instance().WriteJson(settingsJson);
|
||||
settings::PaletteSettings::Instance().WriteJson(settingsJson);
|
||||
settings::ProductSettings::Instance().WriteJson(settingsJson);
|
||||
settings::TextSettings::Instance().WriteJson(settingsJson);
|
||||
settings::UiSettings::Instance().WriteJson(settingsJson);
|
||||
|
||||
|
|
@ -146,6 +149,7 @@ void SettingsManager::Impl::GenerateDefaultSettings()
|
|||
settings::AudioSettings::Instance().SetDefaults();
|
||||
settings::MapSettings::Instance().SetDefaults();
|
||||
settings::PaletteSettings::Instance().SetDefaults();
|
||||
settings::ProductSettings::Instance().SetDefaults();
|
||||
settings::TextSettings::Instance().SetDefaults();
|
||||
settings::UiSettings::Instance().SetDefaults();
|
||||
}
|
||||
|
|
@ -161,6 +165,7 @@ bool SettingsManager::Impl::LoadSettings(
|
|||
jsonDirty |= !settings::AudioSettings::Instance().ReadJson(settingsJson);
|
||||
jsonDirty |= !settings::MapSettings::Instance().ReadJson(settingsJson);
|
||||
jsonDirty |= !settings::PaletteSettings::Instance().ReadJson(settingsJson);
|
||||
jsonDirty |= !settings::ProductSettings::Instance().ReadJson(settingsJson);
|
||||
jsonDirty |= !settings::TextSettings::Instance().ReadJson(settingsJson);
|
||||
jsonDirty |= !settings::UiSettings::Instance().ReadJson(settingsJson);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include <scwx/qt/map/color_table_layer.hpp>
|
||||
#include <scwx/qt/gl/shader_program.hpp>
|
||||
#include <scwx/qt/view/radar_product_view.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
#include <scwx/qt/map/map_context.hpp>
|
||||
#include <scwx/qt/map/map_settings.hpp>
|
||||
#include <scwx/qt/view/overlay_product_view.hpp>
|
||||
#include <scwx/qt/view/radar_product_view.hpp>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
|
|
@ -11,26 +14,23 @@ class MapContext::Impl
|
|||
{
|
||||
public:
|
||||
explicit Impl(std::shared_ptr<view::RadarProductView> radarProductView) :
|
||||
map_ {},
|
||||
settings_ {},
|
||||
pixelRatio_ {1.0f},
|
||||
radarProductView_ {radarProductView},
|
||||
radarProductGroup_ {common::RadarProductGroup::Unknown},
|
||||
radarProduct_ {"???"},
|
||||
radarProductCode_ {0}
|
||||
radarProductView_ {radarProductView}
|
||||
{
|
||||
}
|
||||
|
||||
~Impl() {}
|
||||
|
||||
std::weak_ptr<QMapLibreGL::Map> map_;
|
||||
MapSettings settings_;
|
||||
float pixelRatio_;
|
||||
std::shared_ptr<view::RadarProductView> radarProductView_;
|
||||
common::RadarProductGroup radarProductGroup_;
|
||||
std::string radarProduct_;
|
||||
int16_t radarProductCode_;
|
||||
std::weak_ptr<QMapLibreGL::Map> map_ {};
|
||||
MapSettings settings_ {};
|
||||
float pixelRatio_ {1.0f};
|
||||
common::RadarProductGroup radarProductGroup_ {
|
||||
common::RadarProductGroup::Unknown};
|
||||
std::string radarProduct_ {"???"};
|
||||
int16_t radarProductCode_ {0};
|
||||
QMapLibreGL::CustomLayerRenderParameters renderParameters_ {};
|
||||
|
||||
std::shared_ptr<view::OverlayProductView> overlayProductView_ {nullptr};
|
||||
std::shared_ptr<view::RadarProductView> radarProductView_;
|
||||
};
|
||||
|
||||
MapContext::MapContext(
|
||||
|
|
@ -58,6 +58,12 @@ float MapContext::pixel_ratio() const
|
|||
return p->pixelRatio_;
|
||||
}
|
||||
|
||||
std::shared_ptr<view::OverlayProductView>
|
||||
MapContext::overlay_product_view() const
|
||||
{
|
||||
return p->overlayProductView_;
|
||||
}
|
||||
|
||||
std::shared_ptr<view::RadarProductView> MapContext::radar_product_view() const
|
||||
{
|
||||
return p->radarProductView_;
|
||||
|
|
@ -83,18 +89,24 @@ QMapLibreGL::CustomLayerRenderParameters MapContext::render_parameters() const
|
|||
return p->renderParameters_;
|
||||
}
|
||||
|
||||
void MapContext::set_map(std::shared_ptr<QMapLibreGL::Map> map)
|
||||
void MapContext::set_map(const std::shared_ptr<QMapLibreGL::Map>& map)
|
||||
{
|
||||
p->map_ = map;
|
||||
}
|
||||
|
||||
void MapContext::set_overlay_product_view(
|
||||
const std::shared_ptr<view::OverlayProductView>& overlayProductView)
|
||||
{
|
||||
p->overlayProductView_ = overlayProductView;
|
||||
}
|
||||
|
||||
void MapContext::set_pixel_ratio(float pixelRatio)
|
||||
{
|
||||
p->pixelRatio_ = pixelRatio;
|
||||
}
|
||||
|
||||
void MapContext::set_radar_product_view(
|
||||
std::shared_ptr<view::RadarProductView> radarProductView)
|
||||
const std::shared_ptr<view::RadarProductView>& radarProductView)
|
||||
{
|
||||
p->radarProductView_ = radarProductView;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/qt/gl/gl_context.hpp>
|
||||
#include <scwx/qt/map/map_settings.hpp>
|
||||
#include <scwx/qt/view/radar_product_view.hpp>
|
||||
#include <scwx/common/products.hpp>
|
||||
|
||||
#include <QMapLibreGL/QMapLibreGL>
|
||||
|
||||
|
|
@ -10,9 +9,19 @@ namespace scwx
|
|||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace view
|
||||
{
|
||||
|
||||
class OverlayProductView;
|
||||
class RadarProductView;
|
||||
|
||||
} // namespace view
|
||||
|
||||
namespace map
|
||||
{
|
||||
|
||||
struct MapSettings;
|
||||
|
||||
class MapContext : public gl::GlContext
|
||||
{
|
||||
public:
|
||||
|
|
@ -26,19 +35,22 @@ public:
|
|||
MapContext(MapContext&&) noexcept;
|
||||
MapContext& operator=(MapContext&&) noexcept;
|
||||
|
||||
std::weak_ptr<QMapLibreGL::Map> map() const;
|
||||
MapSettings& settings();
|
||||
float pixel_ratio() const;
|
||||
std::shared_ptr<view::RadarProductView> radar_product_view() const;
|
||||
common::RadarProductGroup radar_product_group() const;
|
||||
std::string radar_product() const;
|
||||
int16_t radar_product_code() const;
|
||||
QMapLibreGL::CustomLayerRenderParameters render_parameters() const;
|
||||
std::weak_ptr<QMapLibreGL::Map> map() const;
|
||||
MapSettings& settings();
|
||||
float pixel_ratio() const;
|
||||
std::shared_ptr<view::OverlayProductView> overlay_product_view() const;
|
||||
std::shared_ptr<view::RadarProductView> radar_product_view() const;
|
||||
common::RadarProductGroup radar_product_group() const;
|
||||
std::string radar_product() const;
|
||||
int16_t radar_product_code() const;
|
||||
QMapLibreGL::CustomLayerRenderParameters render_parameters() const;
|
||||
|
||||
void set_map(std::shared_ptr<QMapLibreGL::Map> map);
|
||||
void set_map(const std::shared_ptr<QMapLibreGL::Map>& map);
|
||||
void set_overlay_product_view(
|
||||
const std::shared_ptr<view::OverlayProductView>& overlayProductView);
|
||||
void set_pixel_ratio(float pixelRatio);
|
||||
void set_radar_product_view(
|
||||
std::shared_ptr<view::RadarProductView> radarProductView);
|
||||
const std::shared_ptr<view::RadarProductView>& radarProductView);
|
||||
void set_radar_product_group(common::RadarProductGroup radarProductGroup);
|
||||
void set_radar_product(const std::string& radarProduct);
|
||||
void set_radar_product_code(int16_t radarProductCode);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@
|
|||
#include <scwx/qt/map/color_table_layer.hpp>
|
||||
#include <scwx/qt/map/layer_wrapper.hpp>
|
||||
#include <scwx/qt/map/map_provider.hpp>
|
||||
#include <scwx/qt/map/map_settings.hpp>
|
||||
#include <scwx/qt/map/overlay_layer.hpp>
|
||||
#include <scwx/qt/map/overlay_product_layer.hpp>
|
||||
#include <scwx/qt/map/placefile_layer.hpp>
|
||||
#include <scwx/qt/map/radar_product_layer.hpp>
|
||||
#include <scwx/qt/map/radar_range_layer.hpp>
|
||||
|
|
@ -19,6 +21,7 @@
|
|||
#include <scwx/qt/util/file.hpp>
|
||||
#include <scwx/qt/util/maplibre.hpp>
|
||||
#include <scwx/qt/util/tooltip.hpp>
|
||||
#include <scwx/qt/view/overlay_product_view.hpp>
|
||||
#include <scwx/qt/view/radar_product_view_factory.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
|
@ -86,6 +89,14 @@ public:
|
|||
prevBearing_ {0.0},
|
||||
prevPitch_ {0.0}
|
||||
{
|
||||
// Create views
|
||||
auto overlayProductView = std::make_shared<view::OverlayProductView>();
|
||||
overlayProductView->SetAutoRefresh(autoRefreshEnabled_);
|
||||
overlayProductView->SetAutoUpdate(autoUpdateEnabled_);
|
||||
|
||||
// Initialize context
|
||||
context_->set_overlay_product_view(overlayProductView);
|
||||
|
||||
auto& generalSettings = settings::GeneralSettings::Instance();
|
||||
|
||||
SetRadarSite(generalSettings.default_radar_site().GetValue());
|
||||
|
|
@ -181,12 +192,13 @@ public:
|
|||
manager::PlacefileManager::Instance()};
|
||||
std::shared_ptr<manager::RadarProductManager> radarProductManager_;
|
||||
|
||||
std::shared_ptr<RadarProductLayer> radarProductLayer_;
|
||||
std::shared_ptr<AlertLayer> alertLayer_;
|
||||
std::shared_ptr<OverlayLayer> overlayLayer_;
|
||||
std::shared_ptr<PlacefileLayer> placefileLayer_;
|
||||
std::shared_ptr<ColorTableLayer> colorTableLayer_;
|
||||
std::shared_ptr<RadarSiteLayer> radarSiteLayer_ {nullptr};
|
||||
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_;
|
||||
std::shared_ptr<ColorTableLayer> colorTableLayer_;
|
||||
std::shared_ptr<RadarSiteLayer> radarSiteLayer_ {nullptr};
|
||||
|
||||
std::list<std::shared_ptr<PlacefileLayer>> placefileLayers_ {};
|
||||
|
||||
|
|
@ -439,7 +451,7 @@ std::chrono::system_clock::time_point MapWidget::GetSelectedTime() const
|
|||
if (radarProductView != nullptr)
|
||||
{
|
||||
// Select the time associated with the active radar product
|
||||
time = radarProductView->GetSelectedTime();
|
||||
time = radarProductView->selected_time();
|
||||
}
|
||||
|
||||
return time;
|
||||
|
|
@ -618,6 +630,9 @@ void MapWidget::SelectTime(std::chrono::system_clock::time_point time)
|
|||
{
|
||||
auto radarProductView = p->context_->radar_product_view();
|
||||
|
||||
// Update other views
|
||||
p->context_->overlay_product_view()->SelectTime(time);
|
||||
|
||||
// If there is an active radar product view
|
||||
if (radarProductView != nullptr)
|
||||
{
|
||||
|
|
@ -651,12 +666,16 @@ void MapWidget::SetAutoRefresh(bool enabled)
|
|||
true,
|
||||
p->uuid_);
|
||||
}
|
||||
|
||||
p->context_->overlay_product_view()->SetAutoRefresh(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
void MapWidget::SetAutoUpdate(bool enabled)
|
||||
{
|
||||
p->autoUpdateEnabled_ = enabled;
|
||||
|
||||
p->context_->overlay_product_view()->SetAutoUpdate(enabled);
|
||||
}
|
||||
|
||||
void MapWidget::SetMapLocation(double latitude,
|
||||
|
|
@ -912,6 +931,16 @@ void MapWidgetImpl::AddLayer(types::LayerType type,
|
|||
{
|
||||
switch (std::get<types::DataLayer>(description))
|
||||
{
|
||||
// If there is a radar product view, create the overlay product layer
|
||||
case types::DataLayer::OverlayProduct:
|
||||
if (radarProductView != nullptr)
|
||||
{
|
||||
overlayProductLayer_ =
|
||||
std::make_shared<OverlayProductLayer>(context_);
|
||||
AddLayer(layerName, overlayProductLayer_, before);
|
||||
}
|
||||
break;
|
||||
|
||||
// If there is a radar product view, create the radar range layer
|
||||
case types::DataLayer::RadarRange:
|
||||
if (radarProductView != nullptr)
|
||||
|
|
@ -1492,6 +1521,10 @@ void MapWidgetImpl::SetRadarSite(const std::string& radarSite)
|
|||
// Set new RadarProductManager
|
||||
radarProductManager_ = manager::RadarProductManager::Instance(radarSite);
|
||||
|
||||
// Update views
|
||||
context_->overlay_product_view()->set_radar_product_manager(
|
||||
radarProductManager_);
|
||||
|
||||
// Connect signals to new RadarProductManager
|
||||
RadarProductManagerConnect();
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
#include <scwx/qt/gl/draw/icons.hpp>
|
||||
#include <scwx/qt/gl/draw/rectangle.hpp>
|
||||
#include <scwx/qt/manager/position_manager.hpp>
|
||||
#include <scwx/qt/map/map_settings.hpp>
|
||||
#include <scwx/qt/types/texture_types.hpp>
|
||||
#include <scwx/qt/view/radar_product_view.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
||||
|
|
|
|||
443
scwx-qt/source/scwx/qt/map/overlay_product_layer.cpp
Normal file
443
scwx-qt/source/scwx/qt/map/overlay_product_layer.cpp
Normal file
|
|
@ -0,0 +1,443 @@
|
|||
#include <scwx/qt/map/overlay_product_layer.hpp>
|
||||
#include <scwx/qt/gl/draw/linked_vectors.hpp>
|
||||
#include <scwx/qt/manager/radar_product_manager.hpp>
|
||||
#include <scwx/qt/settings/product_settings.hpp>
|
||||
#include <scwx/qt/view/overlay_product_view.hpp>
|
||||
#include <scwx/wsr88d/rpg/linked_vector_packet.hpp>
|
||||
#include <scwx/wsr88d/rpg/rpg_types.hpp>
|
||||
#include <scwx/wsr88d/rpg/scit_data_packet.hpp>
|
||||
#include <scwx/wsr88d/rpg/storm_id_symbol_packet.hpp>
|
||||
#include <scwx/wsr88d/rpg/storm_tracking_information_message.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace map
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ = "scwx::qt::map::overlay_product_layer";
|
||||
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
|
||||
|
||||
class OverlayProductLayer::Impl
|
||||
{
|
||||
public:
|
||||
explicit Impl(OverlayProductLayer* self,
|
||||
const std::shared_ptr<MapContext>& context) :
|
||||
self_ {self},
|
||||
linkedVectors_ {std::make_shared<gl::draw::LinkedVectors>(context)}
|
||||
{
|
||||
auto& productSettings = settings::ProductSettings::Instance();
|
||||
|
||||
productSettings.sti_forecast_enabled().RegisterValueStagedCallback(
|
||||
[=, this](const bool& value)
|
||||
{
|
||||
stiForecastEnabled_ = value;
|
||||
stiNeedsUpdate_ = true;
|
||||
|
||||
Q_EMIT self_->NeedsRendering();
|
||||
});
|
||||
productSettings.sti_past_enabled().RegisterValueStagedCallback(
|
||||
[=, this](const bool& value)
|
||||
{
|
||||
stiPastEnabled_ = value;
|
||||
stiNeedsUpdate_ = true;
|
||||
|
||||
Q_EMIT self_->NeedsRendering();
|
||||
});
|
||||
|
||||
stiForecastEnabled_ =
|
||||
productSettings.sti_forecast_enabled().GetStagedOrValue();
|
||||
stiPastEnabled_ = productSettings.sti_past_enabled().GetStagedOrValue();
|
||||
}
|
||||
~Impl() = default;
|
||||
|
||||
void UpdateStormTrackingInformation();
|
||||
|
||||
static void HandleLinkedVectorPacket(
|
||||
const std::shared_ptr<const wsr88d::rpg::Packet>& packet,
|
||||
const common::Coordinate& center,
|
||||
const std::string& hoverText,
|
||||
boost::gil::rgba32f_pixel_t color,
|
||||
units::length::nautical_miles<float> tickRadius,
|
||||
units::length::nautical_miles<float> tickRadiusIncrement,
|
||||
std::shared_ptr<gl::draw::LinkedVectors>& linkedVectors);
|
||||
void HandleScitDataPacket(
|
||||
const std::shared_ptr<const wsr88d::rpg::StormTrackingInformationMessage>&
|
||||
sti,
|
||||
const std::shared_ptr<const wsr88d::rpg::Packet>& packet,
|
||||
const common::Coordinate& center,
|
||||
const std::string& stormId,
|
||||
const std::string& hoverText,
|
||||
std::shared_ptr<gl::draw::LinkedVectors>& linkedVectors);
|
||||
|
||||
static void HandleStormIdPacket(
|
||||
const std::shared_ptr<const wsr88d::rpg::StormTrackingInformationMessage>&
|
||||
sti,
|
||||
const std::shared_ptr<const wsr88d::rpg::Packet>& packet,
|
||||
std::string& stormId,
|
||||
std::string& hoverText);
|
||||
|
||||
static std::string BuildHoverText(
|
||||
const std::shared_ptr<
|
||||
const scwx::wsr88d::rpg::StormTrackingInformationMessage>& sti,
|
||||
std::string& stormId);
|
||||
|
||||
OverlayProductLayer* self_;
|
||||
|
||||
bool stiForecastEnabled_ {true};
|
||||
bool stiPastEnabled_ {true};
|
||||
|
||||
bool stiNeedsUpdate_ {false};
|
||||
|
||||
std::shared_ptr<gl::draw::LinkedVectors> linkedVectors_;
|
||||
};
|
||||
|
||||
OverlayProductLayer::OverlayProductLayer(std::shared_ptr<MapContext> context) :
|
||||
DrawLayer(context), p(std::make_unique<Impl>(this, context))
|
||||
{
|
||||
auto overlayProductView = context->overlay_product_view();
|
||||
connect(overlayProductView.get(),
|
||||
&view::OverlayProductView::ProductUpdated,
|
||||
this,
|
||||
[this](std::string product)
|
||||
{
|
||||
if (product == "NST")
|
||||
{
|
||||
p->stiNeedsUpdate_ = true;
|
||||
Q_EMIT NeedsRendering();
|
||||
}
|
||||
});
|
||||
|
||||
AddDrawItem(p->linkedVectors_);
|
||||
}
|
||||
|
||||
OverlayProductLayer::~OverlayProductLayer() = default;
|
||||
|
||||
void OverlayProductLayer::Initialize()
|
||||
{
|
||||
logger_->debug("Initialize()");
|
||||
|
||||
p->UpdateStormTrackingInformation();
|
||||
|
||||
DrawLayer::Initialize();
|
||||
}
|
||||
|
||||
void OverlayProductLayer::Render(
|
||||
const QMapLibreGL::CustomLayerRenderParameters& params)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
DrawLayer::Render(params);
|
||||
|
||||
SCWX_GL_CHECK_ERROR();
|
||||
}
|
||||
|
||||
void OverlayProductLayer::Deinitialize()
|
||||
{
|
||||
logger_->debug("Deinitialize()");
|
||||
|
||||
DrawLayer::Deinitialize();
|
||||
}
|
||||
|
||||
void OverlayProductLayer::Impl::UpdateStormTrackingInformation()
|
||||
{
|
||||
logger_->debug("Update Storm Tracking Information");
|
||||
|
||||
stiNeedsUpdate_ = false;
|
||||
|
||||
auto overlayProductView = self_->context()->overlay_product_view();
|
||||
auto radarProductManager = overlayProductView->radar_product_manager();
|
||||
auto message = overlayProductView->radar_product_message("NST");
|
||||
|
||||
float latitude = 0.0f;
|
||||
float longitude = 0.0f;
|
||||
|
||||
std::shared_ptr<wsr88d::rpg::StormTrackingInformationMessage> sti = nullptr;
|
||||
std::shared_ptr<wsr88d::rpg::ProductSymbologyBlock> psb = nullptr;
|
||||
if (message != nullptr)
|
||||
{
|
||||
sti = std::dynamic_pointer_cast<
|
||||
wsr88d::rpg::StormTrackingInformationMessage>(message);
|
||||
}
|
||||
if (sti != nullptr)
|
||||
{
|
||||
psb = sti->symbology_block();
|
||||
}
|
||||
|
||||
linkedVectors_->StartVectors();
|
||||
|
||||
if (psb != nullptr)
|
||||
{
|
||||
std::shared_ptr<config::RadarSite> radarSite = nullptr;
|
||||
if (radarProductManager != nullptr)
|
||||
{
|
||||
radarSite = radarProductManager->radar_site();
|
||||
}
|
||||
if (radarSite != nullptr)
|
||||
{
|
||||
latitude = radarSite->latitude();
|
||||
longitude = radarSite->longitude();
|
||||
}
|
||||
|
||||
std::string stormId = "?";
|
||||
std::string hoverText {};
|
||||
|
||||
for (std::size_t i = 0; i < psb->number_of_layers(); ++i)
|
||||
{
|
||||
auto packetList = psb->packet_list(static_cast<std::uint16_t>(i));
|
||||
for (auto& packet : packetList)
|
||||
{
|
||||
switch (packet->packet_code())
|
||||
{
|
||||
case static_cast<std::uint16_t>(wsr88d::rpg::PacketCode::StormId):
|
||||
HandleStormIdPacket(sti, packet, stormId, hoverText);
|
||||
break;
|
||||
|
||||
case static_cast<std::uint16_t>(
|
||||
wsr88d::rpg::PacketCode::ScitPastData):
|
||||
case static_cast<std::uint16_t>(
|
||||
wsr88d::rpg::PacketCode::ScitForecastData):
|
||||
HandleScitDataPacket(sti,
|
||||
packet,
|
||||
{latitude, longitude},
|
||||
stormId,
|
||||
hoverText,
|
||||
linkedVectors_);
|
||||
break;
|
||||
|
||||
default:
|
||||
logger_->trace("Ignoring packet type: {}",
|
||||
packet->packet_code());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger_->trace("No Storm Tracking Information found");
|
||||
}
|
||||
|
||||
linkedVectors_->FinishVectors();
|
||||
}
|
||||
|
||||
void OverlayProductLayer::Impl::HandleStormIdPacket(
|
||||
const std::shared_ptr<const wsr88d::rpg::StormTrackingInformationMessage>&
|
||||
sti,
|
||||
const std::shared_ptr<const wsr88d::rpg::Packet>& packet,
|
||||
std::string& stormId,
|
||||
std::string& hoverText)
|
||||
{
|
||||
auto stormIdPacket =
|
||||
std::dynamic_pointer_cast<const wsr88d::rpg::StormIdSymbolPacket>(packet);
|
||||
|
||||
if (stormIdPacket != nullptr && stormIdPacket->RecordCount() > 0)
|
||||
{
|
||||
stormId = stormIdPacket->storm_id(0);
|
||||
hoverText = BuildHoverText(sti, stormId);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger_->warn("Invalid Storm ID Packet");
|
||||
|
||||
stormId = "?";
|
||||
hoverText.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void OverlayProductLayer::Impl::HandleScitDataPacket(
|
||||
const std::shared_ptr<const wsr88d::rpg::StormTrackingInformationMessage>&
|
||||
sti,
|
||||
const std::shared_ptr<const wsr88d::rpg::Packet>& packet,
|
||||
const common::Coordinate& center,
|
||||
const std::string& stormId,
|
||||
const std::string& hoverText,
|
||||
std::shared_ptr<gl::draw::LinkedVectors>& linkedVectors)
|
||||
{
|
||||
auto scitDataPacket =
|
||||
std::dynamic_pointer_cast<const wsr88d::rpg::ScitDataPacket>(packet);
|
||||
|
||||
if (scitDataPacket != nullptr)
|
||||
{
|
||||
boost::gil::rgba32f_pixel_t color {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
|
||||
units::length::nautical_miles<float> tickRadius {0.5f};
|
||||
units::length::nautical_miles<float> tickRadiusIncrement {0.0f};
|
||||
|
||||
auto stiRecord = sti->sti_record(stormId);
|
||||
|
||||
if (scitDataPacket->packet_code() ==
|
||||
static_cast<std::uint16_t>(wsr88d::rpg::PacketCode::ScitPastData))
|
||||
{
|
||||
if (!stiPastEnabled_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is past data, the default tick radius and increment with a
|
||||
// darker color
|
||||
color = {0.5f, 0.5f, 0.5f, 1.0f};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!stiForecastEnabled_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (stiRecord != nullptr && stiRecord->meanError_.has_value())
|
||||
{
|
||||
// If this is forecast data, use the mean error as the radius
|
||||
// (minimum of the default value), incrementing by the mean error
|
||||
tickRadiusIncrement = stiRecord->meanError_.value();
|
||||
tickRadius = std::max(tickRadius, tickRadiusIncrement);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& subpacket : scitDataPacket->packet_list())
|
||||
{
|
||||
switch (subpacket->packet_code())
|
||||
{
|
||||
case static_cast<std::uint16_t>(
|
||||
wsr88d::rpg::PacketCode::LinkedVectorNoValue):
|
||||
HandleLinkedVectorPacket(subpacket,
|
||||
center,
|
||||
hoverText,
|
||||
color,
|
||||
tickRadius,
|
||||
tickRadiusIncrement,
|
||||
linkedVectors);
|
||||
break;
|
||||
|
||||
default:
|
||||
logger_->trace("Ignoring SCIT subpacket type: {}",
|
||||
subpacket->packet_code());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger_->warn("Invalid SCIT Data Packet");
|
||||
}
|
||||
}
|
||||
|
||||
void OverlayProductLayer::Impl::HandleLinkedVectorPacket(
|
||||
const std::shared_ptr<const wsr88d::rpg::Packet>& packet,
|
||||
const common::Coordinate& center,
|
||||
const std::string& hoverText,
|
||||
boost::gil::rgba32f_pixel_t color,
|
||||
units::length::nautical_miles<float> tickRadius,
|
||||
units::length::nautical_miles<float> tickRadiusIncrement,
|
||||
std::shared_ptr<gl::draw::LinkedVectors>& linkedVectors)
|
||||
{
|
||||
auto linkedVectorPacket =
|
||||
std::dynamic_pointer_cast<const wsr88d::rpg::LinkedVectorPacket>(packet);
|
||||
|
||||
if (linkedVectorPacket != nullptr)
|
||||
{
|
||||
auto di = linkedVectors->AddVector(center, linkedVectorPacket);
|
||||
gl::draw::LinkedVectors::SetVectorWidth(di, 1.0f);
|
||||
gl::draw::LinkedVectors::SetVectorModulate(di, color);
|
||||
gl::draw::LinkedVectors::SetVectorHoverText(di, hoverText);
|
||||
gl::draw::LinkedVectors::SetVectorTicksEnabled(di, true);
|
||||
gl::draw::LinkedVectors::SetVectorTickRadius(di, tickRadius);
|
||||
gl::draw::LinkedVectors::SetVectorTickRadiusIncrement(
|
||||
di, tickRadiusIncrement);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger_->warn("Invalid Linked Vector Packet");
|
||||
}
|
||||
}
|
||||
|
||||
std::string OverlayProductLayer::Impl::BuildHoverText(
|
||||
const std::shared_ptr<
|
||||
const scwx::wsr88d::rpg::StormTrackingInformationMessage>& sti,
|
||||
std::string& stormId)
|
||||
{
|
||||
std::string hoverText = fmt::format("Storm ID: {}", stormId);
|
||||
|
||||
auto stiRecord = sti->sti_record(stormId);
|
||||
|
||||
if (stiRecord != nullptr)
|
||||
{
|
||||
if (stiRecord->direction_.has_value() && stiRecord->speed_.has_value())
|
||||
{
|
||||
hoverText +=
|
||||
fmt::format("\nMovement: {} @ {}",
|
||||
units::to_string(stiRecord->direction_.value()),
|
||||
units::to_string(stiRecord->speed_.value()));
|
||||
}
|
||||
|
||||
if (stiRecord->maxDbz_.has_value() &&
|
||||
stiRecord->maxDbzHeight_.has_value())
|
||||
{
|
||||
hoverText +=
|
||||
fmt::format("\nMax dBZ: {} ({} kft)",
|
||||
stiRecord->maxDbz_.value(),
|
||||
stiRecord->maxDbzHeight_.value().value() / 1000.0f);
|
||||
}
|
||||
|
||||
if (stiRecord->forecastError_.has_value())
|
||||
{
|
||||
hoverText +=
|
||||
fmt::format("\nForecast Error: {}",
|
||||
units::to_string(stiRecord->forecastError_.value()));
|
||||
}
|
||||
|
||||
if (stiRecord->meanError_.has_value())
|
||||
{
|
||||
hoverText +=
|
||||
fmt::format("\nMean Error: {}",
|
||||
units::to_string(stiRecord->meanError_.value()));
|
||||
}
|
||||
}
|
||||
|
||||
auto dateTime = sti->date_time();
|
||||
if (dateTime.has_value())
|
||||
{
|
||||
hoverText +=
|
||||
fmt::format("\nDate/Time: {}", util::TimeString(dateTime.value()));
|
||||
}
|
||||
|
||||
auto forecastInterval = sti->forecast_interval();
|
||||
if (forecastInterval.has_value())
|
||||
{
|
||||
hoverText += fmt::format("\nForecast Interval: {} min",
|
||||
forecastInterval.value().count());
|
||||
}
|
||||
|
||||
return hoverText;
|
||||
}
|
||||
|
||||
bool OverlayProductLayer::RunMousePicking(
|
||||
const QMapLibreGL::CustomLayerRenderParameters& params,
|
||||
const QPointF& mouseLocalPos,
|
||||
const QPointF& mouseGlobalPos,
|
||||
const glm::vec2& mouseCoords,
|
||||
const common::Coordinate& mouseGeoCoords,
|
||||
std::shared_ptr<types::EventHandler>& eventHandler)
|
||||
{
|
||||
return DrawLayer::RunMousePicking(params,
|
||||
mouseLocalPos,
|
||||
mouseGlobalPos,
|
||||
mouseCoords,
|
||||
mouseGeoCoords,
|
||||
eventHandler);
|
||||
}
|
||||
|
||||
} // namespace map
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
37
scwx-qt/source/scwx/qt/map/overlay_product_layer.hpp
Normal file
37
scwx-qt/source/scwx/qt/map/overlay_product_layer.hpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/qt/map/draw_layer.hpp>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace map
|
||||
{
|
||||
|
||||
class OverlayProductLayer : public DrawLayer
|
||||
{
|
||||
public:
|
||||
explicit OverlayProductLayer(std::shared_ptr<MapContext> context);
|
||||
~OverlayProductLayer();
|
||||
|
||||
void Initialize() override final;
|
||||
void Render(const QMapLibreGL::CustomLayerRenderParameters&) override final;
|
||||
void Deinitialize() override final;
|
||||
|
||||
bool RunMousePicking(
|
||||
const QMapLibreGL::CustomLayerRenderParameters& params,
|
||||
const QPointF& mouseLocalPos,
|
||||
const QPointF& mouseGlobalPos,
|
||||
const glm::vec2& mouseCoords,
|
||||
const common::Coordinate& mouseGeoCoords,
|
||||
std::shared_ptr<types::EventHandler>& eventHandler) override final;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace map
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
#include <scwx/qt/gl/shader_program.hpp>
|
||||
#include <scwx/qt/util/maplibre.hpp>
|
||||
#include <scwx/qt/util/tooltip.hpp>
|
||||
#include <scwx/qt/view/radar_product_view.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
#include <execution>
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ static const std::vector<types::LayerInfo> kDefaultLayers_ {
|
|||
{types::LayerType::Alert, awips::Phenomenon::FlashFlood, true},
|
||||
{types::LayerType::Alert, awips::Phenomenon::Marine, true},
|
||||
{types::LayerType::Map, types::MapLayer::MapSymbology, false},
|
||||
{types::LayerType::Data, types::DataLayer::OverlayProduct, true},
|
||||
{types::LayerType::Radar, std::monostate {}, true},
|
||||
{types::LayerType::Map, types::MapLayer::MapUnderlay, false},
|
||||
};
|
||||
|
|
@ -243,9 +244,6 @@ void LayerModel::Impl::ValidateLayerSettings(types::LayerVector& layers)
|
|||
|
||||
// Validate immovable layers
|
||||
std::vector<types::LayerVector::iterator> immovableIterators {};
|
||||
types::LayerVector::iterator radarSiteIterator {};
|
||||
types::LayerVector::iterator mapSymbologyIterator {};
|
||||
types::LayerVector::iterator mapUnderlayIterator {};
|
||||
for (auto& immovableLayer : kImmovableLayers_)
|
||||
{
|
||||
// Set the default displayed state for a layer that is not found
|
||||
|
|
@ -288,102 +286,35 @@ void LayerModel::Impl::ValidateLayerSettings(types::LayerVector& layers)
|
|||
it->displayed_ = displayed;
|
||||
}
|
||||
|
||||
// Store positional iterators
|
||||
if (it->type_ == types::LayerType::Information)
|
||||
{
|
||||
switch (std::get<types::InformationLayer>(it->description_))
|
||||
{
|
||||
case types::InformationLayer::RadarSite:
|
||||
radarSiteIterator = it;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (it->type_ == types::LayerType::Map)
|
||||
{
|
||||
switch (std::get<types::MapLayer>(it->description_))
|
||||
{
|
||||
case types::MapLayer::MapSymbology:
|
||||
mapSymbologyIterator = it;
|
||||
break;
|
||||
|
||||
case types::MapLayer::MapUnderlay:
|
||||
mapUnderlayIterator = it;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the immovable iterator to the list
|
||||
immovableIterators.push_back(it);
|
||||
}
|
||||
|
||||
// Validate data layers
|
||||
std::vector<types::LayerVector::iterator> dataIterators {};
|
||||
for (const auto& dataLayer : types::DataLayerIterator())
|
||||
// Validate the remainder of the default layer list
|
||||
auto previousLayer = layers.end();
|
||||
for (auto defaultIt = kDefaultLayers_.rbegin();
|
||||
defaultIt != kDefaultLayers_.rend();
|
||||
++defaultIt)
|
||||
{
|
||||
// Find the data layer
|
||||
auto it = std::find_if(layers.begin(),
|
||||
layers.end(),
|
||||
[&dataLayer](const types::LayerInfo& layer)
|
||||
{
|
||||
return layer.type_ == types::LayerType::Data &&
|
||||
std::get<types::DataLayer>(
|
||||
layer.description_) == dataLayer;
|
||||
});
|
||||
// Find the default layer in the current layer list
|
||||
auto currentIt =
|
||||
std::find_if(layers.begin(),
|
||||
layers.end(),
|
||||
[&defaultIt](const types::LayerInfo& layer)
|
||||
{
|
||||
return layer.type_ == defaultIt->type_ &&
|
||||
layer.description_ == defaultIt->description_;
|
||||
});
|
||||
|
||||
if (it == layers.end())
|
||||
// If the default layer was not found in the current layer list
|
||||
if (currentIt == layers.end())
|
||||
{
|
||||
// If this is the first data layer, insert after the radar site layer,
|
||||
// otherwise, insert after the previous data layer
|
||||
types::LayerVector::iterator insertPosition =
|
||||
dataIterators.empty() ? radarSiteIterator + 1 :
|
||||
dataIterators.back() + 1;
|
||||
it =
|
||||
layers.insert(insertPosition, {types::LayerType::Data, dataLayer});
|
||||
// Insert before the previously found layer
|
||||
currentIt = layers.insert(previousLayer, *defaultIt);
|
||||
}
|
||||
|
||||
dataIterators.push_back(it);
|
||||
}
|
||||
|
||||
// Validate alert layers
|
||||
std::vector<types::LayerVector::iterator> alertIterators {};
|
||||
for (auto& phenomenon : kAlertPhenomena_)
|
||||
{
|
||||
// Find the alert layer
|
||||
auto it = std::find_if(layers.begin(),
|
||||
layers.end(),
|
||||
[&phenomenon](const types::LayerInfo& layer)
|
||||
{
|
||||
return layer.type_ == types::LayerType::Alert &&
|
||||
std::get<awips::Phenomenon>(
|
||||
layer.description_) == phenomenon;
|
||||
});
|
||||
|
||||
if (it == layers.end())
|
||||
{
|
||||
// Insert before the map symbology layer
|
||||
it = layers.insert(mapSymbologyIterator,
|
||||
{types::LayerType::Alert, phenomenon});
|
||||
}
|
||||
|
||||
alertIterators.push_back(it);
|
||||
}
|
||||
|
||||
// Validate the radar layer
|
||||
auto it = std::find_if(layers.begin(),
|
||||
layers.end(),
|
||||
[](const types::LayerInfo& layer)
|
||||
{ return layer.type_ == types::LayerType::Radar; });
|
||||
if (it == layers.end())
|
||||
{
|
||||
// Insert before the map underlay layer
|
||||
it = layers.insert(mapUnderlayIterator,
|
||||
{types::LayerType::Radar, std::monostate {}});
|
||||
// Store the current layer as the previous
|
||||
previousLayer = currentIt;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
75
scwx-qt/source/scwx/qt/settings/product_settings.cpp
Normal file
75
scwx-qt/source/scwx/qt/settings/product_settings.cpp
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#include <scwx/qt/settings/product_settings.hpp>
|
||||
#include <scwx/qt/settings/settings_container.hpp>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace settings
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ = "scwx::qt::settings::product_settings";
|
||||
|
||||
class ProductSettings::Impl
|
||||
{
|
||||
public:
|
||||
explicit Impl()
|
||||
{
|
||||
stiForecastEnabled_.SetDefault(true);
|
||||
stiPastEnabled_.SetDefault(true);
|
||||
}
|
||||
|
||||
~Impl() {}
|
||||
|
||||
SettingsVariable<bool> stiForecastEnabled_ {"sti_forecast_enabled"};
|
||||
SettingsVariable<bool> stiPastEnabled_ {"sti_past_enabled"};
|
||||
};
|
||||
|
||||
ProductSettings::ProductSettings() :
|
||||
SettingsCategory("product"), p(std::make_unique<Impl>())
|
||||
{
|
||||
RegisterVariables({&p->stiForecastEnabled_, &p->stiPastEnabled_});
|
||||
SetDefaults();
|
||||
}
|
||||
ProductSettings::~ProductSettings() = default;
|
||||
|
||||
ProductSettings::ProductSettings(ProductSettings&&) noexcept = default;
|
||||
ProductSettings&
|
||||
ProductSettings::operator=(ProductSettings&&) noexcept = default;
|
||||
|
||||
SettingsVariable<bool>& ProductSettings::sti_forecast_enabled() const
|
||||
{
|
||||
return p->stiForecastEnabled_;
|
||||
}
|
||||
|
||||
SettingsVariable<bool>& ProductSettings::sti_past_enabled() const
|
||||
{
|
||||
return p->stiPastEnabled_;
|
||||
}
|
||||
|
||||
bool ProductSettings::Shutdown()
|
||||
{
|
||||
bool dataChanged = false;
|
||||
|
||||
// Commit settings that are managed separate from the settings dialog
|
||||
dataChanged |= p->stiForecastEnabled_.Commit();
|
||||
dataChanged |= p->stiPastEnabled_.Commit();
|
||||
|
||||
return dataChanged;
|
||||
}
|
||||
|
||||
ProductSettings& ProductSettings::Instance()
|
||||
{
|
||||
static ProductSettings generalSettings_;
|
||||
return generalSettings_;
|
||||
}
|
||||
|
||||
bool operator==(const ProductSettings& lhs, const ProductSettings& rhs)
|
||||
{
|
||||
return (lhs.p->stiForecastEnabled_ == rhs.p->stiForecastEnabled_ &&
|
||||
lhs.p->stiPastEnabled_ == rhs.p->stiPastEnabled_);
|
||||
}
|
||||
|
||||
} // namespace settings
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
45
scwx-qt/source/scwx/qt/settings/product_settings.hpp
Normal file
45
scwx-qt/source/scwx/qt/settings/product_settings.hpp
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/qt/settings/settings_category.hpp>
|
||||
#include <scwx/qt/settings/settings_variable.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace settings
|
||||
{
|
||||
|
||||
class ProductSettings : public SettingsCategory
|
||||
{
|
||||
public:
|
||||
explicit ProductSettings();
|
||||
~ProductSettings();
|
||||
|
||||
ProductSettings(const ProductSettings&) = delete;
|
||||
ProductSettings& operator=(const ProductSettings&) = delete;
|
||||
|
||||
ProductSettings(ProductSettings&&) noexcept;
|
||||
ProductSettings& operator=(ProductSettings&&) noexcept;
|
||||
|
||||
SettingsVariable<bool>& sti_forecast_enabled() const;
|
||||
SettingsVariable<bool>& sti_past_enabled() const;
|
||||
|
||||
static ProductSettings& Instance();
|
||||
|
||||
friend bool operator==(const ProductSettings& lhs,
|
||||
const ProductSettings& rhs);
|
||||
|
||||
bool Shutdown();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace settings
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
|
|
@ -23,7 +23,9 @@ static const std::unordered_map<LayerType, std::string> layerTypeName_ {
|
|||
{LayerType::Unknown, "?"}};
|
||||
|
||||
static const std::unordered_map<DataLayer, std::string> dataLayerName_ {
|
||||
{DataLayer::RadarRange, "Radar Range"}, {DataLayer::Unknown, "?"}};
|
||||
{DataLayer::OverlayProduct, "Overlay Product"},
|
||||
{DataLayer::RadarRange, "Radar Range"},
|
||||
{DataLayer::Unknown, "?"}};
|
||||
|
||||
static const std::unordered_map<InformationLayer, std::string>
|
||||
informationLayerName_ {{InformationLayer::MapOverlay, "Map Overlay"},
|
||||
|
|
|
|||
|
|
@ -31,11 +31,12 @@ enum class LayerType
|
|||
|
||||
enum class DataLayer
|
||||
{
|
||||
OverlayProduct,
|
||||
RadarRange,
|
||||
Unknown
|
||||
};
|
||||
typedef scwx::util::
|
||||
Iterator<DataLayer, DataLayer::RadarRange, DataLayer::RadarRange>
|
||||
Iterator<DataLayer, DataLayer::OverlayProduct, DataLayer::RadarRange>
|
||||
DataLayerIterator;
|
||||
|
||||
enum class InformationLayer
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
#include <scwx/qt/ui/level3_products_widget.hpp>
|
||||
#include <scwx/qt/ui/flow_layout.hpp>
|
||||
#include <scwx/qt/settings/product_settings.hpp>
|
||||
#include <scwx/qt/settings/settings_interface.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
#include <execution>
|
||||
#include <shared_mutex>
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QMenu>
|
||||
#include <QToolButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
|
|
@ -25,13 +29,17 @@ class Level3ProductsWidgetImpl : public QObject
|
|||
public:
|
||||
explicit Level3ProductsWidgetImpl(Level3ProductsWidget* self) :
|
||||
self_ {self},
|
||||
layout_ {new ui::FlowLayout(self)},
|
||||
layout_ {new QVBoxLayout(self)},
|
||||
productsWidget_ {new QWidget(self)},
|
||||
productsLayout_ {new ui::FlowLayout(productsWidget_)},
|
||||
categoryButtons_ {},
|
||||
productTiltMap_ {},
|
||||
awipsProductMap_ {},
|
||||
awipsProductMutex_ {}
|
||||
{
|
||||
layout_->setContentsMargins(0, 0, 0, 0);
|
||||
layout_->addWidget(productsWidget_);
|
||||
productsLayout_->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
for (common::Level3ProductCategory category :
|
||||
common::Level3ProductCategoryIterator())
|
||||
|
|
@ -42,7 +50,7 @@ public:
|
|||
toolButton->setStatusTip(
|
||||
tr(common::GetLevel3CategoryDescription(category).c_str()));
|
||||
toolButton->setPopupMode(QToolButton::MenuButtonPopup);
|
||||
layout_->addWidget(toolButton);
|
||||
productsLayout_->addWidget(toolButton);
|
||||
categoryButtons_.push_back(toolButton);
|
||||
|
||||
QObject::connect(toolButton,
|
||||
|
|
@ -99,6 +107,26 @@ public:
|
|||
|
||||
toolButton->setEnabled(false);
|
||||
}
|
||||
|
||||
// Storm Tracking Information
|
||||
QCheckBox* stiPastEnableCheckBox = new QCheckBox();
|
||||
QCheckBox* stiForecastEnableCheckBox = new QCheckBox();
|
||||
|
||||
stiPastEnableCheckBox->setText(QObject::tr("Storm Tracks (Past)"));
|
||||
stiForecastEnableCheckBox->setText(
|
||||
QObject::tr("Storm Tracks (Forecast)"));
|
||||
|
||||
layout_->addWidget(stiPastEnableCheckBox);
|
||||
layout_->addWidget(stiForecastEnableCheckBox);
|
||||
|
||||
auto& productSettings = settings::ProductSettings::Instance();
|
||||
|
||||
stiPastEnabled_.SetSettingsVariable(productSettings.sti_past_enabled());
|
||||
stiForecastEnabled_.SetSettingsVariable(
|
||||
productSettings.sti_forecast_enabled());
|
||||
|
||||
stiPastEnabled_.SetEditWidget(stiPastEnableCheckBox);
|
||||
stiForecastEnabled_.SetEditWidget(stiForecastEnableCheckBox);
|
||||
}
|
||||
~Level3ProductsWidgetImpl() = default;
|
||||
|
||||
|
|
@ -109,6 +137,8 @@ public:
|
|||
|
||||
Level3ProductsWidget* self_;
|
||||
QLayout* layout_;
|
||||
QWidget* productsWidget_;
|
||||
QLayout* productsLayout_;
|
||||
std::list<QToolButton*> categoryButtons_;
|
||||
std::unordered_map<common::Level3ProductCategory,
|
||||
std::unordered_map<std::string, QMenu*>>
|
||||
|
|
@ -118,6 +148,9 @@ public:
|
|||
|
||||
std::unordered_map<QAction*, std::string> awipsProductMap_;
|
||||
std::shared_mutex awipsProductMutex_;
|
||||
|
||||
settings::SettingsInterface<bool> stiPastEnabled_ {};
|
||||
settings::SettingsInterface<bool> stiForecastEnabled_ {};
|
||||
};
|
||||
|
||||
Level3ProductsWidget::Level3ProductsWidget(QWidget* parent) :
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
#include <scwx/qt/util/geographic_lib.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
#include <numbers>
|
||||
|
||||
#include <GeographicLib/Gnomonic.hpp>
|
||||
#include <geos/algorithm/PointLocation.h>
|
||||
#include <geos/geom/CoordinateSequence.h>
|
||||
|
|
@ -90,6 +92,42 @@ GetAngle(double lat1, double lon1, double lat2, double lon2)
|
|||
return units::angle::degrees<double> {azi1};
|
||||
}
|
||||
|
||||
common::Coordinate GetCoordinate(const common::Coordinate& center,
|
||||
units::angle::degrees<double> angle,
|
||||
units::length::meters<double> distance)
|
||||
{
|
||||
double latitude;
|
||||
double longitude;
|
||||
|
||||
DefaultGeodesic().Direct(center.latitude_,
|
||||
center.longitude_,
|
||||
angle.value(),
|
||||
distance.value(),
|
||||
latitude,
|
||||
longitude);
|
||||
|
||||
return {latitude, longitude};
|
||||
}
|
||||
|
||||
common::Coordinate GetCoordinate(const common::Coordinate& center,
|
||||
units::meters<double> i,
|
||||
units::meters<double> j)
|
||||
{
|
||||
// Calculate polar coordinates based on i and j
|
||||
const double angle =
|
||||
std::atan2(i.value(), j.value()) * 180.0 / std::numbers::pi;
|
||||
const double range =
|
||||
std::sqrt(i.value() * i.value() + j.value() * j.value());
|
||||
|
||||
double latitude;
|
||||
double longitude;
|
||||
|
||||
DefaultGeodesic().Direct(
|
||||
center.latitude_, center.longitude_, angle, range, latitude, longitude);
|
||||
|
||||
return {latitude, longitude};
|
||||
}
|
||||
|
||||
units::length::meters<double>
|
||||
GetDistance(double lat1, double lon1, double lat2, double lon2)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -49,6 +49,34 @@ bool AreaContainsPoint(const std::vector<common::Coordinate>& area,
|
|||
units::angle::degrees<double>
|
||||
GetAngle(double lat1, double lon1, double lat2, double lon2);
|
||||
|
||||
/**
|
||||
* Get a coordinate from a polar coordinate offset.
|
||||
*
|
||||
* @param [in] center The center coordinate from which the angle and distance
|
||||
* are given
|
||||
* @param [in] angle The angle at which the destination coordinate lies
|
||||
* @param [in] distance The distance from the center coordinate to the
|
||||
* destination coordinate
|
||||
*
|
||||
* @return offset coordinate
|
||||
*/
|
||||
common::Coordinate GetCoordinate(const common::Coordinate& center,
|
||||
units::angle::degrees<double> angle,
|
||||
units::length::meters<double> distance);
|
||||
|
||||
/**
|
||||
* Get a coordinate from an (i, j) offset.
|
||||
*
|
||||
* @param [in] center The center coordinate from which i and j are offset
|
||||
* @param [in] i The easting offset in meters
|
||||
* @param [in] j The northing offset in meters
|
||||
*
|
||||
* @return offset coordinate
|
||||
*/
|
||||
common::Coordinate GetCoordinate(const common::Coordinate& center,
|
||||
units::meters<double> i,
|
||||
units::meters<double> j);
|
||||
|
||||
/**
|
||||
* Get the distance between two points.
|
||||
*
|
||||
|
|
|
|||
318
scwx-qt/source/scwx/qt/view/overlay_product_view.cpp
Normal file
318
scwx-qt/source/scwx/qt/view/overlay_product_view.cpp
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
#include <scwx/qt/view/overlay_product_view.hpp>
|
||||
#include <scwx/qt/manager/radar_product_manager.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/uuid/random_generator.hpp>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
{
|
||||
namespace view
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ = "scwx::qt::view::overlay_product_view";
|
||||
static const auto logger_ = util::Logger::Create(logPrefix_);
|
||||
|
||||
static const std::string kNst_ = "NST";
|
||||
|
||||
class OverlayProductView::Impl
|
||||
{
|
||||
public:
|
||||
explicit Impl(OverlayProductView* self) : self_ {self} {}
|
||||
~Impl() { threadPool_.join(); }
|
||||
|
||||
void ConnectRadarProductManager();
|
||||
void DisconnectRadarProductManager();
|
||||
void LoadProduct(const std::string& product,
|
||||
std::chrono::system_clock::time_point time,
|
||||
bool autoUpdate);
|
||||
void ResetProducts();
|
||||
void Update(const std::string& product);
|
||||
void UpdateAutoRefresh(bool enabled) const;
|
||||
|
||||
OverlayProductView* self_;
|
||||
boost::uuids::uuid uuid_ {boost::uuids::random_generator()()};
|
||||
|
||||
boost::asio::thread_pool threadPool_ {1u};
|
||||
|
||||
bool autoRefreshEnabled_ {false};
|
||||
bool autoUpdateEnabled_ {false};
|
||||
|
||||
std::chrono::system_clock::time_point selectedTime_ {};
|
||||
|
||||
std::shared_ptr<manager::RadarProductManager> radarProductManager_ {nullptr};
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<wsr88d::rpg::Level3Message>>
|
||||
messageMap_ {};
|
||||
std::mutex messageMutex_ {};
|
||||
};
|
||||
|
||||
OverlayProductView::OverlayProductView() : p(std::make_unique<Impl>(this)) {};
|
||||
OverlayProductView::~OverlayProductView() = default;
|
||||
|
||||
std::shared_ptr<manager::RadarProductManager>
|
||||
OverlayProductView::radar_product_manager() const
|
||||
{
|
||||
return p->radarProductManager_;
|
||||
}
|
||||
|
||||
std::shared_ptr<wsr88d::rpg::Level3Message>
|
||||
OverlayProductView::radar_product_message(const std::string& product) const
|
||||
{
|
||||
std::unique_lock lock {p->messageMutex_};
|
||||
|
||||
auto it = p->messageMap_.find(product);
|
||||
if (it != p->messageMap_.cend())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::chrono::system_clock::time_point OverlayProductView::selected_time() const
|
||||
{
|
||||
return p->selectedTime_;
|
||||
}
|
||||
|
||||
void OverlayProductView::set_radar_product_manager(
|
||||
const std::shared_ptr<manager::RadarProductManager>& radarProductManager)
|
||||
{
|
||||
p->UpdateAutoRefresh(false);
|
||||
p->DisconnectRadarProductManager();
|
||||
p->ResetProducts();
|
||||
|
||||
p->radarProductManager_ = radarProductManager;
|
||||
|
||||
p->ConnectRadarProductManager();
|
||||
p->UpdateAutoRefresh(p->autoRefreshEnabled_);
|
||||
}
|
||||
|
||||
void OverlayProductView::Impl::ConnectRadarProductManager()
|
||||
{
|
||||
connect(radarProductManager_.get(),
|
||||
&manager::RadarProductManager::DataReloaded,
|
||||
self_,
|
||||
[this](std::shared_ptr<types::RadarProductRecord> record)
|
||||
{
|
||||
if (record->radar_product_group() ==
|
||||
common::RadarProductGroup::Level3 &&
|
||||
record->radar_product() == kNst_ &&
|
||||
std::chrono::floor<std::chrono::seconds>(record->time()) ==
|
||||
selectedTime_)
|
||||
{
|
||||
// If the data associated with the currently selected time is
|
||||
// reloaded, update the view
|
||||
Update(record->radar_product());
|
||||
}
|
||||
});
|
||||
|
||||
connect(
|
||||
radarProductManager_.get(),
|
||||
&manager::RadarProductManager::NewDataAvailable,
|
||||
self_,
|
||||
[this](common::RadarProductGroup group,
|
||||
const std::string& product,
|
||||
std::chrono::system_clock::time_point latestTime)
|
||||
{
|
||||
if (autoRefreshEnabled_ &&
|
||||
group == common::RadarProductGroup::Level3 && product == kNst_)
|
||||
{
|
||||
LoadProduct(product, latestTime, autoUpdateEnabled_);
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void OverlayProductView::Impl::DisconnectRadarProductManager()
|
||||
{
|
||||
if (radarProductManager_ != nullptr)
|
||||
{
|
||||
disconnect(radarProductManager_.get(),
|
||||
&manager::RadarProductManager::NewDataAvailable,
|
||||
self_,
|
||||
nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void OverlayProductView::Impl::LoadProduct(
|
||||
const std::string& product,
|
||||
std::chrono::system_clock::time_point time,
|
||||
bool autoUpdate)
|
||||
{
|
||||
logger_->debug(
|
||||
"Load Product: {}, {}, {}", product, util::TimeString(time), autoUpdate);
|
||||
|
||||
// Create file request
|
||||
std::shared_ptr<request::NexradFileRequest> request =
|
||||
std::make_shared<request::NexradFileRequest>(
|
||||
radarProductManager_->radar_id());
|
||||
|
||||
if (autoUpdate)
|
||||
{
|
||||
connect(
|
||||
request.get(),
|
||||
&request::NexradFileRequest::RequestComplete,
|
||||
self_,
|
||||
[=, this](std::shared_ptr<request::NexradFileRequest> request)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
// Select loaded record
|
||||
const auto& record = request->radar_product_record();
|
||||
|
||||
std::shared_ptr<wsr88d::Level3File> level3File = nullptr;
|
||||
std::shared_ptr<wsr88d::rpg::Level3Message> message = nullptr;
|
||||
|
||||
if (record != nullptr)
|
||||
{
|
||||
level3File = record->level3_file();
|
||||
}
|
||||
if (level3File != nullptr)
|
||||
{
|
||||
message = level3File->message();
|
||||
}
|
||||
|
||||
// Validate record
|
||||
if (message != nullptr)
|
||||
{
|
||||
const auto& header = message->header();
|
||||
auto productTime = util::TimePoint(
|
||||
header.date_of_message(), header.time_of_message() * 1000);
|
||||
|
||||
// If the record is from the last 30 minutes
|
||||
if (productTime + 30min >= std::chrono::system_clock::now() ||
|
||||
(selectedTime_ != std::chrono::system_clock::time_point {} &&
|
||||
productTime + 30min >= selectedTime_))
|
||||
{
|
||||
// Store loaded record
|
||||
std::unique_lock lock {messageMutex_};
|
||||
|
||||
auto it = messageMap_.find(product);
|
||||
if (it == messageMap_.cend() || it->second != message)
|
||||
{
|
||||
messageMap_.insert_or_assign(product, message);
|
||||
|
||||
lock.unlock();
|
||||
|
||||
Q_EMIT self_->ProductUpdated(product);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If product is more than 30 minutes old, discard
|
||||
std::unique_lock lock {messageMutex_};
|
||||
std::size_t elementsRemoved = messageMap_.erase(product);
|
||||
lock.unlock();
|
||||
|
||||
if (elementsRemoved > 0)
|
||||
{
|
||||
Q_EMIT self_->ProductUpdated(product);
|
||||
}
|
||||
|
||||
logger_->trace("Discarding stale data: {}",
|
||||
util::TimeString(productTime));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the product doesn't exist, erase the stale product
|
||||
std::unique_lock lock {messageMutex_};
|
||||
std::size_t elementsRemoved = messageMap_.erase(product);
|
||||
lock.unlock();
|
||||
|
||||
if (elementsRemoved > 0)
|
||||
{
|
||||
Q_EMIT self_->ProductUpdated(product);
|
||||
}
|
||||
|
||||
logger_->trace("Removing stale product");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Load file
|
||||
boost::asio::post(
|
||||
threadPool_,
|
||||
[=, this]()
|
||||
{ radarProductManager_->LoadLevel3Data(product, time, request); });
|
||||
}
|
||||
|
||||
void OverlayProductView::Impl::ResetProducts()
|
||||
{
|
||||
std::unique_lock lock {messageMutex_};
|
||||
messageMap_.clear();
|
||||
lock.unlock();
|
||||
|
||||
Q_EMIT self_->ProductUpdated(kNst_);
|
||||
}
|
||||
|
||||
void OverlayProductView::SelectTime(std::chrono::system_clock::time_point time)
|
||||
{
|
||||
if (time != p->selectedTime_)
|
||||
{
|
||||
p->selectedTime_ = time;
|
||||
p->Update(kNst_);
|
||||
}
|
||||
}
|
||||
|
||||
void OverlayProductView::SetAutoRefresh(bool enabled)
|
||||
{
|
||||
if (p->autoRefreshEnabled_ != enabled)
|
||||
{
|
||||
p->autoRefreshEnabled_ = enabled;
|
||||
p->UpdateAutoRefresh(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
void OverlayProductView::Impl::Update(const std::string& product)
|
||||
{
|
||||
// Retrieve message from Radar Product Manager
|
||||
std::shared_ptr<wsr88d::rpg::Level3Message> message;
|
||||
std::chrono::system_clock::time_point requestedTime {selectedTime_};
|
||||
std::chrono::system_clock::time_point foundTime;
|
||||
std::tie(message, foundTime) =
|
||||
radarProductManager_->GetLevel3Data(product, requestedTime);
|
||||
|
||||
if (message == nullptr)
|
||||
{
|
||||
logger_->debug("{} data not found", product);
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock lock {messageMutex_};
|
||||
|
||||
// Update message in map
|
||||
auto it = messageMap_.find(product);
|
||||
if (it == messageMap_.cend() || it->second != message)
|
||||
{
|
||||
messageMap_.insert_or_assign(product, message);
|
||||
|
||||
lock.unlock();
|
||||
|
||||
Q_EMIT self_->ProductUpdated(product);
|
||||
}
|
||||
}
|
||||
|
||||
void OverlayProductView::Impl::UpdateAutoRefresh(bool enabled) const
|
||||
{
|
||||
if (radarProductManager_ != nullptr)
|
||||
{
|
||||
radarProductManager_->EnableRefresh(
|
||||
common::RadarProductGroup::Level3, kNst_, enabled, uuid_);
|
||||
}
|
||||
}
|
||||
|
||||
void OverlayProductView::SetAutoUpdate(bool enabled)
|
||||
{
|
||||
p->autoUpdateEnabled_ = enabled;
|
||||
}
|
||||
|
||||
} // namespace view
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
62
scwx-qt/source/scwx/qt/view/overlay_product_view.hpp
Normal file
62
scwx-qt/source/scwx/qt/view/overlay_product_view.hpp
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace wsr88d
|
||||
{
|
||||
namespace rpg
|
||||
{
|
||||
|
||||
class Level3Message;
|
||||
|
||||
} // namespace rpg
|
||||
} // namespace wsr88d
|
||||
|
||||
namespace qt
|
||||
{
|
||||
namespace manager
|
||||
{
|
||||
|
||||
class RadarProductManager;
|
||||
|
||||
} // namespace manager
|
||||
|
||||
namespace view
|
||||
{
|
||||
|
||||
class OverlayProductView : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit OverlayProductView();
|
||||
virtual ~OverlayProductView();
|
||||
|
||||
std::shared_ptr<manager::RadarProductManager> radar_product_manager() const;
|
||||
std::shared_ptr<wsr88d::rpg::Level3Message>
|
||||
radar_product_message(const std::string& product) const;
|
||||
std::chrono::system_clock::time_point selected_time() const;
|
||||
|
||||
void set_radar_product_manager(
|
||||
const std::shared_ptr<manager::RadarProductManager>& radarProductManager);
|
||||
|
||||
void SelectTime(std::chrono::system_clock::time_point time);
|
||||
void SetAutoRefresh(bool enabled);
|
||||
void SetAutoUpdate(bool enabled);
|
||||
|
||||
signals:
|
||||
void ProductUpdated(std::string product);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace view
|
||||
} // namespace qt
|
||||
} // namespace scwx
|
||||
|
|
@ -150,11 +150,6 @@ RadarProductView::GetDescriptionFields() const
|
|||
return {};
|
||||
}
|
||||
|
||||
std::chrono::system_clock::time_point RadarProductView::GetSelectedTime() const
|
||||
{
|
||||
return p->selectedTime_;
|
||||
}
|
||||
|
||||
void RadarProductView::ComputeSweep()
|
||||
{
|
||||
logger_->debug("ComputeSweep()");
|
||||
|
|
|
|||
|
|
@ -76,8 +76,6 @@ public:
|
|||
GetDataLevelCode(std::uint16_t level) const = 0;
|
||||
virtual std::optional<float> GetDataValue(std::uint16_t level) const = 0;
|
||||
|
||||
std::chrono::system_clock::time_point GetSelectedTime() const;
|
||||
|
||||
virtual std::vector<std::pair<std::string, std::string>>
|
||||
GetDescriptionFields() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 90331f7654586302b223d88329846929514bc883
|
||||
Subproject commit 0446ff708b387728faf8d2dbb3862a757067c2ee
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -29,5 +30,14 @@ std::vector<std::string> ParseTokens(const std::string& s,
|
|||
|
||||
std::string ToString(const std::vector<std::string>& v);
|
||||
|
||||
template<typename T>
|
||||
std::optional<T> TryParseNumeric(const std::string& str);
|
||||
|
||||
#if defined(STRINGS_IMPLEMENTATION)
|
||||
template std::optional<std::uint16_t> TryParseNumeric(const std::string& str);
|
||||
template std::optional<std::uint32_t> TryParseNumeric(const std::string& str);
|
||||
template std::optional<float> TryParseNumeric(const std::string& str);
|
||||
#endif
|
||||
|
||||
} // namespace util
|
||||
} // namespace scwx
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
|
||||
#if !defined(_MSC_VER)
|
||||
# include <date/tz.h>
|
||||
|
|
@ -24,5 +25,9 @@ std::string TimeString(std::chrono::system_clock::time_point time,
|
|||
const time_zone* timeZone = nullptr,
|
||||
bool epochValid = true);
|
||||
|
||||
template<typename T>
|
||||
std::optional<std::chrono::sys_time<T>>
|
||||
TryParseDateTime(const std::string& dateTimeFormat, const std::string& str);
|
||||
|
||||
} // namespace util
|
||||
} // namespace scwx
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/awips/message.hpp>
|
||||
#include <scwx/wsr88d/rpg/packet.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
|
|
@ -31,6 +33,8 @@ public:
|
|||
|
||||
size_t data_size() const override;
|
||||
|
||||
const std::vector<std::vector<std::shared_ptr<Packet>>>& page_list() const;
|
||||
|
||||
bool Parse(std::istream& is);
|
||||
|
||||
static constexpr size_t SIZE = 102u;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include <units/length.h>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace wsr88d
|
||||
|
|
@ -21,17 +23,27 @@ public:
|
|||
explicit LinkedVectorPacket();
|
||||
~LinkedVectorPacket();
|
||||
|
||||
LinkedVectorPacket(const LinkedVectorPacket&) = delete;
|
||||
LinkedVectorPacket(const LinkedVectorPacket&) = delete;
|
||||
LinkedVectorPacket& operator=(const LinkedVectorPacket&) = delete;
|
||||
|
||||
LinkedVectorPacket(LinkedVectorPacket&&) noexcept;
|
||||
LinkedVectorPacket& operator=(LinkedVectorPacket&&) noexcept;
|
||||
|
||||
uint16_t packet_code() const override;
|
||||
uint16_t length_of_block() const;
|
||||
std::optional<uint16_t> value_of_vector() const;
|
||||
std::uint16_t packet_code() const override;
|
||||
std::uint16_t length_of_block() const;
|
||||
std::optional<std::uint16_t> value_of_vector() const;
|
||||
|
||||
size_t data_size() const override;
|
||||
std::int16_t start_i() const;
|
||||
std::int16_t start_j() const;
|
||||
std::vector<std::int16_t> end_i() const;
|
||||
std::vector<std::int16_t> end_j() const;
|
||||
|
||||
units::kilometers<double> start_i_km() const;
|
||||
units::kilometers<double> start_j_km() const;
|
||||
std::vector<units::kilometers<double>> end_i_km() const;
|
||||
std::vector<units::kilometers<double>> end_j_km() const;
|
||||
|
||||
std::size_t data_size() const override;
|
||||
|
||||
bool Parse(std::istream& is) override;
|
||||
|
||||
|
|
|
|||
64
wxdata/include/scwx/wsr88d/rpg/rpg_types.hpp
Normal file
64
wxdata/include/scwx/wsr88d/rpg/rpg_types.hpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#pragma once
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace wsr88d
|
||||
{
|
||||
namespace rpg
|
||||
{
|
||||
|
||||
enum class PacketCode : std::uint16_t
|
||||
{
|
||||
TextNoValue = 1,
|
||||
SpecialSymbol = 2,
|
||||
MesocycloneSymbol3 = 3,
|
||||
WindBarbData = 4,
|
||||
VectorArrowData = 5,
|
||||
LinkedVectorNoValue = 6,
|
||||
UnlinkedVectorNoValue = 7,
|
||||
TextUniform = 8,
|
||||
LinkedVectorUniform = 9,
|
||||
UnlinkedVectorUniform = 10,
|
||||
MesocycloneSymbol11 = 11,
|
||||
TornadoVortexSignatureSymbol = 12,
|
||||
HailPositiveSymbol = 13,
|
||||
HailProbableSymbol = 14,
|
||||
StormId = 15,
|
||||
DigitalRadialDataArray = 16,
|
||||
DigitalPrecipitationDataArray = 17,
|
||||
PrecipitationRateDataArray = 18,
|
||||
HdaHailSymbol = 19,
|
||||
PointFeatureSymbol = 20,
|
||||
CellTrendData = 21,
|
||||
CellTrendVolumeScanTimes = 22,
|
||||
ScitPastData = 23,
|
||||
ScitForecastData = 24,
|
||||
StiCircle = 25,
|
||||
ElevatedTornadoVortexSignatureSymbol = 26,
|
||||
GenericData28 = 28,
|
||||
GenericData29 = 29,
|
||||
SetColorLevel = 0x0802,
|
||||
LinkedContourVector = 0x0E03,
|
||||
UnlinkedContourVector = 0x3501,
|
||||
MapMessage0E23 = 0x0E23,
|
||||
MapMessage3521 = 0x3521,
|
||||
MapMessage4E00 = 0x4E00,
|
||||
MapMessage4E01 = 0x4E01,
|
||||
RadialData = 0xAF1F,
|
||||
RasterDataBA07 = 0xBA07,
|
||||
RasterDataBA0F = 0xBA0F
|
||||
};
|
||||
|
||||
enum class SpecialSymbol
|
||||
{
|
||||
PastStormCellPosition,
|
||||
CurrentStormCellPosition,
|
||||
ForecastStormCellPosition,
|
||||
PastMdaPosition,
|
||||
ForecastMdaPosition,
|
||||
None
|
||||
};
|
||||
|
||||
} // namespace rpg
|
||||
} // namespace wsr88d
|
||||
} // namespace scwx
|
||||
43
wxdata/include/scwx/wsr88d/rpg/scit_data_packet.hpp
Normal file
43
wxdata/include/scwx/wsr88d/rpg/scit_data_packet.hpp
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/wsr88d/rpg/special_graphic_symbol_packet.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace wsr88d
|
||||
{
|
||||
namespace rpg
|
||||
{
|
||||
|
||||
class ScitDataPacket : public SpecialGraphicSymbolPacket
|
||||
{
|
||||
public:
|
||||
explicit ScitDataPacket();
|
||||
~ScitDataPacket();
|
||||
|
||||
ScitDataPacket(const ScitDataPacket&) = delete;
|
||||
ScitDataPacket& operator=(const ScitDataPacket&) = delete;
|
||||
|
||||
ScitDataPacket(ScitDataPacket&&) noexcept;
|
||||
ScitDataPacket& operator=(ScitDataPacket&&) noexcept;
|
||||
|
||||
std::vector<std::shared_ptr<Packet>> packet_list() const;
|
||||
|
||||
std::size_t RecordCount() const override;
|
||||
|
||||
static std::shared_ptr<ScitDataPacket> Create(std::istream& is);
|
||||
|
||||
protected:
|
||||
bool ParseData(std::istream& is) override;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace rpg
|
||||
} // namespace wsr88d
|
||||
} // namespace scwx
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/wsr88d/rpg/special_graphic_symbol_packet.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace wsr88d
|
||||
{
|
||||
namespace rpg
|
||||
{
|
||||
|
||||
class ScitForecastDataPacketImpl;
|
||||
|
||||
class ScitForecastDataPacket : public SpecialGraphicSymbolPacket
|
||||
{
|
||||
public:
|
||||
explicit ScitForecastDataPacket();
|
||||
~ScitForecastDataPacket();
|
||||
|
||||
ScitForecastDataPacket(const ScitForecastDataPacket&) = delete;
|
||||
ScitForecastDataPacket& operator=(const ScitForecastDataPacket&) = delete;
|
||||
|
||||
ScitForecastDataPacket(ScitForecastDataPacket&&) noexcept;
|
||||
ScitForecastDataPacket& operator=(ScitForecastDataPacket&&) noexcept;
|
||||
|
||||
const std::vector<uint8_t>& data() const;
|
||||
|
||||
size_t RecordCount() const override;
|
||||
|
||||
static std::shared_ptr<ScitForecastDataPacket> Create(std::istream& is);
|
||||
|
||||
protected:
|
||||
bool ParseData(std::istream& is) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<ScitForecastDataPacketImpl> p;
|
||||
};
|
||||
|
||||
} // namespace rpg
|
||||
} // namespace wsr88d
|
||||
} // namespace scwx
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/wsr88d/rpg/graphic_product_message.hpp>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <units/angle.h>
|
||||
#include <units/length.h>
|
||||
#include <units/velocity.h>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace wsr88d
|
||||
{
|
||||
namespace rpg
|
||||
{
|
||||
|
||||
class StormTrackingInformationMessage : public GraphicProductMessage
|
||||
{
|
||||
public:
|
||||
struct StiRecord
|
||||
{
|
||||
struct Position
|
||||
{
|
||||
std::optional<units::angle::degrees<std::uint16_t>> azimuth_ {};
|
||||
std::optional<units::length::nautical_miles<std::uint16_t>> range_ {};
|
||||
};
|
||||
|
||||
Position currentPosition_ {};
|
||||
std::optional<units::angle::degrees<std::uint16_t>> direction_;
|
||||
std::optional<units::velocity::knots<std::uint16_t>> speed_;
|
||||
|
||||
std::array<Position, 4> forecastPosition_ {};
|
||||
|
||||
std::optional<units::length::nautical_miles<float>> forecastError_ {};
|
||||
std::optional<units::length::nautical_miles<float>> meanError_ {};
|
||||
|
||||
std::optional<std::int16_t> maxDbz_ {};
|
||||
std::optional<units::length::feet<std::uint32_t>> maxDbzHeight_ {};
|
||||
};
|
||||
|
||||
explicit StormTrackingInformationMessage();
|
||||
~StormTrackingInformationMessage();
|
||||
|
||||
StormTrackingInformationMessage(const StormTrackingInformationMessage&) =
|
||||
delete;
|
||||
StormTrackingInformationMessage&
|
||||
operator=(const StormTrackingInformationMessage&) = delete;
|
||||
|
||||
StormTrackingInformationMessage(StormTrackingInformationMessage&&) noexcept;
|
||||
StormTrackingInformationMessage&
|
||||
operator=(StormTrackingInformationMessage&&) noexcept;
|
||||
|
||||
std::optional<std::uint16_t> radar_id() const;
|
||||
std::optional<std::chrono::sys_time<std::chrono::seconds>> date_time() const;
|
||||
std::optional<std::uint16_t> num_storm_cells() const;
|
||||
|
||||
std::optional<units::angle::degrees<std::uint16_t>>
|
||||
default_direction() const;
|
||||
std::optional<units::velocity::meters_per_second<float>>
|
||||
minimum_speed() const;
|
||||
std::optional<units::velocity::knots<float>> default_speed() const;
|
||||
std::optional<units::length::kilometers<std::uint16_t>>
|
||||
allowable_error() const;
|
||||
std::optional<std::chrono::minutes> maximum_time() const;
|
||||
std::optional<std::chrono::minutes> forecast_interval() const;
|
||||
std::optional<std::uint16_t> number_of_past_volumes() const;
|
||||
std::optional<std::uint16_t> number_of_intervals() const;
|
||||
std::optional<units::velocity::meters_per_second<float>>
|
||||
correlation_speed() const;
|
||||
std::optional<std::chrono::minutes> error_interval() const;
|
||||
|
||||
std::optional<units::length::kilometers<float>> filter_kernel_size() const;
|
||||
std::optional<float> filter_fraction() const;
|
||||
std::optional<bool> reflectivity_filtered() const;
|
||||
|
||||
std::shared_ptr<const StiRecord>
|
||||
sti_record(const std::string& stormId) const;
|
||||
|
||||
bool Parse(std::istream& is) override;
|
||||
|
||||
static std::shared_ptr<StormTrackingInformationMessage>
|
||||
Create(Level3MessageHeader&& header, std::istream& is);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace rpg
|
||||
} // namespace wsr88d
|
||||
} // namespace scwx
|
||||
|
|
@ -31,6 +31,8 @@ public:
|
|||
|
||||
size_t data_size() const override;
|
||||
|
||||
const std::vector<std::vector<std::string>>& page_list() const;
|
||||
|
||||
bool Parse(std::istream& is);
|
||||
bool Parse(std::istream& is, bool skipHeader);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/wsr88d/rpg/packet.hpp>
|
||||
#include <scwx/wsr88d/rpg/rpg_types.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
|
@ -14,8 +15,6 @@ namespace wsr88d
|
|||
namespace rpg
|
||||
{
|
||||
|
||||
class TextAndSpecialSymbolPacketImpl;
|
||||
|
||||
class TextAndSpecialSymbolPacket : public Packet
|
||||
{
|
||||
public:
|
||||
|
|
@ -29,21 +28,23 @@ public:
|
|||
TextAndSpecialSymbolPacket(TextAndSpecialSymbolPacket&&) noexcept;
|
||||
TextAndSpecialSymbolPacket& operator=(TextAndSpecialSymbolPacket&&) noexcept;
|
||||
|
||||
uint16_t packet_code() const override;
|
||||
uint16_t length_of_block() const;
|
||||
std::optional<uint16_t> value_of_text() const;
|
||||
int16_t start_i() const;
|
||||
int16_t start_j() const;
|
||||
std::string text() const;
|
||||
std::uint16_t packet_code() const override;
|
||||
std::uint16_t length_of_block() const;
|
||||
std::optional<std::uint16_t> value_of_text() const;
|
||||
std::int16_t start_i() const;
|
||||
std::int16_t start_j() const;
|
||||
std::string text() const;
|
||||
SpecialSymbol special_symbol() const;
|
||||
|
||||
size_t data_size() const override;
|
||||
std::size_t data_size() const override;
|
||||
|
||||
bool Parse(std::istream& is) override;
|
||||
|
||||
static std::shared_ptr<TextAndSpecialSymbolPacket> Create(std::istream& is);
|
||||
|
||||
private:
|
||||
std::unique_ptr<TextAndSpecialSymbolPacketImpl> p;
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace rpg
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
#define STRINGS_IMPLEMENTATION
|
||||
|
||||
#include <scwx/util/strings.hpp>
|
||||
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
|
|
@ -88,5 +91,22 @@ std::string ToString(const std::vector<std::string>& v)
|
|||
return value;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::optional<T> TryParseNumeric(const std::string& str)
|
||||
{
|
||||
std::optional<T> value = std::nullopt;
|
||||
|
||||
try
|
||||
{
|
||||
auto trimmed = boost::algorithm::trim_copy(str);
|
||||
value = boost::lexical_cast<T>(trimmed);
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
} // namespace scwx
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@
|
|||
|
||||
#include <scwx/util/time.hpp>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#if !defined(_MSC_VER)
|
||||
# include <date/date.h>
|
||||
#endif
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace util
|
||||
|
|
@ -55,5 +61,33 @@ std::string TimeString(std::chrono::system_clock::time_point time,
|
|||
return os.str();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::optional<std::chrono::sys_time<T>>
|
||||
TryParseDateTime(const std::string& dateTimeFormat, const std::string& str)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
|
||||
#if !defined(_MSC_VER)
|
||||
using namespace date;
|
||||
#endif
|
||||
|
||||
std::optional<std::chrono::sys_time<T>> value = std::nullopt;
|
||||
std::chrono::sys_time<T> dateTime;
|
||||
std::istringstream ssDateTime {str};
|
||||
|
||||
ssDateTime >> parse(dateTimeFormat, dateTime);
|
||||
|
||||
if (!ssDateTime.fail())
|
||||
{
|
||||
value = dateTime;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
template std::optional<std::chrono::sys_time<std::chrono::seconds>>
|
||||
TryParseDateTime<std::chrono::seconds>(const std::string& dateTimeFormat,
|
||||
const std::string& str);
|
||||
|
||||
} // namespace util
|
||||
} // namespace scwx
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ GraphicAlphanumericBlock::GraphicAlphanumericBlock() :
|
|||
GraphicAlphanumericBlock::~GraphicAlphanumericBlock() = default;
|
||||
|
||||
GraphicAlphanumericBlock::GraphicAlphanumericBlock(
|
||||
GraphicAlphanumericBlock&&) noexcept = default;
|
||||
GraphicAlphanumericBlock&&) noexcept = default;
|
||||
GraphicAlphanumericBlock& GraphicAlphanumericBlock::operator=(
|
||||
GraphicAlphanumericBlock&&) noexcept = default;
|
||||
|
||||
|
|
@ -58,6 +58,12 @@ size_t GraphicAlphanumericBlock::data_size() const
|
|||
return p->lengthOfBlock_;
|
||||
}
|
||||
|
||||
const std::vector<std::vector<std::shared_ptr<Packet>>>&
|
||||
GraphicAlphanumericBlock::page_list() const
|
||||
{
|
||||
return p->pageList_;
|
||||
}
|
||||
|
||||
bool GraphicAlphanumericBlock::Parse(std::istream& is)
|
||||
{
|
||||
bool blockValid = true;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <scwx/wsr88d/rpg/general_status_message.hpp>
|
||||
#include <scwx/wsr88d/rpg/graphic_product_message.hpp>
|
||||
#include <scwx/wsr88d/rpg/radar_coded_message.hpp>
|
||||
#include <scwx/wsr88d/rpg/storm_tracking_information_message.hpp>
|
||||
#include <scwx/wsr88d/rpg/tabular_product_message.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
|
|
@ -44,7 +45,7 @@ static const std::unordered_map<int, CreateLevel3MessageFunction> //
|
|||
{51, GraphicProductMessage::Create},
|
||||
{56, GraphicProductMessage::Create},
|
||||
{57, GraphicProductMessage::Create},
|
||||
{58, GraphicProductMessage::Create},
|
||||
{58, StormTrackingInformationMessage::Create},
|
||||
{59, GraphicProductMessage::Create},
|
||||
{61, GraphicProductMessage::Create},
|
||||
{62, TabularProductMessage::Create},
|
||||
|
|
|
|||
|
|
@ -71,6 +71,56 @@ std::optional<uint16_t> LinkedVectorPacket::value_of_vector() const
|
|||
return value;
|
||||
}
|
||||
|
||||
std::int16_t LinkedVectorPacket::start_i() const
|
||||
{
|
||||
return p->startI_;
|
||||
}
|
||||
|
||||
std::int16_t LinkedVectorPacket::start_j() const
|
||||
{
|
||||
return p->startJ_;
|
||||
}
|
||||
|
||||
std::vector<std::int16_t> LinkedVectorPacket::end_i() const
|
||||
{
|
||||
return p->endI_;
|
||||
}
|
||||
|
||||
std::vector<std::int16_t> LinkedVectorPacket::end_j() const
|
||||
{
|
||||
return p->endJ_;
|
||||
}
|
||||
|
||||
units::kilometers<double> LinkedVectorPacket::start_i_km() const
|
||||
{
|
||||
return units::kilometers<double> {p->startI_ * 0.25};
|
||||
}
|
||||
|
||||
units::kilometers<double> LinkedVectorPacket::start_j_km() const
|
||||
{
|
||||
return units::kilometers<double> {p->startJ_ * 0.25};
|
||||
}
|
||||
|
||||
std::vector<units::kilometers<double>> LinkedVectorPacket::end_i_km() const
|
||||
{
|
||||
std::vector<units::kilometers<double>> endI;
|
||||
for (const auto& i : p->endI_)
|
||||
{
|
||||
endI.emplace_back(units::kilometers<double> {i * 0.25});
|
||||
}
|
||||
return endI;
|
||||
}
|
||||
|
||||
std::vector<units::kilometers<double>> LinkedVectorPacket::end_j_km() const
|
||||
{
|
||||
std::vector<units::kilometers<double>> endJ;
|
||||
for (const auto& j : p->endJ_)
|
||||
{
|
||||
endJ.emplace_back(units::kilometers<double> {j * 0.25});
|
||||
}
|
||||
return endJ;
|
||||
}
|
||||
|
||||
size_t LinkedVectorPacket::data_size() const
|
||||
{
|
||||
return p->lengthOfBlock_ + 4u;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
#include <scwx/wsr88d/rpg/precipitation_rate_data_array_packet.hpp>
|
||||
#include <scwx/wsr88d/rpg/radial_data_packet.hpp>
|
||||
#include <scwx/wsr88d/rpg/raster_data_packet.hpp>
|
||||
#include <scwx/wsr88d/rpg/scit_forecast_data_packet.hpp>
|
||||
#include <scwx/wsr88d/rpg/scit_data_packet.hpp>
|
||||
#include <scwx/wsr88d/rpg/set_color_level_packet.hpp>
|
||||
#include <scwx/wsr88d/rpg/sti_circle_symbol_packet.hpp>
|
||||
#include <scwx/wsr88d/rpg/storm_id_symbol_packet.hpp>
|
||||
|
|
@ -63,8 +63,8 @@ static const std::unordered_map<unsigned int, CreateMessageFunction> create_ {
|
|||
{20, PointFeatureSymbolPacket::Create},
|
||||
{21, CellTrendDataPacket::Create},
|
||||
{22, CellTrendVolumeScanTimes::Create},
|
||||
{23, ScitForecastDataPacket::Create},
|
||||
{24, ScitForecastDataPacket::Create},
|
||||
{23, ScitDataPacket::Create},
|
||||
{24, ScitDataPacket::Create},
|
||||
{25, StiCircleSymbolPacket::Create},
|
||||
{26, PointGraphicSymbolPacket::Create},
|
||||
{28, GenericDataPacket::Create},
|
||||
|
|
|
|||
113
wxdata/source/scwx/wsr88d/rpg/scit_data_packet.cpp
Normal file
113
wxdata/source/scwx/wsr88d/rpg/scit_data_packet.cpp
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
#include <scwx/wsr88d/rpg/scit_data_packet.hpp>
|
||||
#include <scwx/wsr88d/rpg/packet_factory.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
#include <istream>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace wsr88d
|
||||
{
|
||||
namespace rpg
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ = "scwx::wsr88d::rpg::scit_data_packet";
|
||||
static const auto logger_ = util::Logger::Create(logPrefix_);
|
||||
|
||||
static const std::set<std::uint16_t> packetCodes_ = {23, 24};
|
||||
|
||||
class ScitDataPacket::Impl
|
||||
{
|
||||
public:
|
||||
explicit Impl() {}
|
||||
~Impl() = default;
|
||||
|
||||
std::vector<std::shared_ptr<Packet>> packetList_ {};
|
||||
};
|
||||
|
||||
ScitDataPacket::ScitDataPacket() : p(std::make_unique<Impl>()) {}
|
||||
ScitDataPacket::~ScitDataPacket() = default;
|
||||
|
||||
ScitDataPacket::ScitDataPacket(ScitDataPacket&&) noexcept = default;
|
||||
ScitDataPacket& ScitDataPacket::operator=(ScitDataPacket&&) noexcept = default;
|
||||
|
||||
std::vector<std::shared_ptr<Packet>> ScitDataPacket::packet_list() const
|
||||
{
|
||||
return p->packetList_;
|
||||
}
|
||||
|
||||
size_t ScitDataPacket::RecordCount() const
|
||||
{
|
||||
return p->packetList_.size();
|
||||
}
|
||||
|
||||
bool ScitDataPacket::ParseData(std::istream& is)
|
||||
{
|
||||
bool blockValid = true;
|
||||
|
||||
if (!packetCodes_.contains(packet_code()))
|
||||
{
|
||||
logger_->warn("Invalid packet code: {}", packet_code());
|
||||
blockValid = false;
|
||||
}
|
||||
|
||||
if (blockValid)
|
||||
{
|
||||
std::uint32_t bytesRead = 0;
|
||||
std::uint32_t lengthOfBlock = length_of_block();
|
||||
std::streampos dataStart = is.tellg();
|
||||
std::streampos dataEnd =
|
||||
dataStart + static_cast<std::streamoff>(lengthOfBlock);
|
||||
|
||||
while (bytesRead < lengthOfBlock)
|
||||
{
|
||||
std::shared_ptr<Packet> packet = PacketFactory::Create(is);
|
||||
if (packet != nullptr)
|
||||
{
|
||||
p->packetList_.push_back(packet);
|
||||
bytesRead += static_cast<std::uint32_t>(packet->data_size());
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bytesRead < lengthOfBlock)
|
||||
{
|
||||
logger_->trace("Block bytes read smaller than size: {} < {} bytes",
|
||||
bytesRead,
|
||||
lengthOfBlock);
|
||||
blockValid = false;
|
||||
is.seekg(dataEnd, std::ios_base::beg);
|
||||
}
|
||||
if (bytesRead > lengthOfBlock)
|
||||
{
|
||||
logger_->warn("Block bytes read larger than size: {} > {} bytes",
|
||||
bytesRead,
|
||||
lengthOfBlock);
|
||||
blockValid = false;
|
||||
is.seekg(dataEnd, std::ios_base::beg);
|
||||
}
|
||||
}
|
||||
|
||||
return blockValid;
|
||||
}
|
||||
|
||||
std::shared_ptr<ScitDataPacket> ScitDataPacket::Create(std::istream& is)
|
||||
{
|
||||
std::shared_ptr<ScitDataPacket> packet = std::make_shared<ScitDataPacket>();
|
||||
|
||||
if (!packet->Parse(is))
|
||||
{
|
||||
packet.reset();
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
} // namespace rpg
|
||||
} // namespace wsr88d
|
||||
} // namespace scwx
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
#include <scwx/wsr88d/rpg/scit_forecast_data_packet.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
#include <istream>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace wsr88d
|
||||
{
|
||||
namespace rpg
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ =
|
||||
"scwx::wsr88d::rpg::scit_forecast_data_packet";
|
||||
static const auto logger_ = util::Logger::Create(logPrefix_);
|
||||
|
||||
static const std::set<uint16_t> packetCodes_ = {23, 24};
|
||||
|
||||
class ScitForecastDataPacketImpl
|
||||
{
|
||||
public:
|
||||
explicit ScitForecastDataPacketImpl() : data_ {}, recordCount_ {0} {}
|
||||
~ScitForecastDataPacketImpl() = default;
|
||||
|
||||
std::vector<uint8_t> data_;
|
||||
size_t recordCount_;
|
||||
};
|
||||
|
||||
ScitForecastDataPacket::ScitForecastDataPacket() :
|
||||
p(std::make_unique<ScitForecastDataPacketImpl>())
|
||||
{
|
||||
}
|
||||
ScitForecastDataPacket::~ScitForecastDataPacket() = default;
|
||||
|
||||
ScitForecastDataPacket::ScitForecastDataPacket(
|
||||
ScitForecastDataPacket&&) noexcept = default;
|
||||
ScitForecastDataPacket&
|
||||
ScitForecastDataPacket::operator=(ScitForecastDataPacket&&) noexcept = default;
|
||||
|
||||
const std::vector<uint8_t>& ScitForecastDataPacket::data() const
|
||||
{
|
||||
return p->data_;
|
||||
}
|
||||
|
||||
size_t ScitForecastDataPacket::RecordCount() const
|
||||
{
|
||||
return p->recordCount_;
|
||||
}
|
||||
|
||||
bool ScitForecastDataPacket::ParseData(std::istream& is)
|
||||
{
|
||||
bool blockValid = true;
|
||||
|
||||
if (!packetCodes_.contains(packet_code()))
|
||||
{
|
||||
logger_->warn("Invalid packet code: {}", packet_code());
|
||||
blockValid = false;
|
||||
}
|
||||
|
||||
if (blockValid)
|
||||
{
|
||||
p->recordCount_ = length_of_block();
|
||||
p->data_.resize(p->recordCount_);
|
||||
is.read(reinterpret_cast<char*>(p->data_.data()), p->recordCount_);
|
||||
}
|
||||
|
||||
return blockValid;
|
||||
}
|
||||
|
||||
std::shared_ptr<ScitForecastDataPacket>
|
||||
ScitForecastDataPacket::Create(std::istream& is)
|
||||
{
|
||||
std::shared_ptr<ScitForecastDataPacket> packet =
|
||||
std::make_shared<ScitForecastDataPacket>();
|
||||
|
||||
if (!packet->Parse(is))
|
||||
{
|
||||
packet.reset();
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
} // namespace rpg
|
||||
} // namespace wsr88d
|
||||
} // namespace scwx
|
||||
|
|
@ -0,0 +1,767 @@
|
|||
#include <scwx/wsr88d/rpg/storm_tracking_information_message.hpp>
|
||||
#include <scwx/wsr88d/rpg/text_and_special_symbol_packet.hpp>
|
||||
#include <scwx/wsr88d/rpg/rpg_types.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/strings.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace wsr88d
|
||||
{
|
||||
namespace rpg
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ =
|
||||
"scwx::wsr88d::rpg::storm_tracking_information_message";
|
||||
static const auto logger_ = util::Logger::Create(logPrefix_);
|
||||
|
||||
class StormTrackingInformationMessage::Impl
|
||||
{
|
||||
public:
|
||||
explicit Impl() {}
|
||||
~Impl() = default;
|
||||
|
||||
std::shared_ptr<StiRecord>& GetOrCreateStiRecord(const std::string& stormId);
|
||||
|
||||
void ParseGraphicBlock(
|
||||
const std::shared_ptr<const GraphicAlphanumericBlock>& block);
|
||||
void ParseTabularBlock(
|
||||
const std::shared_ptr<const TabularAlphanumericBlock>& block);
|
||||
|
||||
void ParseStormPositionForecastPage(const std::vector<std::string>& page);
|
||||
void ParseStormCellTrackingDataPage(const std::vector<std::string>& page);
|
||||
|
||||
void HandleTextUniformPacket(std::shared_ptr<const Packet> packet,
|
||||
std::vector<std::string>& stormIds);
|
||||
|
||||
// STORM POSITION/FORECAST
|
||||
std::optional<std::uint16_t> radarId_ {};
|
||||
std::optional<std::chrono::sys_time<std::chrono::seconds>> dateTime_ {};
|
||||
std::optional<std::uint16_t> numStormCells_ {};
|
||||
|
||||
// STORM CELL TRACKING/FORECAST ADAPTATION DATA
|
||||
std::optional<units::angle::degrees<std::uint16_t>> defaultDirection_ {};
|
||||
std::optional<units::velocity::meters_per_second<float>> minimumSpeed_ {};
|
||||
std::optional<units::velocity::knots<float>> defaultSpeed_ {};
|
||||
std::optional<units::length::kilometers<std::uint16_t>> allowableError_ {};
|
||||
std::optional<std::chrono::minutes> maximumTime_ {};
|
||||
std::optional<std::chrono::minutes> forecastInterval_ {};
|
||||
std::optional<std::uint16_t> numberOfPastVolumes_ {};
|
||||
std::optional<std::uint16_t> numberOfIntervals_ {};
|
||||
std::optional<units::velocity::meters_per_second<float>>
|
||||
correlationSpeed_ {};
|
||||
std::optional<std::chrono::minutes> errorInterval_ {};
|
||||
|
||||
// SCIT REFLECTIVITY MEDIAN FILTER
|
||||
std::optional<units::length::kilometers<float>> filterKernelSize_ {};
|
||||
std::optional<float> filterFraction_ {};
|
||||
std::optional<bool> reflectivityFiltered_ {};
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<StiRecord>> stiRecords_ {};
|
||||
};
|
||||
|
||||
StormTrackingInformationMessage::StormTrackingInformationMessage() :
|
||||
p(std::make_unique<Impl>())
|
||||
{
|
||||
}
|
||||
StormTrackingInformationMessage::~StormTrackingInformationMessage() = default;
|
||||
|
||||
StormTrackingInformationMessage::StormTrackingInformationMessage(
|
||||
StormTrackingInformationMessage&&) noexcept = default;
|
||||
StormTrackingInformationMessage& StormTrackingInformationMessage::operator=(
|
||||
StormTrackingInformationMessage&&) noexcept = default;
|
||||
|
||||
std::optional<std::uint16_t> StormTrackingInformationMessage::radar_id() const
|
||||
{
|
||||
return p->radarId_;
|
||||
}
|
||||
|
||||
std::optional<std::chrono::sys_time<std::chrono::seconds>>
|
||||
StormTrackingInformationMessage::date_time() const
|
||||
{
|
||||
return p->dateTime_;
|
||||
}
|
||||
|
||||
std::optional<std::uint16_t>
|
||||
StormTrackingInformationMessage::num_storm_cells() const
|
||||
{
|
||||
return p->numStormCells_;
|
||||
}
|
||||
|
||||
std::optional<units::angle::degrees<std::uint16_t>>
|
||||
StormTrackingInformationMessage::default_direction() const
|
||||
{
|
||||
return p->defaultDirection_;
|
||||
}
|
||||
|
||||
std::optional<units::velocity::meters_per_second<float>>
|
||||
StormTrackingInformationMessage::minimum_speed() const
|
||||
{
|
||||
return p->minimumSpeed_;
|
||||
}
|
||||
|
||||
std::optional<units::velocity::knots<float>>
|
||||
StormTrackingInformationMessage::default_speed() const
|
||||
{
|
||||
return p->defaultSpeed_;
|
||||
}
|
||||
|
||||
std::optional<units::length::kilometers<std::uint16_t>>
|
||||
StormTrackingInformationMessage::allowable_error() const
|
||||
{
|
||||
return p->allowableError_;
|
||||
}
|
||||
|
||||
std::optional<std::chrono::minutes>
|
||||
StormTrackingInformationMessage::maximum_time() const
|
||||
{
|
||||
return p->maximumTime_;
|
||||
}
|
||||
|
||||
std::optional<std::chrono::minutes>
|
||||
StormTrackingInformationMessage::forecast_interval() const
|
||||
{
|
||||
return p->forecastInterval_;
|
||||
}
|
||||
|
||||
std::optional<std::uint16_t>
|
||||
StormTrackingInformationMessage::number_of_past_volumes() const
|
||||
{
|
||||
return p->numberOfPastVolumes_;
|
||||
}
|
||||
|
||||
std::optional<std::uint16_t>
|
||||
StormTrackingInformationMessage::number_of_intervals() const
|
||||
{
|
||||
return p->numberOfIntervals_;
|
||||
}
|
||||
|
||||
std::optional<units::velocity::meters_per_second<float>>
|
||||
StormTrackingInformationMessage::correlation_speed() const
|
||||
{
|
||||
return p->correlationSpeed_;
|
||||
}
|
||||
|
||||
std::optional<std::chrono::minutes>
|
||||
StormTrackingInformationMessage::error_interval() const
|
||||
{
|
||||
return p->errorInterval_;
|
||||
}
|
||||
|
||||
std::optional<units::length::kilometers<float>>
|
||||
StormTrackingInformationMessage::filter_kernel_size() const
|
||||
{
|
||||
return p->filterKernelSize_;
|
||||
}
|
||||
|
||||
std::optional<float> StormTrackingInformationMessage::filter_fraction() const
|
||||
{
|
||||
return p->filterFraction_;
|
||||
}
|
||||
|
||||
std::optional<bool>
|
||||
StormTrackingInformationMessage::reflectivity_filtered() const
|
||||
{
|
||||
return p->reflectivityFiltered_;
|
||||
}
|
||||
|
||||
std::shared_ptr<const StormTrackingInformationMessage::StiRecord>
|
||||
StormTrackingInformationMessage::sti_record(const std::string& stormId) const
|
||||
{
|
||||
std::shared_ptr<const StiRecord> record = nullptr;
|
||||
|
||||
auto it = p->stiRecords_.find(stormId);
|
||||
if (it != p->stiRecords_.cend())
|
||||
{
|
||||
record = it->second;
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
std::shared_ptr<StormTrackingInformationMessage::StiRecord>&
|
||||
StormTrackingInformationMessage::Impl::GetOrCreateStiRecord(
|
||||
const std::string& stormId)
|
||||
{
|
||||
auto& record = stiRecords_[stormId];
|
||||
if (record == nullptr)
|
||||
{
|
||||
record = std::make_shared<StiRecord>();
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
bool StormTrackingInformationMessage::Parse(std::istream& is)
|
||||
{
|
||||
bool dataValid = GraphicProductMessage::Parse(is);
|
||||
|
||||
std::shared_ptr<GraphicAlphanumericBlock> graphicBlock = nullptr;
|
||||
std::shared_ptr<TabularAlphanumericBlock> tabularBlock = nullptr;
|
||||
|
||||
if (dataValid)
|
||||
{
|
||||
graphicBlock = graphic_block();
|
||||
tabularBlock = tabular_block();
|
||||
}
|
||||
|
||||
if (graphicBlock != nullptr)
|
||||
{
|
||||
p->ParseGraphicBlock(graphicBlock);
|
||||
}
|
||||
|
||||
if (tabularBlock != nullptr)
|
||||
{
|
||||
p->ParseTabularBlock(tabularBlock);
|
||||
}
|
||||
|
||||
return dataValid;
|
||||
}
|
||||
|
||||
void StormTrackingInformationMessage::Impl::ParseGraphicBlock(
|
||||
const std::shared_ptr<const GraphicAlphanumericBlock>& block)
|
||||
{
|
||||
for (auto& page : block->page_list())
|
||||
{
|
||||
std::vector<std::string> stormIds {};
|
||||
|
||||
for (auto& packet : page)
|
||||
{
|
||||
switch (packet->packet_code())
|
||||
{
|
||||
case static_cast<std::uint16_t>(wsr88d::rpg::PacketCode::TextUniform):
|
||||
HandleTextUniformPacket(packet, stormIds);
|
||||
break;
|
||||
|
||||
default:
|
||||
logger_->trace("Ignoring graphic alphanumeric packet type: {}",
|
||||
packet->packet_code());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StormTrackingInformationMessage::Impl::HandleTextUniformPacket(
|
||||
std::shared_ptr<const Packet> packet, std::vector<std::string>& stormIds)
|
||||
{
|
||||
auto textPacket =
|
||||
std::dynamic_pointer_cast<const wsr88d::rpg::TextAndSpecialSymbolPacket>(
|
||||
packet);
|
||||
|
||||
if (textPacket != nullptr && textPacket->text().size() >= 69)
|
||||
{
|
||||
auto text = textPacket->text();
|
||||
|
||||
// " STORM ID D7 H3 K5 N5 U6 E7"
|
||||
if (text.starts_with(" STORM ID"))
|
||||
{
|
||||
static constexpr std::size_t kMaxStormIds = 6;
|
||||
static constexpr std::size_t kStartOffset = 17;
|
||||
static constexpr std::size_t kStride = 10;
|
||||
|
||||
stormIds.clear();
|
||||
|
||||
for (std::size_t i = 0, offset = kStartOffset; i < kMaxStormIds;
|
||||
++i, offset += kStride)
|
||||
{
|
||||
std::string stormId = text.substr(offset, 2);
|
||||
|
||||
if (std::isupper(stormId[0]) && std::isdigit(stormId[1]))
|
||||
{
|
||||
stormIds.push_back(stormId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// " AZ/RAN 242/ 77 45/ 36 180/139 175/126 23/110 25/ 83"
|
||||
else if (text.starts_with(" AZ/RAN"))
|
||||
{
|
||||
static constexpr std::size_t kAzStartOffset = 11;
|
||||
static constexpr std::size_t kRanStartOffset = 15;
|
||||
static constexpr std::size_t kStride = 10;
|
||||
|
||||
for (std::size_t i = 0,
|
||||
azOffset = kAzStartOffset,
|
||||
ranOffset = kRanStartOffset;
|
||||
i < stormIds.size();
|
||||
++i, azOffset += kStride, ranOffset += kStride)
|
||||
{
|
||||
auto& record = GetOrCreateStiRecord(stormIds[i]);
|
||||
|
||||
if (!record->currentPosition_.azimuth_.has_value())
|
||||
{
|
||||
// Current Position: Azimuth (Degrees) (I3)
|
||||
auto azimuth = util::TryParseNumeric<std::uint16_t>(
|
||||
text.substr(azOffset, 3));
|
||||
if (azimuth.has_value())
|
||||
{
|
||||
record->currentPosition_.azimuth_ =
|
||||
units::angle::degrees<std::uint16_t> {azimuth.value()};
|
||||
}
|
||||
}
|
||||
|
||||
if (!record->currentPosition_.range_.has_value())
|
||||
{
|
||||
// Current Position: Range (Nautical Miles) (I3)
|
||||
auto range = util::TryParseNumeric<std::uint16_t>(
|
||||
text.substr(ranOffset, 3));
|
||||
if (range.has_value())
|
||||
{
|
||||
record->currentPosition_.range_ =
|
||||
units::length::nautical_miles<std::uint16_t> {
|
||||
range.value()};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// " FCST MVT 262/ 56 249/ 48 234/ 46 228/ 48 227/ 66 242/ 48"
|
||||
else if (text.starts_with(" FCST MVT"))
|
||||
{
|
||||
static constexpr std::size_t kDirStartOffset = 11;
|
||||
static constexpr std::size_t kSpeedStartOffset = 15;
|
||||
static constexpr std::size_t kStride = 10;
|
||||
|
||||
for (std::size_t i = 0,
|
||||
dirOffset = kDirStartOffset,
|
||||
speedOffset = kSpeedStartOffset;
|
||||
i < stormIds.size();
|
||||
++i, dirOffset += kStride, speedOffset += kStride)
|
||||
{
|
||||
auto& record = GetOrCreateStiRecord(stormIds[i]);
|
||||
|
||||
if (!record->direction_.has_value())
|
||||
{
|
||||
// Movement: Direction (Degrees) (I3)
|
||||
auto direction = util::TryParseNumeric<std::uint16_t>(
|
||||
text.substr(dirOffset, 3));
|
||||
if (direction.has_value())
|
||||
{
|
||||
record->direction_ =
|
||||
units::angle::degrees<std::uint16_t> {direction.value()};
|
||||
}
|
||||
}
|
||||
|
||||
if (!record->speed_.has_value())
|
||||
{
|
||||
// Movement: Speed (Knots) (I3)
|
||||
auto speed = util::TryParseNumeric<std::uint16_t>(
|
||||
text.substr(speedOffset, 3));
|
||||
if (speed.has_value())
|
||||
{
|
||||
record->speed_ =
|
||||
units::velocity::knots<std::uint16_t> {speed.value()};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// " ERR/MEAN 4.5/ 2.9 0.8/ 1.7 1.4/ 1.4 1.3/ 1.3 1.4/ 1.7 1.2/ 0.8"
|
||||
else if (text.starts_with(" ERR/MEAN"))
|
||||
{
|
||||
static constexpr std::size_t kErrStartOffset = 10;
|
||||
static constexpr std::size_t kMeanStartOffset = 15;
|
||||
static constexpr std::size_t kStride = 10;
|
||||
|
||||
for (std::size_t i = 0,
|
||||
errOffset = kErrStartOffset,
|
||||
meanOffset = kMeanStartOffset;
|
||||
i < stormIds.size();
|
||||
++i, errOffset += kStride, meanOffset += kStride)
|
||||
{
|
||||
auto& record = GetOrCreateStiRecord(stormIds[i]);
|
||||
|
||||
if (!record->forecastError_.has_value())
|
||||
{
|
||||
// Forecast Error (Nautical Miles) (F4.1)
|
||||
auto forecastError =
|
||||
util::TryParseNumeric<float>(text.substr(errOffset, 4));
|
||||
if (forecastError.has_value())
|
||||
{
|
||||
record->forecastError_ =
|
||||
units::length::nautical_miles<float> {
|
||||
forecastError.value()};
|
||||
}
|
||||
}
|
||||
|
||||
if (!record->meanError_.has_value())
|
||||
{
|
||||
// Mean Error (Nautical Miles) (F4.1)
|
||||
auto meanError =
|
||||
util::TryParseNumeric<float>(text.substr(meanOffset, 4));
|
||||
if (meanError.has_value())
|
||||
{
|
||||
record->meanError_ =
|
||||
units::length::nautical_miles<float> {meanError.value()};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// " DBZM HGT 55 7.5 56 4.2 48 20.6 51 17.4 51 14.0 54 8.9"
|
||||
else if (text.starts_with(" DBZM HGT"))
|
||||
{
|
||||
static constexpr std::size_t kDbzmStartOffset = 12;
|
||||
static constexpr std::size_t kHgtStartOffset = 15;
|
||||
static constexpr std::size_t kStride = 10;
|
||||
|
||||
for (std::size_t i = 0,
|
||||
dbzmOffset = kDbzmStartOffset,
|
||||
hgtOffset = kHgtStartOffset;
|
||||
i < stormIds.size();
|
||||
++i, dbzmOffset += kStride, hgtOffset += kStride)
|
||||
{
|
||||
auto& record = GetOrCreateStiRecord(stormIds[i]);
|
||||
|
||||
// Maximum dBZ (I2)
|
||||
record->maxDbz_ =
|
||||
util::TryParseNumeric<std::uint16_t>(text.substr(dbzmOffset, 2));
|
||||
|
||||
// Maximum dBZ Height (Feet) (F4.1)
|
||||
auto height =
|
||||
util::TryParseNumeric<float>(text.substr(hgtOffset, 4));
|
||||
if (height.has_value())
|
||||
{
|
||||
record->maxDbzHeight_ = units::length::feet<std::uint32_t> {
|
||||
static_cast<std::uint32_t>(height.value() * 1000.0f)};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger_->debug("No storm text present");
|
||||
}
|
||||
}
|
||||
|
||||
void StormTrackingInformationMessage::Impl::ParseTabularBlock(
|
||||
const std::shared_ptr<const TabularAlphanumericBlock>& block)
|
||||
{
|
||||
static const std::string kStormPositionForecast_ = "STORM POSITION/FORECAST";
|
||||
static const std::string kStormCellTrackingData_ =
|
||||
"STORM CELL TRACKING/FORECAST ADAPTATION DATA";
|
||||
|
||||
for (auto& page : block->page_list())
|
||||
{
|
||||
if (page.empty())
|
||||
{
|
||||
logger_->warn("Unexpected empty page");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (page[0].find(kStormPositionForecast_) != std::string::npos)
|
||||
{
|
||||
ParseStormPositionForecastPage(page);
|
||||
}
|
||||
else if (page[0].find(kStormCellTrackingData_) != std::string::npos)
|
||||
{
|
||||
ParseStormCellTrackingDataPage(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StormTrackingInformationMessage::Impl::ParseStormPositionForecastPage(
|
||||
const std::vector<std::string>& page)
|
||||
{
|
||||
for (std::size_t i = 1; i < page.size(); ++i)
|
||||
{
|
||||
const std::string& line = page[i];
|
||||
|
||||
// clang-format off
|
||||
// " RADAR ID 308 DATE/TIME 12:11:21/02:15:38 NUMBER OF STORM CELLS 34"
|
||||
// clang-format on
|
||||
if (i == 1 && line.size() >= 74)
|
||||
{
|
||||
if (!radarId_.has_value())
|
||||
{
|
||||
// Radar ID (I3)
|
||||
radarId_ = util::TryParseNumeric<std::uint16_t>(line.substr(14, 3));
|
||||
}
|
||||
if (!dateTime_.has_value())
|
||||
{
|
||||
static const std::string kDateTimeFormat_ {"%m:%d:%y/%H:%M:%S"};
|
||||
|
||||
dateTime_ = util::TryParseDateTime<std::chrono::seconds>(
|
||||
kDateTimeFormat_, line.substr(29, 17));
|
||||
}
|
||||
if (!numStormCells_.has_value())
|
||||
{
|
||||
// Number of Storm Cells (I3)
|
||||
numStormCells_ =
|
||||
util::TryParseNumeric<std::uint16_t>(line.substr(71, 3));
|
||||
}
|
||||
}
|
||||
// clang-format off
|
||||
// " V6 183/147 234/ 63 178/137 172/129 166/122 159/117 0.7/ 0.7"
|
||||
// clang-format on
|
||||
else if (i >= 7 && line.size() >= 80)
|
||||
{
|
||||
std::string stormId = line.substr(2, 2);
|
||||
|
||||
if (std::isupper(stormId[0]) && std::isdigit(stormId[1]))
|
||||
{
|
||||
auto& record = GetOrCreateStiRecord(stormId);
|
||||
|
||||
if (!record->currentPosition_.azimuth_.has_value())
|
||||
{
|
||||
// Current Position: Azimuth (Degrees) (I3)
|
||||
auto azimuth =
|
||||
util::TryParseNumeric<std::uint16_t>(line.substr(9, 3));
|
||||
if (azimuth.has_value())
|
||||
{
|
||||
record->currentPosition_.azimuth_ =
|
||||
units::angle::degrees<std::uint16_t> {azimuth.value()};
|
||||
}
|
||||
}
|
||||
if (!record->currentPosition_.range_.has_value())
|
||||
{
|
||||
// Current Position: Range (Nautical Miles) (I3)
|
||||
auto range =
|
||||
util::TryParseNumeric<std::uint16_t>(line.substr(13, 3));
|
||||
if (range.has_value())
|
||||
{
|
||||
record->currentPosition_.range_ =
|
||||
units::length::nautical_miles<std::uint16_t> {
|
||||
range.value()};
|
||||
}
|
||||
}
|
||||
if (!record->direction_.has_value())
|
||||
{
|
||||
// Movement: Direction (Degrees) (I3)
|
||||
auto direction =
|
||||
util::TryParseNumeric<std::uint16_t>(line.substr(19, 3));
|
||||
if (direction.has_value())
|
||||
{
|
||||
record->direction_ =
|
||||
units::angle::degrees<std::uint16_t> {direction.value()};
|
||||
}
|
||||
}
|
||||
if (!record->speed_.has_value())
|
||||
{
|
||||
// Movement: Speed (Knots) (I3)
|
||||
auto speed =
|
||||
util::TryParseNumeric<std::uint16_t>(line.substr(23, 3));
|
||||
if (speed.has_value())
|
||||
{
|
||||
record->speed_ =
|
||||
units::velocity::knots<std::uint16_t> {speed.value()};
|
||||
}
|
||||
}
|
||||
for (std::size_t j = 0; j < record->forecastPosition_.size(); ++j)
|
||||
{
|
||||
const std::size_t positionOffset = j * 10;
|
||||
|
||||
if (!record->forecastPosition_[j].azimuth_.has_value())
|
||||
{
|
||||
// Forecast Position: Azimuth (Degrees) (I3)
|
||||
std::size_t offset = 31 + positionOffset;
|
||||
|
||||
auto azimuth = util::TryParseNumeric<std::uint16_t>(
|
||||
line.substr(offset, 3));
|
||||
if (azimuth.has_value())
|
||||
{
|
||||
record->forecastPosition_[j].azimuth_ =
|
||||
units::angle::degrees<std::uint16_t> {azimuth.value()};
|
||||
}
|
||||
}
|
||||
if (!record->forecastPosition_[j].range_.has_value())
|
||||
{
|
||||
// Forecast Position: Range (Nautical Miles) (I3)
|
||||
std::size_t offset = 35 + positionOffset;
|
||||
|
||||
auto range = util::TryParseNumeric<std::uint16_t>(
|
||||
line.substr(offset, 3));
|
||||
if (range.has_value())
|
||||
{
|
||||
record->forecastPosition_[j].range_ =
|
||||
units::length::nautical_miles<std::uint16_t> {
|
||||
range.value()};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!record->forecastError_.has_value())
|
||||
{
|
||||
// Forecast Error (Nautical Miles) (F4.1)
|
||||
auto forecastError =
|
||||
util::TryParseNumeric<float>(line.substr(71, 4));
|
||||
if (forecastError.has_value())
|
||||
{
|
||||
record->forecastError_ =
|
||||
units::length::nautical_miles<float> {
|
||||
forecastError.value()};
|
||||
}
|
||||
}
|
||||
if (!record->meanError_.has_value())
|
||||
{
|
||||
// Mean Error (Nautical Miles) (F4.1)
|
||||
auto meanError =
|
||||
util::TryParseNumeric<float>(line.substr(76, 4));
|
||||
if (meanError.has_value())
|
||||
{
|
||||
record->meanError_ =
|
||||
units::length::nautical_miles<float> {meanError.value()};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StormTrackingInformationMessage::Impl::ParseStormCellTrackingDataPage(
|
||||
const std::vector<std::string>& page)
|
||||
{
|
||||
for (std::size_t i = 1; i < page.size(); ++i)
|
||||
{
|
||||
const std::string& line = page[i];
|
||||
|
||||
// clang-format off
|
||||
// " 260 (DEG) DEFAULT (DIRECTION) 2.5 (M/S) THRESH (MINIMUM SPEED)"
|
||||
// clang-format on
|
||||
if (i == 2 && line.size() >= 75)
|
||||
{
|
||||
// Default Direction (Degrees) (I3)
|
||||
auto direction =
|
||||
util::TryParseNumeric<std::uint16_t>(line.substr(4, 3));
|
||||
if (direction.has_value())
|
||||
{
|
||||
defaultDirection_ =
|
||||
units::angle::degrees<std::uint16_t> {direction.value()};
|
||||
}
|
||||
|
||||
// Minimum Speed (Threshold) (m/s) (F4.1)
|
||||
auto threshold = util::TryParseNumeric<float>(line.substr(40, 4));
|
||||
if (threshold.has_value())
|
||||
{
|
||||
minimumSpeed_ =
|
||||
units::velocity::meters_per_second<float> {threshold.value()};
|
||||
}
|
||||
}
|
||||
|
||||
// " 36.0 (KTS) DEFAULT (SPEED) 20 (KM) ALLOWABLE ERROR"
|
||||
if (i == 3 && line.size() >= 68)
|
||||
{
|
||||
// Default Speed (Knots) (F4.1)
|
||||
auto speed = util::TryParseNumeric<float>(line.substr(3, 4));
|
||||
if (speed.has_value())
|
||||
{
|
||||
defaultSpeed_ = units::velocity::knots<float> {speed.value()};
|
||||
}
|
||||
|
||||
// Allowable Error (Kilometers) (I2)
|
||||
auto error = util::TryParseNumeric<std::uint16_t>(line.substr(42, 2));
|
||||
if (error.has_value())
|
||||
{
|
||||
allowableError_ =
|
||||
units::length::kilometers<std::uint16_t> {error.value()};
|
||||
}
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
// " 20 (MIN) TIME (MAXIMUM) 15 (MIN) FORECAST INTERVAL"
|
||||
// clang-format on
|
||||
if (i == 4 && line.size() >= 70)
|
||||
{
|
||||
// Maximum Time (Minutes) (I5)
|
||||
auto time = util::TryParseNumeric<std::uint32_t>(line.substr(2, 5));
|
||||
if (time.has_value())
|
||||
{
|
||||
maximumTime_ = std::chrono::minutes {time.value()};
|
||||
}
|
||||
|
||||
// Forecast Interval (Minutes) (I2)
|
||||
auto interval =
|
||||
util::TryParseNumeric<std::uint16_t>(line.substr(42, 2));
|
||||
if (interval.has_value())
|
||||
{
|
||||
forecastInterval_ = std::chrono::minutes {interval.value()};
|
||||
}
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
// " 10 NUMBER OF PAST VOLUMES 4 NUMBER OF INTERVALS"
|
||||
// clang-format on
|
||||
if (i == 5 && line.size() >= 72)
|
||||
{
|
||||
// Number of Past Volumes (I2)
|
||||
numberOfPastVolumes_ =
|
||||
util::TryParseNumeric<std::uint16_t>(line.substr(5, 2));
|
||||
|
||||
// Number of Intervals (I2)
|
||||
numberOfIntervals_ =
|
||||
util::TryParseNumeric<std::uint16_t>(line.substr(42, 2));
|
||||
}
|
||||
|
||||
// " 30.0 (M/S) CORRELATION SPEED 15 (MIN) ERROR INTERVAL"
|
||||
if (i == 6 && line.size() >= 67)
|
||||
{
|
||||
// Correlation Speed (m/s) (F4.1)
|
||||
auto speed = util::TryParseNumeric<float>(line.substr(3, 4));
|
||||
if (speed.has_value())
|
||||
{
|
||||
correlationSpeed_ =
|
||||
units::velocity::meters_per_second<float> {speed.value()};
|
||||
}
|
||||
|
||||
// Error Interval (Minutes) (I2)
|
||||
auto interval =
|
||||
util::TryParseNumeric<std::uint16_t>(line.substr(42, 2));
|
||||
if (interval.has_value())
|
||||
{
|
||||
errorInterval_ = std::chrono::minutes {interval.value()};
|
||||
}
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
// " 7.0 (KM) FILTER KERNEL SIZE 0.5 THRESH (FILTER FRACTION)"
|
||||
// clang-format on
|
||||
if (i == 11 && line.size() >= 77)
|
||||
{
|
||||
// Filter Kernel Size (Kilometers) (F4.1)
|
||||
auto kernelSize = util::TryParseNumeric<float>(line.substr(3, 4));
|
||||
if (kernelSize.has_value())
|
||||
{
|
||||
filterKernelSize_ =
|
||||
units::length::kilometers<float> {kernelSize.value()};
|
||||
}
|
||||
|
||||
// Minimum Speed (Threshold) (m/s) (F4.1)
|
||||
filterFraction_ = util::TryParseNumeric<float>(line.substr(40, 4));
|
||||
}
|
||||
|
||||
// " Yes REFLECTIVITY FILTERED"
|
||||
if (i == 12 && line.size() >= 37)
|
||||
{
|
||||
if (line.substr(4, 3) == "Yes")
|
||||
{
|
||||
reflectivityFiltered_ = true;
|
||||
}
|
||||
else if (line.substr(5, 2) == "No")
|
||||
{
|
||||
reflectivityFiltered_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<StormTrackingInformationMessage>
|
||||
StormTrackingInformationMessage::Create(Level3MessageHeader&& header,
|
||||
std::istream& is)
|
||||
{
|
||||
std::shared_ptr<StormTrackingInformationMessage> message =
|
||||
std::make_shared<StormTrackingInformationMessage>();
|
||||
message->set_header(std::move(header));
|
||||
|
||||
if (!message->Parse(is))
|
||||
{
|
||||
message.reset();
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
} // namespace rpg
|
||||
} // namespace wsr88d
|
||||
} // namespace scwx
|
||||
|
|
@ -54,7 +54,7 @@ TabularAlphanumericBlock::TabularAlphanumericBlock() :
|
|||
TabularAlphanumericBlock::~TabularAlphanumericBlock() = default;
|
||||
|
||||
TabularAlphanumericBlock::TabularAlphanumericBlock(
|
||||
TabularAlphanumericBlock&&) noexcept = default;
|
||||
TabularAlphanumericBlock&&) noexcept = default;
|
||||
TabularAlphanumericBlock& TabularAlphanumericBlock::operator=(
|
||||
TabularAlphanumericBlock&&) noexcept = default;
|
||||
|
||||
|
|
@ -68,6 +68,12 @@ size_t TabularAlphanumericBlock::data_size() const
|
|||
return p->lengthOfBlock_;
|
||||
}
|
||||
|
||||
const std::vector<std::vector<std::string>>&
|
||||
TabularAlphanumericBlock::page_list() const
|
||||
{
|
||||
return p->pageList_;
|
||||
}
|
||||
|
||||
bool TabularAlphanumericBlock::Parse(std::istream& is)
|
||||
{
|
||||
return Parse(is, false);
|
||||
|
|
|
|||
|
|
@ -15,53 +15,45 @@ static const std::string logPrefix_ =
|
|||
"scwx::wsr88d::rpg::text_and_special_symbol_packet";
|
||||
static const auto logger_ = util::Logger::Create(logPrefix_);
|
||||
|
||||
class TextAndSpecialSymbolPacketImpl
|
||||
class TextAndSpecialSymbolPacket::Impl
|
||||
{
|
||||
public:
|
||||
explicit TextAndSpecialSymbolPacketImpl() :
|
||||
packetCode_ {0},
|
||||
lengthOfBlock_ {0},
|
||||
valueOfText_ {0},
|
||||
startI_ {0},
|
||||
startJ_ {0},
|
||||
text_ {}
|
||||
{
|
||||
}
|
||||
~TextAndSpecialSymbolPacketImpl() = default;
|
||||
explicit Impl() {}
|
||||
~Impl() = default;
|
||||
|
||||
uint16_t packetCode_;
|
||||
uint16_t lengthOfBlock_;
|
||||
uint16_t valueOfText_;
|
||||
int16_t startI_;
|
||||
int16_t startJ_;
|
||||
std::uint16_t packetCode_ {0};
|
||||
std::uint16_t lengthOfBlock_ {0};
|
||||
std::uint16_t valueOfText_ {0};
|
||||
std::int16_t startI_ {0};
|
||||
std::int16_t startJ_ {0};
|
||||
|
||||
std::string text_;
|
||||
std::string text_ {};
|
||||
};
|
||||
|
||||
TextAndSpecialSymbolPacket::TextAndSpecialSymbolPacket() :
|
||||
p(std::make_unique<TextAndSpecialSymbolPacketImpl>())
|
||||
p(std::make_unique<Impl>())
|
||||
{
|
||||
}
|
||||
TextAndSpecialSymbolPacket::~TextAndSpecialSymbolPacket() = default;
|
||||
|
||||
TextAndSpecialSymbolPacket::TextAndSpecialSymbolPacket(
|
||||
TextAndSpecialSymbolPacket&&) noexcept = default;
|
||||
TextAndSpecialSymbolPacket&&) noexcept = default;
|
||||
TextAndSpecialSymbolPacket& TextAndSpecialSymbolPacket::operator=(
|
||||
TextAndSpecialSymbolPacket&&) noexcept = default;
|
||||
|
||||
uint16_t TextAndSpecialSymbolPacket::packet_code() const
|
||||
std::uint16_t TextAndSpecialSymbolPacket::packet_code() const
|
||||
{
|
||||
return p->packetCode_;
|
||||
}
|
||||
|
||||
uint16_t TextAndSpecialSymbolPacket::length_of_block() const
|
||||
std::uint16_t TextAndSpecialSymbolPacket::length_of_block() const
|
||||
{
|
||||
return p->lengthOfBlock_;
|
||||
}
|
||||
|
||||
std::optional<uint16_t> TextAndSpecialSymbolPacket::value_of_text() const
|
||||
std::optional<std::uint16_t> TextAndSpecialSymbolPacket::value_of_text() const
|
||||
{
|
||||
std::optional<uint16_t> value;
|
||||
std::optional<std::uint16_t> value;
|
||||
|
||||
if (p->packetCode_ == 8)
|
||||
{
|
||||
|
|
@ -71,12 +63,12 @@ std::optional<uint16_t> TextAndSpecialSymbolPacket::value_of_text() const
|
|||
return value;
|
||||
}
|
||||
|
||||
int16_t TextAndSpecialSymbolPacket::start_i() const
|
||||
std::int16_t TextAndSpecialSymbolPacket::start_i() const
|
||||
{
|
||||
return p->startI_;
|
||||
}
|
||||
|
||||
int16_t TextAndSpecialSymbolPacket::start_j() const
|
||||
std::int16_t TextAndSpecialSymbolPacket::start_j() const
|
||||
{
|
||||
return p->startJ_;
|
||||
}
|
||||
|
|
@ -86,7 +78,43 @@ std::string TextAndSpecialSymbolPacket::text() const
|
|||
return p->text_;
|
||||
}
|
||||
|
||||
size_t TextAndSpecialSymbolPacket::data_size() const
|
||||
SpecialSymbol TextAndSpecialSymbolPacket::special_symbol() const
|
||||
{
|
||||
SpecialSymbol symbol = SpecialSymbol::None;
|
||||
|
||||
if (!p->text_.empty())
|
||||
{
|
||||
switch (p->text_.at(0))
|
||||
{
|
||||
case '!': // 0x21
|
||||
symbol = SpecialSymbol::PastStormCellPosition;
|
||||
break;
|
||||
|
||||
case '"': // 0x22
|
||||
symbol = SpecialSymbol::CurrentStormCellPosition;
|
||||
break;
|
||||
|
||||
case '#': // 0x23
|
||||
symbol = SpecialSymbol::ForecastStormCellPosition;
|
||||
break;
|
||||
|
||||
case '$': // 0x24
|
||||
symbol = SpecialSymbol::PastMdaPosition;
|
||||
break;
|
||||
|
||||
case '%': // 0x25
|
||||
symbol = SpecialSymbol::ForecastMdaPosition;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return symbol;
|
||||
}
|
||||
|
||||
std::size_t TextAndSpecialSymbolPacket::data_size() const
|
||||
{
|
||||
return p->lengthOfBlock_ + 4u;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -152,11 +152,13 @@ set(HDR_WSR88D_RPG include/scwx/wsr88d/rpg/ccb_header.hpp
|
|||
include/scwx/wsr88d/rpg/radar_coded_message.hpp
|
||||
include/scwx/wsr88d/rpg/radial_data_packet.hpp
|
||||
include/scwx/wsr88d/rpg/raster_data_packet.hpp
|
||||
include/scwx/wsr88d/rpg/scit_forecast_data_packet.hpp
|
||||
include/scwx/wsr88d/rpg/rpg_types.hpp
|
||||
include/scwx/wsr88d/rpg/scit_data_packet.hpp
|
||||
include/scwx/wsr88d/rpg/set_color_level_packet.hpp
|
||||
include/scwx/wsr88d/rpg/special_graphic_symbol_packet.hpp
|
||||
include/scwx/wsr88d/rpg/sti_circle_symbol_packet.hpp
|
||||
include/scwx/wsr88d/rpg/storm_id_symbol_packet.hpp
|
||||
include/scwx/wsr88d/rpg/storm_tracking_information_message.hpp
|
||||
include/scwx/wsr88d/rpg/tabular_alphanumeric_block.hpp
|
||||
include/scwx/wsr88d/rpg/tabular_product_message.hpp
|
||||
include/scwx/wsr88d/rpg/text_and_special_symbol_packet.hpp
|
||||
|
|
@ -191,11 +193,12 @@ set(SRC_WSR88D_RPG source/scwx/wsr88d/rpg/ccb_header.cpp
|
|||
source/scwx/wsr88d/rpg/radar_coded_message.cpp
|
||||
source/scwx/wsr88d/rpg/radial_data_packet.cpp
|
||||
source/scwx/wsr88d/rpg/raster_data_packet.cpp
|
||||
source/scwx/wsr88d/rpg/scit_forecast_data_packet.cpp
|
||||
source/scwx/wsr88d/rpg/scit_data_packet.cpp
|
||||
source/scwx/wsr88d/rpg/set_color_level_packet.cpp
|
||||
source/scwx/wsr88d/rpg/special_graphic_symbol_packet.cpp
|
||||
source/scwx/wsr88d/rpg/sti_circle_symbol_packet.cpp
|
||||
source/scwx/wsr88d/rpg/storm_id_symbol_packet.cpp
|
||||
source/scwx/wsr88d/rpg/storm_tracking_information_message.cpp
|
||||
source/scwx/wsr88d/rpg/tabular_alphanumeric_block.cpp
|
||||
source/scwx/wsr88d/rpg/tabular_product_message.cpp
|
||||
source/scwx/wsr88d/rpg/text_and_special_symbol_packet.cpp
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue