mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-11-02 14:30: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)
|
source/scwx/qt/gl/shader_program.cpp)
|
||||||
set(HDR_GL_DRAW source/scwx/qt/gl/draw/draw_item.hpp
|
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_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/icons.hpp
|
||||||
|
source/scwx/qt/gl/draw/linked_vectors.hpp
|
||||||
source/scwx/qt/gl/draw/placefile_icons.hpp
|
source/scwx/qt/gl/draw/placefile_icons.hpp
|
||||||
source/scwx/qt/gl/draw/placefile_images.hpp
|
source/scwx/qt/gl/draw/placefile_images.hpp
|
||||||
source/scwx/qt/gl/draw/placefile_lines.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)
|
source/scwx/qt/gl/draw/rectangle.hpp)
|
||||||
set(SRC_GL_DRAW source/scwx/qt/gl/draw/draw_item.cpp
|
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_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/icons.cpp
|
||||||
|
source/scwx/qt/gl/draw/linked_vectors.cpp
|
||||||
source/scwx/qt/gl/draw/placefile_icons.cpp
|
source/scwx/qt/gl/draw/placefile_icons.cpp
|
||||||
source/scwx/qt/gl/draw/placefile_images.cpp
|
source/scwx/qt/gl/draw/placefile_images.cpp
|
||||||
source/scwx/qt/gl/draw/placefile_lines.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_settings.hpp
|
||||||
source/scwx/qt/map/map_widget.hpp
|
source/scwx/qt/map/map_widget.hpp
|
||||||
source/scwx/qt/map/overlay_layer.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/placefile_layer.hpp
|
||||||
source/scwx/qt/map/radar_product_layer.hpp
|
source/scwx/qt/map/radar_product_layer.hpp
|
||||||
source/scwx/qt/map/radar_range_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_provider.cpp
|
||||||
source/scwx/qt/map/map_widget.cpp
|
source/scwx/qt/map/map_widget.cpp
|
||||||
source/scwx/qt/map/overlay_layer.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/placefile_layer.cpp
|
||||||
source/scwx/qt/map/radar_product_layer.cpp
|
source/scwx/qt/map/radar_product_layer.cpp
|
||||||
source/scwx/qt/map/radar_range_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/general_settings.hpp
|
||||||
source/scwx/qt/settings/map_settings.hpp
|
source/scwx/qt/settings/map_settings.hpp
|
||||||
source/scwx/qt/settings/palette_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_category.hpp
|
||||||
source/scwx/qt/settings/settings_container.hpp
|
source/scwx/qt/settings/settings_container.hpp
|
||||||
source/scwx/qt/settings/settings_definitions.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/general_settings.cpp
|
||||||
source/scwx/qt/settings/map_settings.cpp
|
source/scwx/qt/settings/map_settings.cpp
|
||||||
source/scwx/qt/settings/palette_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_category.cpp
|
||||||
source/scwx/qt/settings/settings_container.cpp
|
source/scwx/qt/settings/settings_container.cpp
|
||||||
source/scwx/qt/settings/settings_interface.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_product_view.hpp
|
||||||
source/scwx/qt/view/level3_radial_view.hpp
|
source/scwx/qt/view/level3_radial_view.hpp
|
||||||
source/scwx/qt/view/level3_raster_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.hpp
|
||||||
source/scwx/qt/view/radar_product_view_factory.hpp)
|
source/scwx/qt/view/radar_product_view_factory.hpp)
|
||||||
set(SRC_VIEW source/scwx/qt/view/level2_product_view.cpp
|
set(SRC_VIEW source/scwx/qt/view/level2_product_view.cpp
|
||||||
source/scwx/qt/view/level3_product_view.cpp
|
source/scwx/qt/view/level3_product_view.cpp
|
||||||
source/scwx/qt/view/level3_radial_view.cpp
|
source/scwx/qt/view/level3_radial_view.cpp
|
||||||
source/scwx/qt/view/level3_raster_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.cpp
|
||||||
source/scwx/qt/view/radar_product_view_factory.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/types/qt_types.hpp>
|
||||||
#include <scwx/qt/ui/setup/setup_wizard.hpp>
|
#include <scwx/qt/ui/setup/setup_wizard.hpp>
|
||||||
#include <scwx/network/cpr.hpp>
|
#include <scwx/network/cpr.hpp>
|
||||||
|
#include <scwx/util/environment.hpp>
|
||||||
#include <scwx/util/logger.hpp>
|
#include <scwx/util/logger.hpp>
|
||||||
#include <scwx/util/threads.hpp>
|
#include <scwx/util/threads.hpp>
|
||||||
|
|
||||||
|
|
@ -19,6 +20,7 @@
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QStandardPaths>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
|
|
||||||
static const std::string logPrefix_ = "scwx::main";
|
static const std::string logPrefix_ = "scwx::main";
|
||||||
|
|
@ -45,6 +47,11 @@ int main(int argc, char* argv[])
|
||||||
QCoreApplication::installTranslator(&translator);
|
QCoreApplication::installTranslator(&translator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!scwx::util::GetEnvironment("SCWX_TEST").empty())
|
||||||
|
{
|
||||||
|
QStandardPaths::setTestModeEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
// Start the io_context main loop
|
// Start the io_context main loop
|
||||||
boost::asio::io_context& ioContext = scwx::util::io_context();
|
boost::asio::io_context& ioContext = scwx::util::io_context();
|
||||||
auto work = boost::asio::make_work_guard(ioContext);
|
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 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>>
|
static std::unordered_map<std::string, std::weak_ptr<RadarProductManager>>
|
||||||
instanceMap_;
|
instanceMap_;
|
||||||
|
|
@ -638,10 +639,12 @@ void RadarProductManagerImpl::RefreshData(
|
||||||
threadPool_,
|
threadPool_,
|
||||||
[=, this]()
|
[=, this]()
|
||||||
{
|
{
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
auto [newObjects, totalObjects] =
|
auto [newObjects, totalObjects] =
|
||||||
providerManager->provider_->Refresh();
|
providerManager->provider_->Refresh();
|
||||||
|
|
||||||
std::chrono::milliseconds interval = kRetryInterval_;
|
std::chrono::milliseconds interval = kFastRetryInterval_;
|
||||||
|
|
||||||
if (totalObjects > 0)
|
if (totalObjects > 0)
|
||||||
{
|
{
|
||||||
|
|
@ -651,12 +654,25 @@ void RadarProductManagerImpl::RefreshData(
|
||||||
|
|
||||||
auto updatePeriod = providerManager->provider_->update_period();
|
auto updatePeriod = providerManager->provider_->update_period();
|
||||||
auto lastModified = providerManager->provider_->last_modified();
|
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>(
|
interval = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
updatePeriod -
|
updatePeriod - sinceLastModified);
|
||||||
(std::chrono::system_clock::now() - lastModified));
|
|
||||||
if (interval < std::chrono::milliseconds {kRetryInterval_})
|
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)
|
if (newObjects > 0)
|
||||||
|
|
@ -669,10 +685,10 @@ void RadarProductManagerImpl::RefreshData(
|
||||||
}
|
}
|
||||||
else if (providerManager->refreshEnabled_)
|
else if (providerManager->refreshEnabled_)
|
||||||
{
|
{
|
||||||
logger_->info("[{}] No data found, disabling refresh",
|
logger_->info("[{}] No data found", providerManager->name());
|
||||||
providerManager->name());
|
|
||||||
|
|
||||||
providerManager->refreshEnabled_ = false;
|
// If no data is found, retry at the slow retry interval
|
||||||
|
interval = kSlowRetryInterval_;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (providerManager->refreshEnabled_)
|
if (providerManager->refreshEnabled_)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include <scwx/qt/settings/general_settings.hpp>
|
#include <scwx/qt/settings/general_settings.hpp>
|
||||||
#include <scwx/qt/settings/map_settings.hpp>
|
#include <scwx/qt/settings/map_settings.hpp>
|
||||||
#include <scwx/qt/settings/palette_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/text_settings.hpp>
|
||||||
#include <scwx/qt/settings/ui_settings.hpp>
|
#include <scwx/qt/settings/ui_settings.hpp>
|
||||||
#include <scwx/qt/util/json.hpp>
|
#include <scwx/qt/util/json.hpp>
|
||||||
|
|
@ -116,6 +117,7 @@ void SettingsManager::Shutdown()
|
||||||
|
|
||||||
dataChanged |= settings::GeneralSettings::Instance().Shutdown();
|
dataChanged |= settings::GeneralSettings::Instance().Shutdown();
|
||||||
dataChanged |= settings::MapSettings::Instance().Shutdown();
|
dataChanged |= settings::MapSettings::Instance().Shutdown();
|
||||||
|
dataChanged |= settings::ProductSettings::Instance().Shutdown();
|
||||||
dataChanged |= settings::UiSettings::Instance().Shutdown();
|
dataChanged |= settings::UiSettings::Instance().Shutdown();
|
||||||
|
|
||||||
if (dataChanged)
|
if (dataChanged)
|
||||||
|
|
@ -132,6 +134,7 @@ boost::json::value SettingsManager::Impl::ConvertSettingsToJson()
|
||||||
settings::AudioSettings::Instance().WriteJson(settingsJson);
|
settings::AudioSettings::Instance().WriteJson(settingsJson);
|
||||||
settings::MapSettings::Instance().WriteJson(settingsJson);
|
settings::MapSettings::Instance().WriteJson(settingsJson);
|
||||||
settings::PaletteSettings::Instance().WriteJson(settingsJson);
|
settings::PaletteSettings::Instance().WriteJson(settingsJson);
|
||||||
|
settings::ProductSettings::Instance().WriteJson(settingsJson);
|
||||||
settings::TextSettings::Instance().WriteJson(settingsJson);
|
settings::TextSettings::Instance().WriteJson(settingsJson);
|
||||||
settings::UiSettings::Instance().WriteJson(settingsJson);
|
settings::UiSettings::Instance().WriteJson(settingsJson);
|
||||||
|
|
||||||
|
|
@ -146,6 +149,7 @@ void SettingsManager::Impl::GenerateDefaultSettings()
|
||||||
settings::AudioSettings::Instance().SetDefaults();
|
settings::AudioSettings::Instance().SetDefaults();
|
||||||
settings::MapSettings::Instance().SetDefaults();
|
settings::MapSettings::Instance().SetDefaults();
|
||||||
settings::PaletteSettings::Instance().SetDefaults();
|
settings::PaletteSettings::Instance().SetDefaults();
|
||||||
|
settings::ProductSettings::Instance().SetDefaults();
|
||||||
settings::TextSettings::Instance().SetDefaults();
|
settings::TextSettings::Instance().SetDefaults();
|
||||||
settings::UiSettings::Instance().SetDefaults();
|
settings::UiSettings::Instance().SetDefaults();
|
||||||
}
|
}
|
||||||
|
|
@ -161,6 +165,7 @@ bool SettingsManager::Impl::LoadSettings(
|
||||||
jsonDirty |= !settings::AudioSettings::Instance().ReadJson(settingsJson);
|
jsonDirty |= !settings::AudioSettings::Instance().ReadJson(settingsJson);
|
||||||
jsonDirty |= !settings::MapSettings::Instance().ReadJson(settingsJson);
|
jsonDirty |= !settings::MapSettings::Instance().ReadJson(settingsJson);
|
||||||
jsonDirty |= !settings::PaletteSettings::Instance().ReadJson(settingsJson);
|
jsonDirty |= !settings::PaletteSettings::Instance().ReadJson(settingsJson);
|
||||||
|
jsonDirty |= !settings::ProductSettings::Instance().ReadJson(settingsJson);
|
||||||
jsonDirty |= !settings::TextSettings::Instance().ReadJson(settingsJson);
|
jsonDirty |= !settings::TextSettings::Instance().ReadJson(settingsJson);
|
||||||
jsonDirty |= !settings::UiSettings::Instance().ReadJson(settingsJson);
|
jsonDirty |= !settings::UiSettings::Instance().ReadJson(settingsJson);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include <scwx/qt/map/color_table_layer.hpp>
|
#include <scwx/qt/map/color_table_layer.hpp>
|
||||||
#include <scwx/qt/gl/shader_program.hpp>
|
#include <scwx/qt/gl/shader_program.hpp>
|
||||||
|
#include <scwx/qt/view/radar_product_view.hpp>
|
||||||
#include <scwx/util/logger.hpp>
|
#include <scwx/util/logger.hpp>
|
||||||
|
|
||||||
#if defined(_MSC_VER)
|
#if defined(_MSC_VER)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
#include <scwx/qt/map/map_context.hpp>
|
#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
|
namespace scwx
|
||||||
{
|
{
|
||||||
|
|
@ -11,26 +14,23 @@ class MapContext::Impl
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit Impl(std::shared_ptr<view::RadarProductView> radarProductView) :
|
explicit Impl(std::shared_ptr<view::RadarProductView> radarProductView) :
|
||||||
map_ {},
|
radarProductView_ {radarProductView}
|
||||||
settings_ {},
|
|
||||||
pixelRatio_ {1.0f},
|
|
||||||
radarProductView_ {radarProductView},
|
|
||||||
radarProductGroup_ {common::RadarProductGroup::Unknown},
|
|
||||||
radarProduct_ {"???"},
|
|
||||||
radarProductCode_ {0}
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
~Impl() {}
|
~Impl() {}
|
||||||
|
|
||||||
std::weak_ptr<QMapLibreGL::Map> map_;
|
std::weak_ptr<QMapLibreGL::Map> map_ {};
|
||||||
MapSettings settings_;
|
MapSettings settings_ {};
|
||||||
float pixelRatio_;
|
float pixelRatio_ {1.0f};
|
||||||
std::shared_ptr<view::RadarProductView> radarProductView_;
|
common::RadarProductGroup radarProductGroup_ {
|
||||||
common::RadarProductGroup radarProductGroup_;
|
common::RadarProductGroup::Unknown};
|
||||||
std::string radarProduct_;
|
std::string radarProduct_ {"???"};
|
||||||
int16_t radarProductCode_;
|
int16_t radarProductCode_ {0};
|
||||||
QMapLibreGL::CustomLayerRenderParameters renderParameters_ {};
|
QMapLibreGL::CustomLayerRenderParameters renderParameters_ {};
|
||||||
|
|
||||||
|
std::shared_ptr<view::OverlayProductView> overlayProductView_ {nullptr};
|
||||||
|
std::shared_ptr<view::RadarProductView> radarProductView_;
|
||||||
};
|
};
|
||||||
|
|
||||||
MapContext::MapContext(
|
MapContext::MapContext(
|
||||||
|
|
@ -58,6 +58,12 @@ float MapContext::pixel_ratio() const
|
||||||
return p->pixelRatio_;
|
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
|
std::shared_ptr<view::RadarProductView> MapContext::radar_product_view() const
|
||||||
{
|
{
|
||||||
return p->radarProductView_;
|
return p->radarProductView_;
|
||||||
|
|
@ -83,18 +89,24 @@ QMapLibreGL::CustomLayerRenderParameters MapContext::render_parameters() const
|
||||||
return p->renderParameters_;
|
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;
|
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)
|
void MapContext::set_pixel_ratio(float pixelRatio)
|
||||||
{
|
{
|
||||||
p->pixelRatio_ = pixelRatio;
|
p->pixelRatio_ = pixelRatio;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MapContext::set_radar_product_view(
|
void MapContext::set_radar_product_view(
|
||||||
std::shared_ptr<view::RadarProductView> radarProductView)
|
const std::shared_ptr<view::RadarProductView>& radarProductView)
|
||||||
{
|
{
|
||||||
p->radarProductView_ = radarProductView;
|
p->radarProductView_ = radarProductView;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <scwx/qt/gl/gl_context.hpp>
|
#include <scwx/qt/gl/gl_context.hpp>
|
||||||
#include <scwx/qt/map/map_settings.hpp>
|
#include <scwx/common/products.hpp>
|
||||||
#include <scwx/qt/view/radar_product_view.hpp>
|
|
||||||
|
|
||||||
#include <QMapLibreGL/QMapLibreGL>
|
#include <QMapLibreGL/QMapLibreGL>
|
||||||
|
|
||||||
|
|
@ -10,9 +9,19 @@ namespace scwx
|
||||||
{
|
{
|
||||||
namespace qt
|
namespace qt
|
||||||
{
|
{
|
||||||
|
namespace view
|
||||||
|
{
|
||||||
|
|
||||||
|
class OverlayProductView;
|
||||||
|
class RadarProductView;
|
||||||
|
|
||||||
|
} // namespace view
|
||||||
|
|
||||||
namespace map
|
namespace map
|
||||||
{
|
{
|
||||||
|
|
||||||
|
struct MapSettings;
|
||||||
|
|
||||||
class MapContext : public gl::GlContext
|
class MapContext : public gl::GlContext
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -26,19 +35,22 @@ public:
|
||||||
MapContext(MapContext&&) noexcept;
|
MapContext(MapContext&&) noexcept;
|
||||||
MapContext& operator=(MapContext&&) noexcept;
|
MapContext& operator=(MapContext&&) noexcept;
|
||||||
|
|
||||||
std::weak_ptr<QMapLibreGL::Map> map() const;
|
std::weak_ptr<QMapLibreGL::Map> map() const;
|
||||||
MapSettings& settings();
|
MapSettings& settings();
|
||||||
float pixel_ratio() const;
|
float pixel_ratio() const;
|
||||||
std::shared_ptr<view::RadarProductView> radar_product_view() const;
|
std::shared_ptr<view::OverlayProductView> overlay_product_view() const;
|
||||||
common::RadarProductGroup radar_product_group() const;
|
std::shared_ptr<view::RadarProductView> radar_product_view() const;
|
||||||
std::string radar_product() const;
|
common::RadarProductGroup radar_product_group() const;
|
||||||
int16_t radar_product_code() const;
|
std::string radar_product() const;
|
||||||
QMapLibreGL::CustomLayerRenderParameters render_parameters() 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_pixel_ratio(float pixelRatio);
|
||||||
void set_radar_product_view(
|
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_group(common::RadarProductGroup radarProductGroup);
|
||||||
void set_radar_product(const std::string& radarProduct);
|
void set_radar_product(const std::string& radarProduct);
|
||||||
void set_radar_product_code(int16_t radarProductCode);
|
void set_radar_product_code(int16_t radarProductCode);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@
|
||||||
#include <scwx/qt/map/color_table_layer.hpp>
|
#include <scwx/qt/map/color_table_layer.hpp>
|
||||||
#include <scwx/qt/map/layer_wrapper.hpp>
|
#include <scwx/qt/map/layer_wrapper.hpp>
|
||||||
#include <scwx/qt/map/map_provider.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_layer.hpp>
|
||||||
|
#include <scwx/qt/map/overlay_product_layer.hpp>
|
||||||
#include <scwx/qt/map/placefile_layer.hpp>
|
#include <scwx/qt/map/placefile_layer.hpp>
|
||||||
#include <scwx/qt/map/radar_product_layer.hpp>
|
#include <scwx/qt/map/radar_product_layer.hpp>
|
||||||
#include <scwx/qt/map/radar_range_layer.hpp>
|
#include <scwx/qt/map/radar_range_layer.hpp>
|
||||||
|
|
@ -19,6 +21,7 @@
|
||||||
#include <scwx/qt/util/file.hpp>
|
#include <scwx/qt/util/file.hpp>
|
||||||
#include <scwx/qt/util/maplibre.hpp>
|
#include <scwx/qt/util/maplibre.hpp>
|
||||||
#include <scwx/qt/util/tooltip.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/qt/view/radar_product_view_factory.hpp>
|
||||||
#include <scwx/util/logger.hpp>
|
#include <scwx/util/logger.hpp>
|
||||||
#include <scwx/util/time.hpp>
|
#include <scwx/util/time.hpp>
|
||||||
|
|
@ -86,6 +89,14 @@ public:
|
||||||
prevBearing_ {0.0},
|
prevBearing_ {0.0},
|
||||||
prevPitch_ {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();
|
auto& generalSettings = settings::GeneralSettings::Instance();
|
||||||
|
|
||||||
SetRadarSite(generalSettings.default_radar_site().GetValue());
|
SetRadarSite(generalSettings.default_radar_site().GetValue());
|
||||||
|
|
@ -181,12 +192,13 @@ public:
|
||||||
manager::PlacefileManager::Instance()};
|
manager::PlacefileManager::Instance()};
|
||||||
std::shared_ptr<manager::RadarProductManager> radarProductManager_;
|
std::shared_ptr<manager::RadarProductManager> radarProductManager_;
|
||||||
|
|
||||||
std::shared_ptr<RadarProductLayer> radarProductLayer_;
|
std::shared_ptr<RadarProductLayer> radarProductLayer_;
|
||||||
std::shared_ptr<AlertLayer> alertLayer_;
|
std::shared_ptr<AlertLayer> alertLayer_;
|
||||||
std::shared_ptr<OverlayLayer> overlayLayer_;
|
std::shared_ptr<OverlayLayer> overlayLayer_;
|
||||||
std::shared_ptr<PlacefileLayer> placefileLayer_;
|
std::shared_ptr<OverlayProductLayer> overlayProductLayer_ {nullptr};
|
||||||
std::shared_ptr<ColorTableLayer> colorTableLayer_;
|
std::shared_ptr<PlacefileLayer> placefileLayer_;
|
||||||
std::shared_ptr<RadarSiteLayer> radarSiteLayer_ {nullptr};
|
std::shared_ptr<ColorTableLayer> colorTableLayer_;
|
||||||
|
std::shared_ptr<RadarSiteLayer> radarSiteLayer_ {nullptr};
|
||||||
|
|
||||||
std::list<std::shared_ptr<PlacefileLayer>> placefileLayers_ {};
|
std::list<std::shared_ptr<PlacefileLayer>> placefileLayers_ {};
|
||||||
|
|
||||||
|
|
@ -439,7 +451,7 @@ std::chrono::system_clock::time_point MapWidget::GetSelectedTime() const
|
||||||
if (radarProductView != nullptr)
|
if (radarProductView != nullptr)
|
||||||
{
|
{
|
||||||
// Select the time associated with the active radar product
|
// Select the time associated with the active radar product
|
||||||
time = radarProductView->GetSelectedTime();
|
time = radarProductView->selected_time();
|
||||||
}
|
}
|
||||||
|
|
||||||
return time;
|
return time;
|
||||||
|
|
@ -618,6 +630,9 @@ void MapWidget::SelectTime(std::chrono::system_clock::time_point time)
|
||||||
{
|
{
|
||||||
auto radarProductView = p->context_->radar_product_view();
|
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 there is an active radar product view
|
||||||
if (radarProductView != nullptr)
|
if (radarProductView != nullptr)
|
||||||
{
|
{
|
||||||
|
|
@ -651,12 +666,16 @@ void MapWidget::SetAutoRefresh(bool enabled)
|
||||||
true,
|
true,
|
||||||
p->uuid_);
|
p->uuid_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p->context_->overlay_product_view()->SetAutoRefresh(enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MapWidget::SetAutoUpdate(bool enabled)
|
void MapWidget::SetAutoUpdate(bool enabled)
|
||||||
{
|
{
|
||||||
p->autoUpdateEnabled_ = enabled;
|
p->autoUpdateEnabled_ = enabled;
|
||||||
|
|
||||||
|
p->context_->overlay_product_view()->SetAutoUpdate(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MapWidget::SetMapLocation(double latitude,
|
void MapWidget::SetMapLocation(double latitude,
|
||||||
|
|
@ -912,6 +931,16 @@ void MapWidgetImpl::AddLayer(types::LayerType type,
|
||||||
{
|
{
|
||||||
switch (std::get<types::DataLayer>(description))
|
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
|
// If there is a radar product view, create the radar range layer
|
||||||
case types::DataLayer::RadarRange:
|
case types::DataLayer::RadarRange:
|
||||||
if (radarProductView != nullptr)
|
if (radarProductView != nullptr)
|
||||||
|
|
@ -1492,6 +1521,10 @@ void MapWidgetImpl::SetRadarSite(const std::string& radarSite)
|
||||||
// Set new RadarProductManager
|
// Set new RadarProductManager
|
||||||
radarProductManager_ = manager::RadarProductManager::Instance(radarSite);
|
radarProductManager_ = manager::RadarProductManager::Instance(radarSite);
|
||||||
|
|
||||||
|
// Update views
|
||||||
|
context_->overlay_product_view()->set_radar_product_manager(
|
||||||
|
radarProductManager_);
|
||||||
|
|
||||||
// Connect signals to new RadarProductManager
|
// Connect signals to new RadarProductManager
|
||||||
RadarProductManagerConnect();
|
RadarProductManagerConnect();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@
|
||||||
#include <scwx/qt/gl/draw/icons.hpp>
|
#include <scwx/qt/gl/draw/icons.hpp>
|
||||||
#include <scwx/qt/gl/draw/rectangle.hpp>
|
#include <scwx/qt/gl/draw/rectangle.hpp>
|
||||||
#include <scwx/qt/manager/position_manager.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/types/texture_types.hpp>
|
||||||
|
#include <scwx/qt/view/radar_product_view.hpp>
|
||||||
#include <scwx/util/logger.hpp>
|
#include <scwx/util/logger.hpp>
|
||||||
#include <scwx/util/time.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/gl/shader_program.hpp>
|
||||||
#include <scwx/qt/util/maplibre.hpp>
|
#include <scwx/qt/util/maplibre.hpp>
|
||||||
#include <scwx/qt/util/tooltip.hpp>
|
#include <scwx/qt/util/tooltip.hpp>
|
||||||
|
#include <scwx/qt/view/radar_product_view.hpp>
|
||||||
#include <scwx/util/logger.hpp>
|
#include <scwx/util/logger.hpp>
|
||||||
|
|
||||||
#include <execution>
|
#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::FlashFlood, true},
|
||||||
{types::LayerType::Alert, awips::Phenomenon::Marine, true},
|
{types::LayerType::Alert, awips::Phenomenon::Marine, true},
|
||||||
{types::LayerType::Map, types::MapLayer::MapSymbology, false},
|
{types::LayerType::Map, types::MapLayer::MapSymbology, false},
|
||||||
|
{types::LayerType::Data, types::DataLayer::OverlayProduct, true},
|
||||||
{types::LayerType::Radar, std::monostate {}, true},
|
{types::LayerType::Radar, std::monostate {}, true},
|
||||||
{types::LayerType::Map, types::MapLayer::MapUnderlay, false},
|
{types::LayerType::Map, types::MapLayer::MapUnderlay, false},
|
||||||
};
|
};
|
||||||
|
|
@ -243,9 +244,6 @@ void LayerModel::Impl::ValidateLayerSettings(types::LayerVector& layers)
|
||||||
|
|
||||||
// Validate immovable layers
|
// Validate immovable layers
|
||||||
std::vector<types::LayerVector::iterator> immovableIterators {};
|
std::vector<types::LayerVector::iterator> immovableIterators {};
|
||||||
types::LayerVector::iterator radarSiteIterator {};
|
|
||||||
types::LayerVector::iterator mapSymbologyIterator {};
|
|
||||||
types::LayerVector::iterator mapUnderlayIterator {};
|
|
||||||
for (auto& immovableLayer : kImmovableLayers_)
|
for (auto& immovableLayer : kImmovableLayers_)
|
||||||
{
|
{
|
||||||
// Set the default displayed state for a layer that is not found
|
// 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;
|
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
|
// Add the immovable iterator to the list
|
||||||
immovableIterators.push_back(it);
|
immovableIterators.push_back(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate data layers
|
// Validate the remainder of the default layer list
|
||||||
std::vector<types::LayerVector::iterator> dataIterators {};
|
auto previousLayer = layers.end();
|
||||||
for (const auto& dataLayer : types::DataLayerIterator())
|
for (auto defaultIt = kDefaultLayers_.rbegin();
|
||||||
|
defaultIt != kDefaultLayers_.rend();
|
||||||
|
++defaultIt)
|
||||||
{
|
{
|
||||||
// Find the data layer
|
// Find the default layer in the current layer list
|
||||||
auto it = std::find_if(layers.begin(),
|
auto currentIt =
|
||||||
layers.end(),
|
std::find_if(layers.begin(),
|
||||||
[&dataLayer](const types::LayerInfo& layer)
|
layers.end(),
|
||||||
{
|
[&defaultIt](const types::LayerInfo& layer)
|
||||||
return layer.type_ == types::LayerType::Data &&
|
{
|
||||||
std::get<types::DataLayer>(
|
return layer.type_ == defaultIt->type_ &&
|
||||||
layer.description_) == dataLayer;
|
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,
|
// Insert before the previously found layer
|
||||||
// otherwise, insert after the previous data layer
|
currentIt = layers.insert(previousLayer, *defaultIt);
|
||||||
types::LayerVector::iterator insertPosition =
|
|
||||||
dataIterators.empty() ? radarSiteIterator + 1 :
|
|
||||||
dataIterators.back() + 1;
|
|
||||||
it =
|
|
||||||
layers.insert(insertPosition, {types::LayerType::Data, dataLayer});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dataIterators.push_back(it);
|
// Store the current layer as the previous
|
||||||
}
|
previousLayer = currentIt;
|
||||||
|
|
||||||
// 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 {}});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
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, "?"}};
|
{LayerType::Unknown, "?"}};
|
||||||
|
|
||||||
static const std::unordered_map<DataLayer, std::string> dataLayerName_ {
|
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>
|
static const std::unordered_map<InformationLayer, std::string>
|
||||||
informationLayerName_ {{InformationLayer::MapOverlay, "Map Overlay"},
|
informationLayerName_ {{InformationLayer::MapOverlay, "Map Overlay"},
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,12 @@ enum class LayerType
|
||||||
|
|
||||||
enum class DataLayer
|
enum class DataLayer
|
||||||
{
|
{
|
||||||
|
OverlayProduct,
|
||||||
RadarRange,
|
RadarRange,
|
||||||
Unknown
|
Unknown
|
||||||
};
|
};
|
||||||
typedef scwx::util::
|
typedef scwx::util::
|
||||||
Iterator<DataLayer, DataLayer::RadarRange, DataLayer::RadarRange>
|
Iterator<DataLayer, DataLayer::OverlayProduct, DataLayer::RadarRange>
|
||||||
DataLayerIterator;
|
DataLayerIterator;
|
||||||
|
|
||||||
enum class InformationLayer
|
enum class InformationLayer
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
#include <scwx/qt/ui/level3_products_widget.hpp>
|
#include <scwx/qt/ui/level3_products_widget.hpp>
|
||||||
#include <scwx/qt/ui/flow_layout.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 <scwx/util/logger.hpp>
|
||||||
|
|
||||||
#include <execution>
|
#include <execution>
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
|
|
||||||
|
#include <QCheckBox>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QToolButton>
|
#include <QToolButton>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
namespace scwx
|
namespace scwx
|
||||||
{
|
{
|
||||||
|
|
@ -25,13 +29,17 @@ class Level3ProductsWidgetImpl : public QObject
|
||||||
public:
|
public:
|
||||||
explicit Level3ProductsWidgetImpl(Level3ProductsWidget* self) :
|
explicit Level3ProductsWidgetImpl(Level3ProductsWidget* self) :
|
||||||
self_ {self},
|
self_ {self},
|
||||||
layout_ {new ui::FlowLayout(self)},
|
layout_ {new QVBoxLayout(self)},
|
||||||
|
productsWidget_ {new QWidget(self)},
|
||||||
|
productsLayout_ {new ui::FlowLayout(productsWidget_)},
|
||||||
categoryButtons_ {},
|
categoryButtons_ {},
|
||||||
productTiltMap_ {},
|
productTiltMap_ {},
|
||||||
awipsProductMap_ {},
|
awipsProductMap_ {},
|
||||||
awipsProductMutex_ {}
|
awipsProductMutex_ {}
|
||||||
{
|
{
|
||||||
layout_->setContentsMargins(0, 0, 0, 0);
|
layout_->setContentsMargins(0, 0, 0, 0);
|
||||||
|
layout_->addWidget(productsWidget_);
|
||||||
|
productsLayout_->setContentsMargins(0, 0, 0, 0);
|
||||||
|
|
||||||
for (common::Level3ProductCategory category :
|
for (common::Level3ProductCategory category :
|
||||||
common::Level3ProductCategoryIterator())
|
common::Level3ProductCategoryIterator())
|
||||||
|
|
@ -42,7 +50,7 @@ public:
|
||||||
toolButton->setStatusTip(
|
toolButton->setStatusTip(
|
||||||
tr(common::GetLevel3CategoryDescription(category).c_str()));
|
tr(common::GetLevel3CategoryDescription(category).c_str()));
|
||||||
toolButton->setPopupMode(QToolButton::MenuButtonPopup);
|
toolButton->setPopupMode(QToolButton::MenuButtonPopup);
|
||||||
layout_->addWidget(toolButton);
|
productsLayout_->addWidget(toolButton);
|
||||||
categoryButtons_.push_back(toolButton);
|
categoryButtons_.push_back(toolButton);
|
||||||
|
|
||||||
QObject::connect(toolButton,
|
QObject::connect(toolButton,
|
||||||
|
|
@ -99,6 +107,26 @@ public:
|
||||||
|
|
||||||
toolButton->setEnabled(false);
|
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;
|
~Level3ProductsWidgetImpl() = default;
|
||||||
|
|
||||||
|
|
@ -109,6 +137,8 @@ public:
|
||||||
|
|
||||||
Level3ProductsWidget* self_;
|
Level3ProductsWidget* self_;
|
||||||
QLayout* layout_;
|
QLayout* layout_;
|
||||||
|
QWidget* productsWidget_;
|
||||||
|
QLayout* productsLayout_;
|
||||||
std::list<QToolButton*> categoryButtons_;
|
std::list<QToolButton*> categoryButtons_;
|
||||||
std::unordered_map<common::Level3ProductCategory,
|
std::unordered_map<common::Level3ProductCategory,
|
||||||
std::unordered_map<std::string, QMenu*>>
|
std::unordered_map<std::string, QMenu*>>
|
||||||
|
|
@ -118,6 +148,9 @@ public:
|
||||||
|
|
||||||
std::unordered_map<QAction*, std::string> awipsProductMap_;
|
std::unordered_map<QAction*, std::string> awipsProductMap_;
|
||||||
std::shared_mutex awipsProductMutex_;
|
std::shared_mutex awipsProductMutex_;
|
||||||
|
|
||||||
|
settings::SettingsInterface<bool> stiPastEnabled_ {};
|
||||||
|
settings::SettingsInterface<bool> stiForecastEnabled_ {};
|
||||||
};
|
};
|
||||||
|
|
||||||
Level3ProductsWidget::Level3ProductsWidget(QWidget* parent) :
|
Level3ProductsWidget::Level3ProductsWidget(QWidget* parent) :
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
#include <scwx/qt/util/geographic_lib.hpp>
|
#include <scwx/qt/util/geographic_lib.hpp>
|
||||||
#include <scwx/util/logger.hpp>
|
#include <scwx/util/logger.hpp>
|
||||||
|
|
||||||
|
#include <numbers>
|
||||||
|
|
||||||
#include <GeographicLib/Gnomonic.hpp>
|
#include <GeographicLib/Gnomonic.hpp>
|
||||||
#include <geos/algorithm/PointLocation.h>
|
#include <geos/algorithm/PointLocation.h>
|
||||||
#include <geos/geom/CoordinateSequence.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};
|
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>
|
units::length::meters<double>
|
||||||
GetDistance(double lat1, double lon1, double lat2, double lon2)
|
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>
|
units::angle::degrees<double>
|
||||||
GetAngle(double lat1, double lon1, double lat2, double lon2);
|
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.
|
* 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 {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::chrono::system_clock::time_point RadarProductView::GetSelectedTime() const
|
|
||||||
{
|
|
||||||
return p->selectedTime_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RadarProductView::ComputeSweep()
|
void RadarProductView::ComputeSweep()
|
||||||
{
|
{
|
||||||
logger_->debug("ComputeSweep()");
|
logger_->debug("ComputeSweep()");
|
||||||
|
|
|
||||||
|
|
@ -76,8 +76,6 @@ public:
|
||||||
GetDataLevelCode(std::uint16_t level) const = 0;
|
GetDataLevelCode(std::uint16_t level) const = 0;
|
||||||
virtual std::optional<float> GetDataValue(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>>
|
virtual std::vector<std::pair<std::string, std::string>>
|
||||||
GetDescriptionFields() const;
|
GetDescriptionFields() const;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 90331f7654586302b223d88329846929514bc883
|
Subproject commit 0446ff708b387728faf8d2dbb3862a757067c2ee
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
|
@ -29,5 +30,14 @@ std::vector<std::string> ParseTokens(const std::string& s,
|
||||||
|
|
||||||
std::string ToString(const std::vector<std::string>& v);
|
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 util
|
||||||
} // namespace scwx
|
} // namespace scwx
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#if !defined(_MSC_VER)
|
#if !defined(_MSC_VER)
|
||||||
# include <date/tz.h>
|
# include <date/tz.h>
|
||||||
|
|
@ -24,5 +25,9 @@ std::string TimeString(std::chrono::system_clock::time_point time,
|
||||||
const time_zone* timeZone = nullptr,
|
const time_zone* timeZone = nullptr,
|
||||||
bool epochValid = true);
|
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 util
|
||||||
} // namespace scwx
|
} // namespace scwx
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <scwx/awips/message.hpp>
|
#include <scwx/awips/message.hpp>
|
||||||
|
#include <scwx/wsr88d/rpg/packet.hpp>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace scwx
|
namespace scwx
|
||||||
{
|
{
|
||||||
|
|
@ -31,6 +33,8 @@ public:
|
||||||
|
|
||||||
size_t data_size() const override;
|
size_t data_size() const override;
|
||||||
|
|
||||||
|
const std::vector<std::vector<std::shared_ptr<Packet>>>& page_list() const;
|
||||||
|
|
||||||
bool Parse(std::istream& is);
|
bool Parse(std::istream& is);
|
||||||
|
|
||||||
static constexpr size_t SIZE = 102u;
|
static constexpr size_t SIZE = 102u;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
|
#include <units/length.h>
|
||||||
|
|
||||||
namespace scwx
|
namespace scwx
|
||||||
{
|
{
|
||||||
namespace wsr88d
|
namespace wsr88d
|
||||||
|
|
@ -21,17 +23,27 @@ public:
|
||||||
explicit LinkedVectorPacket();
|
explicit LinkedVectorPacket();
|
||||||
~LinkedVectorPacket();
|
~LinkedVectorPacket();
|
||||||
|
|
||||||
LinkedVectorPacket(const LinkedVectorPacket&) = delete;
|
LinkedVectorPacket(const LinkedVectorPacket&) = delete;
|
||||||
LinkedVectorPacket& operator=(const LinkedVectorPacket&) = delete;
|
LinkedVectorPacket& operator=(const LinkedVectorPacket&) = delete;
|
||||||
|
|
||||||
LinkedVectorPacket(LinkedVectorPacket&&) noexcept;
|
LinkedVectorPacket(LinkedVectorPacket&&) noexcept;
|
||||||
LinkedVectorPacket& operator=(LinkedVectorPacket&&) noexcept;
|
LinkedVectorPacket& operator=(LinkedVectorPacket&&) noexcept;
|
||||||
|
|
||||||
uint16_t packet_code() const override;
|
std::uint16_t packet_code() const override;
|
||||||
uint16_t length_of_block() const;
|
std::uint16_t length_of_block() const;
|
||||||
std::optional<uint16_t> value_of_vector() 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;
|
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;
|
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 Parse(std::istream& is, bool skipHeader);
|
bool Parse(std::istream& is, bool skipHeader);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <scwx/wsr88d/rpg/packet.hpp>
|
#include <scwx/wsr88d/rpg/packet.hpp>
|
||||||
|
#include <scwx/wsr88d/rpg/rpg_types.hpp>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
@ -14,8 +15,6 @@ namespace wsr88d
|
||||||
namespace rpg
|
namespace rpg
|
||||||
{
|
{
|
||||||
|
|
||||||
class TextAndSpecialSymbolPacketImpl;
|
|
||||||
|
|
||||||
class TextAndSpecialSymbolPacket : public Packet
|
class TextAndSpecialSymbolPacket : public Packet
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -29,21 +28,23 @@ public:
|
||||||
TextAndSpecialSymbolPacket(TextAndSpecialSymbolPacket&&) noexcept;
|
TextAndSpecialSymbolPacket(TextAndSpecialSymbolPacket&&) noexcept;
|
||||||
TextAndSpecialSymbolPacket& operator=(TextAndSpecialSymbolPacket&&) noexcept;
|
TextAndSpecialSymbolPacket& operator=(TextAndSpecialSymbolPacket&&) noexcept;
|
||||||
|
|
||||||
uint16_t packet_code() const override;
|
std::uint16_t packet_code() const override;
|
||||||
uint16_t length_of_block() const;
|
std::uint16_t length_of_block() const;
|
||||||
std::optional<uint16_t> value_of_text() const;
|
std::optional<std::uint16_t> value_of_text() const;
|
||||||
int16_t start_i() const;
|
std::int16_t start_i() const;
|
||||||
int16_t start_j() const;
|
std::int16_t start_j() const;
|
||||||
std::string text() 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;
|
bool Parse(std::istream& is) override;
|
||||||
|
|
||||||
static std::shared_ptr<TextAndSpecialSymbolPacket> Create(std::istream& is);
|
static std::shared_ptr<TextAndSpecialSymbolPacket> Create(std::istream& is);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<TextAndSpecialSymbolPacketImpl> p;
|
class Impl;
|
||||||
|
std::unique_ptr<Impl> p;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rpg
|
} // namespace rpg
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
|
#define STRINGS_IMPLEMENTATION
|
||||||
|
|
||||||
#include <scwx/util/strings.hpp>
|
#include <scwx/util/strings.hpp>
|
||||||
|
|
||||||
#include <boost/algorithm/string/trim.hpp>
|
#include <boost/algorithm/string/trim.hpp>
|
||||||
|
#include <boost/lexical_cast.hpp>
|
||||||
|
|
||||||
namespace scwx
|
namespace scwx
|
||||||
{
|
{
|
||||||
|
|
@ -88,5 +91,22 @@ std::string ToString(const std::vector<std::string>& v)
|
||||||
return value;
|
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 util
|
||||||
} // namespace scwx
|
} // namespace scwx
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,12 @@
|
||||||
|
|
||||||
#include <scwx/util/time.hpp>
|
#include <scwx/util/time.hpp>
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#if !defined(_MSC_VER)
|
||||||
|
# include <date/date.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace scwx
|
namespace scwx
|
||||||
{
|
{
|
||||||
namespace util
|
namespace util
|
||||||
|
|
@ -55,5 +61,33 @@ std::string TimeString(std::chrono::system_clock::time_point time,
|
||||||
return os.str();
|
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 util
|
||||||
} // namespace scwx
|
} // namespace scwx
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ GraphicAlphanumericBlock::GraphicAlphanumericBlock() :
|
||||||
GraphicAlphanumericBlock::~GraphicAlphanumericBlock() = default;
|
GraphicAlphanumericBlock::~GraphicAlphanumericBlock() = default;
|
||||||
|
|
||||||
GraphicAlphanumericBlock::GraphicAlphanumericBlock(
|
GraphicAlphanumericBlock::GraphicAlphanumericBlock(
|
||||||
GraphicAlphanumericBlock&&) noexcept = default;
|
GraphicAlphanumericBlock&&) noexcept = default;
|
||||||
GraphicAlphanumericBlock& GraphicAlphanumericBlock::operator=(
|
GraphicAlphanumericBlock& GraphicAlphanumericBlock::operator=(
|
||||||
GraphicAlphanumericBlock&&) noexcept = default;
|
GraphicAlphanumericBlock&&) noexcept = default;
|
||||||
|
|
||||||
|
|
@ -58,6 +58,12 @@ size_t GraphicAlphanumericBlock::data_size() const
|
||||||
return p->lengthOfBlock_;
|
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 GraphicAlphanumericBlock::Parse(std::istream& is)
|
||||||
{
|
{
|
||||||
bool blockValid = true;
|
bool blockValid = true;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include <scwx/wsr88d/rpg/general_status_message.hpp>
|
#include <scwx/wsr88d/rpg/general_status_message.hpp>
|
||||||
#include <scwx/wsr88d/rpg/graphic_product_message.hpp>
|
#include <scwx/wsr88d/rpg/graphic_product_message.hpp>
|
||||||
#include <scwx/wsr88d/rpg/radar_coded_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 <scwx/wsr88d/rpg/tabular_product_message.hpp>
|
||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
@ -44,7 +45,7 @@ static const std::unordered_map<int, CreateLevel3MessageFunction> //
|
||||||
{51, GraphicProductMessage::Create},
|
{51, GraphicProductMessage::Create},
|
||||||
{56, GraphicProductMessage::Create},
|
{56, GraphicProductMessage::Create},
|
||||||
{57, GraphicProductMessage::Create},
|
{57, GraphicProductMessage::Create},
|
||||||
{58, GraphicProductMessage::Create},
|
{58, StormTrackingInformationMessage::Create},
|
||||||
{59, GraphicProductMessage::Create},
|
{59, GraphicProductMessage::Create},
|
||||||
{61, GraphicProductMessage::Create},
|
{61, GraphicProductMessage::Create},
|
||||||
{62, TabularProductMessage::Create},
|
{62, TabularProductMessage::Create},
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,56 @@ std::optional<uint16_t> LinkedVectorPacket::value_of_vector() const
|
||||||
return value;
|
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
|
size_t LinkedVectorPacket::data_size() const
|
||||||
{
|
{
|
||||||
return p->lengthOfBlock_ + 4u;
|
return p->lengthOfBlock_ + 4u;
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
#include <scwx/wsr88d/rpg/precipitation_rate_data_array_packet.hpp>
|
#include <scwx/wsr88d/rpg/precipitation_rate_data_array_packet.hpp>
|
||||||
#include <scwx/wsr88d/rpg/radial_data_packet.hpp>
|
#include <scwx/wsr88d/rpg/radial_data_packet.hpp>
|
||||||
#include <scwx/wsr88d/rpg/raster_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/set_color_level_packet.hpp>
|
||||||
#include <scwx/wsr88d/rpg/sti_circle_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_id_symbol_packet.hpp>
|
||||||
|
|
@ -63,8 +63,8 @@ static const std::unordered_map<unsigned int, CreateMessageFunction> create_ {
|
||||||
{20, PointFeatureSymbolPacket::Create},
|
{20, PointFeatureSymbolPacket::Create},
|
||||||
{21, CellTrendDataPacket::Create},
|
{21, CellTrendDataPacket::Create},
|
||||||
{22, CellTrendVolumeScanTimes::Create},
|
{22, CellTrendVolumeScanTimes::Create},
|
||||||
{23, ScitForecastDataPacket::Create},
|
{23, ScitDataPacket::Create},
|
||||||
{24, ScitForecastDataPacket::Create},
|
{24, ScitDataPacket::Create},
|
||||||
{25, StiCircleSymbolPacket::Create},
|
{25, StiCircleSymbolPacket::Create},
|
||||||
{26, PointGraphicSymbolPacket::Create},
|
{26, PointGraphicSymbolPacket::Create},
|
||||||
{28, GenericDataPacket::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() = default;
|
||||||
|
|
||||||
TabularAlphanumericBlock::TabularAlphanumericBlock(
|
TabularAlphanumericBlock::TabularAlphanumericBlock(
|
||||||
TabularAlphanumericBlock&&) noexcept = default;
|
TabularAlphanumericBlock&&) noexcept = default;
|
||||||
TabularAlphanumericBlock& TabularAlphanumericBlock::operator=(
|
TabularAlphanumericBlock& TabularAlphanumericBlock::operator=(
|
||||||
TabularAlphanumericBlock&&) noexcept = default;
|
TabularAlphanumericBlock&&) noexcept = default;
|
||||||
|
|
||||||
|
|
@ -68,6 +68,12 @@ size_t TabularAlphanumericBlock::data_size() const
|
||||||
return p->lengthOfBlock_;
|
return p->lengthOfBlock_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::vector<std::vector<std::string>>&
|
||||||
|
TabularAlphanumericBlock::page_list() const
|
||||||
|
{
|
||||||
|
return p->pageList_;
|
||||||
|
}
|
||||||
|
|
||||||
bool TabularAlphanumericBlock::Parse(std::istream& is)
|
bool TabularAlphanumericBlock::Parse(std::istream& is)
|
||||||
{
|
{
|
||||||
return Parse(is, false);
|
return Parse(is, false);
|
||||||
|
|
|
||||||
|
|
@ -15,53 +15,45 @@ static const std::string logPrefix_ =
|
||||||
"scwx::wsr88d::rpg::text_and_special_symbol_packet";
|
"scwx::wsr88d::rpg::text_and_special_symbol_packet";
|
||||||
static const auto logger_ = util::Logger::Create(logPrefix_);
|
static const auto logger_ = util::Logger::Create(logPrefix_);
|
||||||
|
|
||||||
class TextAndSpecialSymbolPacketImpl
|
class TextAndSpecialSymbolPacket::Impl
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit TextAndSpecialSymbolPacketImpl() :
|
explicit Impl() {}
|
||||||
packetCode_ {0},
|
~Impl() = default;
|
||||||
lengthOfBlock_ {0},
|
|
||||||
valueOfText_ {0},
|
|
||||||
startI_ {0},
|
|
||||||
startJ_ {0},
|
|
||||||
text_ {}
|
|
||||||
{
|
|
||||||
}
|
|
||||||
~TextAndSpecialSymbolPacketImpl() = default;
|
|
||||||
|
|
||||||
uint16_t packetCode_;
|
std::uint16_t packetCode_ {0};
|
||||||
uint16_t lengthOfBlock_;
|
std::uint16_t lengthOfBlock_ {0};
|
||||||
uint16_t valueOfText_;
|
std::uint16_t valueOfText_ {0};
|
||||||
int16_t startI_;
|
std::int16_t startI_ {0};
|
||||||
int16_t startJ_;
|
std::int16_t startJ_ {0};
|
||||||
|
|
||||||
std::string text_;
|
std::string text_ {};
|
||||||
};
|
};
|
||||||
|
|
||||||
TextAndSpecialSymbolPacket::TextAndSpecialSymbolPacket() :
|
TextAndSpecialSymbolPacket::TextAndSpecialSymbolPacket() :
|
||||||
p(std::make_unique<TextAndSpecialSymbolPacketImpl>())
|
p(std::make_unique<Impl>())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
TextAndSpecialSymbolPacket::~TextAndSpecialSymbolPacket() = default;
|
TextAndSpecialSymbolPacket::~TextAndSpecialSymbolPacket() = default;
|
||||||
|
|
||||||
TextAndSpecialSymbolPacket::TextAndSpecialSymbolPacket(
|
TextAndSpecialSymbolPacket::TextAndSpecialSymbolPacket(
|
||||||
TextAndSpecialSymbolPacket&&) noexcept = default;
|
TextAndSpecialSymbolPacket&&) noexcept = default;
|
||||||
TextAndSpecialSymbolPacket& TextAndSpecialSymbolPacket::operator=(
|
TextAndSpecialSymbolPacket& TextAndSpecialSymbolPacket::operator=(
|
||||||
TextAndSpecialSymbolPacket&&) noexcept = default;
|
TextAndSpecialSymbolPacket&&) noexcept = default;
|
||||||
|
|
||||||
uint16_t TextAndSpecialSymbolPacket::packet_code() const
|
std::uint16_t TextAndSpecialSymbolPacket::packet_code() const
|
||||||
{
|
{
|
||||||
return p->packetCode_;
|
return p->packetCode_;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t TextAndSpecialSymbolPacket::length_of_block() const
|
std::uint16_t TextAndSpecialSymbolPacket::length_of_block() const
|
||||||
{
|
{
|
||||||
return p->lengthOfBlock_;
|
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)
|
if (p->packetCode_ == 8)
|
||||||
{
|
{
|
||||||
|
|
@ -71,12 +63,12 @@ std::optional<uint16_t> TextAndSpecialSymbolPacket::value_of_text() const
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
int16_t TextAndSpecialSymbolPacket::start_i() const
|
std::int16_t TextAndSpecialSymbolPacket::start_i() const
|
||||||
{
|
{
|
||||||
return p->startI_;
|
return p->startI_;
|
||||||
}
|
}
|
||||||
|
|
||||||
int16_t TextAndSpecialSymbolPacket::start_j() const
|
std::int16_t TextAndSpecialSymbolPacket::start_j() const
|
||||||
{
|
{
|
||||||
return p->startJ_;
|
return p->startJ_;
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +78,43 @@ std::string TextAndSpecialSymbolPacket::text() const
|
||||||
return p->text_;
|
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;
|
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/radar_coded_message.hpp
|
||||||
include/scwx/wsr88d/rpg/radial_data_packet.hpp
|
include/scwx/wsr88d/rpg/radial_data_packet.hpp
|
||||||
include/scwx/wsr88d/rpg/raster_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/set_color_level_packet.hpp
|
||||||
include/scwx/wsr88d/rpg/special_graphic_symbol_packet.hpp
|
include/scwx/wsr88d/rpg/special_graphic_symbol_packet.hpp
|
||||||
include/scwx/wsr88d/rpg/sti_circle_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_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_alphanumeric_block.hpp
|
||||||
include/scwx/wsr88d/rpg/tabular_product_message.hpp
|
include/scwx/wsr88d/rpg/tabular_product_message.hpp
|
||||||
include/scwx/wsr88d/rpg/text_and_special_symbol_packet.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/radar_coded_message.cpp
|
||||||
source/scwx/wsr88d/rpg/radial_data_packet.cpp
|
source/scwx/wsr88d/rpg/radial_data_packet.cpp
|
||||||
source/scwx/wsr88d/rpg/raster_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/set_color_level_packet.cpp
|
||||||
source/scwx/wsr88d/rpg/special_graphic_symbol_packet.cpp
|
source/scwx/wsr88d/rpg/special_graphic_symbol_packet.cpp
|
||||||
source/scwx/wsr88d/rpg/sti_circle_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_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_alphanumeric_block.cpp
|
||||||
source/scwx/wsr88d/rpg/tabular_product_message.cpp
|
source/scwx/wsr88d/rpg/tabular_product_message.cpp
|
||||||
source/scwx/wsr88d/rpg/text_and_special_symbol_packet.cpp
|
source/scwx/wsr88d/rpg/text_and_special_symbol_packet.cpp
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue