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-*'
- 'modernize-*'
- 'performance-*'
- '-cppcoreguidelines-pro-type-reinterpret-cast'
- '-misc-include-cleaner'
- '-misc-non-private-member-variables-in-classes'
- '-modernize-use-trailing-return-type'

View file

@ -9,14 +9,14 @@ uniform float uDataMomentScale;
uniform bool uCFPEnabled;
flat in uint dataMoment;
flat in uint cfpMoment;
in float dataMoment;
in float cfpMoment;
layout (location = 0) out vec4 fragColor;
void main()
{
float texCoord = float(dataMoment - uDataMomentOffset) / uDataMomentScale;
float texCoord = (dataMoment - float(uDataMomentOffset)) / uDataMomentScale;
if (uCFPEnabled && cfpMoment > 8u)
{

View file

@ -13,8 +13,8 @@ layout (location = 2) in uint aCfpMoment;
uniform mat4 uMVPMatrix;
uniform vec2 uMapScreenCoord;
flat out uint dataMoment;
flat out uint cfpMoment;
out float dataMoment;
out float cfpMoment;
vec2 latLngToScreenCoordinate(in vec2 latLng)
{

View file

@ -322,6 +322,8 @@ MainWindow::MainWindow(QWidget* parent) :
p->mapSettingsGroup_ = new ui::CollapsibleGroup(tr("Map Settings"), this);
p->mapSettingsGroup_->GetContentsLayout()->addWidget(ui->mapStyleLabel);
p->mapSettingsGroup_->GetContentsLayout()->addWidget(ui->mapStyleComboBox);
p->mapSettingsGroup_->GetContentsLayout()->addWidget(
ui->smoothRadarDataCheckBox);
p->mapSettingsGroup_->GetContentsLayout()->addWidget(
ui->trackLocationCheckBox);
ui->radarToolboxScrollAreaContents->layout()->replaceWidget(
@ -642,6 +644,11 @@ void MainWindow::on_actionDumpRadarProductRecords_triggered()
manager::RadarProductManager::DumpRecords();
}
void MainWindow::on_actionRadarWireframe_triggered(bool checked)
{
p->activeMap_->SetRadarWireframeEnabled(checked);
}
void MainWindow::on_actionUserManual_triggered()
{
QDesktopServices::openUrl(QUrl {"https://supercell-wx.readthedocs.io/"});
@ -1085,6 +1092,25 @@ void MainWindowImpl::ConnectOtherSignals()
}
}
});
connect(
mainWindow_->ui->smoothRadarDataCheckBox,
&QCheckBox::checkStateChanged,
mainWindow_,
[this](Qt::CheckState state)
{
const bool smoothingEnabled = (state == Qt::CheckState::Checked);
auto it = std::find(maps_.cbegin(), maps_.cend(), activeMap_);
if (it != maps_.cend())
{
const std::size_t i = std::distance(maps_.cbegin(), it);
settings::MapSettings::Instance().smoothing_enabled(i).StageValue(
smoothingEnabled);
}
// Turn on smoothing
activeMap_->SetSmoothingEnabled(smoothingEnabled);
});
connect(mainWindow_->ui->trackLocationCheckBox,
&QCheckBox::checkStateChanged,
mainWindow_,
@ -1471,6 +1497,13 @@ void MainWindowImpl::UpdateRadarProductSettings()
{
level2SettingsGroup_->setVisible(false);
}
mainWindow_->ui->smoothRadarDataCheckBox->setCheckState(
activeMap_->GetSmoothingEnabled() ? Qt::CheckState::Checked :
Qt::CheckState::Unchecked);
mainWindow_->ui->actionRadarWireframe->setChecked(
activeMap_->GetRadarWireframeEnabled());
}
void MainWindowImpl::UpdateRadarSite()

View file

@ -29,7 +29,7 @@ public:
void keyPressEvent(QKeyEvent* ev) override final;
void keyReleaseEvent(QKeyEvent* ev) override final;
void showEvent(QShowEvent* event) override;
void closeEvent(QCloseEvent *event) override;
void closeEvent(QCloseEvent* event) override;
signals:
void ActiveMapMoved(double latitude, double longitude);
@ -49,6 +49,7 @@ private slots:
void on_actionImGuiDebug_triggered();
void on_actionDumpLayerList_triggered();
void on_actionDumpRadarProductRecords_triggered();
void on_actionRadarWireframe_triggered(bool checked);
void on_actionUserManual_triggered();
void on_actionDiscord_triggered();
void on_actionGitHubRepository_triggered();

View file

@ -97,6 +97,8 @@
<addaction name="separator"/>
<addaction name="actionDumpLayerList"/>
<addaction name="actionDumpRadarProductRecords"/>
<addaction name="separator"/>
<addaction name="actionRadarWireframe"/>
</widget>
<widget class="QMenu" name="menuTools">
<property name="title">
@ -153,8 +155,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>205</width>
<height>701</height>
<width>190</width>
<height>680</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
@ -329,6 +331,13 @@
<item>
<widget class="QComboBox" name="mapStyleComboBox"/>
</item>
<item>
<widget class="QCheckBox" name="smoothRadarDataCheckBox">
<property name="text">
<string>Smooth Radar Data</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="trackLocationCheckBox">
<property name="text">
@ -497,6 +506,14 @@
<string>Location &amp;Marker Manager</string>
</property>
</action>
<action name="actionRadarWireframe">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Radar &amp;Wireframe</string>
</property>
</action>
</widget>
<resources>
<include location="../../../../scwx-qt.qrc"/>

View file

@ -10,7 +10,6 @@
#include <scwx/util/threads.hpp>
#include <scwx/wsr88d/nexrad_file_factory.hpp>
#include <deque>
#include <execution>
#include <mutex>
#include <shared_mutex>
@ -28,6 +27,7 @@
#include <boost/timer/timer.hpp>
#include <fmt/chrono.h>
#include <qmaplibre.hpp>
#include <units/angle.h>
#if defined(_MSC_VER)
# pragma warning(pop)
@ -63,6 +63,8 @@ static constexpr uint32_t NUM_COORIDNATES_1_DEGREE =
static const std::string kDefaultLevel3Product_ {"N0B"};
static constexpr std::size_t kTimerPlaces_ {6u};
static constexpr std::chrono::seconds kFastRetryInterval_ {15};
static constexpr std::chrono::seconds kSlowRetryInterval_ {120};
@ -206,6 +208,13 @@ public:
void UpdateAvailableProductsSync();
void
CalculateCoordinates(const boost::integer_range<std::uint32_t>& radialGates,
const units::angle::degrees<float> radialAngle,
const units::angle::degrees<float> angleOffset,
const float gateRangeOffset,
std::vector<float>& outputCoordinates);
static void
PopulateProductTimes(std::shared_ptr<ProviderManager> providerManager,
RadarProductRecordMap& productRecordMap,
@ -226,10 +235,12 @@ public:
std::size_t cacheLimit_ {6u};
std::vector<float> coordinates0_5Degree_ {};
std::vector<float> coordinates0_5DegreeSmooth_ {};
std::vector<float> coordinates1Degree_ {};
std::vector<float> coordinates1DegreeSmooth_ {};
RadarProductRecordMap level2ProductRecords_ {};
RadarProductRecordList level2ProductRecentRecords_ {};
RadarProductRecordMap level2ProductRecords_ {};
RadarProductRecordList level2ProductRecentRecords_ {};
std::unordered_map<std::string, RadarProductRecordMap>
level3ProductRecordsMap_ {};
std::unordered_map<std::string, RadarProductRecordList>
@ -361,14 +372,29 @@ void RadarProductManager::DumpRecords()
}
const std::vector<float>&
RadarProductManager::coordinates(common::RadialSize radialSize) const
RadarProductManager::coordinates(common::RadialSize radialSize,
bool smoothingEnabled) const
{
switch (radialSize)
{
case common::RadialSize::_0_5Degree:
return p->coordinates0_5Degree_;
if (smoothingEnabled)
{
return p->coordinates0_5DegreeSmooth_;
}
else
{
return p->coordinates0_5Degree_;
}
case common::RadialSize::_1Degree:
return p->coordinates1Degree_;
if (smoothingEnabled)
{
return p->coordinates1DegreeSmooth_;
}
else
{
return p->coordinates1Degree_;
}
default:
throw std::invalid_argument("Invalid radial size");
}
@ -430,50 +456,51 @@ void RadarProductManager::Initialize()
boost::timer::cpu_timer timer;
const GeographicLib::Geodesic& geodesic(
util::GeographicLib::DefaultGeodesic());
const QMapLibre::Coordinate radar(p->radarSite_->latitude(),
p->radarSite_->longitude());
const float gateSize = gate_size();
// Calculate half degree azimuth coordinates
timer.start();
std::vector<float>& coordinates0_5Degree = p->coordinates0_5Degree_;
coordinates0_5Degree.resize(NUM_COORIDNATES_0_5_DEGREE);
auto radialGates0_5Degree =
const auto radialGates0_5Degree =
boost::irange<uint32_t>(0, NUM_RADIAL_GATES_0_5_DEGREE);
std::for_each(
std::execution::par_unseq,
radialGates0_5Degree.begin(),
radialGates0_5Degree.end(),
[&](uint32_t radialGate)
{
const uint16_t gate =
static_cast<uint16_t>(radialGate % common::MAX_DATA_MOMENT_GATES);
const uint16_t radial =
static_cast<uint16_t>(radialGate / common::MAX_DATA_MOMENT_GATES);
// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers): Values are given
// descriptions
p->CalculateCoordinates(
radialGates0_5Degree,
units::angle::degrees<float> {0.5f}, // Radial angle
units::angle::degrees<float> {0.0f}, // Angle offset
// Far end of the first gate is the gate size distance from the radar site
1.0f,
coordinates0_5Degree);
// NOLINTEND(cppcoreguidelines-avoid-magic-numbers)
const float angle = radial * 0.5f; // 0.5 degree radial
const float range = (gate + 1) * gateSize;
const size_t offset = radialGate * 2;
double latitude;
double longitude;
geodesic.Direct(
radar.first, radar.second, angle, range, latitude, longitude);
coordinates0_5Degree[offset] = latitude;
coordinates0_5Degree[offset + 1] = longitude;
});
timer.stop();
logger_->debug("Coordinates (0.5 degree) calculated in {}",
timer.format(6, "%ws"));
timer.format(kTimerPlaces_, "%ws"));
// Calculate half degree smooth azimuth coordinates
timer.start();
std::vector<float>& coordinates0_5DegreeSmooth =
p->coordinates0_5DegreeSmooth_;
coordinates0_5DegreeSmooth.resize(NUM_COORIDNATES_0_5_DEGREE);
// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers): Values are given
// descriptions
p->CalculateCoordinates(radialGates0_5Degree,
units::angle::degrees<float> {0.5f}, // Radial angle
units::angle::degrees<float> {0.25f}, // Angle offset
// Center of the first gate is half the gate size
// distance from the radar site
0.5f,
coordinates0_5DegreeSmooth);
// NOLINTEND(cppcoreguidelines-avoid-magic-numbers)
timer.stop();
logger_->debug("Coordinates (0.5 degree smooth) calculated in {}",
timer.format(kTimerPlaces_, "%ws"));
// Calculate 1 degree azimuth coordinates
timer.start();
@ -481,38 +508,89 @@ void RadarProductManager::Initialize()
coordinates1Degree.resize(NUM_COORIDNATES_1_DEGREE);
auto radialGates1Degree =
const auto radialGates1Degree =
boost::irange<uint32_t>(0, NUM_RADIAL_GATES_1_DEGREE);
// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers): Values are given
// descriptions
p->CalculateCoordinates(
radialGates1Degree,
units::angle::degrees<float> {1.0f}, // Radial angle
units::angle::degrees<float> {0.0f}, // Angle offset
// Far end of the first gate is the gate size distance from the radar site
1.0f,
coordinates1Degree);
// NOLINTEND(cppcoreguidelines-avoid-magic-numbers)
timer.stop();
logger_->debug("Coordinates (1 degree) calculated in {}",
timer.format(kTimerPlaces_, "%ws"));
// Calculate 1 degree smooth azimuth coordinates
timer.start();
std::vector<float>& coordinates1DegreeSmooth = p->coordinates1DegreeSmooth_;
coordinates1DegreeSmooth.resize(NUM_COORIDNATES_1_DEGREE);
// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers): Values are given
// descriptions
p->CalculateCoordinates(radialGates1Degree,
units::angle::degrees<float> {1.0f}, // Radial angle
units::angle::degrees<float> {0.5f}, // Angle offset
// Center of the first gate is half the gate size
// distance from the radar site
0.5f,
coordinates1DegreeSmooth);
// NOLINTEND(cppcoreguidelines-avoid-magic-numbers)
timer.stop();
logger_->debug("Coordinates (1 degree smooth) calculated in {}",
timer.format(kTimerPlaces_, "%ws"));
p->initialized_ = true;
}
void RadarProductManagerImpl::CalculateCoordinates(
const boost::integer_range<std::uint32_t>& radialGates,
const units::angle::degrees<float> radialAngle,
const units::angle::degrees<float> angleOffset,
const float gateRangeOffset,
std::vector<float>& outputCoordinates)
{
const GeographicLib::Geodesic& geodesic(
util::GeographicLib::DefaultGeodesic());
const QMapLibre::Coordinate radar(radarSite_->latitude(),
radarSite_->longitude());
const float gateSize = self_->gate_size();
std::for_each(
std::execution::par_unseq,
radialGates1Degree.begin(),
radialGates1Degree.end(),
radialGates.begin(),
radialGates.end(),
[&](uint32_t radialGate)
{
const uint16_t gate =
static_cast<uint16_t>(radialGate % common::MAX_DATA_MOMENT_GATES);
const uint16_t radial =
static_cast<uint16_t>(radialGate / common::MAX_DATA_MOMENT_GATES);
const auto gate = static_cast<std::uint16_t>(
radialGate % common::MAX_DATA_MOMENT_GATES);
const auto radial = static_cast<std::uint16_t>(
radialGate / common::MAX_DATA_MOMENT_GATES);
const float angle = radial * 1.0f; // 1 degree radial
const float range = (gate + 1) * gateSize;
const size_t offset = radialGate * 2;
const float angle = static_cast<float>(radial) * radialAngle.value() +
angleOffset.value();
const float range =
(static_cast<float>(gate) + gateRangeOffset) * gateSize;
const std::size_t offset = static_cast<std::size_t>(radialGate) * 2;
double latitude;
double longitude;
double latitude = 0.0;
double longitude = 0.0;
geodesic.Direct(
radar.first, radar.second, angle, range, latitude, longitude);
coordinates1Degree[offset] = latitude;
coordinates1Degree[offset + 1] = longitude;
outputCoordinates[offset] = static_cast<float>(latitude);
outputCoordinates[offset + 1] = static_cast<float>(longitude);
});
timer.stop();
logger_->debug("Coordinates (1 degree) calculated in {}",
timer.format(6, "%ws"));
p->initialized_ = true;
}
std::shared_ptr<ProviderManager>

View file

@ -41,11 +41,12 @@ public:
*/
static void DumpRecords();
const std::vector<float>& coordinates(common::RadialSize radialSize) const;
const scwx::util::time_zone* default_time_zone() const;
float gate_size() const;
std::string radar_id() const;
std::shared_ptr<config::RadarSite> radar_site() const;
[[nodiscard]] const std::vector<float>&
coordinates(common::RadialSize radialSize, bool smoothingEnabled) const;
[[nodiscard]] const scwx::util::time_zone* default_time_zone() const;
[[nodiscard]] float gate_size() const;
[[nodiscard]] std::string radar_id() const;
[[nodiscard]] std::shared_ptr<config::RadarSite> radar_site() const;
void Initialize();

View file

@ -9,16 +9,17 @@ namespace map
struct MapSettings
{
explicit MapSettings() : isActive_ {false} {}
~MapSettings() = default;
explicit MapSettings() = default;
~MapSettings() = default;
MapSettings(const MapSettings&) = delete;
MapSettings(const MapSettings&) = delete;
MapSettings& operator=(const MapSettings&) = delete;
MapSettings(MapSettings&&) noexcept = default;
MapSettings(MapSettings&&) noexcept = default;
MapSettings& operator=(MapSettings&&) noexcept = default;
bool isActive_;
bool isActive_ {false};
bool radarWireframeEnabled_ {false};
};
} // namespace map

View file

@ -19,6 +19,7 @@
#include <scwx/qt/model/imgui_context_model.hpp>
#include <scwx/qt/model/layer_model.hpp>
#include <scwx/qt/settings/general_settings.hpp>
#include <scwx/qt/settings/map_settings.hpp>
#include <scwx/qt/settings/palette_settings.hpp>
#include <scwx/qt/util/file.hpp>
#include <scwx/qt/util/maplibre.hpp>
@ -105,13 +106,16 @@ public:
map::AlertLayer::InitializeHandler();
auto& generalSettings = settings::GeneralSettings::Instance();
auto& mapSettings = settings::MapSettings::Instance();
// Initialize context
context_->set_map_provider(
GetMapProvider(generalSettings.map_provider().GetValue()));
context_->set_overlay_product_view(overlayProductView);
// Initialize map data
SetRadarSite(generalSettings.default_radar_site().GetValue());
smoothingEnabled_ = mapSettings.smoothing_enabled(id).GetValue();
// Create ImGui Context
static size_t currentMapId_ {0u};
@ -225,7 +229,7 @@ public:
std::shared_ptr<OverlayLayer> overlayLayer_;
std::shared_ptr<OverlayProductLayer> overlayProductLayer_ {nullptr};
std::shared_ptr<PlacefileLayer> placefileLayer_;
std::shared_ptr<MarkerLayer> markerLayer_;
std::shared_ptr<MarkerLayer> markerLayer_;
std::shared_ptr<ColorTableLayer> colorTableLayer_;
std::shared_ptr<RadarSiteLayer> radarSiteLayer_ {nullptr};
@ -233,6 +237,7 @@ public:
bool autoRefreshEnabled_;
bool autoUpdateEnabled_;
bool smoothingEnabled_ {false};
common::Level2Product selectedLevel2Product_;
@ -727,6 +732,35 @@ std::uint16_t MapWidget::GetVcp() const
}
}
bool MapWidget::GetRadarWireframeEnabled() const
{
return p->context_->settings().radarWireframeEnabled_;
}
void MapWidget::SetRadarWireframeEnabled(bool wireframeEnabled)
{
p->context_->settings().radarWireframeEnabled_ = wireframeEnabled;
QMetaObject::invokeMethod(
this, static_cast<void (QWidget::*)()>(&QWidget::update));
}
bool MapWidget::GetSmoothingEnabled() const
{
return p->smoothingEnabled_;
}
void MapWidget::SetSmoothingEnabled(bool smoothingEnabled)
{
p->smoothingEnabled_ = smoothingEnabled;
auto radarProductView = p->context_->radar_product_view();
if (radarProductView != nullptr)
{
radarProductView->set_smoothing_enabled(smoothingEnabled);
radarProductView->Update();
}
}
void MapWidget::SelectElevation(float elevation)
{
auto radarProductView = p->context_->radar_product_view();
@ -775,6 +809,7 @@ void MapWidget::SelectRadarProduct(common::RadarProductGroup group,
radarProductView = view::RadarProductViewFactory::Create(
group, productName, productCode, p->radarProductManager_);
radarProductView->set_smoothing_enabled(p->smoothingEnabled_);
p->context_->set_radar_product_view(radarProductView);
p->RadarProductViewConnect();

View file

@ -39,16 +39,19 @@ public:
void DumpLayerList() const;
common::Level3ProductCategoryMap GetAvailableLevel3Categories();
float GetElevation() const;
std::vector<float> GetElevationCuts() const;
std::vector<std::string> GetLevel3Products();
std::string GetMapStyle() const;
common::RadarProductGroup GetRadarProductGroup() const;
std::string GetRadarProductName() const;
std::shared_ptr<config::RadarSite> GetRadarSite() const;
std::chrono::system_clock::time_point GetSelectedTime() const;
std::uint16_t GetVcp() const;
[[nodiscard]] common::Level3ProductCategoryMap
GetAvailableLevel3Categories();
[[nodiscard]] float GetElevation() const;
[[nodiscard]] std::vector<float> GetElevationCuts() const;
[[nodiscard]] std::vector<std::string> GetLevel3Products();
[[nodiscard]] std::string GetMapStyle() const;
[[nodiscard]] common::RadarProductGroup GetRadarProductGroup() const;
[[nodiscard]] std::string GetRadarProductName() const;
[[nodiscard]] std::shared_ptr<config::RadarSite> GetRadarSite() const;
[[nodiscard]] bool GetRadarWireframeEnabled() const;
[[nodiscard]] std::chrono::system_clock::time_point GetSelectedTime() const;
[[nodiscard]] bool GetSmoothingEnabled() const;
[[nodiscard]] std::uint16_t GetVcp() const;
void SelectElevation(float elevation);
@ -117,6 +120,8 @@ public:
double pitch);
void SetInitialMapStyle(const std::string& styleName);
void SetMapStyle(const std::string& styleName);
void SetRadarWireframeEnabled(bool enabled);
void SetSmoothingEnabled(bool enabled);
/**
* Updates the coordinates associated with mouse movement from another map.

View file

@ -1,12 +1,11 @@
#include <scwx/qt/map/radar_product_layer.hpp>
#include <scwx/qt/map/map_settings.hpp>
#include <scwx/qt/gl/shader_program.hpp>
#include <scwx/qt/util/maplibre.hpp>
#include <scwx/qt/util/tooltip.hpp>
#include <scwx/qt/view/radar_product_view.hpp>
#include <scwx/util/logger.hpp>
#include <execution>
#if defined(_MSC_VER)
# pragma warning(push, 0)
#endif
@ -267,6 +266,13 @@ void RadarProductLayer::Render(
// Set OpenGL blend mode for transparency
gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
const bool wireframeEnabled = context()->settings().radarWireframeEnabled_;
if (wireframeEnabled)
{
// Set polygon mode to draw wireframe
gl.glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
if (p->colorTableNeedsUpdate_)
{
UpdateColorTable();
@ -303,6 +309,12 @@ void RadarProductLayer::Render(
gl.glBindVertexArray(p->vao_);
gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_);
if (wireframeEnabled)
{
// Restore polygon mode to default
gl.glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
SCWX_GL_CHECK_ERROR();
}

View file

@ -6,7 +6,6 @@
#include <scwx/util/logger.hpp>
#include <array>
#include <execution>
#include <boost/json.hpp>
@ -27,12 +26,15 @@ static const std::string kMapStyleName_ {"map_style"};
static const std::string kRadarSiteName_ {"radar_site"};
static const std::string kRadarProductGroupName_ {"radar_product_group"};
static const std::string kRadarProductName_ {"radar_product"};
static const std::string kSmoothingEnabledName_ {"smoothing_enabled"};
static const std::string kDefaultMapStyle_ {"?"};
static const std::string kDefaultMapStyle_ {"?"};
static const std::string kDefaultRadarProductGroupString_ = "L3";
static const std::array<std::string, kCount_> kDefaultRadarProduct_ {
"N0B", "N0G", "N0C", "N0X"};
static constexpr bool kDefaultSmoothingEnabled_ {false};
class MapSettings::Impl
{
public:
@ -43,26 +45,28 @@ public:
SettingsVariable<std::string> radarProductGroup_ {
kRadarProductGroupName_};
SettingsVariable<std::string> radarProduct_ {kRadarProductName_};
SettingsVariable<bool> smoothingEnabled_ {kSmoothingEnabledName_};
};
explicit Impl()
{
for (std::size_t i = 0; i < kCount_; i++)
{
map_[i].mapStyle_.SetDefault(kDefaultMapStyle_);
map_[i].radarSite_.SetDefault(kDefaultRadarSite_);
map_[i].radarProductGroup_.SetDefault(
map_.at(i).mapStyle_.SetDefault(kDefaultMapStyle_);
map_.at(i).radarSite_.SetDefault(kDefaultRadarSite_);
map_.at(i).radarProductGroup_.SetDefault(
kDefaultRadarProductGroupString_);
map_[i].radarProduct_.SetDefault(kDefaultRadarProduct_[i]);
map_.at(i).radarProduct_.SetDefault(kDefaultRadarProduct_.at(i));
map_.at(i).smoothingEnabled_.SetDefault(kDefaultSmoothingEnabled_);
map_[i].radarSite_.SetValidator(
map_.at(i).radarSite_.SetValidator(
[](const std::string& value)
{
// Radar site must exist
return config::RadarSite::Get(value) != nullptr;
});
map_[i].radarProductGroup_.SetValidator(
map_.at(i).radarProductGroup_.SetValidator(
[](const std::string& value)
{
// Radar product group must be valid
@ -71,12 +75,12 @@ public:
return radarProductGroup != common::RadarProductGroup::Unknown;
});
map_[i].radarProduct_.SetValidator(
map_.at(i).radarProduct_.SetValidator(
[this, i](const std::string& value)
{
common::RadarProductGroup radarProductGroup =
common::GetRadarProductGroup(
map_[i].radarProductGroup_.GetValue());
map_.at(i).radarProductGroup_.GetValue());
if (radarProductGroup == common::RadarProductGroup::Level2)
{
@ -92,10 +96,11 @@ public:
});
variables_.insert(variables_.cend(),
{&map_[i].mapStyle_,
&map_[i].radarSite_,
&map_[i].radarProductGroup_,
&map_[i].radarProduct_});
{&map_.at(i).mapStyle_,
&map_.at(i).radarSite_,
&map_.at(i).radarProductGroup_,
&map_.at(i).radarProduct_,
&map_.at(i).smoothingEnabled_});
}
}
@ -103,10 +108,11 @@ public:
void SetDefaults(std::size_t i)
{
map_[i].mapStyle_.SetValueToDefault();
map_[i].radarSite_.SetValueToDefault();
map_[i].radarProductGroup_.SetValueToDefault();
map_[i].radarProduct_.SetValueToDefault();
map_.at(i).mapStyle_.SetValueToDefault();
map_.at(i).radarSite_.SetValueToDefault();
map_.at(i).radarProductGroup_.SetValueToDefault();
map_.at(i).radarProduct_.SetValueToDefault();
map_.at(i).smoothingEnabled_.SetValueToDefault();
}
friend void tag_invoke(boost::json::value_from_tag,
@ -116,7 +122,8 @@ public:
jv = {{kMapStyleName_, data.mapStyle_.GetValue()},
{kRadarSiteName_, data.radarSite_.GetValue()},
{kRadarProductGroupName_, data.radarProductGroup_.GetValue()},
{kRadarProductName_, data.radarProduct_.GetValue()}};
{kRadarProductName_, data.radarProduct_.GetValue()},
{kSmoothingEnabledName_, data.smoothingEnabled_.GetValue()}};
}
friend bool operator==(const MapData& lhs, const MapData& rhs)
@ -124,7 +131,8 @@ public:
return (lhs.mapStyle_ == rhs.mapStyle_ && //
lhs.radarSite_ == rhs.radarSite_ &&
lhs.radarProductGroup_ == rhs.radarProductGroup_ &&
lhs.radarProduct_ == rhs.radarProduct_);
lhs.radarProduct_ == rhs.radarProduct_ &&
lhs.smoothingEnabled_ == rhs.smoothingEnabled_);
}
std::array<MapData, kCount_> map_ {};
@ -149,25 +157,29 @@ std::size_t MapSettings::count() const
return kCount_;
}
SettingsVariable<std::string>& MapSettings::map_style(std::size_t i) const
SettingsVariable<std::string>& MapSettings::map_style(std::size_t i)
{
return p->map_[i].mapStyle_;
return p->map_.at(i).mapStyle_;
}
SettingsVariable<std::string>& MapSettings::radar_site(std::size_t i) const
SettingsVariable<std::string>& MapSettings::radar_site(std::size_t i)
{
return p->map_[i].radarSite_;
return p->map_.at(i).radarSite_;
}
SettingsVariable<std::string>&
MapSettings::radar_product_group(std::size_t i) const
SettingsVariable<std::string>& MapSettings::radar_product_group(std::size_t i)
{
return p->map_[i].radarProductGroup_;
return p->map_.at(i).radarProductGroup_;
}
SettingsVariable<std::string>& MapSettings::radar_product(std::size_t i) const
SettingsVariable<std::string>& MapSettings::radar_product(std::size_t i)
{
return p->map_[i].radarProduct_;
return p->map_.at(i).radarProduct_;
}
SettingsVariable<bool>& MapSettings::smoothing_enabled(std::size_t i)
{
return p->map_.at(i).smoothingEnabled_;
}
bool MapSettings::Shutdown()
@ -177,9 +189,10 @@ bool MapSettings::Shutdown()
// Commit settings that are managed separate from the settings dialog
for (std::size_t i = 0; i < kCount_; ++i)
{
Impl::MapData& mapRecordSettings = p->map_[i];
Impl::MapData& mapRecordSettings = p->map_.at(i);
dataChanged |= mapRecordSettings.mapStyle_.Commit();
dataChanged |= mapRecordSettings.smoothingEnabled_.Commit();
}
return dataChanged;
@ -200,13 +213,15 @@ bool MapSettings::ReadJson(const boost::json::object& json)
if (i < mapArray.size() && mapArray.at(i).is_object())
{
const boost::json::object& mapRecord = mapArray.at(i).as_object();
Impl::MapData& mapRecordSettings = p->map_[i];
Impl::MapData& mapRecordSettings = p->map_.at(i);
// Load JSON Elements
validated &= mapRecordSettings.mapStyle_.ReadValue(mapRecord);
validated &= mapRecordSettings.radarSite_.ReadValue(mapRecord);
validated &=
mapRecordSettings.radarProductGroup_.ReadValue(mapRecord);
validated &=
mapRecordSettings.smoothingEnabled_.ReadValue(mapRecord);
bool productValidated =
mapRecordSettings.radarProduct_.ReadValue(mapRecord);

View file

@ -26,10 +26,11 @@ public:
MapSettings& operator=(MapSettings&&) noexcept;
std::size_t count() const;
SettingsVariable<std::string>& map_style(std::size_t i) const;
SettingsVariable<std::string>& radar_site(std::size_t i) const;
SettingsVariable<std::string>& radar_product_group(std::size_t i) const;
SettingsVariable<std::string>& radar_product(std::size_t i) const;
SettingsVariable<std::string>& map_style(std::size_t i);
SettingsVariable<std::string>& radar_site(std::size_t i);
SettingsVariable<std::string>& radar_product_group(std::size_t i);
SettingsVariable<std::string>& radar_product(std::size_t i);
SettingsVariable<bool>& smoothing_enabled(std::size_t i);
bool Shutdown();

View file

@ -15,12 +15,15 @@ class ProductSettings::Impl
public:
explicit Impl()
{
showSmoothedRangeFolding_.SetDefault(false);
stiForecastEnabled_.SetDefault(true);
stiPastEnabled_.SetDefault(true);
}
~Impl() {}
SettingsVariable<bool> showSmoothedRangeFolding_ {
"show_smoothed_range_folding"};
SettingsVariable<bool> stiForecastEnabled_ {"sti_forecast_enabled"};
SettingsVariable<bool> stiPastEnabled_ {"sti_past_enabled"};
};
@ -28,7 +31,9 @@ public:
ProductSettings::ProductSettings() :
SettingsCategory("product"), p(std::make_unique<Impl>())
{
RegisterVariables({&p->stiForecastEnabled_, &p->stiPastEnabled_});
RegisterVariables({&p->showSmoothedRangeFolding_,
&p->stiForecastEnabled_,
&p->stiPastEnabled_});
SetDefaults();
}
ProductSettings::~ProductSettings() = default;
@ -37,12 +42,17 @@ ProductSettings::ProductSettings(ProductSettings&&) noexcept = default;
ProductSettings&
ProductSettings::operator=(ProductSettings&&) noexcept = default;
SettingsVariable<bool>& ProductSettings::sti_forecast_enabled() const
SettingsVariable<bool>& ProductSettings::show_smoothed_range_folding()
{
return p->showSmoothedRangeFolding_;
}
SettingsVariable<bool>& ProductSettings::sti_forecast_enabled()
{
return p->stiForecastEnabled_;
}
SettingsVariable<bool>& ProductSettings::sti_past_enabled() const
SettingsVariable<bool>& ProductSettings::sti_past_enabled()
{
return p->stiPastEnabled_;
}
@ -66,7 +76,9 @@ ProductSettings& ProductSettings::Instance()
bool operator==(const ProductSettings& lhs, const ProductSettings& rhs)
{
return (lhs.p->stiForecastEnabled_ == rhs.p->stiForecastEnabled_ &&
return (lhs.p->showSmoothedRangeFolding_ ==
rhs.p->showSmoothedRangeFolding_ &&
lhs.p->stiForecastEnabled_ == rhs.p->stiForecastEnabled_ &&
lhs.p->stiPastEnabled_ == rhs.p->stiPastEnabled_);
}

View file

@ -25,8 +25,9 @@ public:
ProductSettings(ProductSettings&&) noexcept;
ProductSettings& operator=(ProductSettings&&) noexcept;
SettingsVariable<bool>& sti_forecast_enabled() const;
SettingsVariable<bool>& sti_past_enabled() const;
SettingsVariable<bool>& show_smoothed_range_folding();
SettingsVariable<bool>& sti_forecast_enabled();
SettingsVariable<bool>& sti_past_enabled();
static ProductSettings& Instance();

View file

@ -12,6 +12,7 @@
#include <scwx/qt/settings/audio_settings.hpp>
#include <scwx/qt/settings/general_settings.hpp>
#include <scwx/qt/settings/palette_settings.hpp>
#include <scwx/qt/settings/product_settings.hpp>
#include <scwx/qt/settings/settings_interface.hpp>
#include <scwx/qt/settings/text_settings.hpp>
#include <scwx/qt/settings/unit_settings.hpp>
@ -136,6 +137,7 @@ public:
&showMapAttribution_,
&showMapCenter_,
&showMapLogo_,
&showSmoothedRangeFolding_,
&updateNotificationsEnabled_,
&cursorIconAlwaysOn_,
&debugEnabled_,
@ -251,6 +253,7 @@ public:
settings::SettingsInterface<bool> showMapAttribution_ {};
settings::SettingsInterface<bool> showMapCenter_ {};
settings::SettingsInterface<bool> showMapLogo_ {};
settings::SettingsInterface<bool> showSmoothedRangeFolding_ {};
settings::SettingsInterface<bool> updateNotificationsEnabled_ {};
settings::SettingsInterface<bool> cursorIconAlwaysOn_ {};
settings::SettingsInterface<bool> debugEnabled_ {};
@ -527,21 +530,22 @@ void SettingsDialogImpl::SetupGeneralTab()
{
settings::GeneralSettings& generalSettings =
settings::GeneralSettings::Instance();
settings::ProductSettings& productSettings =
settings::ProductSettings::Instance();
QObject::connect(
self_->ui->themeComboBox,
&QComboBox::currentTextChanged,
self_,
[this](const QString& text)
{
types::UiStyle style = types::GetUiStyle(text.toStdString());
bool themeFileEnabled = style == types::UiStyle::FusionCustom;
self_->ui->themeComboBox,
&QComboBox::currentTextChanged,
self_,
[this](const QString& text)
{
const types::UiStyle style = types::GetUiStyle(text.toStdString());
const bool themeFileEnabled = style == types::UiStyle::FusionCustom;
self_->ui->themeFileLineEdit->setEnabled(themeFileEnabled);
self_->ui->themeFileSelectButton->setEnabled(themeFileEnabled);
self_->ui->resetThemeFileButton->setEnabled(themeFileEnabled);
});
self_->ui->themeFileLineEdit->setEnabled(themeFileEnabled);
self_->ui->themeFileSelectButton->setEnabled(themeFileEnabled);
self_->ui->resetThemeFileButton->setEnabled(themeFileEnabled);
});
theme_.SetSettingsVariable(generalSettings.theme());
SCWX_SETTINGS_COMBO_BOX(theme_,
@ -759,6 +763,11 @@ void SettingsDialogImpl::SetupGeneralTab()
showMapLogo_.SetSettingsVariable(generalSettings.show_map_logo());
showMapLogo_.SetEditWidget(self_->ui->showMapLogoCheckBox);
showSmoothedRangeFolding_.SetSettingsVariable(
productSettings.show_smoothed_range_folding());
showSmoothedRangeFolding_.SetEditWidget(
self_->ui->showSmoothedRangeFoldingCheckBox);
updateNotificationsEnabled_.SetSettingsVariable(
generalSettings.update_notifications_enabled());
updateNotificationsEnabled_.SetEditWidget(

View file

@ -135,9 +135,9 @@
<property name="geometry">
<rect>
<x>0</x>
<y>-246</y>
<width>511</width>
<height>703</height>
<y>-272</y>
<width>513</width>
<height>702</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@ -562,6 +562,19 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cursorIconAlwaysOnCheckBox">
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="toolTip">
<string/>
</property>
<property name="text">
<string>Multi-Pane Cursor Marker Always On</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="showMapAttributionCheckBox">
<property name="text">
@ -584,22 +597,16 @@
</widget>
</item>
<item>
<widget class="QCheckBox" name="enableUpdateNotificationsCheckBox">
<widget class="QCheckBox" name="showSmoothedRangeFoldingCheckBox">
<property name="text">
<string>Update Notifications Enabled</string>
<string>Show Range Folding when Smoothing Radar Data</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cursorIconAlwaysOnCheckBox">
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="toolTip">
<string/>
</property>
<widget class="QCheckBox" name="enableUpdateNotificationsCheckBox">
<property name="text">
<string>Multi-Pane Cursor Marker Always On</string>
<string>Update Notifications Enabled</string>
</property>
</widget>
</item>

View file

@ -25,6 +25,11 @@ static constexpr std::uint32_t kMaxRadialGates_ =
common::MAX_0_5_DEGREE_RADIALS * common::MAX_DATA_MOMENT_GATES;
static constexpr std::uint32_t kMaxCoordinates_ = kMaxRadialGates_ * 2u;
static constexpr std::uint8_t kDataWordSize8_ = 8u;
static constexpr std::size_t kVerticesPerGate_ = 6u;
static constexpr std::size_t kVerticesPerOriginGate_ = 3u;
static constexpr uint16_t RANGE_FOLDED = 1u;
static constexpr uint32_t VERTICES_PER_BIN = 6u;
static constexpr uint32_t VALUES_PER_VERTEX = 2u;
@ -53,11 +58,10 @@ static const std::unordered_map<common::Level2Product, std::string>
{common::Level2Product::CorrelationCoefficient, "%"},
{common::Level2Product::ClutterFilterPowerRemoved, "dB"}};
class Level2ProductViewImpl
class Level2ProductView::Impl
{
public:
explicit Level2ProductViewImpl(Level2ProductView* self,
common::Level2Product product) :
explicit Impl(Level2ProductView* self, common::Level2Product product) :
self_ {self},
product_ {product},
selectedElevation_ {0.0f},
@ -94,7 +98,7 @@ public:
UpdateOtherUnits(unitSettings.other_units().GetValue());
UpdateSpeedUnits(unitSettings.speed_units().GetValue());
}
~Level2ProductViewImpl()
~Impl()
{
auto& unitSettings = settings::UnitSettings::Instance();
@ -106,23 +110,36 @@ public:
threadPool_.join();
};
Impl(const Impl&) = delete;
Impl& operator=(const Impl&) = delete;
Impl(Impl&&) noexcept = delete;
Impl& operator=(Impl&&) noexcept = delete;
void ComputeCoordinates(
const std::shared_ptr<wsr88d::rda::ElevationScan>& radarData);
const std::shared_ptr<wsr88d::rda::ElevationScan>& radarData,
bool smoothingEnabled);
void SetProduct(const std::string& productName);
void SetProduct(common::Level2Product product);
void UpdateOtherUnits(const std::string& name);
void UpdateSpeedUnits(const std::string& name);
void ComputeEdgeValue();
template<typename T>
[[nodiscard]] inline T RemapDataMoment(T dataMoment) const;
static bool IsRadarDataIncomplete(
const std::shared_ptr<const wsr88d::rda::ElevationScan>& radarData);
static units::degrees<float> NormalizeAngle(units::degrees<float> angle);
Level2ProductView* self_;
boost::asio::thread_pool threadPool_ {1u};
common::Level2Product product_;
wsr88d::rda::DataBlockType dataBlockType_;
wsr88d::rda::DataBlockType dataBlockType_ {
wsr88d::rda::DataBlockType::Unknown};
float selectedElevation_;
@ -130,11 +147,17 @@ public:
std::shared_ptr<wsr88d::rda::GenericRadarData::MomentDataBlock>
momentDataBlock0_;
bool lastShowSmoothedRangeFolding_ {false};
bool lastSmoothingEnabled_ {false};
std::vector<float> coordinates_ {};
std::vector<float> vertices_ {};
std::vector<uint8_t> dataMoments8_ {};
std::vector<uint16_t> dataMoments16_ {};
std::vector<uint8_t> cfpMoments_ {};
std::uint16_t edgeValue_ {};
bool showSmoothedRangeFolding_ {false};
float latitude_;
float longitude_;
@ -164,7 +187,7 @@ Level2ProductView::Level2ProductView(
common::Level2Product product,
std::shared_ptr<manager::RadarProductManager> radarProductManager) :
RadarProductView(radarProductManager),
p(std::make_unique<Level2ProductViewImpl>(this, product))
p(std::make_unique<Impl>(this, product))
{
ConnectRadarProductManager();
}
@ -379,12 +402,12 @@ void Level2ProductView::SelectProduct(const std::string& productName)
p->SetProduct(productName);
}
void Level2ProductViewImpl::SetProduct(const std::string& productName)
void Level2ProductView::Impl::SetProduct(const std::string& productName)
{
SetProduct(common::GetLevel2Product(productName));
}
void Level2ProductViewImpl::SetProduct(common::Level2Product product)
void Level2ProductView::Impl::SetProduct(common::Level2Product product)
{
product_ = product;
@ -401,12 +424,12 @@ void Level2ProductViewImpl::SetProduct(common::Level2Product product)
}
}
void Level2ProductViewImpl::UpdateOtherUnits(const std::string& name)
void Level2ProductView::Impl::UpdateOtherUnits(const std::string& name)
{
otherUnits_ = types::GetOtherUnitsFromName(name);
}
void Level2ProductViewImpl::UpdateSpeedUnits(const std::string& name)
void Level2ProductView::Impl::UpdateSpeedUnits(const std::string& name)
{
speedUnits_ = types::GetSpeedUnitsFromName(name);
}
@ -511,6 +534,9 @@ void Level2ProductView::ComputeSweep()
std::shared_ptr<manager::RadarProductManager> radarProductManager =
radar_product_manager();
const bool smoothingEnabled = smoothing_enabled();
p->showSmoothedRangeFolding_ = show_smoothed_range_folding();
const bool& showSmoothedRangeFolding = p->showSmoothedRangeFolding_;
std::shared_ptr<wsr88d::rda::ElevationScan> radarData;
std::chrono::system_clock::time_point requestedTime {selected_time()};
@ -523,12 +549,18 @@ void Level2ProductView::ComputeSweep()
Q_EMIT SweepNotComputed(types::NoUpdateReason::NotLoaded);
return;
}
if (radarData == p->elevationScan_)
if (radarData == p->elevationScan_ &&
smoothingEnabled == p->lastSmoothingEnabled_ &&
(showSmoothedRangeFolding == p->lastShowSmoothedRangeFolding_ ||
!smoothingEnabled))
{
Q_EMIT SweepNotComputed(types::NoUpdateReason::NoChange);
return;
}
p->lastShowSmoothedRangeFolding_ = showSmoothedRangeFolding;
p->lastSmoothingEnabled_ = smoothingEnabled;
logger_->debug("Computing Sweep");
std::size_t radials = radarData->crbegin()->first + 1;
@ -536,8 +568,7 @@ void Level2ProductView::ComputeSweep()
// When there is missing data, insert another empty vertex radial at the end
// to avoid stretching
const bool isRadarDataIncomplete =
Level2ProductViewImpl::IsRadarDataIncomplete(radarData);
const bool isRadarDataIncomplete = Impl::IsRadarDataIncomplete(radarData);
if (isRadarDataIncomplete)
{
++vertexRadials;
@ -548,7 +579,7 @@ void Level2ProductView::ComputeSweep()
vertexRadials =
std::min<std::size_t>(vertexRadials, common::MAX_0_5_DEGREE_RADIALS);
p->ComputeCoordinates(radarData);
p->ComputeCoordinates(radarData, smoothingEnabled);
const std::vector<float>& coordinates = p->coordinates_;
@ -627,11 +658,20 @@ void Level2ProductView::ComputeSweep()
// Start radial is always 0, as coordinates are calculated for each sweep
constexpr std::uint16_t startRadial = 0u;
for (auto& radialPair : *radarData)
// For most products other than reflectivity, the edge should not go to the
// bottom of the color table
if (smoothingEnabled)
{
p->ComputeEdgeValue();
}
for (auto it = radarData->cbegin(); it != radarData->cend(); ++it)
{
const auto& radialPair = *it;
std::uint16_t radial = radialPair.first;
auto& radialData = radialPair.second;
auto momentData = radialData->moment_data_block(p->dataBlockType_);
const auto& radialData = radialPair.second;
const std::shared_ptr<wsr88d::rda::GenericRadarData::MomentDataBlock>
momentData = radialData->moment_data_block(p->dataBlockType_);
if (momentData0->data_word_size() != momentData->data_word_size())
{
@ -653,7 +693,7 @@ void Level2ProductView::ComputeSweep()
std::max<std::int32_t>(1, dataMomentInterval / gateSizeMeters);
// Compute gate range [startGate, endGate)
const std::int32_t startGate =
std::int32_t startGate =
(dataMomentRange - dataMomentIntervalH) / gateSizeMeters;
const std::int32_t numberOfDataMomentGates =
std::min<std::int32_t>(momentData->number_of_data_moment_gates(),
@ -662,9 +702,19 @@ void Level2ProductView::ComputeSweep()
startGate + numberOfDataMomentGates * gateSize,
static_cast<std::int32_t>(common::MAX_DATA_MOMENT_GATES));
const std::uint8_t* dataMomentsArray8 = nullptr;
const std::uint16_t* dataMomentsArray16 = nullptr;
const std::uint8_t* cfpMomentsArray = nullptr;
if (smoothingEnabled)
{
// If smoothing is enabled, the start gate is incremented by one, as we
// are skipping the radar site origin. The end gate is unaffected, as
// we need to draw one less data point.
++startGate;
}
const std::uint8_t* dataMomentsArray8 = nullptr;
const std::uint16_t* dataMomentsArray16 = nullptr;
const std::uint8_t* nextDataMomentsArray8 = nullptr;
const std::uint16_t* nextDataMomentsArray16 = nullptr;
const std::uint8_t* cfpMomentsArray = nullptr;
if (momentData->data_word_size() == 8)
{
@ -684,6 +734,45 @@ void Level2ProductView::ComputeSweep()
->data_moments());
}
std::shared_ptr<wsr88d::rda::GenericRadarData::MomentDataBlock>
nextMomentData = nullptr;
std::int32_t numberOfNextDataMomentGates = 0;
if (smoothingEnabled)
{
// Smoothing requires the next radial pair as well
auto nextIt = std::next(it);
if (nextIt == radarData->cend())
{
nextIt = radarData->cbegin();
}
const auto& nextRadialPair = *(nextIt);
const auto& nextRadialData = nextRadialPair.second;
nextMomentData = nextRadialData->moment_data_block(p->dataBlockType_);
if (momentData->data_word_size() != nextMomentData->data_word_size())
{
// Data should be consistent between radials
logger_->warn("Invalid data moment size");
continue;
}
if (nextMomentData->data_word_size() == kDataWordSize8_)
{
nextDataMomentsArray8 = reinterpret_cast<const std::uint8_t*>(
nextMomentData->data_moments());
}
else
{
nextDataMomentsArray16 = reinterpret_cast<const std::uint16_t*>(
nextMomentData->data_moments());
}
numberOfNextDataMomentGates = std::min<std::int32_t>(
nextMomentData->number_of_data_moment_gates(),
static_cast<std::int32_t>(gates));
}
for (std::int32_t gate = startGate, i = 0; gate + gateSize <= endGate;
gate += gateSize, ++i)
{
@ -692,57 +781,172 @@ void Level2ProductView::ComputeSweep()
continue;
}
std::size_t vertexCount = (gate > 0) ? 6 : 3;
const std::size_t vertexCount =
(gate > 0) ? kVerticesPerGate_ : kVerticesPerOriginGate_;
// Allow pointer arithmetic here, as bounds have already been checked
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
// Store data moment value
if (dataMomentsArray8 != nullptr)
{
std::uint8_t dataValue = dataMomentsArray8[i];
if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
if (!smoothingEnabled)
{
continue;
}
for (std::size_t m = 0; m < vertexCount; m++)
{
dataMoments8[mIndex++] = dataMomentsArray8[i];
if (cfpMomentsArray != nullptr)
const std::uint8_t& dataValue = dataMomentsArray8[i];
if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
{
cfpMoments[mIndex - 1] = cfpMomentsArray[i];
continue;
}
for (std::size_t m = 0; m < vertexCount; m++)
{
dataMoments8[mIndex++] = dataValue;
if (cfpMomentsArray != nullptr)
{
cfpMoments[mIndex - 1] = cfpMomentsArray[i];
}
}
}
else if (gate > 0)
{
// Validate indices are all in range
if (i + 1 >= numberOfDataMomentGates ||
i + 1 >= numberOfNextDataMomentGates)
{
continue;
}
const std::uint8_t& dm1 = dataMomentsArray8[i];
const std::uint8_t& dm2 = dataMomentsArray8[i + 1];
const std::uint8_t& dm3 = nextDataMomentsArray8[i];
const std::uint8_t& dm4 = nextDataMomentsArray8[i + 1];
if ((!showSmoothedRangeFolding && //
(dm1 < snrThreshold || dm1 == RANGE_FOLDED) &&
(dm2 < snrThreshold || dm2 == RANGE_FOLDED) &&
(dm3 < snrThreshold || dm3 == RANGE_FOLDED) &&
(dm4 < snrThreshold || dm4 == RANGE_FOLDED)) ||
(showSmoothedRangeFolding && //
dm1 < snrThreshold && dm1 != RANGE_FOLDED &&
dm2 < snrThreshold && dm2 != RANGE_FOLDED &&
dm3 < snrThreshold && dm3 != RANGE_FOLDED &&
dm4 < snrThreshold && dm4 != RANGE_FOLDED))
{
// Skip only if all data moments are hidden
continue;
}
// The order must match the store vertices section below
dataMoments8[mIndex++] = p->RemapDataMoment(dm1);
dataMoments8[mIndex++] = p->RemapDataMoment(dm2);
dataMoments8[mIndex++] = p->RemapDataMoment(dm4);
dataMoments8[mIndex++] = p->RemapDataMoment(dm1);
dataMoments8[mIndex++] = p->RemapDataMoment(dm3);
dataMoments8[mIndex++] = p->RemapDataMoment(dm4);
// cfpMoments is unused, so not populated here
}
else
{
// If smoothing is enabled, gate should never start at zero
// (radar site origin)
logger_->error(
"Smoothing enabled, gate should not start at zero");
continue;
}
}
else
{
std::uint16_t dataValue = dataMomentsArray16[i];
if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
if (!smoothingEnabled)
{
const std::uint16_t& dataValue = dataMomentsArray16[i];
if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
{
continue;
}
for (std::size_t m = 0; m < vertexCount; m++)
{
dataMoments16[mIndex++] = dataValue;
}
}
else if (gate > 0)
{
// Validate indices are all in range
if (i + 1 >= numberOfDataMomentGates ||
i + 1 >= numberOfNextDataMomentGates)
{
continue;
}
const std::uint16_t& dm1 = dataMomentsArray16[i];
const std::uint16_t& dm2 = dataMomentsArray16[i + 1];
const std::uint16_t& dm3 = nextDataMomentsArray16[i];
const std::uint16_t& dm4 = nextDataMomentsArray16[i + 1];
if ((!showSmoothedRangeFolding && //
(dm1 < snrThreshold || dm1 == RANGE_FOLDED) &&
(dm2 < snrThreshold || dm2 == RANGE_FOLDED) &&
(dm3 < snrThreshold || dm3 == RANGE_FOLDED) &&
(dm4 < snrThreshold || dm4 == RANGE_FOLDED)) ||
(showSmoothedRangeFolding && //
dm1 < snrThreshold && dm1 != RANGE_FOLDED &&
dm2 < snrThreshold && dm2 != RANGE_FOLDED &&
dm3 < snrThreshold && dm3 != RANGE_FOLDED &&
dm4 < snrThreshold && dm4 != RANGE_FOLDED))
{
// Skip only if all data moments are hidden
continue;
}
// The order must match the store vertices section below
dataMoments16[mIndex++] = p->RemapDataMoment(dm1);
dataMoments16[mIndex++] = p->RemapDataMoment(dm2);
dataMoments16[mIndex++] = p->RemapDataMoment(dm4);
dataMoments16[mIndex++] = p->RemapDataMoment(dm1);
dataMoments16[mIndex++] = p->RemapDataMoment(dm3);
dataMoments16[mIndex++] = p->RemapDataMoment(dm4);
// cfpMoments is unused, so not populated here
}
else
{
// If smoothing is enabled, gate should never start at zero
// (radar site origin)
continue;
}
for (std::size_t m = 0; m < vertexCount; m++)
{
dataMoments16[mIndex++] = dataMomentsArray16[i];
}
}
// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
// Store vertices
if (gate > 0)
{
// Draw two triangles per gate
//
// 2 +---+ 4
// | /|
// | / |
// |/ |
// 1 +---+ 3
const std::uint16_t baseCoord = gate - 1;
std::size_t offset1 = ((startRadial + radial) % vertexRadials *
common::MAX_DATA_MOMENT_GATES +
baseCoord) *
2;
std::size_t offset2 = offset1 + gateSize * 2;
std::size_t offset3 =
const std::size_t offset1 =
((startRadial + radial) % vertexRadials *
common::MAX_DATA_MOMENT_GATES +
baseCoord) *
2;
const std::size_t offset2 =
offset1 + static_cast<std::size_t>(gateSize) * 2;
const std::size_t offset3 =
(((startRadial + radial + 1) % vertexRadials) *
common::MAX_DATA_MOMENT_GATES +
baseCoord) *
2;
std::size_t offset4 = offset3 + gateSize * 2;
const std::size_t offset4 =
offset3 + static_cast<std::size_t>(gateSize) * 2;
vertices[vIndex++] = coordinates[offset1];
vertices[vIndex++] = coordinates[offset1 + 1];
@ -750,19 +954,17 @@ void Level2ProductView::ComputeSweep()
vertices[vIndex++] = coordinates[offset2];
vertices[vIndex++] = coordinates[offset2 + 1];
vertices[vIndex++] = coordinates[offset3];
vertices[vIndex++] = coordinates[offset3 + 1];
vertices[vIndex++] = coordinates[offset4];
vertices[vIndex++] = coordinates[offset4 + 1];
vertices[vIndex++] = coordinates[offset1];
vertices[vIndex++] = coordinates[offset1 + 1];
vertices[vIndex++] = coordinates[offset3];
vertices[vIndex++] = coordinates[offset3 + 1];
vertices[vIndex++] = coordinates[offset4];
vertices[vIndex++] = coordinates[offset4 + 1];
vertices[vIndex++] = coordinates[offset2];
vertices[vIndex++] = coordinates[offset2 + 1];
vertexCount = 6;
}
else
{
@ -786,8 +988,6 @@ void Level2ProductView::ComputeSweep()
vertices[vIndex++] = coordinates[offset2];
vertices[vIndex++] = coordinates[offset2 + 1];
vertexCount = 3;
}
}
}
@ -819,8 +1019,50 @@ void Level2ProductView::ComputeSweep()
Q_EMIT SweepComputed();
}
void Level2ProductViewImpl::ComputeCoordinates(
const std::shared_ptr<wsr88d::rda::ElevationScan>& radarData)
void Level2ProductView::Impl::ComputeEdgeValue()
{
const float offset = momentDataBlock0_->offset();
switch (dataBlockType_)
{
case wsr88d::rda::DataBlockType::MomentVel:
case wsr88d::rda::DataBlockType::MomentZdr:
edgeValue_ = static_cast<std::uint16_t>(offset);
break;
case wsr88d::rda::DataBlockType::MomentSw:
case wsr88d::rda::DataBlockType::MomentPhi:
edgeValue_ = 2;
break;
case wsr88d::rda::DataBlockType::MomentRho:
edgeValue_ = std::numeric_limits<std::uint8_t>::max();
break;
case wsr88d::rda::DataBlockType::MomentRef:
default:
edgeValue_ = 0;
break;
}
}
template<typename T>
T Level2ProductView::Impl::RemapDataMoment(T dataMoment) const
{
if (dataMoment != 0 &&
(dataMoment != RANGE_FOLDED || showSmoothedRangeFolding_))
{
return dataMoment;
}
else
{
return edgeValue_;
}
}
void Level2ProductView::Impl::ComputeCoordinates(
const std::shared_ptr<wsr88d::rda::ElevationScan>& radarData,
bool smoothingEnabled)
{
logger_->debug("ComputeCoordinates()");
@ -860,6 +1102,14 @@ void Level2ProductViewImpl::ComputeCoordinates(
auto radials = boost::irange<std::uint32_t>(0u, numRadials);
auto gates = boost::irange<std::uint32_t>(0u, numRangeBins);
const float gateRangeOffset = (smoothingEnabled) ?
// Center of the first gate is half the gate
// size distance from the radar site
0.5f :
// Far end of the first gate is the gate
// size distance from the radar site
1.0f;
std::for_each(
std::execution::par_unseq,
radials.begin(),
@ -869,7 +1119,7 @@ void Level2ProductViewImpl::ComputeCoordinates(
units::degrees<float> angle {};
auto radialData = radarData->find(radial);
if (radialData != radarData->cend())
if (radialData != radarData->cend() && !smoothingEnabled)
{
angle = radialData->second->azimuth_angle();
}
@ -880,19 +1130,60 @@ void Level2ProductViewImpl::ComputeCoordinates(
auto prevRadial2 = radarData->find(
(radial >= 2) ? radial - 2 : numRadials - (2 - radial));
if (prevRadial1 != radarData->cend() &&
prevRadial2 != radarData->cend())
if (radialData != radarData->cend() &&
prevRadial1 != radarData->cend() && smoothingEnabled)
{
const units::degrees<float> currentAngle =
radialData->second->azimuth_angle();
const units::degrees<float> prevAngle =
prevRadial1->second->azimuth_angle();
// Calculate delta angle
const units::degrees<float> deltaAngle =
NormalizeAngle(currentAngle - prevAngle);
// Delta scale is half the delta angle to reach the center of the
// bin, because smoothing is enabled
constexpr float deltaScale = 0.5f;
angle = currentAngle + deltaAngle * deltaScale;
}
else if (radialData != radarData->cend() && smoothingEnabled)
{
const units::degrees<float> currentAngle =
radialData->second->azimuth_angle();
// Assume a half degree delta if there aren't enough angles
// to determine a delta angle
constexpr units::degrees<float> deltaAngle {0.5f};
// Delta scale is half the delta angle to reach the center of the
// bin, because smoothing is enabled
constexpr float deltaScale = 0.5f;
angle = currentAngle + deltaAngle * deltaScale;
}
else if (prevRadial1 != radarData->cend() &&
prevRadial2 != radarData->cend())
{
const units::degrees<float> prevAngle1 =
prevRadial1->second->azimuth_angle();
const units::degrees<float> prevAngle2 =
prevRadial2->second->azimuth_angle();
// No wrapping required since angle is only used for geodesic
// calculation
const units::degrees<float> deltaAngle = prevAngle1 - prevAngle2;
// Calculate delta angle
const units::degrees<float> deltaAngle =
NormalizeAngle(prevAngle1 - prevAngle2);
angle = prevAngle1 + deltaAngle;
const float deltaScale =
(smoothingEnabled) ?
// Delta scale is 1.5x the delta angle to reach the center
// of the next bin, because smoothing is enabled
1.5f :
// Delta scale is 1.0x the delta angle
1.0f;
angle = prevAngle1 + deltaAngle * deltaScale;
}
else if (prevRadial1 != radarData->cend())
{
@ -903,7 +1194,15 @@ void Level2ProductViewImpl::ComputeCoordinates(
// to determine a delta angle
constexpr units::degrees<float> deltaAngle {0.5f};
angle = prevAngle1 + deltaAngle;
const float deltaScale =
(smoothingEnabled) ?
// Delta scale is 1.5x the delta angle to reach the center
// of the next bin, because smoothing is enabled
1.5f :
// Delta scale is 1.0x the delta angle
1.0f;
angle = prevAngle1 + deltaAngle * deltaScale;
}
else
{
@ -912,35 +1211,38 @@ void Level2ProductViewImpl::ComputeCoordinates(
}
}
std::for_each(std::execution::par_unseq,
gates.begin(),
gates.end(),
[&](std::uint32_t gate)
{
const std::uint32_t radialGate =
radial * common::MAX_DATA_MOMENT_GATES + gate;
const float range = (gate + 1) * gateSize;
const std::size_t offset = radialGate * 2;
std::for_each(
std::execution::par_unseq,
gates.begin(),
gates.end(),
[&](std::uint32_t gate)
{
const std::uint32_t radialGate =
radial * common::MAX_DATA_MOMENT_GATES + gate;
const float range =
(static_cast<float>(gate) + gateRangeOffset) * gateSize;
const std::size_t offset =
static_cast<std::size_t>(radialGate) * 2;
double latitude;
double longitude;
double latitude = 0.0;
double longitude = 0.0;
geodesic.Direct(radarLatitude,
radarLongitude,
angle.value(),
range,
latitude,
longitude);
geodesic.Direct(radarLatitude,
radarLongitude,
angle.value(),
range,
latitude,
longitude);
coordinates_[offset] = latitude;
coordinates_[offset + 1] = longitude;
});
coordinates_[offset] = static_cast<float>(latitude);
coordinates_[offset + 1] = static_cast<float>(longitude);
});
});
timer.stop();
logger_->debug("Coordinates calculated in {}", timer.format(6, "%ws"));
}
bool Level2ProductViewImpl::IsRadarDataIncomplete(
bool Level2ProductView::Impl::IsRadarDataIncomplete(
const std::shared_ptr<const wsr88d::rda::ElevationScan>& radarData)
{
// Assume the data is incomplete when the delta between the first and last
@ -957,6 +1259,25 @@ bool Level2ProductViewImpl::IsRadarDataIncomplete(
return angleDelta > kIncompleteDataAngleThreshold_;
}
units::degrees<float>
Level2ProductView::Impl::NormalizeAngle(units::degrees<float> angle)
{
constexpr auto angleLimit = units::degrees<float> {180.0f};
constexpr auto fullAngle = units::degrees<float> {360.0f};
// Normalize angle to [-180, 180)
while (angle < -angleLimit)
{
angle += fullAngle;
}
while (angle >= angleLimit)
{
angle -= fullAngle;
}
return angle;
}
std::optional<std::uint16_t>
Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const
{
@ -1003,7 +1324,7 @@ Level2ProductView::GetBinLevel(const common::Coordinate& coordinate) const
static_cast<std::uint16_t>(radarData->crbegin()->first + 1);
// Add an extra radial when incomplete data exists
if (Level2ProductViewImpl::IsRadarDataIncomplete(radarData))
if (Impl::IsRadarDataIncomplete(radarData))
{
++numRadials;
}

View file

@ -15,8 +15,6 @@ namespace qt
namespace view
{
class Level2ProductViewImpl;
class Level2ProductView : public RadarProductView
{
Q_OBJECT
@ -73,7 +71,8 @@ protected slots:
void ComputeSweep() override;
private:
std::unique_ptr<Level2ProductViewImpl> p;
class Impl;
std::unique_ptr<Impl> p;
};
} // namespace view

View file

@ -485,6 +485,52 @@ void Level3ProductView::UpdateColorTableLut()
Q_EMIT ColorTableLutUpdated();
}
std::uint8_t Level3ProductView::ComputeEdgeValue() const
{
std::uint8_t edgeValue = 0;
const std::shared_ptr<wsr88d::rpg::ProductDescriptionBlock>
descriptionBlock = p->graphicMessage_->description_block();
const float offset = descriptionBlock->offset();
const float scale = descriptionBlock->scale();
switch (p->category_)
{
case common::Level3ProductCategory::Velocity:
edgeValue = static_cast<std::uint8_t>((scale > 0.0f) ? (-offset / scale) :
-offset);
break;
case common::Level3ProductCategory::DifferentialReflectivity:
edgeValue = static_cast<std::uint8_t>(-offset);
break;
case common::Level3ProductCategory::SpectrumWidth:
case common::Level3ProductCategory::SpecificDifferentialPhase:
edgeValue = 2;
break;
case common::Level3ProductCategory::CorrelationCoefficient:
edgeValue = static_cast<std::uint8_t>(
std::max<std::uint16_t>(std::numeric_limits<std::uint8_t>::max(),
descriptionBlock->number_of_levels()));
break;
case common::Level3ProductCategory::Reflectivity:
case common::Level3ProductCategory::StormRelativeVelocity:
case common::Level3ProductCategory::VerticallyIntegratedLiquid:
case common::Level3ProductCategory::EchoTops:
case common::Level3ProductCategory::HydrometeorClassification:
case common::Level3ProductCategory::PrecipitationAccumulation:
default:
edgeValue = 0;
break;
}
return edgeValue;
}
std::optional<wsr88d::DataLevelCode>
Level3ProductView::GetDataLevelCode(std::uint16_t level) const
{

View file

@ -58,6 +58,8 @@ protected:
void DisconnectRadarProductManager() override;
void UpdateColorTableLut() override;
[[nodiscard]] std::uint8_t ComputeEdgeValue() const;
private:
class Impl;
std::unique_ptr<Impl> p;

View file

@ -44,7 +44,11 @@ public:
~Impl() { threadPool_.join(); };
void ComputeCoordinates(
const std::shared_ptr<wsr88d::rpg::GenericRadialDataPacket>& radialData);
const std::shared_ptr<wsr88d::rpg::GenericRadialDataPacket>& radialData,
bool smoothingEnabled);
[[nodiscard]] inline std::uint8_t
RemapDataMoment(std::uint8_t dataMoment) const;
Level3RadialView* self_;
@ -53,8 +57,13 @@ public:
std::vector<float> coordinates_ {};
std::vector<float> vertices_ {};
std::vector<std::uint8_t> dataMoments8_ {};
std::uint8_t edgeValue_ {};
bool showSmoothedRangeFolding_ {false};
std::shared_ptr<wsr88d::rpg::GenericRadialDataPacket> lastRadialData_ {};
bool lastShowSmoothedRangeFolding_ {false};
bool lastSmoothingEnabled_ {false};
float latitude_;
float longitude_;
@ -125,6 +134,9 @@ void Level3RadialView::ComputeSweep()
std::shared_ptr<manager::RadarProductManager> radarProductManager =
radar_product_manager();
const bool smoothingEnabled = smoothing_enabled();
p->showSmoothedRangeFolding_ = show_smoothed_range_folding();
const bool& showSmoothedRangeFolding = p->showSmoothedRangeFolding_;
// Retrieve message from Radar Product Manager
std::shared_ptr<wsr88d::rpg::Level3Message> message;
@ -155,7 +167,10 @@ void Level3RadialView::ComputeSweep()
Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData);
return;
}
else if (gpm == graphic_product_message())
else if (gpm == graphic_product_message() &&
smoothingEnabled == p->lastSmoothingEnabled_ &&
(showSmoothedRangeFolding == p->lastShowSmoothedRangeFolding_ ||
!smoothingEnabled))
{
// Skip if this is the message we previously processed
Q_EMIT SweepNotComputed(types::NoUpdateReason::NoChange);
@ -163,6 +178,9 @@ void Level3RadialView::ComputeSweep()
}
set_graphic_product_message(gpm);
p->lastShowSmoothedRangeFolding_ = showSmoothedRangeFolding;
p->lastSmoothingEnabled_ = smoothingEnabled;
// A message with radial data should have a Product Description Block and
// Product Symbology Block
std::shared_ptr<wsr88d::rpg::ProductDescriptionBlock> descriptionBlock =
@ -267,11 +285,11 @@ void Level3RadialView::ComputeSweep()
const std::vector<float>& coordinates =
(radialSize == common::RadialSize::NonStandard) ?
p->coordinates_ :
radarProductManager->coordinates(radialSize);
radarProductManager->coordinates(radialSize, smoothingEnabled);
// There should be a positive number of range bins in radial data
const uint16_t gates = radialData->number_of_range_bins();
if (gates < 1)
const uint16_t numberOfDataMomentGates = radialData->number_of_range_bins();
if (numberOfDataMomentGates < 1)
{
logger_->warn("No range bins in radial data");
Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData);
@ -293,13 +311,14 @@ void Level3RadialView::ComputeSweep()
std::vector<float>& vertices = p->vertices_;
size_t vIndex = 0;
vertices.clear();
vertices.resize(radials * gates * VERTICES_PER_BIN * VALUES_PER_VERTEX);
vertices.resize(radials * numberOfDataMomentGates * VERTICES_PER_BIN *
VALUES_PER_VERTEX);
// Setup data moment vector
std::vector<uint8_t>& dataMoments8 = p->dataMoments8_;
size_t mIndex = 0;
dataMoments8.resize(radials * gates * VERTICES_PER_BIN);
dataMoments8.resize(radials * numberOfDataMomentGates * VERTICES_PER_BIN);
// Compute threshold at which to display an individual bin
const uint16_t snrThreshold = descriptionBlock->threshold();
@ -308,7 +327,7 @@ void Level3RadialView::ComputeSweep()
std::uint16_t startRadial;
if (radialSize == common::RadialSize::NonStandard)
{
p->ComputeCoordinates(radialData);
p->ComputeCoordinates(radialData, smoothingEnabled);
startRadial = 0;
}
else
@ -318,40 +337,105 @@ void Level3RadialView::ComputeSweep()
startRadial = std::lroundf(startAngle * radialMultiplier);
}
for (uint16_t radial = 0; radial < radialData->number_of_radials(); radial++)
// Compute gate interval
const std::uint16_t dataMomentInterval =
descriptionBlock->x_resolution_raw();
// Compute gate size (number of base gates per bin)
const std::uint16_t gateSize = std::max<std::uint16_t>(
1,
dataMomentInterval /
static_cast<std::uint16_t>(radarProductManager->gate_size()));
// Compute gate range [startGate, endGate)
std::uint16_t startGate = 0;
const std::uint16_t endGate =
std::min<std::uint16_t>(startGate + numberOfDataMomentGates * gateSize,
common::MAX_DATA_MOMENT_GATES);
if (smoothingEnabled)
{
const auto dataMomentsArray8 = radialData->level(radial);
// If smoothing is enabled, the start gate is incremented by one, as we
// are skipping the radar site origin. The end gate is unaffected, as
// we need to draw one less data point.
++startGate;
// Compute gate interval
const uint16_t dataMomentInterval = descriptionBlock->x_resolution_raw();
// For most products other than reflectivity, the edge should not go to
// the bottom of the color table
p->edgeValue_ = ComputeEdgeValue();
}
// Compute gate size (number of base gates per bin)
const uint16_t gateSize = std::max<uint16_t>(
1,
dataMomentInterval /
static_cast<uint16_t>(radarProductManager->gate_size()));
for (std::uint16_t radial = 0; radial < radialData->number_of_radials();
++radial)
{
const auto& dataMomentsArray8 = radialData->level(radial);
// Compute gate range [startGate, endGate)
const uint16_t startGate = 0;
const uint16_t endGate = std::min<uint16_t>(
startGate + gates * gateSize, common::MAX_DATA_MOMENT_GATES);
const std::uint16_t nextRadial =
(radial == radialData->number_of_radials() - 1) ? 0 : radial + 1;
const auto& nextDataMomentsArray8 = radialData->level(nextRadial);
for (uint16_t gate = startGate, i = 0; gate + gateSize <= endGate;
for (std::uint16_t gate = startGate, i = 0; gate + gateSize <= endGate;
gate += gateSize, ++i)
{
size_t vertexCount = (gate > 0) ? 6 : 3;
// Store data moment value
uint8_t dataValue =
(i < dataMomentsArray8.size()) ? dataMomentsArray8[i] : 0;
if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
if (!smoothingEnabled)
{
continue;
}
// Store data moment value
const uint8_t dataValue =
(i < dataMomentsArray8.size()) ? dataMomentsArray8[i] : 0;
if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
{
continue;
}
for (size_t m = 0; m < vertexCount; m++)
for (size_t m = 0; m < vertexCount; m++)
{
dataMoments8[mIndex++] = dataValue;
}
}
else if (gate > 0)
{
dataMoments8[mIndex++] = dataValue;
// Validate indices are all in range
if (i + 1 >= numberOfDataMomentGates)
{
continue;
}
const std::uint8_t& dm1 = dataMomentsArray8[i];
const std::uint8_t& dm2 = dataMomentsArray8[i + 1];
const std::uint8_t& dm3 = nextDataMomentsArray8[i];
const std::uint8_t& dm4 = nextDataMomentsArray8[i + 1];
if ((!showSmoothedRangeFolding && //
(dm1 < snrThreshold || dm1 == RANGE_FOLDED) &&
(dm2 < snrThreshold || dm2 == RANGE_FOLDED) &&
(dm3 < snrThreshold || dm3 == RANGE_FOLDED) &&
(dm4 < snrThreshold || dm4 == RANGE_FOLDED)) ||
(showSmoothedRangeFolding && //
dm1 < snrThreshold && dm1 != RANGE_FOLDED &&
dm2 < snrThreshold && dm2 != RANGE_FOLDED &&
dm3 < snrThreshold && dm3 != RANGE_FOLDED &&
dm4 < snrThreshold && dm4 != RANGE_FOLDED))
{
// Skip only if all data moments are hidden
continue;
}
// The order must match the store vertices section below
dataMoments8[mIndex++] = p->RemapDataMoment(dm1);
dataMoments8[mIndex++] = p->RemapDataMoment(dm2);
dataMoments8[mIndex++] = p->RemapDataMoment(dm4);
dataMoments8[mIndex++] = p->RemapDataMoment(dm1);
dataMoments8[mIndex++] = p->RemapDataMoment(dm3);
dataMoments8[mIndex++] = p->RemapDataMoment(dm4);
}
else
{
// If smoothing is enabled, gate should never start at zero
// (radar site origin)
logger_->error("Smoothing enabled, gate should not start at zero");
continue;
}
// Store vertices
@ -376,19 +460,17 @@ void Level3RadialView::ComputeSweep()
vertices[vIndex++] = coordinates[offset2];
vertices[vIndex++] = coordinates[offset2 + 1];
vertices[vIndex++] = coordinates[offset3];
vertices[vIndex++] = coordinates[offset3 + 1];
vertices[vIndex++] = coordinates[offset4];
vertices[vIndex++] = coordinates[offset4 + 1];
vertices[vIndex++] = coordinates[offset1];
vertices[vIndex++] = coordinates[offset1 + 1];
vertices[vIndex++] = coordinates[offset3];
vertices[vIndex++] = coordinates[offset3 + 1];
vertices[vIndex++] = coordinates[offset4];
vertices[vIndex++] = coordinates[offset4 + 1];
vertices[vIndex++] = coordinates[offset2];
vertices[vIndex++] = coordinates[offset2 + 1];
vertexCount = 6;
}
else
{
@ -411,8 +493,6 @@ void Level3RadialView::ComputeSweep()
vertices[vIndex++] = coordinates[offset2];
vertices[vIndex++] = coordinates[offset2 + 1];
vertexCount = 3;
}
}
}
@ -430,8 +510,23 @@ void Level3RadialView::ComputeSweep()
Q_EMIT SweepComputed();
}
std::uint8_t
Level3RadialView::Impl::RemapDataMoment(std::uint8_t dataMoment) const
{
if (dataMoment != 0 &&
(dataMoment != RANGE_FOLDED || showSmoothedRangeFolding_))
{
return dataMoment;
}
else
{
return edgeValue_;
}
}
void Level3RadialView::Impl::ComputeCoordinates(
const std::shared_ptr<wsr88d::rpg::GenericRadialDataPacket>& radialData)
const std::shared_ptr<wsr88d::rpg::GenericRadialDataPacket>& radialData,
bool smoothingEnabled)
{
logger_->debug("ComputeCoordinates()");
@ -455,38 +550,54 @@ void Level3RadialView::Impl::ComputeCoordinates(
auto radials = boost::irange<std::uint32_t>(0u, numRadials);
auto gates = boost::irange<std::uint32_t>(0u, numRangeBins);
std::for_each(std::execution::par_unseq,
radials.begin(),
radials.end(),
[&](std::uint32_t radial)
{
const float angle = radialData->start_angle(radial);
const float gateRangeOffset = (smoothingEnabled) ?
// Center of the first gate is half the gate
// size distance from the radar site
0.5f :
// Far end of the first gate is the gate
// size distance from the radar site
1.0f;
std::for_each(std::execution::par_unseq,
gates.begin(),
gates.end(),
[&](std::uint32_t gate)
{
const std::uint32_t radialGate =
radial * common::MAX_DATA_MOMENT_GATES +
gate;
const float range = (gate + 1) * gateSize;
const std::size_t offset = radialGate * 2;
std::for_each(
std::execution::par_unseq,
radials.begin(),
radials.end(),
[&](std::uint32_t radial)
{
float angle = radialData->start_angle(radial);
double latitude;
double longitude;
if (smoothingEnabled)
{
static constexpr float kDeltaAngleFactor = 0.5f;
angle += radialData->delta_angle(radial) * kDeltaAngleFactor;
}
geodesic.Direct(radarLatitude,
radarLongitude,
angle,
range,
latitude,
longitude);
std::for_each(
std::execution::par_unseq,
gates.begin(),
gates.end(),
[&](std::uint32_t gate)
{
const std::uint32_t radialGate =
radial * common::MAX_DATA_MOMENT_GATES + gate;
const float range =
(static_cast<float>(gate) + gateRangeOffset) * gateSize;
const std::size_t offset = static_cast<size_t>(radialGate) * 2;
coordinates_[offset] = latitude;
coordinates_[offset + 1] = longitude;
});
});
double latitude = 0.0;
double longitude = 0.0;
geodesic.Direct(radarLatitude,
radarLongitude,
angle,
range,
latitude,
longitude);
coordinates_[offset] = static_cast<float>(latitude);
coordinates_[offset + 1] = static_cast<float>(longitude);
});
});
timer.stop();
logger_->debug("Coordinates calculated in {}", timer.format(6, "%ws"));
}

View file

@ -33,12 +33,20 @@ public:
}
~Level3RasterViewImpl() { threadPool_.join(); };
[[nodiscard]] inline std::uint8_t
RemapDataMoment(std::uint8_t dataMoment) const;
boost::asio::thread_pool threadPool_ {1u};
std::vector<float> vertices_;
std::vector<uint8_t> dataMoments8_;
std::vector<float> vertices_ {};
std::vector<std::uint8_t> dataMoments8_ {};
std::uint8_t edgeValue_ {};
bool showSmoothedRangeFolding_ {false};
std::shared_ptr<wsr88d::rpg::RasterDataPacket> lastRasterData_ {};
bool lastShowSmoothedRangeFolding_ {false};
bool lastSmoothingEnabled_ {false};
float latitude_;
float longitude_;
@ -109,6 +117,9 @@ void Level3RasterView::ComputeSweep()
std::shared_ptr<manager::RadarProductManager> radarProductManager =
radar_product_manager();
const bool smoothingEnabled = smoothing_enabled();
p->showSmoothedRangeFolding_ = show_smoothed_range_folding();
const bool& showSmoothedRangeFolding = p->showSmoothedRangeFolding_;
// Retrieve message from Radar Product Manager
std::shared_ptr<wsr88d::rpg::Level3Message> message;
@ -139,7 +150,10 @@ void Level3RasterView::ComputeSweep()
Q_EMIT SweepNotComputed(types::NoUpdateReason::InvalidData);
return;
}
else if (gpm == graphic_product_message())
else if (gpm == graphic_product_message() &&
smoothingEnabled == p->lastSmoothingEnabled_ &&
(showSmoothedRangeFolding == p->lastShowSmoothedRangeFolding_ ||
!smoothingEnabled))
{
// Skip if this is the message we previously processed
Q_EMIT SweepNotComputed(types::NoUpdateReason::NoChange);
@ -147,6 +161,9 @@ void Level3RasterView::ComputeSweep()
}
set_graphic_product_message(gpm);
p->lastShowSmoothedRangeFolding_ = showSmoothedRangeFolding;
p->lastSmoothingEnabled_ = smoothingEnabled;
// A message with radial data should have a Product Description Block and
// Product Symbology Block
std::shared_ptr<wsr88d::rpg::ProductDescriptionBlock> descriptionBlock =
@ -231,16 +248,18 @@ void Level3RasterView::ComputeSweep()
const GeographicLib::Geodesic& geodesic =
util::GeographicLib::DefaultGeodesic();
const uint16_t xResolution = descriptionBlock->x_resolution_raw();
const uint16_t yResolution = descriptionBlock->y_resolution_raw();
double iCoordinate =
const std::uint16_t xResolution = descriptionBlock->x_resolution_raw();
const std::uint16_t yResolution = descriptionBlock->y_resolution_raw();
const double iCoordinate =
(-rasterData->i_coordinate_start() - 1.0 - p->range_) * 1000.0;
double jCoordinate =
const double jCoordinate =
(rasterData->j_coordinate_start() + 1.0 + p->range_) * 1000.0;
const double xOffset = (smoothingEnabled) ? xResolution * 0.5 : 0.0;
const double yOffset = (smoothingEnabled) ? yResolution * 0.5 : 0.0;
size_t numCoordinates =
const std::size_t numCoordinates =
static_cast<size_t>(rows + 1) * static_cast<size_t>(maxColumns + 1);
auto coordinateRange =
const auto coordinateRange =
boost::irange<uint32_t>(0, static_cast<uint32_t>(numCoordinates));
std::vector<float> coordinates;
@ -260,8 +279,8 @@ void Level3RasterView::ComputeSweep()
const uint32_t col = index % (rows + 1);
const uint32_t row = index / (rows + 1);
const double i = iCoordinate + xResolution * col;
const double j = jCoordinate - yResolution * row;
const double i = iCoordinate + xResolution * col + xOffset;
const double j = jCoordinate - yResolution * row - yOffset;
// Calculate polar coordinates based on i and j
const double angle = std::atan2(i, j) * 180.0 / M_PI;
@ -299,25 +318,83 @@ void Level3RasterView::ComputeSweep()
// Compute threshold at which to display an individual bin
const uint16_t snrThreshold = descriptionBlock->threshold();
for (size_t row = 0; row < rasterData->number_of_rows(); ++row)
const std::size_t rowCount = (smoothingEnabled) ?
rasterData->number_of_rows() - 1 :
rasterData->number_of_rows();
if (smoothingEnabled)
{
const auto dataMomentsArray8 =
// For most products other than reflectivity, the edge should not go to
// the bottom of the color table
p->edgeValue_ = ComputeEdgeValue();
}
for (std::size_t row = 0; row < rowCount; ++row)
{
const std::size_t nextRow =
(row == static_cast<std::size_t>(rasterData->number_of_rows() - 1)) ?
0 :
row + 1;
const auto& dataMomentsArray8 =
rasterData->level(static_cast<uint16_t>(row));
const auto& nextDataMomentsArray8 =
rasterData->level(static_cast<uint16_t>(nextRow));
for (size_t bin = 0; bin < dataMomentsArray8.size(); ++bin)
{
constexpr size_t vertexCount = 6;
// Store data moment value
uint8_t dataValue = dataMomentsArray8[bin];
if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
if (!smoothingEnabled)
{
continue;
static constexpr std::size_t vertexCount = 6;
// Store data moment value
const std::uint8_t& dataValue = dataMomentsArray8[bin];
if (dataValue < snrThreshold && dataValue != RANGE_FOLDED)
{
continue;
}
for (size_t m = 0; m < vertexCount; m++)
{
dataMoments8[mIndex++] = dataValue;
}
}
for (size_t m = 0; m < vertexCount; m++)
else
{
dataMoments8[mIndex++] = dataValue;
// Validate indices are all in range
if (bin + 1 >= dataMomentsArray8.size() ||
bin + 1 >= nextDataMomentsArray8.size())
{
continue;
}
const std::uint8_t& dm1 = dataMomentsArray8[bin];
const std::uint8_t& dm2 = dataMomentsArray8[bin + 1];
const std::uint8_t& dm3 = nextDataMomentsArray8[bin];
const std::uint8_t& dm4 = nextDataMomentsArray8[bin + 1];
if ((!showSmoothedRangeFolding && //
(dm1 < snrThreshold || dm1 == RANGE_FOLDED) &&
(dm2 < snrThreshold || dm2 == RANGE_FOLDED) &&
(dm3 < snrThreshold || dm3 == RANGE_FOLDED) &&
(dm4 < snrThreshold || dm4 == RANGE_FOLDED)) ||
(showSmoothedRangeFolding && //
dm1 < snrThreshold && dm1 != RANGE_FOLDED &&
dm2 < snrThreshold && dm2 != RANGE_FOLDED &&
dm3 < snrThreshold && dm3 != RANGE_FOLDED &&
dm4 < snrThreshold && dm4 != RANGE_FOLDED))
{
// Skip only if all data moments are hidden
continue;
}
// The order must match the store vertices section below
dataMoments8[mIndex++] = p->RemapDataMoment(dm1);
dataMoments8[mIndex++] = p->RemapDataMoment(dm2);
dataMoments8[mIndex++] = p->RemapDataMoment(dm4);
dataMoments8[mIndex++] = p->RemapDataMoment(dm1);
dataMoments8[mIndex++] = p->RemapDataMoment(dm3);
dataMoments8[mIndex++] = p->RemapDataMoment(dm4);
}
// Store vertices
@ -332,17 +409,17 @@ void Level3RasterView::ComputeSweep()
vertices[vIndex++] = coordinates[offset2];
vertices[vIndex++] = coordinates[offset2 + 1];
vertices[vIndex++] = coordinates[offset3];
vertices[vIndex++] = coordinates[offset3 + 1];
vertices[vIndex++] = coordinates[offset4];
vertices[vIndex++] = coordinates[offset4 + 1];
vertices[vIndex++] = coordinates[offset1];
vertices[vIndex++] = coordinates[offset1 + 1];
vertices[vIndex++] = coordinates[offset3];
vertices[vIndex++] = coordinates[offset3 + 1];
vertices[vIndex++] = coordinates[offset4];
vertices[vIndex++] = coordinates[offset4 + 1];
vertices[vIndex++] = coordinates[offset2];
vertices[vIndex++] = coordinates[offset2 + 1];
}
}
vertices.resize(vIndex);
@ -359,6 +436,20 @@ void Level3RasterView::ComputeSweep()
Q_EMIT SweepComputed();
}
std::uint8_t
Level3RasterViewImpl::RemapDataMoment(std::uint8_t dataMoment) const
{
if (dataMoment != 0 &&
(dataMoment != RANGE_FOLDED || showSmoothedRangeFolding_))
{
return dataMoment;
}
else
{
return edgeValue_;
}
}
std::optional<std::uint16_t>
Level3RasterView::GetBinLevel(const common::Coordinate& coordinate) const
{

View file

@ -1,4 +1,5 @@
#include <scwx/qt/view/radar_product_view.hpp>
#include <scwx/qt/settings/product_settings.hpp>
#include <scwx/common/constants.hpp>
#include <scwx/util/logger.hpp>
@ -28,26 +29,44 @@ class RadarProductViewImpl
{
public:
explicit RadarProductViewImpl(
RadarProductView* self,
std::shared_ptr<manager::RadarProductManager> radarProductManager) :
self_ {self},
initialized_ {false},
sweepMutex_ {},
selectedTime_ {},
radarProductManager_ {radarProductManager}
{
auto& productSettings = settings::ProductSettings::Instance();
connection_ = productSettings.changed_signal().connect(
[this]()
{
showSmoothedRangeFolding_ = settings::ProductSettings::Instance()
.show_smoothed_range_folding()
.GetValue();
self_->Update();
});
;
}
~RadarProductViewImpl() {}
RadarProductView* self_;
bool initialized_;
std::mutex sweepMutex_;
std::chrono::system_clock::time_point selectedTime_;
bool showSmoothedRangeFolding_ {false};
bool smoothingEnabled_ {false};
std::shared_ptr<manager::RadarProductManager> radarProductManager_;
boost::signals2::scoped_connection connection_;
};
RadarProductView::RadarProductView(
std::shared_ptr<manager::RadarProductManager> radarProductManager) :
p(std::make_unique<RadarProductViewImpl>(radarProductManager)) {};
p(std::make_unique<RadarProductViewImpl>(this, radarProductManager)) {};
RadarProductView::~RadarProductView() = default;
const std::vector<boost::gil::rgba8_pixel_t>&
@ -87,6 +106,16 @@ std::chrono::system_clock::time_point RadarProductView::selected_time() const
return p->selectedTime_;
}
bool RadarProductView::show_smoothed_range_folding() const
{
return p->showSmoothedRangeFolding_;
}
bool RadarProductView::smoothing_enabled() const
{
return p->smoothingEnabled_;
}
std::chrono::system_clock::time_point RadarProductView::sweep_time() const
{
return {};
@ -105,6 +134,11 @@ void RadarProductView::set_radar_product_manager(
ConnectRadarProductManager();
}
void RadarProductView::set_smoothing_enabled(bool smoothingEnabled)
{
p->smoothingEnabled_ = smoothingEnabled;
}
void RadarProductView::Initialize()
{
ComputeSweep();

View file

@ -47,12 +47,16 @@ public:
virtual std::uint16_t vcp() const = 0;
virtual const std::vector<float>& vertices() const = 0;
std::shared_ptr<manager::RadarProductManager> radar_product_manager() const;
std::chrono::system_clock::time_point selected_time() const;
std::mutex& sweep_mutex();
[[nodiscard]] std::shared_ptr<manager::RadarProductManager>
radar_product_manager() const;
[[nodiscard]] std::chrono::system_clock::time_point selected_time() const;
[[nodiscard]] bool show_smoothed_range_folding() const;
[[nodiscard]] bool smoothing_enabled() const;
[[nodiscard]] std::mutex& sweep_mutex();
void set_radar_product_manager(
std::shared_ptr<manager::RadarProductManager> radarProductManager);
void set_smoothing_enabled(bool smoothingEnabled);
void Initialize();
virtual void

@ -1 +1 @@
Subproject commit eaf8f185ce2b3a3248da1a4d6c8e2e9265638f15
Subproject commit 0eb475909f9e64ce81e7b8b39420d980b81b3baa