mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 06:10:04 +00:00 
			
		
		
		
	Merge pull request #322 from dpaulat/feature/radar-smoothing
Radar Smoothing
This commit is contained in:
		
						commit
						bef8628bb6
					
				
					 27 changed files with 1184 additions and 347 deletions
				
			
		|  | @ -6,6 +6,7 @@ Checks: | |||
|   - 'misc-*' | ||||
|   - 'modernize-*' | ||||
|   - 'performance-*' | ||||
|   - '-cppcoreguidelines-pro-type-reinterpret-cast' | ||||
|   - '-misc-include-cleaner' | ||||
|   - '-misc-non-private-member-variables-in-classes' | ||||
|   - '-modernize-use-trailing-return-type' | ||||
|  |  | |||
|  | @ -9,14 +9,14 @@ uniform float uDataMomentScale; | |||
| 
 | ||||
| uniform bool uCFPEnabled; | ||||
| 
 | ||||
| flat in uint dataMoment; | ||||
| flat in uint cfpMoment; | ||||
| in float dataMoment; | ||||
| in float cfpMoment; | ||||
| 
 | ||||
| layout (location = 0) out vec4 fragColor; | ||||
| 
 | ||||
| void main() | ||||
| { | ||||
|    float texCoord = float(dataMoment - uDataMomentOffset) / uDataMomentScale; | ||||
|    float texCoord = (dataMoment - float(uDataMomentOffset)) / uDataMomentScale; | ||||
| 
 | ||||
|    if (uCFPEnabled && cfpMoment > 8u) | ||||
|    { | ||||
|  |  | |||
|  | @ -13,8 +13,8 @@ layout (location = 2) in uint aCfpMoment; | |||
| uniform mat4 uMVPMatrix; | ||||
| uniform vec2 uMapScreenCoord; | ||||
| 
 | ||||
| flat out uint dataMoment; | ||||
| flat out uint cfpMoment; | ||||
| out float dataMoment; | ||||
| out float cfpMoment; | ||||
| 
 | ||||
| vec2 latLngToScreenCoordinate(in vec2 latLng) | ||||
| { | ||||
|  |  | |||
|  | @ -322,6 +322,8 @@ MainWindow::MainWindow(QWidget* parent) : | |||
|    p->mapSettingsGroup_ = new ui::CollapsibleGroup(tr("Map Settings"), this); | ||||
|    p->mapSettingsGroup_->GetContentsLayout()->addWidget(ui->mapStyleLabel); | ||||
|    p->mapSettingsGroup_->GetContentsLayout()->addWidget(ui->mapStyleComboBox); | ||||
|    p->mapSettingsGroup_->GetContentsLayout()->addWidget( | ||||
|       ui->smoothRadarDataCheckBox); | ||||
|    p->mapSettingsGroup_->GetContentsLayout()->addWidget( | ||||
|       ui->trackLocationCheckBox); | ||||
|    ui->radarToolboxScrollAreaContents->layout()->replaceWidget( | ||||
|  | @ -642,6 +644,11 @@ void MainWindow::on_actionDumpRadarProductRecords_triggered() | |||
|    manager::RadarProductManager::DumpRecords(); | ||||
| } | ||||
| 
 | ||||
| void MainWindow::on_actionRadarWireframe_triggered(bool checked) | ||||
| { | ||||
|    p->activeMap_->SetRadarWireframeEnabled(checked); | ||||
| } | ||||
| 
 | ||||
| void MainWindow::on_actionUserManual_triggered() | ||||
| { | ||||
|    QDesktopServices::openUrl(QUrl {"https://supercell-wx.readthedocs.io/"}); | ||||
|  | @ -1085,6 +1092,25 @@ void MainWindowImpl::ConnectOtherSignals() | |||
|                  } | ||||
|               } | ||||
|            }); | ||||
|    connect( | ||||
|       mainWindow_->ui->smoothRadarDataCheckBox, | ||||
|       &QCheckBox::checkStateChanged, | ||||
|       mainWindow_, | ||||
|       [this](Qt::CheckState state) | ||||
|       { | ||||
|          const bool smoothingEnabled = (state == Qt::CheckState::Checked); | ||||
| 
 | ||||
|          auto it = std::find(maps_.cbegin(), maps_.cend(), activeMap_); | ||||
|          if (it != maps_.cend()) | ||||
|          { | ||||
|             const std::size_t i = std::distance(maps_.cbegin(), it); | ||||
|             settings::MapSettings::Instance().smoothing_enabled(i).StageValue( | ||||
|                smoothingEnabled); | ||||
|          } | ||||
| 
 | ||||
|          // Turn on smoothing
 | ||||
|          activeMap_->SetSmoothingEnabled(smoothingEnabled); | ||||
|       }); | ||||
|    connect(mainWindow_->ui->trackLocationCheckBox, | ||||
|            &QCheckBox::checkStateChanged, | ||||
|            mainWindow_, | ||||
|  | @ -1471,6 +1497,13 @@ void MainWindowImpl::UpdateRadarProductSettings() | |||
|    { | ||||
|       level2SettingsGroup_->setVisible(false); | ||||
|    } | ||||
| 
 | ||||
|    mainWindow_->ui->smoothRadarDataCheckBox->setCheckState( | ||||
|       activeMap_->GetSmoothingEnabled() ? Qt::CheckState::Checked : | ||||
|                                           Qt::CheckState::Unchecked); | ||||
| 
 | ||||
|    mainWindow_->ui->actionRadarWireframe->setChecked( | ||||
|       activeMap_->GetRadarWireframeEnabled()); | ||||
| } | ||||
| 
 | ||||
| void MainWindowImpl::UpdateRadarSite() | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ public: | |||
|    void keyPressEvent(QKeyEvent* ev) override final; | ||||
|    void keyReleaseEvent(QKeyEvent* ev) override final; | ||||
|    void showEvent(QShowEvent* event) override; | ||||
|    void closeEvent(QCloseEvent *event) override; | ||||
|    void closeEvent(QCloseEvent* event) override; | ||||
| 
 | ||||
| signals: | ||||
|    void ActiveMapMoved(double latitude, double longitude); | ||||
|  | @ -49,6 +49,7 @@ private slots: | |||
|    void on_actionImGuiDebug_triggered(); | ||||
|    void on_actionDumpLayerList_triggered(); | ||||
|    void on_actionDumpRadarProductRecords_triggered(); | ||||
|    void on_actionRadarWireframe_triggered(bool checked); | ||||
|    void on_actionUserManual_triggered(); | ||||
|    void on_actionDiscord_triggered(); | ||||
|    void on_actionGitHubRepository_triggered(); | ||||
|  |  | |||
|  | @ -97,6 +97,8 @@ | |||
|     <addaction name="separator"/> | ||||
|     <addaction name="actionDumpLayerList"/> | ||||
|     <addaction name="actionDumpRadarProductRecords"/> | ||||
|     <addaction name="separator"/> | ||||
|     <addaction name="actionRadarWireframe"/> | ||||
|    </widget> | ||||
|    <widget class="QMenu" name="menuTools"> | ||||
|     <property name="title"> | ||||
|  | @ -153,8 +155,8 @@ | |||
|          <rect> | ||||
|           <x>0</x> | ||||
|           <y>0</y> | ||||
|           <width>205</width> | ||||
|           <height>701</height> | ||||
|           <width>190</width> | ||||
|           <height>680</height> | ||||
|          </rect> | ||||
|         </property> | ||||
|         <layout class="QVBoxLayout" name="verticalLayout_6"> | ||||
|  | @ -329,6 +331,13 @@ | |||
|             <item> | ||||
|              <widget class="QComboBox" name="mapStyleComboBox"/> | ||||
|             </item> | ||||
|             <item> | ||||
|              <widget class="QCheckBox" name="smoothRadarDataCheckBox"> | ||||
|               <property name="text"> | ||||
|                <string>Smooth Radar Data</string> | ||||
|               </property> | ||||
|              </widget> | ||||
|             </item> | ||||
|             <item> | ||||
|              <widget class="QCheckBox" name="trackLocationCheckBox"> | ||||
|               <property name="text"> | ||||
|  | @ -497,6 +506,14 @@ | |||
|     <string>Location &Marker Manager</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="actionRadarWireframe"> | ||||
|    <property name="checkable"> | ||||
|     <bool>true</bool> | ||||
|    </property> | ||||
|    <property name="text"> | ||||
|     <string>Radar &Wireframe</string> | ||||
|    </property> | ||||
|   </action> | ||||
|  </widget> | ||||
|  <resources> | ||||
|   <include location="../../../../scwx-qt.qrc"/> | ||||
|  |  | |||
|  | @ -10,7 +10,6 @@ | |||
| #include <scwx/util/threads.hpp> | ||||
| #include <scwx/wsr88d/nexrad_file_factory.hpp> | ||||
| 
 | ||||
| #include <deque> | ||||
| #include <execution> | ||||
| #include <mutex> | ||||
| #include <shared_mutex> | ||||
|  | @ -28,6 +27,7 @@ | |||
| #include <boost/timer/timer.hpp> | ||||
| #include <fmt/chrono.h> | ||||
| #include <qmaplibre.hpp> | ||||
| #include <units/angle.h> | ||||
| 
 | ||||
| #if defined(_MSC_VER) | ||||
| #   pragma warning(pop) | ||||
|  | @ -63,6 +63,8 @@ static constexpr uint32_t NUM_COORIDNATES_1_DEGREE = | |||
| 
 | ||||
| static const std::string kDefaultLevel3Product_ {"N0B"}; | ||||
| 
 | ||||
| static constexpr std::size_t kTimerPlaces_ {6u}; | ||||
| 
 | ||||
| static constexpr std::chrono::seconds kFastRetryInterval_ {15}; | ||||
| static constexpr std::chrono::seconds kSlowRetryInterval_ {120}; | ||||
| 
 | ||||
|  | @ -206,6 +208,13 @@ public: | |||
| 
 | ||||
|    void UpdateAvailableProductsSync(); | ||||
| 
 | ||||
|    void | ||||
|    CalculateCoordinates(const boost::integer_range<std::uint32_t>& radialGates, | ||||
|                         const units::angle::degrees<float>         radialAngle, | ||||
|                         const units::angle::degrees<float>         angleOffset, | ||||
|                         const float         gateRangeOffset, | ||||
|                         std::vector<float>& outputCoordinates); | ||||
| 
 | ||||
|    static void | ||||
|    PopulateProductTimes(std::shared_ptr<ProviderManager> providerManager, | ||||
|                         RadarProductRecordMap&           productRecordMap, | ||||
|  | @ -226,10 +235,12 @@ public: | |||
|    std::size_t                        cacheLimit_ {6u}; | ||||
| 
 | ||||
|    std::vector<float> coordinates0_5Degree_ {}; | ||||
|    std::vector<float> coordinates0_5DegreeSmooth_ {}; | ||||
|    std::vector<float> coordinates1Degree_ {}; | ||||
|    std::vector<float> coordinates1DegreeSmooth_ {}; | ||||
| 
 | ||||
|    RadarProductRecordMap   level2ProductRecords_ {}; | ||||
|    RadarProductRecordList  level2ProductRecentRecords_ {}; | ||||
|    RadarProductRecordMap  level2ProductRecords_ {}; | ||||
|    RadarProductRecordList level2ProductRecentRecords_ {}; | ||||
|    std::unordered_map<std::string, RadarProductRecordMap> | ||||
|       level3ProductRecordsMap_ {}; | ||||
|    std::unordered_map<std::string, RadarProductRecordList> | ||||
|  | @ -361,14 +372,29 @@ void RadarProductManager::DumpRecords() | |||
| } | ||||
| 
 | ||||
| const std::vector<float>& | ||||
| RadarProductManager::coordinates(common::RadialSize radialSize) const | ||||
| RadarProductManager::coordinates(common::RadialSize radialSize, | ||||
|                                  bool               smoothingEnabled) const | ||||
| { | ||||
|    switch (radialSize) | ||||
|    { | ||||
|    case common::RadialSize::_0_5Degree: | ||||
|       return p->coordinates0_5Degree_; | ||||
|       if (smoothingEnabled) | ||||
|       { | ||||
|          return p->coordinates0_5DegreeSmooth_; | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|          return p->coordinates0_5Degree_; | ||||
|       } | ||||
|    case common::RadialSize::_1Degree: | ||||
|       return p->coordinates1Degree_; | ||||
|       if (smoothingEnabled) | ||||
|       { | ||||
|          return p->coordinates1DegreeSmooth_; | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|          return p->coordinates1Degree_; | ||||
|       } | ||||
|    default: | ||||
|       throw std::invalid_argument("Invalid radial size"); | ||||
|    } | ||||
|  | @ -430,50 +456,51 @@ void RadarProductManager::Initialize() | |||
| 
 | ||||
|    boost::timer::cpu_timer timer; | ||||
| 
 | ||||
|    const GeographicLib::Geodesic& geodesic( | ||||
|       util::GeographicLib::DefaultGeodesic()); | ||||
| 
 | ||||
|    const QMapLibre::Coordinate radar(p->radarSite_->latitude(), | ||||
|                                      p->radarSite_->longitude()); | ||||
| 
 | ||||
|    const float gateSize = gate_size(); | ||||
| 
 | ||||
|    // Calculate half degree azimuth coordinates
 | ||||
|    timer.start(); | ||||
|    std::vector<float>& coordinates0_5Degree = p->coordinates0_5Degree_; | ||||
| 
 | ||||
|    coordinates0_5Degree.resize(NUM_COORIDNATES_0_5_DEGREE); | ||||
| 
 | ||||
|    auto radialGates0_5Degree = | ||||
|    const auto radialGates0_5Degree = | ||||
|       boost::irange<uint32_t>(0, NUM_RADIAL_GATES_0_5_DEGREE); | ||||
| 
 | ||||
|    std::for_each( | ||||
|       std::execution::par_unseq, | ||||
|       radialGates0_5Degree.begin(), | ||||
|       radialGates0_5Degree.end(), | ||||
|       [&](uint32_t radialGate) | ||||
|       { | ||||
|          const uint16_t gate = | ||||
|             static_cast<uint16_t>(radialGate % common::MAX_DATA_MOMENT_GATES); | ||||
|          const uint16_t radial = | ||||
|             static_cast<uint16_t>(radialGate / common::MAX_DATA_MOMENT_GATES); | ||||
|    // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers): Values are given
 | ||||
|    // descriptions
 | ||||
|    p->CalculateCoordinates( | ||||
|       radialGates0_5Degree, | ||||
|       units::angle::degrees<float> {0.5f}, // Radial angle
 | ||||
|       units::angle::degrees<float> {0.0f}, // Angle offset
 | ||||
|       // Far end of the first gate is the gate size distance from the radar site
 | ||||
|       1.0f, | ||||
|       coordinates0_5Degree); | ||||
|    // NOLINTEND(cppcoreguidelines-avoid-magic-numbers)
 | ||||
| 
 | ||||
|          const float  angle  = radial * 0.5f; // 0.5 degree radial
 | ||||
|          const float  range  = (gate + 1) * gateSize; | ||||
|          const size_t offset = radialGate * 2; | ||||
| 
 | ||||
|          double latitude; | ||||
|          double longitude; | ||||
| 
 | ||||
|          geodesic.Direct( | ||||
|             radar.first, radar.second, angle, range, latitude, longitude); | ||||
| 
 | ||||
|          coordinates0_5Degree[offset]     = latitude; | ||||
|          coordinates0_5Degree[offset + 1] = longitude; | ||||
|       }); | ||||
|    timer.stop(); | ||||
|    logger_->debug("Coordinates (0.5 degree) calculated in {}", | ||||
|                   timer.format(6, "%ws")); | ||||
|                   timer.format(kTimerPlaces_, "%ws")); | ||||
| 
 | ||||
|    // Calculate half degree smooth azimuth coordinates
 | ||||
|    timer.start(); | ||||
|    std::vector<float>& coordinates0_5DegreeSmooth = | ||||
|       p->coordinates0_5DegreeSmooth_; | ||||
| 
 | ||||
|    coordinates0_5DegreeSmooth.resize(NUM_COORIDNATES_0_5_DEGREE); | ||||
| 
 | ||||
|    // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers): Values are given
 | ||||
|    // descriptions
 | ||||
|    p->CalculateCoordinates(radialGates0_5Degree, | ||||
|                            units::angle::degrees<float> {0.5f},  // Radial angle
 | ||||
|                            units::angle::degrees<float> {0.25f}, // Angle offset
 | ||||
|                            // Center of the first gate is half the gate size
 | ||||
|                            // distance from the radar site
 | ||||
|                            0.5f, | ||||
|                            coordinates0_5DegreeSmooth); | ||||
|    // NOLINTEND(cppcoreguidelines-avoid-magic-numbers)
 | ||||
| 
 | ||||
|    timer.stop(); | ||||
|    logger_->debug("Coordinates (0.5 degree smooth) calculated in {}", | ||||
|                   timer.format(kTimerPlaces_, "%ws")); | ||||
| 
 | ||||
|    // Calculate 1 degree azimuth coordinates
 | ||||
|    timer.start(); | ||||
|  | @ -481,38 +508,89 @@ void RadarProductManager::Initialize() | |||
| 
 | ||||
|    coordinates1Degree.resize(NUM_COORIDNATES_1_DEGREE); | ||||
| 
 | ||||
|    auto radialGates1Degree = | ||||
|    const auto radialGates1Degree = | ||||
|       boost::irange<uint32_t>(0, NUM_RADIAL_GATES_1_DEGREE); | ||||
| 
 | ||||
|    // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers): Values are given
 | ||||
|    // descriptions
 | ||||
|    p->CalculateCoordinates( | ||||
|       radialGates1Degree, | ||||
|       units::angle::degrees<float> {1.0f}, // Radial angle
 | ||||
|       units::angle::degrees<float> {0.0f}, // Angle offset
 | ||||
|       // Far end of the first gate is the gate size distance from the radar site
 | ||||
|       1.0f, | ||||
|       coordinates1Degree); | ||||
|    // NOLINTEND(cppcoreguidelines-avoid-magic-numbers)
 | ||||
| 
 | ||||
|    timer.stop(); | ||||
|    logger_->debug("Coordinates (1 degree) calculated in {}", | ||||
|                   timer.format(kTimerPlaces_, "%ws")); | ||||
| 
 | ||||
|    // Calculate 1 degree smooth azimuth coordinates
 | ||||
|    timer.start(); | ||||
|    std::vector<float>& coordinates1DegreeSmooth = p->coordinates1DegreeSmooth_; | ||||
| 
 | ||||
|    coordinates1DegreeSmooth.resize(NUM_COORIDNATES_1_DEGREE); | ||||
| 
 | ||||
|    // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers): Values are given
 | ||||
|    // descriptions
 | ||||
|    p->CalculateCoordinates(radialGates1Degree, | ||||
|                            units::angle::degrees<float> {1.0f}, // Radial angle
 | ||||
|                            units::angle::degrees<float> {0.5f}, // Angle offset
 | ||||
|                            // Center of the first gate is half the gate size
 | ||||
|                            // distance from the radar site
 | ||||
|                            0.5f, | ||||
|                            coordinates1DegreeSmooth); | ||||
|    // NOLINTEND(cppcoreguidelines-avoid-magic-numbers)
 | ||||
| 
 | ||||
|    timer.stop(); | ||||
|    logger_->debug("Coordinates (1 degree smooth) calculated in {}", | ||||
|                   timer.format(kTimerPlaces_, "%ws")); | ||||
| 
 | ||||
|    p->initialized_ = true; | ||||
| } | ||||
| 
 | ||||
| void RadarProductManagerImpl::CalculateCoordinates( | ||||
|    const boost::integer_range<std::uint32_t>& radialGates, | ||||
|    const units::angle::degrees<float>         radialAngle, | ||||
|    const units::angle::degrees<float>         angleOffset, | ||||
|    const float                                gateRangeOffset, | ||||
|    std::vector<float>&                        outputCoordinates) | ||||
| { | ||||
|    const GeographicLib::Geodesic& geodesic( | ||||
|       util::GeographicLib::DefaultGeodesic()); | ||||
| 
 | ||||
|    const QMapLibre::Coordinate radar(radarSite_->latitude(), | ||||
|                                      radarSite_->longitude()); | ||||
| 
 | ||||
|    const float gateSize = self_->gate_size(); | ||||
| 
 | ||||
|    std::for_each( | ||||
|       std::execution::par_unseq, | ||||
|       radialGates1Degree.begin(), | ||||
|       radialGates1Degree.end(), | ||||
|       radialGates.begin(), | ||||
|       radialGates.end(), | ||||
|       [&](uint32_t radialGate) | ||||
|       { | ||||
|          const uint16_t gate = | ||||
|             static_cast<uint16_t>(radialGate % common::MAX_DATA_MOMENT_GATES); | ||||
|          const uint16_t radial = | ||||
|             static_cast<uint16_t>(radialGate / common::MAX_DATA_MOMENT_GATES); | ||||
|          const auto gate = static_cast<std::uint16_t>( | ||||
|             radialGate % common::MAX_DATA_MOMENT_GATES); | ||||
|          const auto radial = static_cast<std::uint16_t>( | ||||
|             radialGate / common::MAX_DATA_MOMENT_GATES); | ||||
| 
 | ||||
|          const float  angle  = radial * 1.0f; // 1 degree radial
 | ||||
|          const float  range  = (gate + 1) * gateSize; | ||||
|          const size_t offset = radialGate * 2; | ||||
|          const float angle = static_cast<float>(radial) * radialAngle.value() + | ||||
|                              angleOffset.value(); | ||||
|          const float range = | ||||
|             (static_cast<float>(gate) + gateRangeOffset) * gateSize; | ||||
|          const std::size_t offset = static_cast<std::size_t>(radialGate) * 2; | ||||
| 
 | ||||
|          double latitude; | ||||
|          double longitude; | ||||
|          double latitude  = 0.0; | ||||
|          double longitude = 0.0; | ||||
| 
 | ||||
|          geodesic.Direct( | ||||
|             radar.first, radar.second, angle, range, latitude, longitude); | ||||
| 
 | ||||
|          coordinates1Degree[offset]     = latitude; | ||||
|          coordinates1Degree[offset + 1] = longitude; | ||||
|          outputCoordinates[offset]     = static_cast<float>(latitude); | ||||
|          outputCoordinates[offset + 1] = static_cast<float>(longitude); | ||||
|       }); | ||||
|    timer.stop(); | ||||
|    logger_->debug("Coordinates (1 degree) calculated in {}", | ||||
|                   timer.format(6, "%ws")); | ||||
| 
 | ||||
|    p->initialized_ = true; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<ProviderManager> | ||||
|  |  | |||
|  | @ -41,11 +41,12 @@ public: | |||
|     */ | ||||
|    static void DumpRecords(); | ||||
| 
 | ||||
|    const std::vector<float>& coordinates(common::RadialSize radialSize) const; | ||||
|    const scwx::util::time_zone*       default_time_zone() const; | ||||
|    float                              gate_size() const; | ||||
|    std::string                        radar_id() const; | ||||
|    std::shared_ptr<config::RadarSite> radar_site() const; | ||||
|    [[nodiscard]] const std::vector<float>& | ||||
|    coordinates(common::RadialSize radialSize, bool smoothingEnabled) const; | ||||
|    [[nodiscard]] const scwx::util::time_zone*       default_time_zone() const; | ||||
|    [[nodiscard]] float                              gate_size() const; | ||||
|    [[nodiscard]] std::string                        radar_id() const; | ||||
|    [[nodiscard]] std::shared_ptr<config::RadarSite> radar_site() const; | ||||
| 
 | ||||
|    void Initialize(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,16 +9,17 @@ namespace map | |||
| 
 | ||||
| struct MapSettings | ||||
| { | ||||
|    explicit MapSettings() : isActive_ {false} {} | ||||
|    ~MapSettings() = default; | ||||
|    explicit MapSettings() = default; | ||||
|    ~MapSettings()         = default; | ||||
| 
 | ||||
|    MapSettings(const MapSettings&) = delete; | ||||
|    MapSettings(const MapSettings&)            = delete; | ||||
|    MapSettings& operator=(const MapSettings&) = delete; | ||||
| 
 | ||||
|    MapSettings(MapSettings&&) noexcept = default; | ||||
|    MapSettings(MapSettings&&) noexcept            = default; | ||||
|    MapSettings& operator=(MapSettings&&) noexcept = default; | ||||
| 
 | ||||
|    bool isActive_; | ||||
|    bool isActive_ {false}; | ||||
|    bool radarWireframeEnabled_ {false}; | ||||
| }; | ||||
| 
 | ||||
| } // namespace map
 | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ | |||
| #include <scwx/qt/model/imgui_context_model.hpp> | ||||
| #include <scwx/qt/model/layer_model.hpp> | ||||
| #include <scwx/qt/settings/general_settings.hpp> | ||||
| #include <scwx/qt/settings/map_settings.hpp> | ||||
| #include <scwx/qt/settings/palette_settings.hpp> | ||||
| #include <scwx/qt/util/file.hpp> | ||||
| #include <scwx/qt/util/maplibre.hpp> | ||||
|  | @ -105,13 +106,16 @@ public: | |||
|       map::AlertLayer::InitializeHandler(); | ||||
| 
 | ||||
|       auto& generalSettings = settings::GeneralSettings::Instance(); | ||||
|       auto& mapSettings     = settings::MapSettings::Instance(); | ||||
| 
 | ||||
|       // Initialize context
 | ||||
|       context_->set_map_provider( | ||||
|          GetMapProvider(generalSettings.map_provider().GetValue())); | ||||
|       context_->set_overlay_product_view(overlayProductView); | ||||
| 
 | ||||
|       // Initialize map data
 | ||||
|       SetRadarSite(generalSettings.default_radar_site().GetValue()); | ||||
|       smoothingEnabled_ = mapSettings.smoothing_enabled(id).GetValue(); | ||||
| 
 | ||||
|       // Create ImGui Context
 | ||||
|       static size_t currentMapId_ {0u}; | ||||
|  | @ -225,7 +229,7 @@ public: | |||
|    std::shared_ptr<OverlayLayer>        overlayLayer_; | ||||
|    std::shared_ptr<OverlayProductLayer> overlayProductLayer_ {nullptr}; | ||||
|    std::shared_ptr<PlacefileLayer>      placefileLayer_; | ||||
|    std::shared_ptr<MarkerLayer>            markerLayer_; | ||||
|    std::shared_ptr<MarkerLayer>         markerLayer_; | ||||
|    std::shared_ptr<ColorTableLayer>     colorTableLayer_; | ||||
|    std::shared_ptr<RadarSiteLayer>      radarSiteLayer_ {nullptr}; | ||||
| 
 | ||||
|  | @ -233,6 +237,7 @@ public: | |||
| 
 | ||||
|    bool autoRefreshEnabled_; | ||||
|    bool autoUpdateEnabled_; | ||||
|    bool smoothingEnabled_ {false}; | ||||
| 
 | ||||
|    common::Level2Product selectedLevel2Product_; | ||||
| 
 | ||||
|  | @ -727,6 +732,35 @@ std::uint16_t MapWidget::GetVcp() const | |||
|    } | ||||
| } | ||||
| 
 | ||||
| bool MapWidget::GetRadarWireframeEnabled() const | ||||
| { | ||||
|    return p->context_->settings().radarWireframeEnabled_; | ||||
| } | ||||
| 
 | ||||
| void MapWidget::SetRadarWireframeEnabled(bool wireframeEnabled) | ||||
| { | ||||
|    p->context_->settings().radarWireframeEnabled_ = wireframeEnabled; | ||||
|    QMetaObject::invokeMethod( | ||||
|       this, static_cast<void (QWidget::*)()>(&QWidget::update)); | ||||
| } | ||||
| 
 | ||||
| bool MapWidget::GetSmoothingEnabled() const | ||||
| { | ||||
|    return p->smoothingEnabled_; | ||||
| } | ||||
| 
 | ||||
| void MapWidget::SetSmoothingEnabled(bool smoothingEnabled) | ||||
| { | ||||
|    p->smoothingEnabled_ = smoothingEnabled; | ||||
| 
 | ||||
|    auto radarProductView = p->context_->radar_product_view(); | ||||
|    if (radarProductView != nullptr) | ||||
|    { | ||||
|       radarProductView->set_smoothing_enabled(smoothingEnabled); | ||||
|       radarProductView->Update(); | ||||
|    } | ||||
| } | ||||
| 
 | ||||
| void MapWidget::SelectElevation(float elevation) | ||||
| { | ||||
|    auto radarProductView = p->context_->radar_product_view(); | ||||
|  | @ -775,6 +809,7 @@ void MapWidget::SelectRadarProduct(common::RadarProductGroup group, | |||
| 
 | ||||
|       radarProductView = view::RadarProductViewFactory::Create( | ||||
|          group, productName, productCode, p->radarProductManager_); | ||||
|       radarProductView->set_smoothing_enabled(p->smoothingEnabled_); | ||||
|       p->context_->set_radar_product_view(radarProductView); | ||||
| 
 | ||||
|       p->RadarProductViewConnect(); | ||||
|  |  | |||
|  | @ -39,16 +39,19 @@ public: | |||
| 
 | ||||
|    void DumpLayerList() const; | ||||
| 
 | ||||
|    common::Level3ProductCategoryMap      GetAvailableLevel3Categories(); | ||||
|    float                                 GetElevation() const; | ||||
|    std::vector<float>                    GetElevationCuts() const; | ||||
|    std::vector<std::string>              GetLevel3Products(); | ||||
|    std::string                           GetMapStyle() const; | ||||
|    common::RadarProductGroup             GetRadarProductGroup() const; | ||||
|    std::string                           GetRadarProductName() const; | ||||
|    std::shared_ptr<config::RadarSite>    GetRadarSite() const; | ||||
|    std::chrono::system_clock::time_point GetSelectedTime() const; | ||||
|    std::uint16_t                         GetVcp() const; | ||||
|    [[nodiscard]] common::Level3ProductCategoryMap | ||||
|                                            GetAvailableLevel3Categories(); | ||||
|    [[nodiscard]] float                     GetElevation() const; | ||||
|    [[nodiscard]] std::vector<float>        GetElevationCuts() const; | ||||
|    [[nodiscard]] std::vector<std::string>  GetLevel3Products(); | ||||
|    [[nodiscard]] std::string               GetMapStyle() const; | ||||
|    [[nodiscard]] common::RadarProductGroup GetRadarProductGroup() const; | ||||
|    [[nodiscard]] std::string               GetRadarProductName() const; | ||||
|    [[nodiscard]] std::shared_ptr<config::RadarSite> GetRadarSite() const; | ||||
|    [[nodiscard]] bool GetRadarWireframeEnabled() const; | ||||
|    [[nodiscard]] std::chrono::system_clock::time_point GetSelectedTime() const; | ||||
|    [[nodiscard]] bool          GetSmoothingEnabled() const; | ||||
|    [[nodiscard]] std::uint16_t GetVcp() const; | ||||
| 
 | ||||
|    void SelectElevation(float elevation); | ||||
| 
 | ||||
|  | @ -117,6 +120,8 @@ public: | |||
|                          double pitch); | ||||
|    void SetInitialMapStyle(const std::string& styleName); | ||||
|    void SetMapStyle(const std::string& styleName); | ||||
|    void SetRadarWireframeEnabled(bool enabled); | ||||
|    void SetSmoothingEnabled(bool enabled); | ||||
| 
 | ||||
|    /**
 | ||||
|     * Updates the coordinates associated with mouse movement from another map. | ||||
|  |  | |||
|  | @ -1,12 +1,11 @@ | |||
| #include <scwx/qt/map/radar_product_layer.hpp> | ||||
| #include <scwx/qt/map/map_settings.hpp> | ||||
| #include <scwx/qt/gl/shader_program.hpp> | ||||
| #include <scwx/qt/util/maplibre.hpp> | ||||
| #include <scwx/qt/util/tooltip.hpp> | ||||
| #include <scwx/qt/view/radar_product_view.hpp> | ||||
| #include <scwx/util/logger.hpp> | ||||
| 
 | ||||
| #include <execution> | ||||
| 
 | ||||
| #if defined(_MSC_VER) | ||||
| #   pragma warning(push, 0) | ||||
| #endif | ||||
|  | @ -267,6 +266,13 @@ void RadarProductLayer::Render( | |||
|    // Set OpenGL blend mode for transparency
 | ||||
|    gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | ||||
| 
 | ||||
|    const bool wireframeEnabled = context()->settings().radarWireframeEnabled_; | ||||
|    if (wireframeEnabled) | ||||
|    { | ||||
|       // Set polygon mode to draw wireframe
 | ||||
|       gl.glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); | ||||
|    } | ||||
| 
 | ||||
|    if (p->colorTableNeedsUpdate_) | ||||
|    { | ||||
|       UpdateColorTable(); | ||||
|  | @ -303,6 +309,12 @@ void RadarProductLayer::Render( | |||
|    gl.glBindVertexArray(p->vao_); | ||||
|    gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); | ||||
| 
 | ||||
|    if (wireframeEnabled) | ||||
|    { | ||||
|       // Restore polygon mode to default
 | ||||
|       gl.glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); | ||||
|    } | ||||
| 
 | ||||
|    SCWX_GL_CHECK_ERROR(); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,7 +6,6 @@ | |||
| #include <scwx/util/logger.hpp> | ||||
| 
 | ||||
| #include <array> | ||||
| #include <execution> | ||||
| 
 | ||||
| #include <boost/json.hpp> | ||||
| 
 | ||||
|  | @ -27,12 +26,15 @@ static const std::string kMapStyleName_ {"map_style"}; | |||
| static const std::string kRadarSiteName_ {"radar_site"}; | ||||
| static const std::string kRadarProductGroupName_ {"radar_product_group"}; | ||||
| static const std::string kRadarProductName_ {"radar_product"}; | ||||
| static const std::string kSmoothingEnabledName_ {"smoothing_enabled"}; | ||||
| 
 | ||||
| static const std::string                   kDefaultMapStyle_ {"?"}; | ||||
| static const std::string kDefaultMapStyle_ {"?"}; | ||||
| static const std::string kDefaultRadarProductGroupString_ = "L3"; | ||||
| static const std::array<std::string, kCount_> kDefaultRadarProduct_ { | ||||
|    "N0B", "N0G", "N0C", "N0X"}; | ||||
| 
 | ||||
| static constexpr bool kDefaultSmoothingEnabled_ {false}; | ||||
| 
 | ||||
| class MapSettings::Impl | ||||
| { | ||||
| public: | ||||
|  | @ -43,26 +45,28 @@ public: | |||
|       SettingsVariable<std::string> radarProductGroup_ { | ||||
|          kRadarProductGroupName_}; | ||||
|       SettingsVariable<std::string> radarProduct_ {kRadarProductName_}; | ||||
|       SettingsVariable<bool>        smoothingEnabled_ {kSmoothingEnabledName_}; | ||||
|    }; | ||||
| 
 | ||||
|    explicit Impl() | ||||
|    { | ||||
|       for (std::size_t i = 0; i < kCount_; i++) | ||||
|       { | ||||
|          map_[i].mapStyle_.SetDefault(kDefaultMapStyle_); | ||||
|          map_[i].radarSite_.SetDefault(kDefaultRadarSite_); | ||||
|          map_[i].radarProductGroup_.SetDefault( | ||||
|          map_.at(i).mapStyle_.SetDefault(kDefaultMapStyle_); | ||||
|          map_.at(i).radarSite_.SetDefault(kDefaultRadarSite_); | ||||
|          map_.at(i).radarProductGroup_.SetDefault( | ||||
|             kDefaultRadarProductGroupString_); | ||||
|          map_[i].radarProduct_.SetDefault(kDefaultRadarProduct_[i]); | ||||
|          map_.at(i).radarProduct_.SetDefault(kDefaultRadarProduct_.at(i)); | ||||
|          map_.at(i).smoothingEnabled_.SetDefault(kDefaultSmoothingEnabled_); | ||||
| 
 | ||||
|          map_[i].radarSite_.SetValidator( | ||||
|          map_.at(i).radarSite_.SetValidator( | ||||
|             [](const std::string& value) | ||||
|             { | ||||
|                // Radar site must exist
 | ||||
|                return config::RadarSite::Get(value) != nullptr; | ||||
|             }); | ||||
| 
 | ||||
|          map_[i].radarProductGroup_.SetValidator( | ||||
|          map_.at(i).radarProductGroup_.SetValidator( | ||||
|             [](const std::string& value) | ||||
|             { | ||||
|                // Radar product group must be valid
 | ||||
|  | @ -71,12 +75,12 @@ public: | |||
|                return radarProductGroup != common::RadarProductGroup::Unknown; | ||||
|             }); | ||||
| 
 | ||||
|          map_[i].radarProduct_.SetValidator( | ||||
|          map_.at(i).radarProduct_.SetValidator( | ||||
|             [this, i](const std::string& value) | ||||
|             { | ||||
|                common::RadarProductGroup radarProductGroup = | ||||
|                   common::GetRadarProductGroup( | ||||
|                      map_[i].radarProductGroup_.GetValue()); | ||||
|                      map_.at(i).radarProductGroup_.GetValue()); | ||||
| 
 | ||||
|                if (radarProductGroup == common::RadarProductGroup::Level2) | ||||
|                { | ||||
|  | @ -92,10 +96,11 @@ public: | |||
|             }); | ||||
| 
 | ||||
|          variables_.insert(variables_.cend(), | ||||
|                            {&map_[i].mapStyle_, | ||||
|                             &map_[i].radarSite_, | ||||
|                             &map_[i].radarProductGroup_, | ||||
|                             &map_[i].radarProduct_}); | ||||
|                            {&map_.at(i).mapStyle_, | ||||
|                             &map_.at(i).radarSite_, | ||||
|                             &map_.at(i).radarProductGroup_, | ||||
|                             &map_.at(i).radarProduct_, | ||||
|                             &map_.at(i).smoothingEnabled_}); | ||||
|       } | ||||
|    } | ||||
| 
 | ||||
|  | @ -103,10 +108,11 @@ public: | |||
| 
 | ||||
|    void SetDefaults(std::size_t i) | ||||
|    { | ||||
|       map_[i].mapStyle_.SetValueToDefault(); | ||||
|       map_[i].radarSite_.SetValueToDefault(); | ||||
|       map_[i].radarProductGroup_.SetValueToDefault(); | ||||
|       map_[i].radarProduct_.SetValueToDefault(); | ||||
|       map_.at(i).mapStyle_.SetValueToDefault(); | ||||
|       map_.at(i).radarSite_.SetValueToDefault(); | ||||
|       map_.at(i).radarProductGroup_.SetValueToDefault(); | ||||
|       map_.at(i).radarProduct_.SetValueToDefault(); | ||||
|       map_.at(i).smoothingEnabled_.SetValueToDefault(); | ||||
|    } | ||||
| 
 | ||||
|    friend void tag_invoke(boost::json::value_from_tag, | ||||
|  | @ -116,7 +122,8 @@ public: | |||
|       jv = {{kMapStyleName_, data.mapStyle_.GetValue()}, | ||||
|             {kRadarSiteName_, data.radarSite_.GetValue()}, | ||||
|             {kRadarProductGroupName_, data.radarProductGroup_.GetValue()}, | ||||
|             {kRadarProductName_, data.radarProduct_.GetValue()}}; | ||||
|             {kRadarProductName_, data.radarProduct_.GetValue()}, | ||||
|             {kSmoothingEnabledName_, data.smoothingEnabled_.GetValue()}}; | ||||
|    } | ||||
| 
 | ||||
|    friend bool operator==(const MapData& lhs, const MapData& rhs) | ||||
|  | @ -124,7 +131,8 @@ public: | |||
|       return (lhs.mapStyle_ == rhs.mapStyle_ && //
 | ||||
|               lhs.radarSite_ == rhs.radarSite_ && | ||||
|               lhs.radarProductGroup_ == rhs.radarProductGroup_ && | ||||
|               lhs.radarProduct_ == rhs.radarProduct_); | ||||
|               lhs.radarProduct_ == rhs.radarProduct_ && | ||||
|               lhs.smoothingEnabled_ == rhs.smoothingEnabled_); | ||||
|    } | ||||
| 
 | ||||
|    std::array<MapData, kCount_>       map_ {}; | ||||
|  | @ -149,25 +157,29 @@ std::size_t MapSettings::count() const | |||
|    return kCount_; | ||||
| } | ||||
| 
 | ||||
| SettingsVariable<std::string>& MapSettings::map_style(std::size_t i) const | ||||
| SettingsVariable<std::string>& MapSettings::map_style(std::size_t i) | ||||
| { | ||||
|    return p->map_[i].mapStyle_; | ||||
|    return p->map_.at(i).mapStyle_; | ||||
| } | ||||
| 
 | ||||
| SettingsVariable<std::string>& MapSettings::radar_site(std::size_t i) const | ||||
| SettingsVariable<std::string>& MapSettings::radar_site(std::size_t i) | ||||
| { | ||||
|    return p->map_[i].radarSite_; | ||||
|    return p->map_.at(i).radarSite_; | ||||
| } | ||||
| 
 | ||||
| SettingsVariable<std::string>& | ||||
| MapSettings::radar_product_group(std::size_t i) const | ||||
| SettingsVariable<std::string>& MapSettings::radar_product_group(std::size_t i) | ||||
| { | ||||
|    return p->map_[i].radarProductGroup_; | ||||
|    return p->map_.at(i).radarProductGroup_; | ||||
| } | ||||
| 
 | ||||
| SettingsVariable<std::string>& MapSettings::radar_product(std::size_t i) const | ||||
| SettingsVariable<std::string>& MapSettings::radar_product(std::size_t i) | ||||
| { | ||||
|    return p->map_[i].radarProduct_; | ||||
|    return p->map_.at(i).radarProduct_; | ||||
| } | ||||
| 
 | ||||
| SettingsVariable<bool>& MapSettings::smoothing_enabled(std::size_t i) | ||||
| { | ||||
|    return p->map_.at(i).smoothingEnabled_; | ||||
| } | ||||
| 
 | ||||
| bool MapSettings::Shutdown() | ||||
|  | @ -177,9 +189,10 @@ bool MapSettings::Shutdown() | |||
|    // Commit settings that are managed separate from the settings dialog
 | ||||
|    for (std::size_t i = 0; i < kCount_; ++i) | ||||
|    { | ||||
|       Impl::MapData& mapRecordSettings = p->map_[i]; | ||||
|       Impl::MapData& mapRecordSettings = p->map_.at(i); | ||||
| 
 | ||||
|       dataChanged |= mapRecordSettings.mapStyle_.Commit(); | ||||
|       dataChanged |= mapRecordSettings.smoothingEnabled_.Commit(); | ||||
|    } | ||||
| 
 | ||||
|    return dataChanged; | ||||
|  | @ -200,13 +213,15 @@ bool MapSettings::ReadJson(const boost::json::object& json) | |||
|          if (i < mapArray.size() && mapArray.at(i).is_object()) | ||||
|          { | ||||
|             const boost::json::object& mapRecord = mapArray.at(i).as_object(); | ||||
|             Impl::MapData&             mapRecordSettings = p->map_[i]; | ||||
|             Impl::MapData&             mapRecordSettings = p->map_.at(i); | ||||
| 
 | ||||
|             // Load JSON Elements
 | ||||
|             validated &= mapRecordSettings.mapStyle_.ReadValue(mapRecord); | ||||
|             validated &= mapRecordSettings.radarSite_.ReadValue(mapRecord); | ||||
|             validated &= | ||||
|                mapRecordSettings.radarProductGroup_.ReadValue(mapRecord); | ||||
|             validated &= | ||||
|                mapRecordSettings.smoothingEnabled_.ReadValue(mapRecord); | ||||
| 
 | ||||
|             bool productValidated = | ||||
|                mapRecordSettings.radarProduct_.ReadValue(mapRecord); | ||||
|  |  | |||
|  | @ -26,10 +26,11 @@ public: | |||
|    MapSettings& operator=(MapSettings&&) noexcept; | ||||
| 
 | ||||
|    std::size_t                    count() const; | ||||
|    SettingsVariable<std::string>& map_style(std::size_t i) const; | ||||
|    SettingsVariable<std::string>& radar_site(std::size_t i) const; | ||||
|    SettingsVariable<std::string>& radar_product_group(std::size_t i) const; | ||||
|    SettingsVariable<std::string>& radar_product(std::size_t i) const; | ||||
|    SettingsVariable<std::string>& map_style(std::size_t i); | ||||
|    SettingsVariable<std::string>& radar_site(std::size_t i); | ||||
|    SettingsVariable<std::string>& radar_product_group(std::size_t i); | ||||
|    SettingsVariable<std::string>& radar_product(std::size_t i); | ||||
|    SettingsVariable<bool>&        smoothing_enabled(std::size_t i); | ||||
| 
 | ||||
|    bool Shutdown(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -15,12 +15,15 @@ class ProductSettings::Impl | |||
| public: | ||||
|    explicit Impl() | ||||
|    { | ||||
|       showSmoothedRangeFolding_.SetDefault(false); | ||||
|       stiForecastEnabled_.SetDefault(true); | ||||
|       stiPastEnabled_.SetDefault(true); | ||||
|    } | ||||
| 
 | ||||
|    ~Impl() {} | ||||
| 
 | ||||
|    SettingsVariable<bool> showSmoothedRangeFolding_ { | ||||
|       "show_smoothed_range_folding"}; | ||||
|    SettingsVariable<bool> stiForecastEnabled_ {"sti_forecast_enabled"}; | ||||
|    SettingsVariable<bool> stiPastEnabled_ {"sti_past_enabled"}; | ||||
| }; | ||||
|  | @ -28,7 +31,9 @@ public: | |||
| ProductSettings::ProductSettings() : | ||||
|     SettingsCategory("product"), p(std::make_unique<Impl>()) | ||||
| { | ||||
|    RegisterVariables({&p->stiForecastEnabled_, &p->stiPastEnabled_}); | ||||
|    RegisterVariables({&p->showSmoothedRangeFolding_, | ||||
|                       &p->stiForecastEnabled_, | ||||
|                       &p->stiPastEnabled_}); | ||||
|    SetDefaults(); | ||||
| } | ||||
| ProductSettings::~ProductSettings() = default; | ||||
|  | @ -37,12 +42,17 @@ ProductSettings::ProductSettings(ProductSettings&&) noexcept = default; | |||
| ProductSettings& | ||||
| ProductSettings::operator=(ProductSettings&&) noexcept = default; | ||||
| 
 | ||||
| SettingsVariable<bool>& ProductSettings::sti_forecast_enabled() const | ||||
| SettingsVariable<bool>& ProductSettings::show_smoothed_range_folding() | ||||
| { | ||||
|    return p->showSmoothedRangeFolding_; | ||||
| } | ||||
| 
 | ||||
| SettingsVariable<bool>& ProductSettings::sti_forecast_enabled() | ||||
| { | ||||
|    return p->stiForecastEnabled_; | ||||
| } | ||||
| 
 | ||||
| SettingsVariable<bool>& ProductSettings::sti_past_enabled() const | ||||
| SettingsVariable<bool>& ProductSettings::sti_past_enabled() | ||||
| { | ||||
|    return p->stiPastEnabled_; | ||||
| } | ||||
|  | @ -66,7 +76,9 @@ ProductSettings& ProductSettings::Instance() | |||
| 
 | ||||
| bool operator==(const ProductSettings& lhs, const ProductSettings& rhs) | ||||
| { | ||||
|    return (lhs.p->stiForecastEnabled_ == rhs.p->stiForecastEnabled_ && | ||||
|    return (lhs.p->showSmoothedRangeFolding_ == | ||||
|               rhs.p->showSmoothedRangeFolding_ && | ||||
|            lhs.p->stiForecastEnabled_ == rhs.p->stiForecastEnabled_ && | ||||
|            lhs.p->stiPastEnabled_ == rhs.p->stiPastEnabled_); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -25,8 +25,9 @@ public: | |||
|    ProductSettings(ProductSettings&&) noexcept; | ||||
|    ProductSettings& operator=(ProductSettings&&) noexcept; | ||||
| 
 | ||||
|    SettingsVariable<bool>& sti_forecast_enabled() const; | ||||
|    SettingsVariable<bool>& sti_past_enabled() const; | ||||
|    SettingsVariable<bool>& show_smoothed_range_folding(); | ||||
|    SettingsVariable<bool>& sti_forecast_enabled(); | ||||
|    SettingsVariable<bool>& sti_past_enabled(); | ||||
| 
 | ||||
|    static ProductSettings& Instance(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ | |||
| #include <scwx/qt/settings/audio_settings.hpp> | ||||
| #include <scwx/qt/settings/general_settings.hpp> | ||||
| #include <scwx/qt/settings/palette_settings.hpp> | ||||
| #include <scwx/qt/settings/product_settings.hpp> | ||||
| #include <scwx/qt/settings/settings_interface.hpp> | ||||
| #include <scwx/qt/settings/text_settings.hpp> | ||||
| #include <scwx/qt/settings/unit_settings.hpp> | ||||
|  | @ -136,6 +137,7 @@ public: | |||
|           &showMapAttribution_, | ||||
|           &showMapCenter_, | ||||
|           &showMapLogo_, | ||||
|           &showSmoothedRangeFolding_, | ||||
|           &updateNotificationsEnabled_, | ||||
|           &cursorIconAlwaysOn_, | ||||
|           &debugEnabled_, | ||||
|  | @ -251,6 +253,7 @@ public: | |||
|    settings::SettingsInterface<bool>         showMapAttribution_ {}; | ||||
|    settings::SettingsInterface<bool>         showMapCenter_ {}; | ||||
|    settings::SettingsInterface<bool>         showMapLogo_ {}; | ||||
|    settings::SettingsInterface<bool>         showSmoothedRangeFolding_ {}; | ||||
|    settings::SettingsInterface<bool>         updateNotificationsEnabled_ {}; | ||||
|    settings::SettingsInterface<bool>         cursorIconAlwaysOn_ {}; | ||||
|    settings::SettingsInterface<bool>         debugEnabled_ {}; | ||||
|  | @ -527,21 +530,22 @@ void SettingsDialogImpl::SetupGeneralTab() | |||
| { | ||||
|    settings::GeneralSettings& generalSettings = | ||||
|       settings::GeneralSettings::Instance(); | ||||
| 
 | ||||
|    settings::ProductSettings& productSettings = | ||||
|       settings::ProductSettings::Instance(); | ||||
| 
 | ||||
|    QObject::connect( | ||||
|          self_->ui->themeComboBox, | ||||
|          &QComboBox::currentTextChanged, | ||||
|          self_, | ||||
|          [this](const QString& text) | ||||
|          { | ||||
|             types::UiStyle style = types::GetUiStyle(text.toStdString()); | ||||
|             bool themeFileEnabled = style == types::UiStyle::FusionCustom; | ||||
|       self_->ui->themeComboBox, | ||||
|       &QComboBox::currentTextChanged, | ||||
|       self_, | ||||
|       [this](const QString& text) | ||||
|       { | ||||
|          const types::UiStyle style  = types::GetUiStyle(text.toStdString()); | ||||
|          const bool themeFileEnabled = style == types::UiStyle::FusionCustom; | ||||
| 
 | ||||
|             self_->ui->themeFileLineEdit->setEnabled(themeFileEnabled); | ||||
|             self_->ui->themeFileSelectButton->setEnabled(themeFileEnabled); | ||||
|             self_->ui->resetThemeFileButton->setEnabled(themeFileEnabled); | ||||
|          }); | ||||
|          self_->ui->themeFileLineEdit->setEnabled(themeFileEnabled); | ||||
|          self_->ui->themeFileSelectButton->setEnabled(themeFileEnabled); | ||||
|          self_->ui->resetThemeFileButton->setEnabled(themeFileEnabled); | ||||
|       }); | ||||
| 
 | ||||
|    theme_.SetSettingsVariable(generalSettings.theme()); | ||||
|    SCWX_SETTINGS_COMBO_BOX(theme_, | ||||
|  | @ -759,6 +763,11 @@ void SettingsDialogImpl::SetupGeneralTab() | |||
|    showMapLogo_.SetSettingsVariable(generalSettings.show_map_logo()); | ||||
|    showMapLogo_.SetEditWidget(self_->ui->showMapLogoCheckBox); | ||||
| 
 | ||||
|    showSmoothedRangeFolding_.SetSettingsVariable( | ||||
|       productSettings.show_smoothed_range_folding()); | ||||
|    showSmoothedRangeFolding_.SetEditWidget( | ||||
|       self_->ui->showSmoothedRangeFoldingCheckBox); | ||||
| 
 | ||||
|    updateNotificationsEnabled_.SetSettingsVariable( | ||||
|       generalSettings.update_notifications_enabled()); | ||||
|    updateNotificationsEnabled_.SetEditWidget( | ||||
|  |  | |||
|  | @ -135,9 +135,9 @@ | |||
|               <property name="geometry"> | ||||
|                <rect> | ||||
|                 <x>0</x> | ||||
|                 <y>-246</y> | ||||
|                 <width>511</width> | ||||
|                 <height>703</height> | ||||
|                 <y>-272</y> | ||||
|                 <width>513</width> | ||||
|                 <height>702</height> | ||||
|                </rect> | ||||
|               </property> | ||||
|               <layout class="QVBoxLayout" name="verticalLayout"> | ||||
|  | @ -562,6 +562,19 @@ | |||
|                  </property> | ||||
|                 </widget> | ||||
|                </item> | ||||
|                <item> | ||||
|                 <widget class="QCheckBox" name="cursorIconAlwaysOnCheckBox"> | ||||
|                  <property name="acceptDrops"> | ||||
|                   <bool>false</bool> | ||||
|                  </property> | ||||
|                  <property name="toolTip"> | ||||
|                   <string/> | ||||
|                  </property> | ||||
|                  <property name="text"> | ||||
|                   <string>Multi-Pane Cursor Marker Always On</string> | ||||
|                  </property> | ||||
|                 </widget> | ||||
|                </item> | ||||
|                <item> | ||||
|                 <widget class="QCheckBox" name="showMapAttributionCheckBox"> | ||||
|                  <property name="text"> | ||||
|  | @ -584,22 +597,16 @@ | |||
|                 </widget> | ||||
|                </item> | ||||
|                <item> | ||||
|                 <widget class="QCheckBox" name="enableUpdateNotificationsCheckBox"> | ||||
|                 <widget class="QCheckBox" name="showSmoothedRangeFoldingCheckBox"> | ||||
|                  <property name="text"> | ||||
|                   <string>Update Notifications Enabled</string> | ||||
|                   <string>Show Range Folding when Smoothing Radar Data</string> | ||||
|                  </property> | ||||
|                 </widget> | ||||
|                </item> | ||||
|                <item> | ||||
|                 <widget class="QCheckBox" name="cursorIconAlwaysOnCheckBox"> | ||||
|                  <property name="acceptDrops"> | ||||
|                   <bool>false</bool> | ||||
|                  </property> | ||||
|                  <property name="toolTip"> | ||||
|                   <string/> | ||||
|                  </property> | ||||
|                 <widget class="QCheckBox" name="enableUpdateNotificationsCheckBox"> | ||||
|                  <property name="text"> | ||||
|                   <string>Multi-Pane Cursor Marker Always On</string> | ||||
|                   <string>Update Notifications Enabled</string> | ||||
|                  </property> | ||||
|                 </widget> | ||||
|                </item> | ||||
|  |  | |||
|  | @ -25,6 +25,11 @@ static constexpr std::uint32_t kMaxRadialGates_ = | |||
|    common::MAX_0_5_DEGREE_RADIALS * common::MAX_DATA_MOMENT_GATES; | ||||
| static constexpr std::uint32_t kMaxCoordinates_ = kMaxRadialGates_ * 2u; | ||||
| 
 | ||||
| static constexpr std::uint8_t kDataWordSize8_ = 8u; | ||||
| 
 | ||||
| static constexpr std::size_t kVerticesPerGate_       = 6u; | ||||
| static constexpr std::size_t kVerticesPerOriginGate_ = 3u; | ||||
| 
 | ||||
| static constexpr uint16_t RANGE_FOLDED      = 1u; | ||||
| static constexpr uint32_t VERTICES_PER_BIN  = 6u; | ||||
| static constexpr uint32_t VALUES_PER_VERTEX = 2u; | ||||
|  | @ -53,11 +58,10 @@ static const std::unordered_map<common::Level2Product, std::string> | |||
|                   {common::Level2Product::CorrelationCoefficient, "%"}, | ||||
|                   {common::Level2Product::ClutterFilterPowerRemoved, "dB"}}; | ||||
| 
 | ||||
| class Level2ProductViewImpl | ||||
| class Level2ProductView::Impl | ||||
| { | ||||
| public: | ||||
|    explicit Level2ProductViewImpl(Level2ProductView*    self, | ||||
|                                   common::Level2Product product) : | ||||
|    explicit Impl(Level2ProductView* self, common::Level2Product product) : | ||||
|        self_ {self}, | ||||
|        product_ {product}, | ||||
|        selectedElevation_ {0.0f}, | ||||
|  | @ -94,7 +98,7 @@ public: | |||
|       UpdateOtherUnits(unitSettings.other_units().GetValue()); | ||||
|       UpdateSpeedUnits(unitSettings.speed_units().GetValue()); | ||||
|    } | ||||
|    ~Level2ProductViewImpl() | ||||
|    ~Impl() | ||||
|    { | ||||
|       auto& unitSettings = settings::UnitSettings::Instance(); | ||||
| 
 | ||||
|  | @ -106,23 +110,36 @@ public: | |||
|       threadPool_.join(); | ||||
|    }; | ||||
| 
 | ||||
|    Impl(const Impl&)            = delete; | ||||
|    Impl& operator=(const Impl&) = delete; | ||||
| 
 | ||||
|    Impl(Impl&&) noexcept            = delete; | ||||
|    Impl& operator=(Impl&&) noexcept = delete; | ||||
| 
 | ||||
|    void ComputeCoordinates( | ||||
|       const std::shared_ptr<wsr88d::rda::ElevationScan>& radarData); | ||||
|       const std::shared_ptr<wsr88d::rda::ElevationScan>& radarData, | ||||
|       bool                                               smoothingEnabled); | ||||
| 
 | ||||
|    void SetProduct(const std::string& productName); | ||||
|    void SetProduct(common::Level2Product product); | ||||
|    void UpdateOtherUnits(const std::string& name); | ||||
|    void UpdateSpeedUnits(const std::string& name); | ||||
| 
 | ||||
|    void ComputeEdgeValue(); | ||||
|    template<typename T> | ||||
|    [[nodiscard]] inline T RemapDataMoment(T dataMoment) const; | ||||
| 
 | ||||
|    static bool IsRadarDataIncomplete( | ||||
|       const std::shared_ptr<const wsr88d::rda::ElevationScan>& radarData); | ||||
|    static units::degrees<float> NormalizeAngle(units::degrees<float> angle); | ||||
| 
 | ||||
|    Level2ProductView* self_; | ||||
| 
 | ||||
|    boost::asio::thread_pool threadPool_ {1u}; | ||||
| 
 | ||||
|    common::Level2Product      product_; | ||||
|    wsr88d::rda::DataBlockType dataBlockType_; | ||||
|    wsr88d::rda::DataBlockType dataBlockType_ { | ||||
|       wsr88d::rda::DataBlockType::Unknown}; | ||||
| 
 | ||||
|    float selectedElevation_; | ||||
| 
 | ||||
|  | @ -130,11 +147,17 @@ public: | |||
|    std::shared_ptr<wsr88d::rda::GenericRadarData::MomentDataBlock> | ||||
|       momentDataBlock0_; | ||||
| 
 | ||||
|    bool lastShowSmoothedRangeFolding_ {false}; | ||||
|    bool lastSmoothingEnabled_ {false}; | ||||
| 
 | ||||
|    std::vector<float>    coordinates_ {}; | ||||
|    std::vector<float>    vertices_ {}; | ||||
|    std::vector<uint8_t>  dataMoments8_ {}; | ||||
|    std::vector<uint16_t> dataMoments16_ {}; | ||||
|    std::vector<uint8_t>  cfpMoments_ {}; | ||||
|    std::uint16_t         edgeValue_ {}; | ||||
| 
 | ||||
|    bool showSmoothedRangeFolding_ {false}; | ||||
| 
 | ||||
|    float                    latitude_; | ||||
|    float                    longitude_; | ||||
|  | @ -164,7 +187,7 @@ Level2ProductView::Level2ProductView( | |||
|    common::Level2Product                         product, | ||||
|    std::shared_ptr<manager::RadarProductManager> radarProductManager) : | ||||
|     RadarProductView(radarProductManager), | ||||
|     p(std::make_unique<Level2ProductViewImpl>(this, product)) | ||||
|     p(std::make_unique<Impl>(this, product)) | ||||
| { | ||||
|    ConnectRadarProductManager(); | ||||
| } | ||||
|  | @ -379,12 +402,12 @@ void Level2ProductView::SelectProduct(const std::string& productName) | |||
|    p->SetProduct(productName); | ||||
| } | ||||
| 
 | ||||
| void Level2ProductViewImpl::SetProduct(const std::string& productName) | ||||
| void Level2ProductView::Impl::SetProduct(const std::string& productName) | ||||
| { | ||||
|    SetProduct(common::GetLevel2Product(productName)); | ||||
| } | ||||
| 
 | ||||
| void Level2ProductViewImpl::SetProduct(common::Level2Product product) | ||||
| void Level2ProductView::Impl::SetProduct(common::Level2Product product) | ||||
| { | ||||
|    product_ = product; | ||||
| 
 | ||||
|  | @ -401,12 +424,12 @@ void Level2ProductViewImpl::SetProduct(common::Level2Product product) | |||
|    } | ||||
| } | ||||
| 
 | ||||
| void Level2ProductViewImpl::UpdateOtherUnits(const std::string& name) | ||||
| void Level2ProductView::Impl::UpdateOtherUnits(const std::string& name) | ||||
| { | ||||
|    otherUnits_ = types::GetOtherUnitsFromName(name); | ||||
| } | ||||
| 
 | ||||
| void Level2ProductViewImpl::UpdateSpeedUnits(const std::string& name) | ||||
| void Level2ProductView::Impl::UpdateSpeedUnits(const std::string& name) | ||||
| { | ||||
|    speedUnits_ = types::GetSpeedUnitsFromName(name); | ||||
| } | ||||
|  | @ -511,6 +534,9 @@ void Level2ProductView::ComputeSweep() | |||
| 
 | ||||
|    std::shared_ptr<manager::RadarProductManager> radarProductManager = | ||||
|       radar_product_manager(); | ||||
|    const bool smoothingEnabled          = smoothing_enabled(); | ||||
|    p->showSmoothedRangeFolding_         = show_smoothed_range_folding(); | ||||
|    const bool& showSmoothedRangeFolding = p->showSmoothedRangeFolding_; | ||||
| 
 | ||||
|    std::shared_ptr<wsr88d::rda::ElevationScan> radarData; | ||||
|    std::chrono::system_clock::time_point       requestedTime {selected_time()}; | ||||
|  | @ -523,12 +549,18 @@ void Level2ProductView::ComputeSweep() | |||
|       Q_EMIT SweepNotComputed(types::NoUpdateReason::NotLoaded); | ||||
|       return; | ||||
|    } | ||||
|    if (radarData == p->elevationScan_) | ||||
|    if (radarData == p->elevationScan_ && | ||||
|        smoothingEnabled == p->lastSmoothingEnabled_ && | ||||
|        (showSmoothedRangeFolding == p->lastShowSmoothedRangeFolding_ || | ||||
|         !smoothingEnabled)) | ||||
|    { | ||||
|       Q_EMIT SweepNotComputed(types::NoUpdateReason::NoChange); | ||||
|       return; | ||||
|    } | ||||
| 
 | ||||
|    p->lastShowSmoothedRangeFolding_ = showSmoothedRangeFolding; | ||||
|    p->lastSmoothingEnabled_         = smoothingEnabled; | ||||
| 
 | ||||
|    logger_->debug("Computing Sweep"); | ||||
| 
 | ||||
|    std::size_t radials       = radarData->crbegin()->first + 1; | ||||
|  | @ -536,8 +568,7 @@ void Level2ProductView::ComputeSweep() | |||
| 
 | ||||
|    // When there is missing data, insert another empty vertex radial at the end
 | ||||
|    // to avoid stretching
 | ||||
|    const bool isRadarDataIncomplete = | ||||
|       Level2ProductViewImpl::IsRadarDataIncomplete(radarData); | ||||
|    const bool isRadarDataIncomplete = Impl::IsRadarDataIncomplete(radarData); | ||||
|    if (isRadarDataIncomplete) | ||||
|    { | ||||
|       ++vertexRadials; | ||||
|  | @ -548,7 +579,7 @@ void Level2ProductView::ComputeSweep() | |||
|    vertexRadials = | ||||
|       std::min<std::size_t>(vertexRadials, common::MAX_0_5_DEGREE_RADIALS); | ||||
| 
 | ||||
|    p->ComputeCoordinates(radarData); | ||||
|    p->ComputeCoordinates(radarData, smoothingEnabled); | ||||
| 
 | ||||
|    const std::vector<float>& coordinates = p->coordinates_; | ||||
| 
 | ||||
|  | @ -627,11 +658,20 @@ void Level2ProductView::ComputeSweep() | |||
|    // Start radial is always 0, as coordinates are calculated for each sweep
 | ||||
|    constexpr std::uint16_t startRadial = 0u; | ||||
| 
 | ||||
|    for (auto& radialPair : *radarData) | ||||
|    // For most products other than reflectivity, the edge should not go to the
 | ||||
|    // bottom of the color table
 | ||||
|    if (smoothingEnabled) | ||||
|    { | ||||
|       p->ComputeEdgeValue(); | ||||
|    } | ||||
| 
 | ||||
|    for (auto it = radarData->cbegin(); it != radarData->cend(); ++it) | ||||
|    { | ||||
|       const auto&   radialPair = *it; | ||||
|       std::uint16_t radial     = radialPair.first; | ||||
|       auto&         radialData = radialPair.second; | ||||
|       auto momentData = radialData->moment_data_block(p->dataBlockType_); | ||||
|       const auto&   radialData = radialPair.second; | ||||
|       const std::shared_ptr<wsr88d::rda::GenericRadarData::MomentDataBlock> | ||||
|          momentData = radialData->moment_data_block(p->dataBlockType_); | ||||
| 
 | ||||
|       if (momentData0->data_word_size() != momentData->data_word_size()) | ||||
|       { | ||||
|  | @ -653,7 +693,7 @@ void Level2ProductView::ComputeSweep() | |||
|          std::max<std::int32_t>(1, dataMomentInterval / gateSizeMeters); | ||||
| 
 | ||||
|       // Compute gate range [startGate, endGate)
 | ||||
|       const std::int32_t startGate = | ||||
|       std::int32_t startGate = | ||||
|          (dataMomentRange - dataMomentIntervalH) / gateSizeMeters; | ||||
|       const std::int32_t numberOfDataMomentGates = | ||||
|          std::min<std::int32_t>(momentData->number_of_data_moment_gates(), | ||||
|  | @ -662,9 +702,19 @@ void Level2ProductView::ComputeSweep() | |||
|          startGate + numberOfDataMomentGates * gateSize, | ||||
|          static_cast<std::int32_t>(common::MAX_DATA_MOMENT_GATES)); | ||||
| 
 | ||||
|       const std::uint8_t*  dataMomentsArray8  = nullptr; | ||||
|       const std::uint16_t* dataMomentsArray16 = nullptr; | ||||
|       const std::uint8_t*  cfpMomentsArray    = nullptr; | ||||
|       if (smoothingEnabled) | ||||
|       { | ||||
|          // If smoothing is enabled, the start gate is incremented by one, as we
 | ||||
|          // are skipping the radar site origin. The end gate is unaffected, as
 | ||||
|          // we need to draw one less data point.
 | ||||
|          ++startGate; | ||||
|       } | ||||
| 
 | ||||
|       const std::uint8_t*  dataMomentsArray8      = nullptr; | ||||
|       const std::uint16_t* dataMomentsArray16     = nullptr; | ||||
|       const std::uint8_t*  nextDataMomentsArray8  = nullptr; | ||||
|       const std::uint16_t* nextDataMomentsArray16 = nullptr; | ||||
|       const std::uint8_t*  cfpMomentsArray        = nullptr; | ||||
| 
 | ||||
|       if (momentData->data_word_size() == 8) | ||||
|       { | ||||
|  | @ -684,6 +734,45 @@ void Level2ProductView::ComputeSweep() | |||
|                ->data_moments()); | ||||
|       } | ||||
| 
 | ||||
|       std::shared_ptr<wsr88d::rda::GenericRadarData::MomentDataBlock> | ||||
|                    nextMomentData              = nullptr; | ||||
|       std::int32_t numberOfNextDataMomentGates = 0; | ||||
|       if (smoothingEnabled) | ||||
|       { | ||||
|          // Smoothing requires the next radial pair as well
 | ||||
|          auto nextIt = std::next(it); | ||||
|          if (nextIt == radarData->cend()) | ||||
|          { | ||||
|             nextIt = radarData->cbegin(); | ||||
|          } | ||||
| 
 | ||||
|          const auto& nextRadialPair = *(nextIt); | ||||
|          const auto& nextRadialData = nextRadialPair.second; | ||||
|          nextMomentData = nextRadialData->moment_data_block(p->dataBlockType_); | ||||
| 
 | ||||
|          if (momentData->data_word_size() != nextMomentData->data_word_size()) | ||||
|          { | ||||
|             // Data should be consistent between radials
 | ||||
|             logger_->warn("Invalid data moment size"); | ||||
|             continue; | ||||
|          } | ||||
| 
 | ||||
|          if (nextMomentData->data_word_size() == kDataWordSize8_) | ||||
|          { | ||||
|             nextDataMomentsArray8 = reinterpret_cast<const std::uint8_t*>( | ||||
|                nextMomentData->data_moments()); | ||||
|          } | ||||
|          else | ||||
|          { | ||||
|             nextDataMomentsArray16 = reinterpret_cast<const std::uint16_t*>( | ||||
|                nextMomentData->data_moments()); | ||||
|          } | ||||
| 
 | ||||
|          numberOfNextDataMomentGates = std::min<std::int32_t>( | ||||
|             nextMomentData->number_of_data_moment_gates(), | ||||
|             static_cast<std::int32_t>(gates)); | ||||
|       } | ||||
| 
 | ||||
|       for (std::int32_t gate = startGate, i = 0; gate + gateSize <= endGate; | ||||
|            gate += gateSize, ++i) | ||||
|       { | ||||
|  | @ -692,57 +781,172 @@ void Level2ProductView::ComputeSweep() | |||
|             continue; | ||||
|          } | ||||
| 
 | ||||
|          std::size_t vertexCount = (gate > 0) ? 6 : 3; | ||||
|          const std::size_t vertexCount = | ||||
|             (gate > 0) ? kVerticesPerGate_ : kVerticesPerOriginGate_; | ||||
| 
 | ||||
|          // Allow pointer arithmetic here, as bounds have already been checked
 | ||||
|          // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
 | ||||
| 
 | ||||
|          // Store data moment value
 | ||||
|          if (dataMomentsArray8 != nullptr) | ||||
|          { | ||||
|             std::uint8_t dataValue = dataMomentsArray8[i]; | ||||
|             if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) | ||||
|             if (!smoothingEnabled) | ||||
|             { | ||||
|                continue; | ||||
|             } | ||||
| 
 | ||||
|             for (std::size_t m = 0; m < vertexCount; m++) | ||||
|             { | ||||
|                dataMoments8[mIndex++] = dataMomentsArray8[i]; | ||||
| 
 | ||||
|                if (cfpMomentsArray != nullptr) | ||||
|                const std::uint8_t& dataValue = dataMomentsArray8[i]; | ||||
|                if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) | ||||
|                { | ||||
|                   cfpMoments[mIndex - 1] = cfpMomentsArray[i]; | ||||
|                   continue; | ||||
|                } | ||||
| 
 | ||||
|                for (std::size_t m = 0; m < vertexCount; m++) | ||||
|                { | ||||
|                   dataMoments8[mIndex++] = dataValue; | ||||
| 
 | ||||
|                   if (cfpMomentsArray != nullptr) | ||||
|                   { | ||||
|                      cfpMoments[mIndex - 1] = cfpMomentsArray[i]; | ||||
|                   } | ||||
|                } | ||||
|             } | ||||
|             else if (gate > 0) | ||||
|             { | ||||
|                // Validate indices are all in range
 | ||||
|                if (i + 1 >= numberOfDataMomentGates || | ||||
|                    i + 1 >= numberOfNextDataMomentGates) | ||||
|                { | ||||
|                   continue; | ||||
|                } | ||||
| 
 | ||||
|                const std::uint8_t& dm1 = dataMomentsArray8[i]; | ||||
|                const std::uint8_t& dm2 = dataMomentsArray8[i + 1]; | ||||
|                const std::uint8_t& dm3 = nextDataMomentsArray8[i]; | ||||
|                const std::uint8_t& dm4 = nextDataMomentsArray8[i + 1]; | ||||
| 
 | ||||
|                if ((!showSmoothedRangeFolding && //
 | ||||
|                     (dm1 < snrThreshold || dm1 == RANGE_FOLDED) && | ||||
|                     (dm2 < snrThreshold || dm2 == RANGE_FOLDED) && | ||||
|                     (dm3 < snrThreshold || dm3 == RANGE_FOLDED) && | ||||
|                     (dm4 < snrThreshold || dm4 == RANGE_FOLDED)) || | ||||
|                    (showSmoothedRangeFolding && //
 | ||||
|                     dm1 < snrThreshold && dm1 != RANGE_FOLDED && | ||||
|                     dm2 < snrThreshold && dm2 != RANGE_FOLDED && | ||||
|                     dm3 < snrThreshold && dm3 != RANGE_FOLDED && | ||||
|                     dm4 < snrThreshold && dm4 != RANGE_FOLDED)) | ||||
|                { | ||||
|                   // Skip only if all data moments are hidden
 | ||||
|                   continue; | ||||
|                } | ||||
| 
 | ||||
|                // The order must match the store vertices section below
 | ||||
|                dataMoments8[mIndex++] = p->RemapDataMoment(dm1); | ||||
|                dataMoments8[mIndex++] = p->RemapDataMoment(dm2); | ||||
|                dataMoments8[mIndex++] = p->RemapDataMoment(dm4); | ||||
|                dataMoments8[mIndex++] = p->RemapDataMoment(dm1); | ||||
|                dataMoments8[mIndex++] = p->RemapDataMoment(dm3); | ||||
|                dataMoments8[mIndex++] = p->RemapDataMoment(dm4); | ||||
| 
 | ||||
|                // cfpMoments is unused, so not populated here
 | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                // If smoothing is enabled, gate should never start at zero
 | ||||
|                // (radar site origin)
 | ||||
|                logger_->error( | ||||
|                   "Smoothing enabled, gate should not start at zero"); | ||||
|                continue; | ||||
|             } | ||||
|          } | ||||
|          else | ||||
|          { | ||||
|             std::uint16_t dataValue = dataMomentsArray16[i]; | ||||
|             if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) | ||||
|             if (!smoothingEnabled) | ||||
|             { | ||||
|                const std::uint16_t& dataValue = dataMomentsArray16[i]; | ||||
|                if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) | ||||
|                { | ||||
|                   continue; | ||||
|                } | ||||
| 
 | ||||
|                for (std::size_t m = 0; m < vertexCount; m++) | ||||
|                { | ||||
|                   dataMoments16[mIndex++] = dataValue; | ||||
|                } | ||||
|             } | ||||
|             else if (gate > 0) | ||||
|             { | ||||
|                // Validate indices are all in range
 | ||||
|                if (i + 1 >= numberOfDataMomentGates || | ||||
|                    i + 1 >= numberOfNextDataMomentGates) | ||||
|                { | ||||
|                   continue; | ||||
|                } | ||||
| 
 | ||||
|                const std::uint16_t& dm1 = dataMomentsArray16[i]; | ||||
|                const std::uint16_t& dm2 = dataMomentsArray16[i + 1]; | ||||
|                const std::uint16_t& dm3 = nextDataMomentsArray16[i]; | ||||
|                const std::uint16_t& dm4 = nextDataMomentsArray16[i + 1]; | ||||
| 
 | ||||
|                if ((!showSmoothedRangeFolding && //
 | ||||
|                     (dm1 < snrThreshold || dm1 == RANGE_FOLDED) && | ||||
|                     (dm2 < snrThreshold || dm2 == RANGE_FOLDED) && | ||||
|                     (dm3 < snrThreshold || dm3 == RANGE_FOLDED) && | ||||
|                     (dm4 < snrThreshold || dm4 == RANGE_FOLDED)) || | ||||
|                    (showSmoothedRangeFolding && //
 | ||||
|                     dm1 < snrThreshold && dm1 != RANGE_FOLDED && | ||||
|                     dm2 < snrThreshold && dm2 != RANGE_FOLDED && | ||||
|                     dm3 < snrThreshold && dm3 != RANGE_FOLDED && | ||||
|                     dm4 < snrThreshold && dm4 != RANGE_FOLDED)) | ||||
|                { | ||||
|                   // Skip only if all data moments are hidden
 | ||||
|                   continue; | ||||
|                } | ||||
| 
 | ||||
|                // The order must match the store vertices section below
 | ||||
|                dataMoments16[mIndex++] = p->RemapDataMoment(dm1); | ||||
|                dataMoments16[mIndex++] = p->RemapDataMoment(dm2); | ||||
|                dataMoments16[mIndex++] = p->RemapDataMoment(dm4); | ||||
|                dataMoments16[mIndex++] = p->RemapDataMoment(dm1); | ||||
|                dataMoments16[mIndex++] = p->RemapDataMoment(dm3); | ||||
|                dataMoments16[mIndex++] = p->RemapDataMoment(dm4); | ||||
| 
 | ||||
|                // cfpMoments is unused, so not populated here
 | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                // If smoothing is enabled, gate should never start at zero
 | ||||
|                // (radar site origin)
 | ||||
|                continue; | ||||
|             } | ||||
| 
 | ||||
|             for (std::size_t m = 0; m < vertexCount; m++) | ||||
|             { | ||||
|                dataMoments16[mIndex++] = dataMomentsArray16[i]; | ||||
|             } | ||||
|          } | ||||
| 
 | ||||
|          // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
 | ||||
| 
 | ||||
|          // Store vertices
 | ||||
|          if (gate > 0) | ||||
|          { | ||||
|             // Draw two triangles per gate
 | ||||
|             //
 | ||||
|             // 2 +---+ 4
 | ||||
|             //   |  /|
 | ||||
|             //   | / |
 | ||||
|             //   |/  |
 | ||||
|             // 1 +---+ 3
 | ||||
| 
 | ||||
|             const std::uint16_t baseCoord = gate - 1; | ||||
| 
 | ||||
|             std::size_t offset1 = ((startRadial + radial) % vertexRadials * | ||||
|                                       common::MAX_DATA_MOMENT_GATES + | ||||
|                                    baseCoord) * | ||||
|                                   2; | ||||
|             std::size_t offset2 = offset1 + gateSize * 2; | ||||
|             std::size_t offset3 = | ||||
|             const std::size_t offset1 = | ||||
|                ((startRadial + radial) % vertexRadials * | ||||
|                    common::MAX_DATA_MOMENT_GATES + | ||||
|                 baseCoord) * | ||||
|                2; | ||||
|             const std::size_t offset2 = | ||||
|                offset1 + static_cast<std::size_t>(gateSize) * 2; | ||||
|             const std::size_t offset3 = | ||||
|                (((startRadial + radial + 1) % vertexRadials) * | ||||
|                    common::MAX_DATA_MOMENT_GATES + | ||||
|                 baseCoord) * | ||||
|                2; | ||||
|             std::size_t offset4 = offset3 + gateSize * 2; | ||||
|             const std::size_t offset4 = | ||||
|                offset3 + static_cast<std::size_t>(gateSize) * 2; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset1]; | ||||
|             vertices[vIndex++] = coordinates[offset1 + 1]; | ||||
|  | @ -750,19 +954,17 @@ void Level2ProductView::ComputeSweep() | |||
|             vertices[vIndex++] = coordinates[offset2]; | ||||
|             vertices[vIndex++] = coordinates[offset2 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset3]; | ||||
|             vertices[vIndex++] = coordinates[offset3 + 1]; | ||||
|             vertices[vIndex++] = coordinates[offset4]; | ||||
|             vertices[vIndex++] = coordinates[offset4 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset1]; | ||||
|             vertices[vIndex++] = coordinates[offset1 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset3]; | ||||
|             vertices[vIndex++] = coordinates[offset3 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset4]; | ||||
|             vertices[vIndex++] = coordinates[offset4 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset2]; | ||||
|             vertices[vIndex++] = coordinates[offset2 + 1]; | ||||
| 
 | ||||
|             vertexCount = 6; | ||||
|          } | ||||
|          else | ||||
|          { | ||||
|  | @ -786,8 +988,6 @@ void Level2ProductView::ComputeSweep() | |||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset2]; | ||||
|             vertices[vIndex++] = coordinates[offset2 + 1]; | ||||
| 
 | ||||
|             vertexCount = 3; | ||||
|          } | ||||
|       } | ||||
|    } | ||||
|  | @ -819,8 +1019,50 @@ void Level2ProductView::ComputeSweep() | |||
|    Q_EMIT SweepComputed(); | ||||
| } | ||||
| 
 | ||||
| void Level2ProductViewImpl::ComputeCoordinates( | ||||
|    const std::shared_ptr<wsr88d::rda::ElevationScan>& radarData) | ||||
| void Level2ProductView::Impl::ComputeEdgeValue() | ||||
| { | ||||
|    const float offset = momentDataBlock0_->offset(); | ||||
| 
 | ||||
|    switch (dataBlockType_) | ||||
|    { | ||||
|    case wsr88d::rda::DataBlockType::MomentVel: | ||||
|    case wsr88d::rda::DataBlockType::MomentZdr: | ||||
|       edgeValue_ = static_cast<std::uint16_t>(offset); | ||||
|       break; | ||||
| 
 | ||||
|    case wsr88d::rda::DataBlockType::MomentSw: | ||||
|    case wsr88d::rda::DataBlockType::MomentPhi: | ||||
|       edgeValue_ = 2; | ||||
|       break; | ||||
| 
 | ||||
|    case wsr88d::rda::DataBlockType::MomentRho: | ||||
|       edgeValue_ = std::numeric_limits<std::uint8_t>::max(); | ||||
|       break; | ||||
| 
 | ||||
|    case wsr88d::rda::DataBlockType::MomentRef: | ||||
|    default: | ||||
|       edgeValue_ = 0; | ||||
|       break; | ||||
|    } | ||||
| } | ||||
| 
 | ||||
| template<typename T> | ||||
| T Level2ProductView::Impl::RemapDataMoment(T dataMoment) const | ||||
| { | ||||
|    if (dataMoment != 0 && | ||||
|        (dataMoment != RANGE_FOLDED || showSmoothedRangeFolding_)) | ||||
|    { | ||||
|       return dataMoment; | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       return edgeValue_; | ||||
|    } | ||||
| } | ||||
| 
 | ||||
| void Level2ProductView::Impl::ComputeCoordinates( | ||||
|    const std::shared_ptr<wsr88d::rda::ElevationScan>& radarData, | ||||
|    bool                                               smoothingEnabled) | ||||
| { | ||||
|    logger_->debug("ComputeCoordinates()"); | ||||
| 
 | ||||
|  | @ -860,6 +1102,14 @@ void Level2ProductViewImpl::ComputeCoordinates( | |||
|    auto radials = boost::irange<std::uint32_t>(0u, numRadials); | ||||
|    auto gates   = boost::irange<std::uint32_t>(0u, numRangeBins); | ||||
| 
 | ||||
|    const float gateRangeOffset = (smoothingEnabled) ? | ||||
|                                     // Center of the first gate is half the gate
 | ||||
|                                     // size distance from the radar site
 | ||||
|                                     0.5f : | ||||
|                                     // Far end of the first gate is the gate
 | ||||
|                                     // size distance from the radar site
 | ||||
|                                     1.0f; | ||||
| 
 | ||||
|    std::for_each( | ||||
|       std::execution::par_unseq, | ||||
|       radials.begin(), | ||||
|  | @ -869,7 +1119,7 @@ void Level2ProductViewImpl::ComputeCoordinates( | |||
|          units::degrees<float> angle {}; | ||||
| 
 | ||||
|          auto radialData = radarData->find(radial); | ||||
|          if (radialData != radarData->cend()) | ||||
|          if (radialData != radarData->cend() && !smoothingEnabled) | ||||
|          { | ||||
|             angle = radialData->second->azimuth_angle(); | ||||
|          } | ||||
|  | @ -880,19 +1130,60 @@ void Level2ProductViewImpl::ComputeCoordinates( | |||
|             auto prevRadial2 = radarData->find( | ||||
|                (radial >= 2) ? radial - 2 : numRadials - (2 - radial)); | ||||
| 
 | ||||
|             if (prevRadial1 != radarData->cend() && | ||||
|                 prevRadial2 != radarData->cend()) | ||||
|             if (radialData != radarData->cend() && | ||||
|                 prevRadial1 != radarData->cend() && smoothingEnabled) | ||||
|             { | ||||
|                const units::degrees<float> currentAngle = | ||||
|                   radialData->second->azimuth_angle(); | ||||
|                const units::degrees<float> prevAngle = | ||||
|                   prevRadial1->second->azimuth_angle(); | ||||
| 
 | ||||
|                // Calculate delta angle
 | ||||
|                const units::degrees<float> deltaAngle = | ||||
|                   NormalizeAngle(currentAngle - prevAngle); | ||||
| 
 | ||||
|                // Delta scale is half the delta angle to reach the center of the
 | ||||
|                // bin, because smoothing is enabled
 | ||||
|                constexpr float deltaScale = 0.5f; | ||||
| 
 | ||||
|                angle = currentAngle + deltaAngle * deltaScale; | ||||
|             } | ||||
|             else if (radialData != radarData->cend() && smoothingEnabled) | ||||
|             { | ||||
|                const units::degrees<float> currentAngle = | ||||
|                   radialData->second->azimuth_angle(); | ||||
| 
 | ||||
|                // Assume a half degree delta if there aren't enough angles
 | ||||
|                // to determine a delta angle
 | ||||
|                constexpr units::degrees<float> deltaAngle {0.5f}; | ||||
| 
 | ||||
|                // Delta scale is half the delta angle to reach the center of the
 | ||||
|                // bin, because smoothing is enabled
 | ||||
|                constexpr float deltaScale = 0.5f; | ||||
| 
 | ||||
|                angle = currentAngle + deltaAngle * deltaScale; | ||||
|             } | ||||
|             else if (prevRadial1 != radarData->cend() && | ||||
|                      prevRadial2 != radarData->cend()) | ||||
|             { | ||||
|                const units::degrees<float> prevAngle1 = | ||||
|                   prevRadial1->second->azimuth_angle(); | ||||
|                const units::degrees<float> prevAngle2 = | ||||
|                   prevRadial2->second->azimuth_angle(); | ||||
| 
 | ||||
|                // No wrapping required since angle is only used for geodesic
 | ||||
|                // calculation
 | ||||
|                const units::degrees<float> deltaAngle = prevAngle1 - prevAngle2; | ||||
|                // Calculate delta angle
 | ||||
|                const units::degrees<float> deltaAngle = | ||||
|                   NormalizeAngle(prevAngle1 - prevAngle2); | ||||
| 
 | ||||
|                angle = prevAngle1 + deltaAngle; | ||||
|                const float deltaScale = | ||||
|                   (smoothingEnabled) ? | ||||
|                      // Delta scale is 1.5x the delta angle to reach the center
 | ||||
|                      // of the next bin, because smoothing is enabled
 | ||||
|                      1.5f : | ||||
|                      // Delta scale is 1.0x the delta angle
 | ||||
|                      1.0f; | ||||
| 
 | ||||
|                angle = prevAngle1 + deltaAngle * deltaScale; | ||||
|             } | ||||
|             else if (prevRadial1 != radarData->cend()) | ||||
|             { | ||||
|  | @ -903,7 +1194,15 @@ void Level2ProductViewImpl::ComputeCoordinates( | |||
|                // to determine a delta angle
 | ||||
|                constexpr units::degrees<float> deltaAngle {0.5f}; | ||||
| 
 | ||||
|                angle = prevAngle1 + deltaAngle; | ||||
|                const float deltaScale = | ||||
|                   (smoothingEnabled) ? | ||||
|                      // Delta scale is 1.5x the delta angle to reach the center
 | ||||
|                      // of the next bin, because smoothing is enabled
 | ||||
|                      1.5f : | ||||
|                      // Delta scale is 1.0x the delta angle
 | ||||
|                      1.0f; | ||||
| 
 | ||||
|                angle = prevAngle1 + deltaAngle * deltaScale; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|  | @ -912,35 +1211,38 @@ void Level2ProductViewImpl::ComputeCoordinates( | |||
|             } | ||||
|          } | ||||
| 
 | ||||
|          std::for_each(std::execution::par_unseq, | ||||
|                        gates.begin(), | ||||
|                        gates.end(), | ||||
|                        [&](std::uint32_t gate) | ||||
|                        { | ||||
|                           const std::uint32_t radialGate = | ||||
|                              radial * common::MAX_DATA_MOMENT_GATES + gate; | ||||
|                           const float       range  = (gate + 1) * gateSize; | ||||
|                           const std::size_t offset = radialGate * 2; | ||||
|          std::for_each( | ||||
|             std::execution::par_unseq, | ||||
|             gates.begin(), | ||||
|             gates.end(), | ||||
|             [&](std::uint32_t gate) | ||||
|             { | ||||
|                const std::uint32_t radialGate = | ||||
|                   radial * common::MAX_DATA_MOMENT_GATES + gate; | ||||
|                const float range = | ||||
|                   (static_cast<float>(gate) + gateRangeOffset) * gateSize; | ||||
|                const std::size_t offset = | ||||
|                   static_cast<std::size_t>(radialGate) * 2; | ||||
| 
 | ||||
|                           double latitude; | ||||
|                           double longitude; | ||||
|                double latitude  = 0.0; | ||||
|                double longitude = 0.0; | ||||
| 
 | ||||
|                           geodesic.Direct(radarLatitude, | ||||
|                                           radarLongitude, | ||||
|                                           angle.value(), | ||||
|                                           range, | ||||
|                                           latitude, | ||||
|                                           longitude); | ||||
|                geodesic.Direct(radarLatitude, | ||||
|                                radarLongitude, | ||||
|                                angle.value(), | ||||
|                                range, | ||||
|                                latitude, | ||||
|                                longitude); | ||||
| 
 | ||||
|                           coordinates_[offset]     = latitude; | ||||
|                           coordinates_[offset + 1] = longitude; | ||||
|                        }); | ||||
|                coordinates_[offset]     = static_cast<float>(latitude); | ||||
|                coordinates_[offset + 1] = static_cast<float>(longitude); | ||||
|             }); | ||||
|       }); | ||||
|    timer.stop(); | ||||
|    logger_->debug("Coordinates calculated in {}", timer.format(6, "%ws")); | ||||
| } | ||||
| 
 | ||||
| bool Level2ProductViewImpl::IsRadarDataIncomplete( | ||||
| bool Level2ProductView::Impl::IsRadarDataIncomplete( | ||||
|    const std::shared_ptr<const wsr88d::rda::ElevationScan>& radarData) | ||||
| { | ||||
|    // Assume the data is incomplete when the delta between the first and last
 | ||||
|  | @ -957,6 +1259,25 @@ bool Level2ProductViewImpl::IsRadarDataIncomplete( | |||
|    return angleDelta > kIncompleteDataAngleThreshold_; | ||||
| } | ||||
| 
 | ||||
| units::degrees<float> | ||||
| Level2ProductView::Impl::NormalizeAngle(units::degrees<float> angle) | ||||
| { | ||||
|    constexpr auto angleLimit = units::degrees<float> {180.0f}; | ||||
|    constexpr auto fullAngle  = units::degrees<float> {360.0f}; | ||||
| 
 | ||||
|    // Normalize angle to [-180, 180)
 | ||||
|    while (angle < -angleLimit) | ||||
|    { | ||||
|       angle += fullAngle; | ||||
|    } | ||||
|    while (angle >= angleLimit) | ||||
|    { | ||||
|       angle -= fullAngle; | ||||
|    } | ||||
| 
 | ||||
|    return angle; | ||||
| } | ||||
| 
 | ||||
| std::optional<std::uint16_t> | ||||
| Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const | ||||
| { | ||||
|  | @ -1003,7 +1324,7 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const | |||
|       static_cast<std::uint16_t>(radarData->crbegin()->first + 1); | ||||
| 
 | ||||
|    // Add an extra radial when incomplete data exists
 | ||||
|    if (Level2ProductViewImpl::IsRadarDataIncomplete(radarData)) | ||||
|    if (Impl::IsRadarDataIncomplete(radarData)) | ||||
|    { | ||||
|       ++numRadials; | ||||
|    } | ||||
|  |  | |||
|  | @ -15,8 +15,6 @@ namespace qt | |||
| namespace view | ||||
| { | ||||
| 
 | ||||
| class Level2ProductViewImpl; | ||||
| 
 | ||||
| class Level2ProductView : public RadarProductView | ||||
| { | ||||
|    Q_OBJECT | ||||
|  | @ -73,7 +71,8 @@ protected slots: | |||
|    void ComputeSweep() override; | ||||
| 
 | ||||
| private: | ||||
|    std::unique_ptr<Level2ProductViewImpl> p; | ||||
|    class Impl; | ||||
|    std::unique_ptr<Impl> p; | ||||
| }; | ||||
| 
 | ||||
| } // namespace view
 | ||||
|  |  | |||
|  | @ -485,6 +485,52 @@ void Level3ProductView::UpdateColorTableLut() | |||
|    Q_EMIT ColorTableLutUpdated(); | ||||
| } | ||||
| 
 | ||||
| std::uint8_t Level3ProductView::ComputeEdgeValue() const | ||||
| { | ||||
|    std::uint8_t edgeValue = 0; | ||||
| 
 | ||||
|    const std::shared_ptr<wsr88d::rpg::ProductDescriptionBlock> | ||||
|       descriptionBlock = p->graphicMessage_->description_block(); | ||||
| 
 | ||||
|    const float offset = descriptionBlock->offset(); | ||||
|    const float scale  = descriptionBlock->scale(); | ||||
| 
 | ||||
|    switch (p->category_) | ||||
|    { | ||||
|    case common::Level3ProductCategory::Velocity: | ||||
|       edgeValue = static_cast<std::uint8_t>((scale > 0.0f) ? (-offset / scale) : | ||||
|                                                              -offset); | ||||
|       break; | ||||
| 
 | ||||
|    case common::Level3ProductCategory::DifferentialReflectivity: | ||||
|       edgeValue = static_cast<std::uint8_t>(-offset); | ||||
|       break; | ||||
| 
 | ||||
|    case common::Level3ProductCategory::SpectrumWidth: | ||||
|    case common::Level3ProductCategory::SpecificDifferentialPhase: | ||||
|       edgeValue = 2; | ||||
|       break; | ||||
| 
 | ||||
|    case common::Level3ProductCategory::CorrelationCoefficient: | ||||
|       edgeValue = static_cast<std::uint8_t>( | ||||
|          std::max<std::uint16_t>(std::numeric_limits<std::uint8_t>::max(), | ||||
|                                  descriptionBlock->number_of_levels())); | ||||
|       break; | ||||
| 
 | ||||
|    case common::Level3ProductCategory::Reflectivity: | ||||
|    case common::Level3ProductCategory::StormRelativeVelocity: | ||||
|    case common::Level3ProductCategory::VerticallyIntegratedLiquid: | ||||
|    case common::Level3ProductCategory::EchoTops: | ||||
|    case common::Level3ProductCategory::HydrometeorClassification: | ||||
|    case common::Level3ProductCategory::PrecipitationAccumulation: | ||||
|    default: | ||||
|       edgeValue = 0; | ||||
|       break; | ||||
|    } | ||||
| 
 | ||||
|    return edgeValue; | ||||
| } | ||||
| 
 | ||||
| std::optional<wsr88d::DataLevelCode> | ||||
| Level3ProductView::GetDataLevelCode(std::uint16_t level) const | ||||
| { | ||||
|  |  | |||
|  | @ -58,6 +58,8 @@ protected: | |||
|    void DisconnectRadarProductManager() override; | ||||
|    void UpdateColorTableLut() override; | ||||
| 
 | ||||
|    [[nodiscard]] std::uint8_t ComputeEdgeValue() const; | ||||
| 
 | ||||
| private: | ||||
|    class Impl; | ||||
|    std::unique_ptr<Impl> p; | ||||
|  |  | |||
|  | @ -44,7 +44,11 @@ public: | |||
|    ~Impl() { threadPool_.join(); }; | ||||
| 
 | ||||
|    void ComputeCoordinates( | ||||
|       const std::shared_ptr<wsr88d::rpg::GenericRadialDataPacket>& radialData); | ||||
|       const std::shared_ptr<wsr88d::rpg::GenericRadialDataPacket>& radialData, | ||||
|       bool smoothingEnabled); | ||||
| 
 | ||||
|    [[nodiscard]] inline std::uint8_t | ||||
|    RemapDataMoment(std::uint8_t dataMoment) const; | ||||
| 
 | ||||
|    Level3RadialView* self_; | ||||
| 
 | ||||
|  | @ -53,8 +57,13 @@ public: | |||
|    std::vector<float>        coordinates_ {}; | ||||
|    std::vector<float>        vertices_ {}; | ||||
|    std::vector<std::uint8_t> dataMoments8_ {}; | ||||
|    std::uint8_t              edgeValue_ {}; | ||||
| 
 | ||||
|    bool showSmoothedRangeFolding_ {false}; | ||||
| 
 | ||||
|    std::shared_ptr<wsr88d::rpg::GenericRadialDataPacket> lastRadialData_ {}; | ||||
|    bool lastShowSmoothedRangeFolding_ {false}; | ||||
|    bool lastSmoothingEnabled_ {false}; | ||||
| 
 | ||||
|    float         latitude_; | ||||
|    float         longitude_; | ||||
|  | @ -125,6 +134,9 @@ void Level3RadialView::ComputeSweep() | |||
| 
 | ||||
|    std::shared_ptr<manager::RadarProductManager> radarProductManager = | ||||
|       radar_product_manager(); | ||||
|    const bool smoothingEnabled          = smoothing_enabled(); | ||||
|    p->showSmoothedRangeFolding_         = show_smoothed_range_folding(); | ||||
|    const bool& showSmoothedRangeFolding = p->showSmoothedRangeFolding_; | ||||
| 
 | ||||
|    // Retrieve message from Radar Product Manager
 | ||||
|    std::shared_ptr<wsr88d::rpg::Level3Message> message; | ||||
|  | @ -155,7 +167,10 @@ void Level3RadialView::ComputeSweep() | |||
|       Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData); | ||||
|       return; | ||||
|    } | ||||
|    else if (gpm == graphic_product_message()) | ||||
|    else if (gpm == graphic_product_message() && | ||||
|             smoothingEnabled == p->lastSmoothingEnabled_ && | ||||
|             (showSmoothedRangeFolding == p->lastShowSmoothedRangeFolding_ || | ||||
|              !smoothingEnabled)) | ||||
|    { | ||||
|       // Skip if this is the message we previously processed
 | ||||
|       Q_EMIT SweepNotComputed(types::NoUpdateReason::NoChange); | ||||
|  | @ -163,6 +178,9 @@ void Level3RadialView::ComputeSweep() | |||
|    } | ||||
|    set_graphic_product_message(gpm); | ||||
| 
 | ||||
|    p->lastShowSmoothedRangeFolding_ = showSmoothedRangeFolding; | ||||
|    p->lastSmoothingEnabled_         = smoothingEnabled; | ||||
| 
 | ||||
|    // A message with radial data should have a Product Description Block and
 | ||||
|    // Product Symbology Block
 | ||||
|    std::shared_ptr<wsr88d::rpg::ProductDescriptionBlock> descriptionBlock = | ||||
|  | @ -267,11 +285,11 @@ void Level3RadialView::ComputeSweep() | |||
|    const std::vector<float>& coordinates = | ||||
|       (radialSize == common::RadialSize::NonStandard) ? | ||||
|          p->coordinates_ : | ||||
|          radarProductManager->coordinates(radialSize); | ||||
|          radarProductManager->coordinates(radialSize, smoothingEnabled); | ||||
| 
 | ||||
|    // There should be a positive number of range bins in radial data
 | ||||
|    const uint16_t gates = radialData->number_of_range_bins(); | ||||
|    if (gates < 1) | ||||
|    const uint16_t numberOfDataMomentGates = radialData->number_of_range_bins(); | ||||
|    if (numberOfDataMomentGates < 1) | ||||
|    { | ||||
|       logger_->warn("No range bins in radial data"); | ||||
|       Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData); | ||||
|  | @ -293,13 +311,14 @@ void Level3RadialView::ComputeSweep() | |||
|    std::vector<float>& vertices = p->vertices_; | ||||
|    size_t              vIndex   = 0; | ||||
|    vertices.clear(); | ||||
|    vertices.resize(radials * gates * VERTICES_PER_BIN * VALUES_PER_VERTEX); | ||||
|    vertices.resize(radials * numberOfDataMomentGates * VERTICES_PER_BIN * | ||||
|                    VALUES_PER_VERTEX); | ||||
| 
 | ||||
|    // Setup data moment vector
 | ||||
|    std::vector<uint8_t>& dataMoments8 = p->dataMoments8_; | ||||
|    size_t                mIndex       = 0; | ||||
| 
 | ||||
|    dataMoments8.resize(radials * gates * VERTICES_PER_BIN); | ||||
|    dataMoments8.resize(radials * numberOfDataMomentGates * VERTICES_PER_BIN); | ||||
| 
 | ||||
|    // Compute threshold at which to display an individual bin
 | ||||
|    const uint16_t snrThreshold = descriptionBlock->threshold(); | ||||
|  | @ -308,7 +327,7 @@ void Level3RadialView::ComputeSweep() | |||
|    std::uint16_t startRadial; | ||||
|    if (radialSize == common::RadialSize::NonStandard) | ||||
|    { | ||||
|       p->ComputeCoordinates(radialData); | ||||
|       p->ComputeCoordinates(radialData, smoothingEnabled); | ||||
|       startRadial = 0; | ||||
|    } | ||||
|    else | ||||
|  | @ -318,40 +337,105 @@ void Level3RadialView::ComputeSweep() | |||
|       startRadial = std::lroundf(startAngle * radialMultiplier); | ||||
|    } | ||||
| 
 | ||||
|    for (uint16_t radial = 0; radial < radialData->number_of_radials(); radial++) | ||||
|    // Compute gate interval
 | ||||
|    const std::uint16_t dataMomentInterval = | ||||
|       descriptionBlock->x_resolution_raw(); | ||||
| 
 | ||||
|    // Compute gate size (number of base gates per bin)
 | ||||
|    const std::uint16_t gateSize = std::max<std::uint16_t>( | ||||
|       1, | ||||
|       dataMomentInterval / | ||||
|          static_cast<std::uint16_t>(radarProductManager->gate_size())); | ||||
| 
 | ||||
|    // Compute gate range [startGate, endGate)
 | ||||
|    std::uint16_t       startGate = 0; | ||||
|    const std::uint16_t endGate = | ||||
|       std::min<std::uint16_t>(startGate + numberOfDataMomentGates * gateSize, | ||||
|                               common::MAX_DATA_MOMENT_GATES); | ||||
| 
 | ||||
|    if (smoothingEnabled) | ||||
|    { | ||||
|       const auto dataMomentsArray8 = radialData->level(radial); | ||||
|       // If smoothing is enabled, the start gate is incremented by one, as we
 | ||||
|       // are skipping the radar site origin. The end gate is unaffected, as
 | ||||
|       // we need to draw one less data point.
 | ||||
|       ++startGate; | ||||
| 
 | ||||
|       // Compute gate interval
 | ||||
|       const uint16_t dataMomentInterval = descriptionBlock->x_resolution_raw(); | ||||
|       // For most products other than reflectivity, the edge should not go to
 | ||||
|       // the bottom of the color table
 | ||||
|       p->edgeValue_ = ComputeEdgeValue(); | ||||
|    } | ||||
| 
 | ||||
|       // Compute gate size (number of base gates per bin)
 | ||||
|       const uint16_t gateSize = std::max<uint16_t>( | ||||
|          1, | ||||
|          dataMomentInterval / | ||||
|             static_cast<uint16_t>(radarProductManager->gate_size())); | ||||
|    for (std::uint16_t radial = 0; radial < radialData->number_of_radials(); | ||||
|         ++radial) | ||||
|    { | ||||
|       const auto& dataMomentsArray8 = radialData->level(radial); | ||||
| 
 | ||||
|       // Compute gate range [startGate, endGate)
 | ||||
|       const uint16_t startGate = 0; | ||||
|       const uint16_t endGate   = std::min<uint16_t>( | ||||
|          startGate + gates * gateSize, common::MAX_DATA_MOMENT_GATES); | ||||
|       const std::uint16_t nextRadial = | ||||
|          (radial == radialData->number_of_radials() - 1) ? 0 : radial + 1; | ||||
|       const auto& nextDataMomentsArray8 = radialData->level(nextRadial); | ||||
| 
 | ||||
|       for (uint16_t gate = startGate, i = 0; gate + gateSize <= endGate; | ||||
|       for (std::uint16_t gate = startGate, i = 0; gate + gateSize <= endGate; | ||||
|            gate += gateSize, ++i) | ||||
|       { | ||||
|          size_t vertexCount = (gate > 0) ? 6 : 3; | ||||
| 
 | ||||
|          // Store data moment value
 | ||||
|          uint8_t dataValue = | ||||
|             (i < dataMomentsArray8.size()) ? dataMomentsArray8[i] : 0; | ||||
|          if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) | ||||
|          if (!smoothingEnabled) | ||||
|          { | ||||
|             continue; | ||||
|          } | ||||
|             // Store data moment value
 | ||||
|             const uint8_t dataValue = | ||||
|                (i < dataMomentsArray8.size()) ? dataMomentsArray8[i] : 0; | ||||
|             if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) | ||||
|             { | ||||
|                continue; | ||||
|             } | ||||
| 
 | ||||
|          for (size_t m = 0; m < vertexCount; m++) | ||||
|             for (size_t m = 0; m < vertexCount; m++) | ||||
|             { | ||||
|                dataMoments8[mIndex++] = dataValue; | ||||
|             } | ||||
|          } | ||||
|          else if (gate > 0) | ||||
|          { | ||||
|             dataMoments8[mIndex++] = dataValue; | ||||
|             // Validate indices are all in range
 | ||||
|             if (i + 1 >= numberOfDataMomentGates) | ||||
|             { | ||||
|                continue; | ||||
|             } | ||||
| 
 | ||||
|             const std::uint8_t& dm1 = dataMomentsArray8[i]; | ||||
|             const std::uint8_t& dm2 = dataMomentsArray8[i + 1]; | ||||
|             const std::uint8_t& dm3 = nextDataMomentsArray8[i]; | ||||
|             const std::uint8_t& dm4 = nextDataMomentsArray8[i + 1]; | ||||
| 
 | ||||
|             if ((!showSmoothedRangeFolding && //
 | ||||
|                  (dm1 < snrThreshold || dm1 == RANGE_FOLDED) && | ||||
|                  (dm2 < snrThreshold || dm2 == RANGE_FOLDED) && | ||||
|                  (dm3 < snrThreshold || dm3 == RANGE_FOLDED) && | ||||
|                  (dm4 < snrThreshold || dm4 == RANGE_FOLDED)) || | ||||
|                 (showSmoothedRangeFolding && //
 | ||||
|                  dm1 < snrThreshold && dm1 != RANGE_FOLDED && | ||||
|                  dm2 < snrThreshold && dm2 != RANGE_FOLDED && | ||||
|                  dm3 < snrThreshold && dm3 != RANGE_FOLDED && | ||||
|                  dm4 < snrThreshold && dm4 != RANGE_FOLDED)) | ||||
|             { | ||||
|                // Skip only if all data moments are hidden
 | ||||
|                continue; | ||||
|             } | ||||
| 
 | ||||
|             // The order must match the store vertices section below
 | ||||
|             dataMoments8[mIndex++] = p->RemapDataMoment(dm1); | ||||
|             dataMoments8[mIndex++] = p->RemapDataMoment(dm2); | ||||
|             dataMoments8[mIndex++] = p->RemapDataMoment(dm4); | ||||
|             dataMoments8[mIndex++] = p->RemapDataMoment(dm1); | ||||
|             dataMoments8[mIndex++] = p->RemapDataMoment(dm3); | ||||
|             dataMoments8[mIndex++] = p->RemapDataMoment(dm4); | ||||
|          } | ||||
|          else | ||||
|          { | ||||
|             // If smoothing is enabled, gate should never start at zero
 | ||||
|             // (radar site origin)
 | ||||
|             logger_->error("Smoothing enabled, gate should not start at zero"); | ||||
|             continue; | ||||
|          } | ||||
| 
 | ||||
|          // Store vertices
 | ||||
|  | @ -376,19 +460,17 @@ void Level3RadialView::ComputeSweep() | |||
|             vertices[vIndex++] = coordinates[offset2]; | ||||
|             vertices[vIndex++] = coordinates[offset2 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset3]; | ||||
|             vertices[vIndex++] = coordinates[offset3 + 1]; | ||||
|             vertices[vIndex++] = coordinates[offset4]; | ||||
|             vertices[vIndex++] = coordinates[offset4 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset1]; | ||||
|             vertices[vIndex++] = coordinates[offset1 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset3]; | ||||
|             vertices[vIndex++] = coordinates[offset3 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset4]; | ||||
|             vertices[vIndex++] = coordinates[offset4 + 1]; | ||||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset2]; | ||||
|             vertices[vIndex++] = coordinates[offset2 + 1]; | ||||
| 
 | ||||
|             vertexCount = 6; | ||||
|          } | ||||
|          else | ||||
|          { | ||||
|  | @ -411,8 +493,6 @@ void Level3RadialView::ComputeSweep() | |||
| 
 | ||||
|             vertices[vIndex++] = coordinates[offset2]; | ||||
|             vertices[vIndex++] = coordinates[offset2 + 1]; | ||||
| 
 | ||||
|             vertexCount = 3; | ||||
|          } | ||||
|       } | ||||
|    } | ||||
|  | @ -430,8 +510,23 @@ void Level3RadialView::ComputeSweep() | |||
|    Q_EMIT SweepComputed(); | ||||
| } | ||||
| 
 | ||||
| std::uint8_t | ||||
| Level3RadialView::Impl::RemapDataMoment(std::uint8_t dataMoment) const | ||||
| { | ||||
|    if (dataMoment != 0 && | ||||
|        (dataMoment != RANGE_FOLDED || showSmoothedRangeFolding_)) | ||||
|    { | ||||
|       return dataMoment; | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       return edgeValue_; | ||||
|    } | ||||
| } | ||||
| 
 | ||||
| void Level3RadialView::Impl::ComputeCoordinates( | ||||
|    const std::shared_ptr<wsr88d::rpg::GenericRadialDataPacket>& radialData) | ||||
|    const std::shared_ptr<wsr88d::rpg::GenericRadialDataPacket>& radialData, | ||||
|    bool smoothingEnabled) | ||||
| { | ||||
|    logger_->debug("ComputeCoordinates()"); | ||||
| 
 | ||||
|  | @ -455,38 +550,54 @@ void Level3RadialView::Impl::ComputeCoordinates( | |||
|    auto radials = boost::irange<std::uint32_t>(0u, numRadials); | ||||
|    auto gates   = boost::irange<std::uint32_t>(0u, numRangeBins); | ||||
| 
 | ||||
|    std::for_each(std::execution::par_unseq, | ||||
|                  radials.begin(), | ||||
|                  radials.end(), | ||||
|                  [&](std::uint32_t radial) | ||||
|                  { | ||||
|                     const float angle = radialData->start_angle(radial); | ||||
|    const float gateRangeOffset = (smoothingEnabled) ? | ||||
|                                     // Center of the first gate is half the gate
 | ||||
|                                     // size distance from the radar site
 | ||||
|                                     0.5f : | ||||
|                                     // Far end of the first gate is the gate
 | ||||
|                                     // size distance from the radar site
 | ||||
|                                     1.0f; | ||||
| 
 | ||||
|                     std::for_each(std::execution::par_unseq, | ||||
|                                   gates.begin(), | ||||
|                                   gates.end(), | ||||
|                                   [&](std::uint32_t gate) | ||||
|                                   { | ||||
|                                      const std::uint32_t radialGate = | ||||
|                                         radial * common::MAX_DATA_MOMENT_GATES + | ||||
|                                         gate; | ||||
|                                      const float range = (gate + 1) * gateSize; | ||||
|                                      const std::size_t offset = radialGate * 2; | ||||
|    std::for_each( | ||||
|       std::execution::par_unseq, | ||||
|       radials.begin(), | ||||
|       radials.end(), | ||||
|       [&](std::uint32_t radial) | ||||
|       { | ||||
|          float angle = radialData->start_angle(radial); | ||||
| 
 | ||||
|                                      double latitude; | ||||
|                                      double longitude; | ||||
|          if (smoothingEnabled) | ||||
|          { | ||||
|             static constexpr float kDeltaAngleFactor = 0.5f; | ||||
|             angle += radialData->delta_angle(radial) * kDeltaAngleFactor; | ||||
|          } | ||||
| 
 | ||||
|                                      geodesic.Direct(radarLatitude, | ||||
|                                                      radarLongitude, | ||||
|                                                      angle, | ||||
|                                                      range, | ||||
|                                                      latitude, | ||||
|                                                      longitude); | ||||
|          std::for_each( | ||||
|             std::execution::par_unseq, | ||||
|             gates.begin(), | ||||
|             gates.end(), | ||||
|             [&](std::uint32_t gate) | ||||
|             { | ||||
|                const std::uint32_t radialGate = | ||||
|                   radial * common::MAX_DATA_MOMENT_GATES + gate; | ||||
|                const float range = | ||||
|                   (static_cast<float>(gate) + gateRangeOffset) * gateSize; | ||||
|                const std::size_t offset = static_cast<size_t>(radialGate) * 2; | ||||
| 
 | ||||
|                                      coordinates_[offset]     = latitude; | ||||
|                                      coordinates_[offset + 1] = longitude; | ||||
|                                   }); | ||||
|                  }); | ||||
|                double latitude  = 0.0; | ||||
|                double longitude = 0.0; | ||||
| 
 | ||||
|                geodesic.Direct(radarLatitude, | ||||
|                                radarLongitude, | ||||
|                                angle, | ||||
|                                range, | ||||
|                                latitude, | ||||
|                                longitude); | ||||
| 
 | ||||
|                coordinates_[offset]     = static_cast<float>(latitude); | ||||
|                coordinates_[offset + 1] = static_cast<float>(longitude); | ||||
|             }); | ||||
|       }); | ||||
|    timer.stop(); | ||||
|    logger_->debug("Coordinates calculated in {}", timer.format(6, "%ws")); | ||||
| } | ||||
|  |  | |||
|  | @ -33,12 +33,20 @@ public: | |||
|    } | ||||
|    ~Level3RasterViewImpl() { threadPool_.join(); }; | ||||
| 
 | ||||
|    [[nodiscard]] inline std::uint8_t | ||||
|    RemapDataMoment(std::uint8_t dataMoment) const; | ||||
| 
 | ||||
|    boost::asio::thread_pool threadPool_ {1u}; | ||||
| 
 | ||||
|    std::vector<float>   vertices_; | ||||
|    std::vector<uint8_t> dataMoments8_; | ||||
|    std::vector<float>        vertices_ {}; | ||||
|    std::vector<std::uint8_t> dataMoments8_ {}; | ||||
|    std::uint8_t              edgeValue_ {}; | ||||
| 
 | ||||
|    bool showSmoothedRangeFolding_ {false}; | ||||
| 
 | ||||
|    std::shared_ptr<wsr88d::rpg::RasterDataPacket> lastRasterData_ {}; | ||||
|    bool lastShowSmoothedRangeFolding_ {false}; | ||||
|    bool lastSmoothingEnabled_ {false}; | ||||
| 
 | ||||
|    float    latitude_; | ||||
|    float    longitude_; | ||||
|  | @ -109,6 +117,9 @@ void Level3RasterView::ComputeSweep() | |||
| 
 | ||||
|    std::shared_ptr<manager::RadarProductManager> radarProductManager = | ||||
|       radar_product_manager(); | ||||
|    const bool smoothingEnabled          = smoothing_enabled(); | ||||
|    p->showSmoothedRangeFolding_         = show_smoothed_range_folding(); | ||||
|    const bool& showSmoothedRangeFolding = p->showSmoothedRangeFolding_; | ||||
| 
 | ||||
|    // Retrieve message from Radar Product Manager
 | ||||
|    std::shared_ptr<wsr88d::rpg::Level3Message> message; | ||||
|  | @ -139,7 +150,10 @@ void Level3RasterView::ComputeSweep() | |||
|       Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData); | ||||
|       return; | ||||
|    } | ||||
|    else if (gpm == graphic_product_message()) | ||||
|    else if (gpm == graphic_product_message() && | ||||
|             smoothingEnabled == p->lastSmoothingEnabled_ && | ||||
|             (showSmoothedRangeFolding == p->lastShowSmoothedRangeFolding_ || | ||||
|              !smoothingEnabled)) | ||||
|    { | ||||
|       // Skip if this is the message we previously processed
 | ||||
|       Q_EMIT SweepNotComputed(types::NoUpdateReason::NoChange); | ||||
|  | @ -147,6 +161,9 @@ void Level3RasterView::ComputeSweep() | |||
|    } | ||||
|    set_graphic_product_message(gpm); | ||||
| 
 | ||||
|    p->lastShowSmoothedRangeFolding_ = showSmoothedRangeFolding; | ||||
|    p->lastSmoothingEnabled_         = smoothingEnabled; | ||||
| 
 | ||||
|    // A message with radial data should have a Product Description Block and
 | ||||
|    // Product Symbology Block
 | ||||
|    std::shared_ptr<wsr88d::rpg::ProductDescriptionBlock> descriptionBlock = | ||||
|  | @ -231,16 +248,18 @@ void Level3RasterView::ComputeSweep() | |||
|    const GeographicLib::Geodesic& geodesic = | ||||
|       util::GeographicLib::DefaultGeodesic(); | ||||
| 
 | ||||
|    const uint16_t xResolution = descriptionBlock->x_resolution_raw(); | ||||
|    const uint16_t yResolution = descriptionBlock->y_resolution_raw(); | ||||
|    double         iCoordinate = | ||||
|    const std::uint16_t xResolution = descriptionBlock->x_resolution_raw(); | ||||
|    const std::uint16_t yResolution = descriptionBlock->y_resolution_raw(); | ||||
|    const double        iCoordinate = | ||||
|       (-rasterData->i_coordinate_start() - 1.0 - p->range_) * 1000.0; | ||||
|    double jCoordinate = | ||||
|    const double jCoordinate = | ||||
|       (rasterData->j_coordinate_start() + 1.0 + p->range_) * 1000.0; | ||||
|    const double xOffset = (smoothingEnabled) ? xResolution * 0.5 : 0.0; | ||||
|    const double yOffset = (smoothingEnabled) ? yResolution * 0.5 : 0.0; | ||||
| 
 | ||||
|    size_t numCoordinates = | ||||
|    const std::size_t numCoordinates = | ||||
|       static_cast<size_t>(rows + 1) * static_cast<size_t>(maxColumns + 1); | ||||
|    auto coordinateRange = | ||||
|    const auto coordinateRange = | ||||
|       boost::irange<uint32_t>(0, static_cast<uint32_t>(numCoordinates)); | ||||
| 
 | ||||
|    std::vector<float> coordinates; | ||||
|  | @ -260,8 +279,8 @@ void Level3RasterView::ComputeSweep() | |||
|          const uint32_t col = index % (rows + 1); | ||||
|          const uint32_t row = index / (rows + 1); | ||||
| 
 | ||||
|          const double i = iCoordinate + xResolution * col; | ||||
|          const double j = jCoordinate - yResolution * row; | ||||
|          const double i = iCoordinate + xResolution * col + xOffset; | ||||
|          const double j = jCoordinate - yResolution * row - yOffset; | ||||
| 
 | ||||
|          // Calculate polar coordinates based on i and j
 | ||||
|          const double angle  = std::atan2(i, j) * 180.0 / M_PI; | ||||
|  | @ -299,25 +318,83 @@ void Level3RasterView::ComputeSweep() | |||
|    // Compute threshold at which to display an individual bin
 | ||||
|    const uint16_t snrThreshold = descriptionBlock->threshold(); | ||||
| 
 | ||||
|    for (size_t row = 0; row < rasterData->number_of_rows(); ++row) | ||||
|    const std::size_t rowCount = (smoothingEnabled) ? | ||||
|                                    rasterData->number_of_rows() - 1 : | ||||
|                                    rasterData->number_of_rows(); | ||||
| 
 | ||||
|    if (smoothingEnabled) | ||||
|    { | ||||
|       const auto dataMomentsArray8 = | ||||
|       // For most products other than reflectivity, the edge should not go to
 | ||||
|       // the bottom of the color table
 | ||||
|       p->edgeValue_ = ComputeEdgeValue(); | ||||
|    } | ||||
| 
 | ||||
|    for (std::size_t row = 0; row < rowCount; ++row) | ||||
|    { | ||||
|       const std::size_t nextRow = | ||||
|          (row == static_cast<std::size_t>(rasterData->number_of_rows() - 1)) ? | ||||
|             0 : | ||||
|             row + 1; | ||||
| 
 | ||||
|       const auto& dataMomentsArray8 = | ||||
|          rasterData->level(static_cast<uint16_t>(row)); | ||||
|       const auto& nextDataMomentsArray8 = | ||||
|          rasterData->level(static_cast<uint16_t>(nextRow)); | ||||
| 
 | ||||
|       for (size_t bin = 0; bin < dataMomentsArray8.size(); ++bin) | ||||
|       { | ||||
|          constexpr size_t vertexCount = 6; | ||||
| 
 | ||||
|          // Store data moment value
 | ||||
|          uint8_t dataValue = dataMomentsArray8[bin]; | ||||
|          if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) | ||||
|          if (!smoothingEnabled) | ||||
|          { | ||||
|             continue; | ||||
|             static constexpr std::size_t vertexCount = 6; | ||||
| 
 | ||||
|             // Store data moment value
 | ||||
|             const std::uint8_t& dataValue = dataMomentsArray8[bin]; | ||||
|             if (dataValue < snrThreshold && dataValue != RANGE_FOLDED) | ||||
|             { | ||||
|                continue; | ||||
|             } | ||||
| 
 | ||||
|             for (size_t m = 0; m < vertexCount; m++) | ||||
|             { | ||||
|                dataMoments8[mIndex++] = dataValue; | ||||
|             } | ||||
|          } | ||||
| 
 | ||||
|          for (size_t m = 0; m < vertexCount; m++) | ||||
|          else | ||||
|          { | ||||
|             dataMoments8[mIndex++] = dataValue; | ||||
|             // Validate indices are all in range
 | ||||
|             if (bin + 1 >= dataMomentsArray8.size() || | ||||
|                 bin + 1 >= nextDataMomentsArray8.size()) | ||||
|             { | ||||
|                continue; | ||||
|             } | ||||
| 
 | ||||
|             const std::uint8_t& dm1 = dataMomentsArray8[bin]; | ||||
|             const std::uint8_t& dm2 = dataMomentsArray8[bin + 1]; | ||||
|             const std::uint8_t& dm3 = nextDataMomentsArray8[bin]; | ||||
|             const std::uint8_t& dm4 = nextDataMomentsArray8[bin + 1]; | ||||
| 
 | ||||
|             if ((!showSmoothedRangeFolding && //
 | ||||
|                  (dm1 < snrThreshold || dm1 == RANGE_FOLDED) && | ||||
|                  (dm2 < snrThreshold || dm2 == RANGE_FOLDED) && | ||||
|                  (dm3 < snrThreshold || dm3 == RANGE_FOLDED) && | ||||
|                  (dm4 < snrThreshold || dm4 == RANGE_FOLDED)) || | ||||
|                 (showSmoothedRangeFolding && //
 | ||||
|                  dm1 < snrThreshold && dm1 != RANGE_FOLDED && | ||||
|                  dm2 < snrThreshold && dm2 != RANGE_FOLDED && | ||||
|                  dm3 < snrThreshold && dm3 != RANGE_FOLDED && | ||||
|                  dm4 < snrThreshold && dm4 != RANGE_FOLDED)) | ||||
|             { | ||||
|                // Skip only if all data moments are hidden
 | ||||
|                continue; | ||||
|             } | ||||
| 
 | ||||
|             // The order must match the store vertices section below
 | ||||
|             dataMoments8[mIndex++] = p->RemapDataMoment(dm1); | ||||
|             dataMoments8[mIndex++] = p->RemapDataMoment(dm2); | ||||
|             dataMoments8[mIndex++] = p->RemapDataMoment(dm4); | ||||
|             dataMoments8[mIndex++] = p->RemapDataMoment(dm1); | ||||
|             dataMoments8[mIndex++] = p->RemapDataMoment(dm3); | ||||
|             dataMoments8[mIndex++] = p->RemapDataMoment(dm4); | ||||
|          } | ||||
| 
 | ||||
|          // Store vertices
 | ||||
|  | @ -332,17 +409,17 @@ void Level3RasterView::ComputeSweep() | |||
|          vertices[vIndex++] = coordinates[offset2]; | ||||
|          vertices[vIndex++] = coordinates[offset2 + 1]; | ||||
| 
 | ||||
|          vertices[vIndex++] = coordinates[offset3]; | ||||
|          vertices[vIndex++] = coordinates[offset3 + 1]; | ||||
|          vertices[vIndex++] = coordinates[offset4]; | ||||
|          vertices[vIndex++] = coordinates[offset4 + 1]; | ||||
| 
 | ||||
|          vertices[vIndex++] = coordinates[offset1]; | ||||
|          vertices[vIndex++] = coordinates[offset1 + 1]; | ||||
| 
 | ||||
|          vertices[vIndex++] = coordinates[offset3]; | ||||
|          vertices[vIndex++] = coordinates[offset3 + 1]; | ||||
| 
 | ||||
|          vertices[vIndex++] = coordinates[offset4]; | ||||
|          vertices[vIndex++] = coordinates[offset4 + 1]; | ||||
| 
 | ||||
|          vertices[vIndex++] = coordinates[offset2]; | ||||
|          vertices[vIndex++] = coordinates[offset2 + 1]; | ||||
|       } | ||||
|    } | ||||
|    vertices.resize(vIndex); | ||||
|  | @ -359,6 +436,20 @@ void Level3RasterView::ComputeSweep() | |||
|    Q_EMIT SweepComputed(); | ||||
| } | ||||
| 
 | ||||
| std::uint8_t | ||||
| Level3RasterViewImpl::RemapDataMoment(std::uint8_t dataMoment) const | ||||
| { | ||||
|    if (dataMoment != 0 && | ||||
|        (dataMoment != RANGE_FOLDED || showSmoothedRangeFolding_)) | ||||
|    { | ||||
|       return dataMoment; | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       return edgeValue_; | ||||
|    } | ||||
| } | ||||
| 
 | ||||
| std::optional<std::uint16_t> | ||||
| Level3RasterView::GetBinLevel(const common::Coordinate& coordinate) const | ||||
| { | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| #include <scwx/qt/view/radar_product_view.hpp> | ||||
| #include <scwx/qt/settings/product_settings.hpp> | ||||
| #include <scwx/common/constants.hpp> | ||||
| #include <scwx/util/logger.hpp> | ||||
| 
 | ||||
|  | @ -28,26 +29,44 @@ class RadarProductViewImpl | |||
| { | ||||
| public: | ||||
|    explicit RadarProductViewImpl( | ||||
|       RadarProductView*                             self, | ||||
|       std::shared_ptr<manager::RadarProductManager> radarProductManager) : | ||||
|        self_ {self}, | ||||
|        initialized_ {false}, | ||||
|        sweepMutex_ {}, | ||||
|        selectedTime_ {}, | ||||
|        radarProductManager_ {radarProductManager} | ||||
|    { | ||||
|       auto& productSettings = settings::ProductSettings::Instance(); | ||||
|       connection_           = productSettings.changed_signal().connect( | ||||
|          [this]() | ||||
|          { | ||||
|             showSmoothedRangeFolding_ = settings::ProductSettings::Instance() | ||||
|                                            .show_smoothed_range_folding() | ||||
|                                            .GetValue(); | ||||
|             self_->Update(); | ||||
|          }); | ||||
|       ; | ||||
|    } | ||||
|    ~RadarProductViewImpl() {} | ||||
| 
 | ||||
|    RadarProductView* self_; | ||||
| 
 | ||||
|    bool       initialized_; | ||||
|    std::mutex sweepMutex_; | ||||
| 
 | ||||
|    std::chrono::system_clock::time_point selectedTime_; | ||||
|    bool                                  showSmoothedRangeFolding_ {false}; | ||||
|    bool                                  smoothingEnabled_ {false}; | ||||
| 
 | ||||
|    std::shared_ptr<manager::RadarProductManager> radarProductManager_; | ||||
| 
 | ||||
|    boost::signals2::scoped_connection connection_; | ||||
| }; | ||||
| 
 | ||||
| RadarProductView::RadarProductView( | ||||
|    std::shared_ptr<manager::RadarProductManager> radarProductManager) : | ||||
|     p(std::make_unique<RadarProductViewImpl>(radarProductManager)) {}; | ||||
|     p(std::make_unique<RadarProductViewImpl>(this, radarProductManager)) {}; | ||||
| RadarProductView::~RadarProductView() = default; | ||||
| 
 | ||||
| const std::vector<boost::gil::rgba8_pixel_t>& | ||||
|  | @ -87,6 +106,16 @@ std::chrono::system_clock::time_point RadarProductView::selected_time() const | |||
|    return p->selectedTime_; | ||||
| } | ||||
| 
 | ||||
| bool RadarProductView::show_smoothed_range_folding() const | ||||
| { | ||||
|    return p->showSmoothedRangeFolding_; | ||||
| } | ||||
| 
 | ||||
| bool RadarProductView::smoothing_enabled() const | ||||
| { | ||||
|    return p->smoothingEnabled_; | ||||
| } | ||||
| 
 | ||||
| std::chrono::system_clock::time_point RadarProductView::sweep_time() const | ||||
| { | ||||
|    return {}; | ||||
|  | @ -105,6 +134,11 @@ void RadarProductView::set_radar_product_manager( | |||
|    ConnectRadarProductManager(); | ||||
| } | ||||
| 
 | ||||
| void RadarProductView::set_smoothing_enabled(bool smoothingEnabled) | ||||
| { | ||||
|    p->smoothingEnabled_ = smoothingEnabled; | ||||
| } | ||||
| 
 | ||||
| void RadarProductView::Initialize() | ||||
| { | ||||
|    ComputeSweep(); | ||||
|  |  | |||
|  | @ -47,12 +47,16 @@ public: | |||
|    virtual std::uint16_t                         vcp() const        = 0; | ||||
|    virtual const std::vector<float>&             vertices() const   = 0; | ||||
| 
 | ||||
|    std::shared_ptr<manager::RadarProductManager> radar_product_manager() const; | ||||
|    std::chrono::system_clock::time_point         selected_time() const; | ||||
|    std::mutex&                                   sweep_mutex(); | ||||
|    [[nodiscard]] std::shared_ptr<manager::RadarProductManager> | ||||
|    radar_product_manager() const; | ||||
|    [[nodiscard]] std::chrono::system_clock::time_point selected_time() const; | ||||
|    [[nodiscard]] bool        show_smoothed_range_folding() const; | ||||
|    [[nodiscard]] bool        smoothing_enabled() const; | ||||
|    [[nodiscard]] std::mutex& sweep_mutex(); | ||||
| 
 | ||||
|    void set_radar_product_manager( | ||||
|       std::shared_ptr<manager::RadarProductManager> radarProductManager); | ||||
|    void set_smoothing_enabled(bool smoothingEnabled); | ||||
| 
 | ||||
|    void Initialize(); | ||||
|    virtual void | ||||
|  |  | |||
|  | @ -1 +1 @@ | |||
| Subproject commit eaf8f185ce2b3a3248da1a4d6c8e2e9265638f15 | ||||
| Subproject commit 0eb475909f9e64ce81e7b8b39420d980b81b3baa | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat