Merge pull request #322 from dpaulat/feature/radar-smoothing

Radar Smoothing
This commit is contained in:
Dan Paulat 2024-12-16 08:18:54 -06:00 committed by GitHub
commit bef8628bb6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 1184 additions and 347 deletions

View file

@ -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'

View file

@ -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)
{ {

View file

@ -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)
{ {

View file

@ -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()

View file

@ -29,7 +29,7 @@ public:
void keyPressEvent(QKeyEvent* ev) override final; void keyPressEvent(QKeyEvent* ev) override final;
void keyReleaseEvent(QKeyEvent* ev) override final; void keyReleaseEvent(QKeyEvent* ev) override final;
void showEvent(QShowEvent* event) override; void showEvent(QShowEvent* event) override;
void closeEvent(QCloseEvent *event) override; void closeEvent(QCloseEvent* event) override;
signals: signals:
void ActiveMapMoved(double latitude, double longitude); void ActiveMapMoved(double latitude, double longitude);
@ -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();

View file

@ -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 &amp;Marker Manager</string> <string>Location &amp;Marker Manager</string>
</property> </property>
</action> </action>
<action name="actionRadarWireframe">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Radar &amp;Wireframe</string>
</property>
</action>
</widget> </widget>
<resources> <resources>
<include location="../../../../scwx-qt.qrc"/> <include location="../../../../scwx-qt.qrc"/>

View file

@ -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,10 +235,12 @@ 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_ {};
std::unordered_map<std::string, RadarProductRecordMap> std::unordered_map<std::string, RadarProductRecordMap>
level3ProductRecordsMap_ {}; level3ProductRecordsMap_ {};
std::unordered_map<std::string, RadarProductRecordList> std::unordered_map<std::string, RadarProductRecordList>
@ -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:
return p->coordinates0_5Degree_; if (smoothingEnabled)
{
return p->coordinates0_5DegreeSmooth_;
}
else
{
return p->coordinates0_5Degree_;
}
case common::RadialSize::_1Degree: case common::RadialSize::_1Degree:
return p->coordinates1Degree_; if (smoothingEnabled)
{
return p->coordinates1DegreeSmooth_;
}
else
{
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>

View file

@ -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();

View file

@ -9,16 +9,17 @@ 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;
MapSettings& operator=(const MapSettings&) = delete; MapSettings& operator=(const MapSettings&) = delete;
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

View file

@ -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};
@ -225,7 +229,7 @@ public:
std::shared_ptr<OverlayLayer> overlayLayer_; std::shared_ptr<OverlayLayer> overlayLayer_;
std::shared_ptr<OverlayProductLayer> overlayProductLayer_ {nullptr}; std::shared_ptr<OverlayProductLayer> overlayProductLayer_ {nullptr};
std::shared_ptr<PlacefileLayer> placefileLayer_; std::shared_ptr<PlacefileLayer> placefileLayer_;
std::shared_ptr<MarkerLayer> markerLayer_; std::shared_ptr<MarkerLayer> markerLayer_;
std::shared_ptr<ColorTableLayer> colorTableLayer_; std::shared_ptr<ColorTableLayer> colorTableLayer_;
std::shared_ptr<RadarSiteLayer> radarSiteLayer_ {nullptr}; std::shared_ptr<RadarSiteLayer> radarSiteLayer_ {nullptr};
@ -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();

View file

@ -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.

View file

@ -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();
} }

View file

@ -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);

View file

@ -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();

View file

@ -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_);
} }

View file

@ -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();

View file

@ -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,21 +530,22 @@ 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,
&QComboBox::currentTextChanged, &QComboBox::currentTextChanged,
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);
self_->ui->resetThemeFileButton->setEnabled(themeFileEnabled); self_->ui->resetThemeFileButton->setEnabled(themeFileEnabled);
}); });
theme_.SetSettingsVariable(generalSettings.theme()); theme_.SetSettingsVariable(generalSettings.theme());
SCWX_SETTINGS_COMBO_BOX(theme_, SCWX_SETTINGS_COMBO_BOX(theme_,
@ -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(

View file

@ -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>

View file

@ -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,9 +702,19 @@ 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));
const std::uint8_t* dataMomentsArray8 = nullptr; if (smoothingEnabled)
const std::uint16_t* dataMomentsArray16 = nullptr; {
const std::uint8_t* cfpMomentsArray = nullptr; // If smoothing is enabled, the start gate is incremented by one, as we
// are skipping the radar site origin. The end gate is unaffected, as
// we need to draw one less data point.
++startGate;
}
const std::uint8_t* dataMomentsArray8 = nullptr;
const std::uint16_t* dataMomentsArray16 = nullptr;
const std::uint8_t* nextDataMomentsArray8 = nullptr;
const std::uint16_t* nextDataMomentsArray16 = nullptr;
const std::uint8_t* cfpMomentsArray = nullptr;
if (momentData->data_word_size() == 8) 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,57 +781,172 @@ 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)
if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
{ {
continue; const std::uint8_t& dataValue = dataMomentsArray8[i];
} if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
for (std::size_t m = 0; m < vertexCount; m++)
{
dataMoments8[mIndex++] = dataMomentsArray8[i];
if (cfpMomentsArray != nullptr)
{ {
cfpMoments[mIndex - 1] = cfpMomentsArray[i]; continue;
} }
for (std::size_t m = 0; m < vertexCount; m++)
{
dataMoments8[mIndex++] = dataValue;
if (cfpMomentsArray != nullptr)
{
cfpMoments[mIndex - 1] = cfpMomentsArray[i];
}
}
}
else if (gate > 0)
{
// Validate indices are all in range
if (i + 1 >= numberOfDataMomentGates ||
i + 1 >= numberOfNextDataMomentGates)
{
continue;
}
const std::uint8_t& dm1 = dataMomentsArray8[i];
const std::uint8_t& dm2 = dataMomentsArray8[i + 1];
const std::uint8_t& dm3 = nextDataMomentsArray8[i];
const std::uint8_t& dm4 = nextDataMomentsArray8[i + 1];
if ((!showSmoothedRangeFolding && //
(dm1 < snrThreshold || dm1 == RANGE_FOLDED) &&
(dm2 < snrThreshold || dm2 == RANGE_FOLDED) &&
(dm3 < snrThreshold || dm3 == RANGE_FOLDED) &&
(dm4 < snrThreshold || dm4 == RANGE_FOLDED)) ||
(showSmoothedRangeFolding && //
dm1 < snrThreshold && dm1 != RANGE_FOLDED &&
dm2 < snrThreshold && dm2 != RANGE_FOLDED &&
dm3 < snrThreshold && dm3 != RANGE_FOLDED &&
dm4 < snrThreshold && dm4 != RANGE_FOLDED))
{
// Skip only if all data moments are hidden
continue;
}
// The order must match the store vertices section below
dataMoments8[mIndex++] = p->RemapDataMoment(dm1);
dataMoments8[mIndex++] = p->RemapDataMoment(dm2);
dataMoments8[mIndex++] = p->RemapDataMoment(dm4);
dataMoments8[mIndex++] = p->RemapDataMoment(dm1);
dataMoments8[mIndex++] = p->RemapDataMoment(dm3);
dataMoments8[mIndex++] = p->RemapDataMoment(dm4);
// cfpMoments is unused, so not populated here
}
else
{
// If smoothing is enabled, gate should never start at zero
// (radar site origin)
logger_->error(
"Smoothing enabled, gate should not start at zero");
continue;
} }
} }
else else
{ {
std::uint16_t dataValue = dataMomentsArray16[i]; if (!smoothingEnabled)
if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
{ {
const std::uint16_t& dataValue = dataMomentsArray16[i];
if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
{
continue;
}
for (std::size_t m = 0; m < vertexCount; m++)
{
dataMoments16[mIndex++] = dataValue;
}
}
else if (gate > 0)
{
// Validate indices are all in range
if (i + 1 >= numberOfDataMomentGates ||
i + 1 >= numberOfNextDataMomentGates)
{
continue;
}
const std::uint16_t& dm1 = dataMomentsArray16[i];
const std::uint16_t& dm2 = dataMomentsArray16[i + 1];
const std::uint16_t& dm3 = nextDataMomentsArray16[i];
const std::uint16_t& dm4 = nextDataMomentsArray16[i + 1];
if ((!showSmoothedRangeFolding && //
(dm1 < snrThreshold || dm1 == RANGE_FOLDED) &&
(dm2 < snrThreshold || dm2 == RANGE_FOLDED) &&
(dm3 < snrThreshold || dm3 == RANGE_FOLDED) &&
(dm4 < snrThreshold || dm4 == RANGE_FOLDED)) ||
(showSmoothedRangeFolding && //
dm1 < snrThreshold && dm1 != RANGE_FOLDED &&
dm2 < snrThreshold && dm2 != RANGE_FOLDED &&
dm3 < snrThreshold && dm3 != RANGE_FOLDED &&
dm4 < snrThreshold && dm4 != RANGE_FOLDED))
{
// Skip only if all data moments are hidden
continue;
}
// The order must match the store vertices section below
dataMoments16[mIndex++] = p->RemapDataMoment(dm1);
dataMoments16[mIndex++] = p->RemapDataMoment(dm2);
dataMoments16[mIndex++] = p->RemapDataMoment(dm4);
dataMoments16[mIndex++] = p->RemapDataMoment(dm1);
dataMoments16[mIndex++] = p->RemapDataMoment(dm3);
dataMoments16[mIndex++] = p->RemapDataMoment(dm4);
// cfpMoments is unused, so not populated here
}
else
{
// If smoothing is enabled, gate should never start at zero
// (radar site origin)
continue; continue;
} }
for (std::size_t m = 0; m < vertexCount; m++)
{
dataMoments16[mIndex++] = dataMomentsArray16[i];
}
} }
// 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 =
common::MAX_DATA_MOMENT_GATES + ((startRadial + radial) % vertexRadials *
baseCoord) * common::MAX_DATA_MOMENT_GATES +
2; baseCoord) *
std::size_t offset2 = offset1 + gateSize * 2; 2;
std::size_t offset3 = const std::size_t offset2 =
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,19 +1130,60 @@ 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() &&
prevRadial2 != radarData->cend()) prevRadial1 != radarData->cend() && smoothingEnabled)
{
const units::degrees<float> currentAngle =
radialData->second->azimuth_angle();
const units::degrees<float> prevAngle =
prevRadial1->second->azimuth_angle();
// Calculate delta angle
const units::degrees<float> deltaAngle =
NormalizeAngle(currentAngle - prevAngle);
// Delta scale is half the delta angle to reach the center of the
// bin, because smoothing is enabled
constexpr float deltaScale = 0.5f;
angle = currentAngle + deltaAngle * deltaScale;
}
else if (radialData != radarData->cend() && smoothingEnabled)
{
const units::degrees<float> currentAngle =
radialData->second->azimuth_angle();
// Assume a half degree delta if there aren't enough angles
// to determine a delta angle
constexpr units::degrees<float> deltaAngle {0.5f};
// Delta scale is half the delta angle to reach the center of the
// bin, because smoothing is enabled
constexpr float deltaScale = 0.5f;
angle = currentAngle + deltaAngle * deltaScale;
}
else if (prevRadial1 != radarData->cend() &&
prevRadial2 != radarData->cend())
{ {
const units::degrees<float> prevAngle1 = const units::degrees<float> prevAngle1 =
prevRadial1->second->azimuth_angle(); prevRadial1->second->azimuth_angle();
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,35 +1211,38 @@ void Level2ProductViewImpl::ComputeCoordinates(
} }
} }
std::for_each(std::execution::par_unseq, std::for_each(
gates.begin(), std::execution::par_unseq,
gates.end(), gates.begin(),
[&](std::uint32_t gate) gates.end(),
{ [&](std::uint32_t gate)
const std::uint32_t radialGate = {
radial * common::MAX_DATA_MOMENT_GATES + gate; const std::uint32_t radialGate =
const float range = (gate + 1) * gateSize; radial * common::MAX_DATA_MOMENT_GATES + gate;
const std::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(radarLatitude, geodesic.Direct(radarLatitude,
radarLongitude, radarLongitude,
angle.value(), angle.value(),
range, range,
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;
} }

View file

@ -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

View file

@ -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
{ {

View file

@ -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;

View file

@ -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,40 +337,105 @@ void Level3RadialView::ComputeSweep()
startRadial = std::lroundf(startAngle * radialMultiplier); startRadial = std::lroundf(startAngle * radialMultiplier);
} }
for (uint16_t radial = 0; radial < radialData->number_of_radials(); radial++) // Compute gate interval
const std::uint16_t dataMomentInterval =
descriptionBlock->x_resolution_raw();
// Compute gate size (number of base gates per bin)
const std::uint16_t gateSize = std::max<std::uint16_t>(
1,
dataMomentInterval /
static_cast<std::uint16_t>(radarProductManager->gate_size()));
// Compute gate range [startGate, endGate)
std::uint16_t startGate = 0;
const std::uint16_t endGate =
std::min<std::uint16_t>(startGate + numberOfDataMomentGates * gateSize,
common::MAX_DATA_MOMENT_GATES);
if (smoothingEnabled)
{ {
const auto dataMomentsArray8 = radialData->level(radial); // If smoothing is enabled, the start gate is incremented by one, as we
// are skipping the radar site origin. The end gate is unaffected, as
// we need to draw one less data point.
++startGate;
// Compute gate interval // For most products other than reflectivity, the edge should not go to
const uint16_t dataMomentInterval = descriptionBlock->x_resolution_raw(); // the bottom of the color table
p->edgeValue_ = ComputeEdgeValue();
}
// Compute gate size (number of base gates per bin) for (std::uint16_t radial = 0; radial < radialData->number_of_radials();
const uint16_t gateSize = std::max<uint16_t>( ++radial)
1, {
dataMomentInterval / const auto& dataMomentsArray8 = radialData->level(radial);
static_cast<uint16_t>(radarProductManager->gate_size()));
// Compute gate range [startGate, endGate) const std::uint16_t nextRadial =
const uint16_t startGate = 0; (radial == radialData->number_of_radials() - 1) ? 0 : radial + 1;
const uint16_t endGate = std::min<uint16_t>( const auto& nextDataMomentsArray8 = radialData->level(nextRadial);
startGate + gates * gateSize, common::MAX_DATA_MOMENT_GATES);
for (uint16_t gate = startGate, i = 0; gate + gateSize <= endGate; 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;
// Store data moment value if (!smoothingEnabled)
uint8_t dataValue =
(i < dataMomentsArray8.size()) ? dataMomentsArray8[i] : 0;
if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
{ {
continue; // Store data moment value
} const uint8_t dataValue =
(i < dataMomentsArray8.size()) ? dataMomentsArray8[i] : 0;
if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
{
continue;
}
for (size_t m = 0; m < vertexCount; m++) for (size_t m = 0; m < vertexCount; m++)
{
dataMoments8[mIndex++] = dataValue;
}
}
else if (gate > 0)
{ {
dataMoments8[mIndex++] = dataValue; // Validate indices are all in range
if (i + 1 >= numberOfDataMomentGates)
{
continue;
}
const std::uint8_t& dm1 = dataMomentsArray8[i];
const std::uint8_t& dm2 = dataMomentsArray8[i + 1];
const std::uint8_t& dm3 = nextDataMomentsArray8[i];
const std::uint8_t& dm4 = nextDataMomentsArray8[i + 1];
if ((!showSmoothedRangeFolding && //
(dm1 < snrThreshold || dm1 == RANGE_FOLDED) &&
(dm2 < snrThreshold || dm2 == RANGE_FOLDED) &&
(dm3 < snrThreshold || dm3 == RANGE_FOLDED) &&
(dm4 < snrThreshold || dm4 == RANGE_FOLDED)) ||
(showSmoothedRangeFolding && //
dm1 < snrThreshold && dm1 != RANGE_FOLDED &&
dm2 < snrThreshold && dm2 != RANGE_FOLDED &&
dm3 < snrThreshold && dm3 != RANGE_FOLDED &&
dm4 < snrThreshold && dm4 != RANGE_FOLDED))
{
// Skip only if all data moments are hidden
continue;
}
// The order must match the store vertices section below
dataMoments8[mIndex++] = p->RemapDataMoment(dm1);
dataMoments8[mIndex++] = p->RemapDataMoment(dm2);
dataMoments8[mIndex++] = p->RemapDataMoment(dm4);
dataMoments8[mIndex++] = p->RemapDataMoment(dm1);
dataMoments8[mIndex++] = p->RemapDataMoment(dm3);
dataMoments8[mIndex++] = p->RemapDataMoment(dm4);
}
else
{
// If smoothing is enabled, gate should never start at zero
// (radar site origin)
logger_->error("Smoothing enabled, gate should not start at zero");
continue;
} }
// Store vertices // Store vertices
@ -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,38 +550,54 @@ 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) ?
radials.begin(), // Center of the first gate is half the gate
radials.end(), // size distance from the radar site
[&](std::uint32_t radial) 0.5f :
{ // Far end of the first gate is the gate
const float angle = radialData->start_angle(radial); // size distance from the radar site
1.0f;
std::for_each(std::execution::par_unseq, std::for_each(
gates.begin(), std::execution::par_unseq,
gates.end(), radials.begin(),
[&](std::uint32_t gate) radials.end(),
{ [&](std::uint32_t radial)
const std::uint32_t radialGate = {
radial * common::MAX_DATA_MOMENT_GATES + float angle = radialData->start_angle(radial);
gate;
const float range = (gate + 1) * gateSize;
const std::size_t offset = radialGate * 2;
double latitude; if (smoothingEnabled)
double longitude; {
static constexpr float kDeltaAngleFactor = 0.5f;
angle += radialData->delta_angle(radial) * kDeltaAngleFactor;
}
geodesic.Direct(radarLatitude, std::for_each(
radarLongitude, std::execution::par_unseq,
angle, gates.begin(),
range, gates.end(),
latitude, [&](std::uint32_t gate)
longitude); {
const std::uint32_t radialGate =
radial * common::MAX_DATA_MOMENT_GATES + gate;
const float range =
(static_cast<float>(gate) + gateRangeOffset) * gateSize;
const std::size_t offset = static_cast<size_t>(radialGate) * 2;
coordinates_[offset] = latitude; double latitude = 0.0;
coordinates_[offset + 1] = longitude; double longitude = 0.0;
});
}); geodesic.Direct(radarLatitude,
radarLongitude,
angle,
range,
latitude,
longitude);
coordinates_[offset] = static_cast<float>(latitude);
coordinates_[offset + 1] = static_cast<float>(longitude);
});
});
timer.stop(); timer.stop();
logger_->debug("Coordinates calculated in {}", timer.format(6, "%ws")); logger_->debug("Coordinates calculated in {}", timer.format(6, "%ws"));
} }

View file

@ -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,25 +318,83 @@ 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)
// Store data moment value
uint8_t dataValue = dataMomentsArray8[bin];
if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
{ {
continue; static constexpr std::size_t vertexCount = 6;
// Store data moment value
const std::uint8_t& dataValue = dataMomentsArray8[bin];
if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
{
continue;
}
for (size_t m = 0; m < vertexCount; m++)
{
dataMoments8[mIndex++] = dataValue;
}
} }
else
for (size_t m = 0; m < vertexCount; m++)
{ {
dataMoments8[mIndex++] = dataValue; // Validate indices are all in range
if (bin + 1 >= dataMomentsArray8.size() ||
bin + 1 >= nextDataMomentsArray8.size())
{
continue;
}
const std::uint8_t& dm1 = dataMomentsArray8[bin];
const std::uint8_t& dm2 = dataMomentsArray8[bin + 1];
const std::uint8_t& dm3 = nextDataMomentsArray8[bin];
const std::uint8_t& dm4 = nextDataMomentsArray8[bin + 1];
if ((!showSmoothedRangeFolding && //
(dm1 < snrThreshold || dm1 == RANGE_FOLDED) &&
(dm2 < snrThreshold || dm2 == RANGE_FOLDED) &&
(dm3 < snrThreshold || dm3 == RANGE_FOLDED) &&
(dm4 < snrThreshold || dm4 == RANGE_FOLDED)) ||
(showSmoothedRangeFolding && //
dm1 < snrThreshold && dm1 != RANGE_FOLDED &&
dm2 < snrThreshold && dm2 != RANGE_FOLDED &&
dm3 < snrThreshold && dm3 != RANGE_FOLDED &&
dm4 < snrThreshold && dm4 != RANGE_FOLDED))
{
// Skip only if all data moments are hidden
continue;
}
// The order must match the store vertices section below
dataMoments8[mIndex++] = p->RemapDataMoment(dm1);
dataMoments8[mIndex++] = p->RemapDataMoment(dm2);
dataMoments8[mIndex++] = p->RemapDataMoment(dm4);
dataMoments8[mIndex++] = p->RemapDataMoment(dm1);
dataMoments8[mIndex++] = p->RemapDataMoment(dm3);
dataMoments8[mIndex++] = p->RemapDataMoment(dm4);
} }
// Store vertices // Store vertices
@ -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
{ {

View file

@ -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();

View file

@ -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