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