mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 19:40:06 +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: | ||||||
|  | @ -29,16 +38,19 @@ public: | ||||||
|    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::OverlayProductView> overlay_product_view() const; | ||||||
|    std::shared_ptr<view::RadarProductView>   radar_product_view() const; |    std::shared_ptr<view::RadarProductView>   radar_product_view() const; | ||||||
|    common::RadarProductGroup                 radar_product_group() const; |    common::RadarProductGroup                 radar_product_group() const; | ||||||
|    std::string                               radar_product() const; |    std::string                               radar_product() const; | ||||||
|    int16_t                                   radar_product_code() const; |    int16_t                                   radar_product_code() const; | ||||||
|    QMapLibreGL::CustomLayerRenderParameters  render_parameters() 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()); | ||||||
|  | @ -184,6 +195,7 @@ public: | ||||||
|    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<OverlayProductLayer> overlayProductLayer_ {nullptr}; | ||||||
|    std::shared_ptr<PlacefileLayer>      placefileLayer_; |    std::shared_ptr<PlacefileLayer>      placefileLayer_; | ||||||
|    std::shared_ptr<ColorTableLayer>     colorTableLayer_; |    std::shared_ptr<ColorTableLayer>     colorTableLayer_; | ||||||
|    std::shared_ptr<RadarSiteLayer>      radarSiteLayer_ {nullptr}; |    std::shared_ptr<RadarSiteLayer>      radarSiteLayer_ {nullptr}; | ||||||
|  | @ -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 = | ||||||
|  |          std::find_if(layers.begin(), | ||||||
|                       layers.end(), |                       layers.end(), | ||||||
|                              [&dataLayer](const types::LayerInfo& layer) |                       [&defaultIt](const types::LayerInfo& layer) | ||||||
|                       { |                       { | ||||||
|                                 return layer.type_ == types::LayerType::Data && |                          return layer.type_ == defaultIt->type_ && | ||||||
|                                        std::get<types::DataLayer>( |                                 layer.description_ == defaultIt->description_; | ||||||
|                                           layer.description_) == dataLayer; |  | ||||||
|                       }); |                       }); | ||||||
| 
 | 
 | ||||||
|       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 | ||||||
|  | @ -27,11 +29,21 @@ public: | ||||||
|    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
 | ||||||
|  |  | ||||||
|  | @ -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
 | ||||||
|  | @ -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,31 +15,23 @@ 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; | ||||||
|  | @ -49,19 +41,19 @@ TextAndSpecialSymbolPacket::TextAndSpecialSymbolPacket( | ||||||
| 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
	
	 Dan Paulat
						Dan Paulat