Merge pull request #250 from AdenKoperczak/audio_location_methods

Radar Site Audio Location Method and Radius
This commit is contained in:
Dan Paulat 2024-07-29 22:32:30 -05:00 committed by GitHub
commit 7b41184fbb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 802 additions and 120 deletions

View file

@ -1466,6 +1466,7 @@ void MainWindowImpl::UpdateRadarSite()
timelineManager_->SetRadarSite("?"); timelineManager_->SetRadarSite("?");
} }
alertManager_->SetRadarSite(radarSite);
placefileManager_->SetRadarSite(radarSite); placefileManager_->SetRadarSite(radarSite);
} }

View file

@ -6,6 +6,8 @@
#include <scwx/qt/types/location_types.hpp> #include <scwx/qt/types/location_types.hpp>
#include <scwx/qt/util/geographic_lib.hpp> #include <scwx/qt/util/geographic_lib.hpp>
#include <scwx/util/logger.hpp> #include <scwx/util/logger.hpp>
#include <scwx/qt/config/radar_site.hpp>
#include <scwx/qt/settings/general_settings.hpp>
#include <boost/asio/post.hpp> #include <boost/asio/post.hpp>
#include <boost/asio/thread_pool.hpp> #include <boost/asio/thread_pool.hpp>
@ -74,6 +76,8 @@ public:
PositionManager::Instance()}; PositionManager::Instance()};
std::shared_ptr<TextEventManager> textEventManager_ { std::shared_ptr<TextEventManager> textEventManager_ {
TextEventManager::Instance()}; TextEventManager::Instance()};
std::shared_ptr<config::RadarSite> radarSite_ {};
}; };
AlertManager::AlertManager() : p(std::make_unique<Impl>(this)) {} AlertManager::AlertManager() : p(std::make_unique<Impl>(this)) {}
@ -100,6 +104,33 @@ common::Coordinate AlertManager::Impl::CurrentCoordinate(
coordinate.longitude_ = trackedCoordinate.longitude(); coordinate.longitude_ = trackedCoordinate.longitude();
} }
} }
else if (locationMethod == types::LocationMethod::RadarSite)
{
std::string radarSiteSelection =
audioSettings.alert_radar_site().GetValue();
std::shared_ptr<config::RadarSite> radarSite;
if (radarSiteSelection == "default")
{
std::string siteId = settings::GeneralSettings::Instance()
.default_radar_site()
.GetValue();
radarSite = config::RadarSite::Get(siteId);
}
else if (radarSiteSelection == "follow")
{
radarSite = radarSite_;
}
else
{
radarSite = config::RadarSite::Get(radarSiteSelection);
}
if (radarSite != nullptr)
{
coordinate.latitude_ = radarSite->latitude();
coordinate.longitude_ = radarSite->longitude();
}
}
return coordinate; return coordinate;
} }
@ -118,6 +149,8 @@ void AlertManager::Impl::HandleAlert(const types::TextEventKey& key,
audioSettings.alert_location_method().GetValue()); audioSettings.alert_location_method().GetValue());
common::Coordinate currentCoordinate = CurrentCoordinate(locationMethod); common::Coordinate currentCoordinate = CurrentCoordinate(locationMethod);
std::string alertCounty = audioSettings.alert_county().GetValue(); std::string alertCounty = audioSettings.alert_county().GetValue();
auto alertRadius = units::length::kilometers<double>(
audioSettings.alert_radius().GetValue());
auto message = textEventManager_->message_list(key).at(messageIndex); auto message = textEventManager_->message_list(key).at(messageIndex);
@ -145,13 +178,16 @@ void AlertManager::Impl::HandleAlert(const types::TextEventKey& key,
bool activeAtLocation = (locationMethod == types::LocationMethod::All); bool activeAtLocation = (locationMethod == types::LocationMethod::All);
if (locationMethod == types::LocationMethod::Fixed || if (locationMethod == types::LocationMethod::Fixed ||
locationMethod == types::LocationMethod::Track) locationMethod == types::LocationMethod::Track ||
locationMethod == types::LocationMethod::RadarSite)
{ {
// Determine if the alert is active at the current coordinte // Determine if the alert is active at the current coordinte
auto alertCoordinates = segment->codedLocation_->coordinates(); auto alertCoordinates = segment->codedLocation_->coordinates();
activeAtLocation = util::GeographicLib::AreaContainsPoint( activeAtLocation = util::GeographicLib::AreaInRangeOfPoint(
alertCoordinates, currentCoordinate); alertCoordinates,
currentCoordinate,
alertRadius);
} }
else if (locationMethod == types::LocationMethod::County) else if (locationMethod == types::LocationMethod::County)
{ {
@ -183,6 +219,27 @@ void AlertManager::Impl::UpdateLocationTracking(
positionManager_->EnablePositionUpdates(uuid_, locationEnabled); positionManager_->EnablePositionUpdates(uuid_, locationEnabled);
} }
void AlertManager::SetRadarSite(
const std::shared_ptr<config::RadarSite>& radarSite)
{
if (p->radarSite_ == radarSite)
{
// No action needed
return;
}
if (radarSite == nullptr)
{
logger_->debug("SetRadarSite: ?");
}
else
{
logger_->debug("SetRadarSite: {}", radarSite->id());
}
p->radarSite_ = radarSite;
}
std::shared_ptr<AlertManager> AlertManager::Instance() std::shared_ptr<AlertManager> AlertManager::Instance()
{ {
static std::weak_ptr<AlertManager> alertManagerReference_ {}; static std::weak_ptr<AlertManager> alertManagerReference_ {};

View file

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <scwx/qt/config/radar_site.hpp>
#include <memory> #include <memory>
#include <QObject> #include <QObject>
@ -20,6 +22,7 @@ public:
explicit AlertManager(); explicit AlertManager();
~AlertManager(); ~AlertManager();
void SetRadarSite(const std::shared_ptr<config::RadarSite>& radarSite);
static std::shared_ptr<AlertManager> Instance(); static std::shared_ptr<AlertManager> Instance();
private: private:

View file

@ -1,13 +1,16 @@
#include <scwx/qt/model/alert_model.hpp> #include <scwx/qt/model/alert_model.hpp>
#include <scwx/qt/config/county_database.hpp> #include <scwx/qt/config/county_database.hpp>
#include <scwx/qt/manager/text_event_manager.hpp> #include <scwx/qt/manager/text_event_manager.hpp>
#include <scwx/qt/settings/unit_settings.hpp>
#include <scwx/qt/types/qt_types.hpp> #include <scwx/qt/types/qt_types.hpp>
#include <scwx/qt/types/unit_types.hpp>
#include <scwx/qt/util/geographic_lib.hpp> #include <scwx/qt/util/geographic_lib.hpp>
#include <scwx/common/geographic.hpp> #include <scwx/common/geographic.hpp>
#include <scwx/util/logger.hpp> #include <scwx/util/logger.hpp>
#include <scwx/util/strings.hpp> #include <scwx/util/strings.hpp>
#include <scwx/util/time.hpp> #include <scwx/util/time.hpp>
#include <format> #include <format>
#include <QApplication> #include <QApplication>
@ -73,7 +76,6 @@ public:
double, double,
types::TextEventHash<types::TextEventKey>> types::TextEventHash<types::TextEventKey>>
distanceMap_; distanceMap_;
scwx::common::DistanceType distanceDisplay_;
scwx::common::Coordinate previousPosition_; scwx::common::Coordinate previousPosition_;
}; };
@ -182,18 +184,19 @@ QVariant AlertModel::data(const QModelIndex& index, int role) const
case static_cast<int>(Column::Distance): case static_cast<int>(Column::Distance):
if (role == Qt::DisplayRole) if (role == Qt::DisplayRole)
{ {
if (p->distanceDisplay_ == scwx::common::DistanceType::Miles) const std::string distanceUnitName =
{ settings::UnitSettings::Instance().distance_units().GetValue();
return QString("%1 mi").arg( types::DistanceUnits distanceUnits =
static_cast<uint32_t>(p->distanceMap_.at(textEventKey) * types::GetDistanceUnitsFromName(distanceUnitName);
scwx::common::kMilesPerMeter)); double distanceScale = types::GetDistanceUnitsScale(distanceUnits);
} std::string abbreviation =
else types::GetDistanceUnitsAbbreviation(distanceUnits);
{
return QString("%1 km").arg( return QString("%1 %2")
static_cast<uint32_t>(p->distanceMap_.at(textEventKey) * .arg(static_cast<uint32_t>(p->distanceMap_.at(textEventKey) *
scwx::common::kKilometersPerMeter)); scwx::common::kKilometersPerMeter *
} distanceScale))
.arg(QString::fromStdString(abbreviation));
} }
else else
{ {
@ -419,7 +422,6 @@ AlertModelImpl::AlertModelImpl() :
textEventKeys_ {}, textEventKeys_ {},
geodesic_(util::GeographicLib::DefaultGeodesic()), geodesic_(util::GeographicLib::DefaultGeodesic()),
distanceMap_ {}, distanceMap_ {},
distanceDisplay_ {scwx::common::DistanceType::Miles},
previousPosition_ {} previousPosition_ {}
{ {
} }

View file

@ -1,6 +1,8 @@
#include <scwx/qt/model/radar_site_model.hpp> #include <scwx/qt/model/radar_site_model.hpp>
#include <scwx/qt/config/radar_site.hpp> #include <scwx/qt/config/radar_site.hpp>
#include <scwx/qt/settings/unit_settings.hpp>
#include <scwx/qt/types/qt_types.hpp> #include <scwx/qt/types/qt_types.hpp>
#include <scwx/qt/types/unit_types.hpp>
#include <scwx/qt/util/geographic_lib.hpp> #include <scwx/qt/util/geographic_lib.hpp>
#include <scwx/qt/util/json.hpp> #include <scwx/qt/util/json.hpp>
#include <scwx/common/geographic.hpp> #include <scwx/common/geographic.hpp>
@ -36,7 +38,6 @@ public:
radarSites_ {}, radarSites_ {},
geodesic_(util::GeographicLib::DefaultGeodesic()), geodesic_(util::GeographicLib::DefaultGeodesic()),
distanceMap_ {}, distanceMap_ {},
distanceDisplay_ {scwx::common::DistanceType::Miles},
previousPosition_ {} previousPosition_ {}
{ {
// Get all loaded radar sites // Get all loaded radar sites
@ -64,7 +65,6 @@ public:
const GeographicLib::Geodesic& geodesic_; const GeographicLib::Geodesic& geodesic_;
std::unordered_map<std::string, double> distanceMap_; std::unordered_map<std::string, double> distanceMap_;
scwx::common::DistanceType distanceDisplay_;
scwx::common::Coordinate previousPosition_; scwx::common::Coordinate previousPosition_;
QIcon starIcon_ {":/res/icons/font-awesome-6/star-solid.svg"}; QIcon starIcon_ {":/res/icons/font-awesome-6/star-solid.svg"};
@ -213,18 +213,19 @@ QVariant RadarSiteModel::data(const QModelIndex& index, int role) const
case static_cast<int>(Column::Distance): case static_cast<int>(Column::Distance):
if (role == Qt::DisplayRole) if (role == Qt::DisplayRole)
{ {
if (p->distanceDisplay_ == scwx::common::DistanceType::Miles) const std::string distanceUnitName =
{ settings::UnitSettings::Instance().distance_units().GetValue();
return QString("%1 mi").arg( types::DistanceUnits distanceUnits =
static_cast<uint32_t>(p->distanceMap_.at(site->id()) * types::GetDistanceUnitsFromName(distanceUnitName);
scwx::common::kMilesPerMeter)); double distanceScale = types::GetDistanceUnitsScale(distanceUnits);
} std::string abbreviation =
else types::GetDistanceUnitsAbbreviation(distanceUnits);
{
return QString("%1 km").arg( return QString("%1 %2")
static_cast<uint32_t>(p->distanceMap_.at(site->id()) * .arg(static_cast<uint32_t>(p->distanceMap_.at(site->id()) *
scwx::common::kKilometersPerMeter)); scwx::common::kKilometersPerMeter *
} distanceScale))
.arg(QString::fromStdString(abbreviation));
} }
else else
{ {

View file

@ -37,12 +37,17 @@ public:
alertLocationMethod_.SetDefault(defaultAlertLocationMethodValue); alertLocationMethod_.SetDefault(defaultAlertLocationMethodValue);
alertLatitude_.SetDefault(0.0); alertLatitude_.SetDefault(0.0);
alertLongitude_.SetDefault(0.0); alertLongitude_.SetDefault(0.0);
alertRadius_.SetDefault(0.0);
alertRadarSite_.SetDefault("default");
ignoreMissingCodecs_.SetDefault(false); ignoreMissingCodecs_.SetDefault(false);
alertLatitude_.SetMinimum(-90.0); alertLatitude_.SetMinimum(-90.0);
alertLatitude_.SetMaximum(90.0); alertLatitude_.SetMaximum(90.0);
alertLongitude_.SetMinimum(-180.0); alertLongitude_.SetMinimum(-180.0);
alertLongitude_.SetMaximum(180.0); alertLongitude_.SetMaximum(180.0);
alertRadius_.SetMinimum(0.0);
alertRadius_.SetMaximum(9999999999);
alertLocationMethod_.SetValidator( alertLocationMethod_.SetValidator(
SCWX_SETTINGS_ENUM_VALIDATOR(types::LocationMethod, SCWX_SETTINGS_ENUM_VALIDATOR(types::LocationMethod,
@ -86,6 +91,8 @@ public:
SettingsVariable<std::string> alertLocationMethod_ {"alert_location_method"}; SettingsVariable<std::string> alertLocationMethod_ {"alert_location_method"};
SettingsVariable<double> alertLatitude_ {"alert_latitude"}; SettingsVariable<double> alertLatitude_ {"alert_latitude"};
SettingsVariable<double> alertLongitude_ {"alert_longitude"}; SettingsVariable<double> alertLongitude_ {"alert_longitude"};
SettingsVariable<std::string> alertRadarSite_ {"alert_radar_site"};
SettingsVariable<double> alertRadius_ {"alert_radius"};
SettingsVariable<std::string> alertCounty_ {"alert_county"}; SettingsVariable<std::string> alertCounty_ {"alert_county"};
SettingsVariable<bool> ignoreMissingCodecs_ {"ignore_missing_codecs"}; SettingsVariable<bool> ignoreMissingCodecs_ {"ignore_missing_codecs"};
@ -101,6 +108,8 @@ AudioSettings::AudioSettings() :
&p->alertLocationMethod_, &p->alertLocationMethod_,
&p->alertLatitude_, &p->alertLatitude_,
&p->alertLongitude_, &p->alertLongitude_,
&p->alertRadarSite_,
&p->alertRadius_,
&p->alertCounty_, &p->alertCounty_,
&p->ignoreMissingCodecs_}); &p->ignoreMissingCodecs_});
RegisterVariables(p->variables_); RegisterVariables(p->variables_);
@ -133,6 +142,16 @@ SettingsVariable<double>& AudioSettings::alert_longitude() const
return p->alertLongitude_; return p->alertLongitude_;
} }
SettingsVariable<std::string>& AudioSettings::alert_radar_site() const
{
return p->alertRadarSite_;
}
SettingsVariable<double>& AudioSettings::alert_radius() const
{
return p->alertRadius_;
}
SettingsVariable<std::string>& AudioSettings::alert_county() const SettingsVariable<std::string>& AudioSettings::alert_county() const
{ {
return p->alertCounty_; return p->alertCounty_;
@ -166,6 +185,8 @@ bool operator==(const AudioSettings& lhs, const AudioSettings& rhs)
lhs.p->alertLocationMethod_ == rhs.p->alertLocationMethod_ && lhs.p->alertLocationMethod_ == rhs.p->alertLocationMethod_ &&
lhs.p->alertLatitude_ == rhs.p->alertLatitude_ && lhs.p->alertLatitude_ == rhs.p->alertLatitude_ &&
lhs.p->alertLongitude_ == rhs.p->alertLongitude_ && lhs.p->alertLongitude_ == rhs.p->alertLongitude_ &&
lhs.p->alertRadarSite_ == rhs.p->alertRadarSite_ &&
lhs.p->alertRadius_ == rhs.p->alertRadius_ &&
lhs.p->alertCounty_ == rhs.p->alertCounty_ && lhs.p->alertCounty_ == rhs.p->alertCounty_ &&
lhs.p->alertEnabled_ == rhs.p->alertEnabled_); lhs.p->alertEnabled_ == rhs.p->alertEnabled_);
} }

View file

@ -30,6 +30,8 @@ public:
SettingsVariable<std::string>& alert_location_method() const; SettingsVariable<std::string>& alert_location_method() const;
SettingsVariable<double>& alert_latitude() const; SettingsVariable<double>& alert_latitude() const;
SettingsVariable<double>& alert_longitude() const; SettingsVariable<double>& alert_longitude() const;
SettingsVariable<double>& alert_radius() const;
SettingsVariable<std::string>& alert_radar_site() const;
SettingsVariable<std::string>& alert_county() const; SettingsVariable<std::string>& alert_county() const;
SettingsVariable<bool>& alert_enabled(awips::Phenomenon phenomenon) const; SettingsVariable<bool>& alert_enabled(awips::Phenomenon phenomenon) const;
SettingsVariable<bool>& ignore_missing_codecs() const; SettingsVariable<bool>& ignore_missing_codecs() const;

View file

@ -40,6 +40,7 @@ public:
void UpdateEditWidget(); void UpdateEditWidget();
void UpdateResetButton(); void UpdateResetButton();
void UpdateUnitLabel();
SettingsInterface<T>* self_; SettingsInterface<T>* self_;
@ -49,9 +50,14 @@ public:
std::unique_ptr<QObject> context_ {std::make_unique<QObject>()}; std::unique_ptr<QObject> context_ {std::make_unique<QObject>()};
QWidget* editWidget_ {nullptr}; QWidget* editWidget_ {nullptr};
QAbstractButton* resetButton_ {nullptr}; QAbstractButton* resetButton_ {nullptr};
QLabel* unitLabel_ {nullptr};
std::function<std::string(const T&)> mapFromValue_ {nullptr}; std::function<std::string(const T&)> mapFromValue_ {nullptr};
std::function<T(const std::string&)> mapToValue_ {nullptr}; std::function<T(const std::string&)> mapToValue_ {nullptr};
double unitScale_ {1};
std::optional<std::string> unitAbbreviation_ {};
bool unitEnabled_ {false};
}; };
template<class T> template<class T>
@ -381,6 +387,11 @@ void SettingsInterface<T>::SetEditWidget(QWidget* widget)
p->context_.get(), p->context_.get(),
[this](double d) [this](double d)
{ {
if (p->unitEnabled_)
{
d = d / p->unitScale_;
}
const T value = p->variable_->GetValue(); const T value = p->variable_->GetValue();
const std::optional<T> staged = p->variable_->GetStaged(); const std::optional<T> staged = p->variable_->GetStaged();
@ -448,6 +459,11 @@ void SettingsInterface<T>::SetResetButton(QAbstractButton* button)
p->UpdateResetButton(); p->UpdateResetButton();
} }
} }
template<class T>
void SettingsInterface<T>::SetUnitLabel(QLabel* label)
{
p->unitLabel_ = label;
}
template<class T> template<class T>
void SettingsInterface<T>::SetMapFromValueFunction( void SettingsInterface<T>::SetMapFromValueFunction(
@ -463,6 +479,17 @@ void SettingsInterface<T>::SetMapToValueFunction(
p->mapToValue_ = function; p->mapToValue_ = function;
} }
template<class T>
void SettingsInterface<T>::SetUnit(const double& scale,
const std::string& abbreviation)
{
p->unitScale_ = scale;
p->unitAbbreviation_ = abbreviation;
p->unitEnabled_ = true;
p->UpdateEditWidget();
p->UpdateUnitLabel();
}
template<class T> template<class T>
template<class U> template<class U>
void SettingsInterface<T>::Impl::SetWidgetText(U* widget, const T& currentValue) void SettingsInterface<T>::Impl::SetWidgetText(U* widget, const T& currentValue)
@ -559,11 +586,27 @@ void SettingsInterface<T>::Impl::UpdateEditWidget()
{ {
if constexpr (std::is_floating_point_v<T>) if constexpr (std::is_floating_point_v<T>)
{ {
doubleSpinBox->setValue(static_cast<double>(currentValue)); double doubleValue = static_cast<double>(currentValue);
if (unitEnabled_)
{
doubleValue = doubleValue * unitScale_;
}
doubleSpinBox->setValue(doubleValue);
} }
} }
} }
template<class T>
void SettingsInterface<T>::Impl::UpdateUnitLabel()
{
if (unitLabel_ == nullptr || !unitEnabled_)
{
return;
}
unitLabel_->setText(QString::fromStdString(unitAbbreviation_.value_or("")));
}
template<class T> template<class T>
void SettingsInterface<T>::Impl::UpdateResetButton() void SettingsInterface<T>::Impl::UpdateResetButton()
{ {

View file

@ -7,6 +7,8 @@
#include <string> #include <string>
#include <vector> #include <vector>
class QLabel;
namespace scwx namespace scwx
{ {
namespace qt namespace qt
@ -91,6 +93,13 @@ public:
*/ */
void SetResetButton(QAbstractButton* button) override; void SetResetButton(QAbstractButton* button) override;
/**
* Sets the label for units from the settings dialog.
*
* @param label Unit label
*/
void SetUnitLabel(QLabel* label);
/** /**
* If the edit widget displays a different value than what is stored in the * If the edit widget displays a different value than what is stored in the
* settings variable, a mapping function must be provided in order to convert * settings variable, a mapping function must be provided in order to convert
@ -109,6 +118,14 @@ public:
*/ */
void SetMapToValueFunction(std::function<T(const std::string&)> function); void SetMapToValueFunction(std::function<T(const std::string&)> function);
/**
* Sets the unit to be used by this setting.
*
* @param scale The radio of the current unit to the base unit
* @param abbreviation The abreviation to be displayed
*/
void SetUnit(const double& scale, const std::string& abbreviation);
private: private:
class Impl; class Impl;
std::unique_ptr<Impl> p; std::unique_ptr<Impl> p;

View file

@ -26,16 +26,20 @@ public:
types::GetOtherUnitsName(types::OtherUnits::Default); types::GetOtherUnitsName(types::OtherUnits::Default);
std::string defaultSpeedUnitsValue = std::string defaultSpeedUnitsValue =
types::GetSpeedUnitsName(types::SpeedUnits::Knots); types::GetSpeedUnitsName(types::SpeedUnits::Knots);
std::string defaultDistanceUnitsValue =
types::GetDistanceUnitsName(types::DistanceUnits::Miles);
boost::to_lower(defaultAccumulationUnitsValue); boost::to_lower(defaultAccumulationUnitsValue);
boost::to_lower(defaultEchoTopsUnitsValue); boost::to_lower(defaultEchoTopsUnitsValue);
boost::to_lower(defaultOtherUnitsValue); boost::to_lower(defaultOtherUnitsValue);
boost::to_lower(defaultSpeedUnitsValue); boost::to_lower(defaultSpeedUnitsValue);
boost::to_lower(defaultDistanceUnitsValue);
accumulationUnits_.SetDefault(defaultAccumulationUnitsValue); accumulationUnits_.SetDefault(defaultAccumulationUnitsValue);
echoTopsUnits_.SetDefault(defaultEchoTopsUnitsValue); echoTopsUnits_.SetDefault(defaultEchoTopsUnitsValue);
otherUnits_.SetDefault(defaultOtherUnitsValue); otherUnits_.SetDefault(defaultOtherUnitsValue);
speedUnits_.SetDefault(defaultSpeedUnitsValue); speedUnits_.SetDefault(defaultSpeedUnitsValue);
distanceUnits_.SetDefault(defaultDistanceUnitsValue);
accumulationUnits_.SetValidator( accumulationUnits_.SetValidator(
SCWX_SETTINGS_ENUM_VALIDATOR(types::AccumulationUnits, SCWX_SETTINGS_ENUM_VALIDATOR(types::AccumulationUnits,
@ -53,6 +57,10 @@ public:
SCWX_SETTINGS_ENUM_VALIDATOR(types::SpeedUnits, SCWX_SETTINGS_ENUM_VALIDATOR(types::SpeedUnits,
types::SpeedUnitsIterator(), types::SpeedUnitsIterator(),
types::GetSpeedUnitsName)); types::GetSpeedUnitsName));
distanceUnits_.SetValidator(
SCWX_SETTINGS_ENUM_VALIDATOR(types::DistanceUnits,
types::DistanceUnitsIterator(),
types::GetDistanceUnitsName));
} }
~Impl() {} ~Impl() {}
@ -61,6 +69,7 @@ public:
SettingsVariable<std::string> echoTopsUnits_ {"echo_tops_units"}; SettingsVariable<std::string> echoTopsUnits_ {"echo_tops_units"};
SettingsVariable<std::string> otherUnits_ {"other_units"}; SettingsVariable<std::string> otherUnits_ {"other_units"};
SettingsVariable<std::string> speedUnits_ {"speed_units"}; SettingsVariable<std::string> speedUnits_ {"speed_units"};
SettingsVariable<std::string> distanceUnits_ {"distance_units"};
}; };
UnitSettings::UnitSettings() : UnitSettings::UnitSettings() :
@ -69,7 +78,8 @@ UnitSettings::UnitSettings() :
RegisterVariables({&p->accumulationUnits_, RegisterVariables({&p->accumulationUnits_,
&p->echoTopsUnits_, &p->echoTopsUnits_,
&p->otherUnits_, &p->otherUnits_,
&p->speedUnits_}); &p->speedUnits_,
&p->distanceUnits_});
SetDefaults(); SetDefaults();
} }
UnitSettings::~UnitSettings() = default; UnitSettings::~UnitSettings() = default;
@ -97,6 +107,11 @@ SettingsVariable<std::string>& UnitSettings::speed_units() const
return p->speedUnits_; return p->speedUnits_;
} }
SettingsVariable<std::string>& UnitSettings::distance_units() const
{
return p->distanceUnits_;
}
UnitSettings& UnitSettings::Instance() UnitSettings& UnitSettings::Instance()
{ {
static UnitSettings generalSettings_; static UnitSettings generalSettings_;
@ -108,7 +123,8 @@ bool operator==(const UnitSettings& lhs, const UnitSettings& rhs)
return (lhs.p->accumulationUnits_ == rhs.p->accumulationUnits_ && return (lhs.p->accumulationUnits_ == rhs.p->accumulationUnits_ &&
lhs.p->echoTopsUnits_ == rhs.p->echoTopsUnits_ && lhs.p->echoTopsUnits_ == rhs.p->echoTopsUnits_ &&
lhs.p->otherUnits_ == rhs.p->otherUnits_ && lhs.p->otherUnits_ == rhs.p->otherUnits_ &&
lhs.p->speedUnits_ == rhs.p->speedUnits_); lhs.p->speedUnits_ == rhs.p->speedUnits_ &&
lhs.p->distanceUnits_ == rhs.p->distanceUnits_);
} }
} // namespace settings } // namespace settings

View file

@ -29,6 +29,7 @@ public:
SettingsVariable<std::string>& echo_tops_units() const; SettingsVariable<std::string>& echo_tops_units() const;
SettingsVariable<std::string>& other_units() const; SettingsVariable<std::string>& other_units() const;
SettingsVariable<std::string>& speed_units() const; SettingsVariable<std::string>& speed_units() const;
SettingsVariable<std::string>& distance_units() const;
static UnitSettings& Instance(); static UnitSettings& Instance();

View file

@ -15,6 +15,7 @@ namespace types
static const std::unordered_map<LocationMethod, std::string> static const std::unordered_map<LocationMethod, std::string>
locationMethodName_ {{LocationMethod::Fixed, "Fixed"}, locationMethodName_ {{LocationMethod::Fixed, "Fixed"},
{LocationMethod::Track, "Track"}, {LocationMethod::Track, "Track"},
{LocationMethod::RadarSite, "Radar Site"},
{LocationMethod::County, "County"}, {LocationMethod::County, "County"},
{LocationMethod::All, "All"}, {LocationMethod::All, "All"},
{LocationMethod::Unknown, "?"}}; {LocationMethod::Unknown, "?"}};

View file

@ -15,6 +15,7 @@ enum class LocationMethod
{ {
Fixed, Fixed,
Track, Track,
RadarSite,
County, County,
All, All,
Unknown Unknown

View file

@ -89,12 +89,35 @@ static const std::unordered_map<SpeedUnits, float> speedUnitsScale_ {
{SpeedUnits::User, 1.0f}, {SpeedUnits::User, 1.0f},
{SpeedUnits::Unknown, 1.0f}}; {SpeedUnits::Unknown, 1.0f}};
static const std::unordered_map<DistanceUnits, std::string>
distanceUnitsAbbreviation_ {{DistanceUnits::Kilometers, "km"},
{DistanceUnits::Miles, "mi"},
{DistanceUnits::User, ""},
{DistanceUnits::Unknown, ""}};
static const std::unordered_map<DistanceUnits, std::string> distanceUnitsName_ {
{DistanceUnits::Kilometers, "Kilometers"},
{DistanceUnits::Miles, "Miles"},
{DistanceUnits::User, "User-defined"},
{DistanceUnits::Unknown, "?"}};
static constexpr auto distanceUnitsBase_ = units::kilometers<double> {1.0f};
static const std::unordered_map<DistanceUnits, double> distanceUnitsScale_ {
{DistanceUnits::Kilometers,
(distanceUnitsBase_ / units::kilometers<double> {1.0f})},
{DistanceUnits::Miles,
(distanceUnitsBase_ / units::miles<double> {1.0f})},
{DistanceUnits::User, 1.0f},
{DistanceUnits::Unknown, 1.0f}};
SCWX_GET_ENUM(AccumulationUnits, SCWX_GET_ENUM(AccumulationUnits,
GetAccumulationUnitsFromName, GetAccumulationUnitsFromName,
accumulationUnitsName_) accumulationUnitsName_)
SCWX_GET_ENUM(EchoTopsUnits, GetEchoTopsUnitsFromName, echoTopsUnitsName_) SCWX_GET_ENUM(EchoTopsUnits, GetEchoTopsUnitsFromName, echoTopsUnitsName_)
SCWX_GET_ENUM(OtherUnits, GetOtherUnitsFromName, otherUnitsName_) SCWX_GET_ENUM(OtherUnits, GetOtherUnitsFromName, otherUnitsName_)
SCWX_GET_ENUM(SpeedUnits, GetSpeedUnitsFromName, speedUnitsName_) SCWX_GET_ENUM(SpeedUnits, GetSpeedUnitsFromName, speedUnitsName_)
SCWX_GET_ENUM(DistanceUnits, GetDistanceUnitsFromName, distanceUnitsName_)
const std::string& GetAccumulationUnitsAbbreviation(AccumulationUnits units) const std::string& GetAccumulationUnitsAbbreviation(AccumulationUnits units)
{ {
@ -146,6 +169,21 @@ float GetSpeedUnitsScale(SpeedUnits units)
return speedUnitsScale_.at(units); return speedUnitsScale_.at(units);
} }
const std::string& GetDistanceUnitsAbbreviation(DistanceUnits units)
{
return distanceUnitsAbbreviation_.at(units);
}
const std::string& GetDistanceUnitsName(DistanceUnits units)
{
return distanceUnitsName_.at(units);
}
double GetDistanceUnitsScale(DistanceUnits units)
{
return distanceUnitsScale_.at(units);
}
} // namespace types } // namespace types
} // namespace qt } // namespace qt
} // namespace scwx } // namespace scwx

View file

@ -56,6 +56,17 @@ typedef scwx::util::
Iterator<SpeedUnits, SpeedUnits::KilometersPerHour, SpeedUnits::User> Iterator<SpeedUnits, SpeedUnits::KilometersPerHour, SpeedUnits::User>
SpeedUnitsIterator; SpeedUnitsIterator;
enum class DistanceUnits
{
Kilometers,
Miles,
User,
Unknown
};
typedef scwx::util::
Iterator<DistanceUnits, DistanceUnits::Kilometers, DistanceUnits::User>
DistanceUnitsIterator;
const std::string& GetAccumulationUnitsAbbreviation(AccumulationUnits units); const std::string& GetAccumulationUnitsAbbreviation(AccumulationUnits units);
const std::string& GetAccumulationUnitsName(AccumulationUnits units); const std::string& GetAccumulationUnitsName(AccumulationUnits units);
AccumulationUnits GetAccumulationUnitsFromName(const std::string& name); AccumulationUnits GetAccumulationUnitsFromName(const std::string& name);
@ -74,6 +85,11 @@ const std::string& GetSpeedUnitsName(SpeedUnits units);
SpeedUnits GetSpeedUnitsFromName(const std::string& name); SpeedUnits GetSpeedUnitsFromName(const std::string& name);
float GetSpeedUnitsScale(SpeedUnits units); float GetSpeedUnitsScale(SpeedUnits units);
const std::string& GetDistanceUnitsAbbreviation(DistanceUnits units);
const std::string& GetDistanceUnitsName(DistanceUnits units);
DistanceUnits GetDistanceUnitsFromName(const std::string& name);
double GetDistanceUnitsScale(DistanceUnits units);
} // namespace types } // namespace types
} // namespace qt } // namespace qt
} // namespace scwx } // namespace scwx

View file

@ -102,6 +102,16 @@ public:
types::GetSpeedUnitsName); types::GetSpeedUnitsName);
AddRow(speedUnits_, "Speed", speedComboBox); AddRow(speedUnits_, "Speed", speedComboBox);
QComboBox* distanceComboBox = new QComboBox(self);
distanceComboBox->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Preferred);
distanceUnits_.SetSettingsVariable(unitSettings.distance_units());
SCWX_SETTINGS_COMBO_BOX(distanceUnits_,
distanceComboBox,
types::DistanceUnitsIterator(),
types::GetDistanceUnitsName);
AddRow(distanceUnits_, "Distance", distanceComboBox);
QComboBox* otherComboBox = new QComboBox(self); QComboBox* otherComboBox = new QComboBox(self);
otherComboBox->setSizePolicy(QSizePolicy::Expanding, otherComboBox->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Preferred); QSizePolicy::Preferred);
@ -127,6 +137,7 @@ public:
settings::SettingsInterface<std::string> echoTopsUnits_ {}; settings::SettingsInterface<std::string> echoTopsUnits_ {};
settings::SettingsInterface<std::string> otherUnits_ {}; settings::SettingsInterface<std::string> otherUnits_ {};
settings::SettingsInterface<std::string> speedUnits_ {}; settings::SettingsInterface<std::string> speedUnits_ {};
settings::SettingsInterface<std::string> distanceUnits_ {};
}; };
UnitSettingsWidget::UnitSettingsWidget(QWidget* parent) : UnitSettingsWidget::UnitSettingsWidget(QWidget* parent) :

View file

@ -14,12 +14,14 @@
#include <scwx/qt/settings/palette_settings.hpp> #include <scwx/qt/settings/palette_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/types/alert_types.hpp> #include <scwx/qt/types/alert_types.hpp>
#include <scwx/qt/types/font_types.hpp> #include <scwx/qt/types/font_types.hpp>
#include <scwx/qt/types/location_types.hpp> #include <scwx/qt/types/location_types.hpp>
#include <scwx/qt/types/qt_types.hpp> #include <scwx/qt/types/qt_types.hpp>
#include <scwx/qt/types/text_types.hpp> #include <scwx/qt/types/text_types.hpp>
#include <scwx/qt/types/time_types.hpp> #include <scwx/qt/types/time_types.hpp>
#include <scwx/qt/types/unit_types.hpp>
#include <scwx/qt/ui/county_dialog.hpp> #include <scwx/qt/ui/county_dialog.hpp>
#include <scwx/qt/ui/radar_site_dialog.hpp> #include <scwx/qt/ui/radar_site_dialog.hpp>
#include <scwx/qt/ui/serial_port_dialog.hpp> #include <scwx/qt/ui/serial_port_dialog.hpp>
@ -103,6 +105,7 @@ public:
explicit SettingsDialogImpl(SettingsDialog* self) : explicit SettingsDialogImpl(SettingsDialog* self) :
self_ {self}, self_ {self},
radarSiteDialog_ {new RadarSiteDialog(self)}, radarSiteDialog_ {new RadarSiteDialog(self)},
alertAudioRadarSiteDialog_ {new RadarSiteDialog(self)},
gpsSourceDialog_ {new SerialPortDialog(self)}, gpsSourceDialog_ {new SerialPortDialog(self)},
countyDialog_ {new CountyDialog(self)}, countyDialog_ {new CountyDialog(self)},
fontDialog_ {new QFontDialog(self)}, fontDialog_ {new QFontDialog(self)},
@ -134,6 +137,8 @@ public:
&alertAudioLocationMethod_, &alertAudioLocationMethod_,
&alertAudioLatitude_, &alertAudioLatitude_,
&alertAudioLongitude_, &alertAudioLongitude_,
&alertAudioRadarSite_,
&alertAudioRadius_,
&alertAudioCounty_, &alertAudioCounty_,
&hoverTextWrap_, &hoverTextWrap_,
&tooltipMethod_, &tooltipMethod_,
@ -175,6 +180,7 @@ public:
void ShowColorDialog(QLineEdit* lineEdit, QFrame* frame = nullptr); void ShowColorDialog(QLineEdit* lineEdit, QFrame* frame = nullptr);
void UpdateRadarDialogLocation(const std::string& id); void UpdateRadarDialogLocation(const std::string& id);
void UpdateAlertRadarDialogLocation(const std::string& id);
QFont GetSelectedFont(); QFont GetSelectedFont();
void SelectFontCategory(types::FontCategory fontCategory); void SelectFontCategory(types::FontCategory fontCategory);
@ -199,6 +205,7 @@ public:
SettingsDialog* self_; SettingsDialog* self_;
RadarSiteDialog* radarSiteDialog_; RadarSiteDialog* radarSiteDialog_;
RadarSiteDialog* alertAudioRadarSiteDialog_;
SerialPortDialog* gpsSourceDialog_; SerialPortDialog* gpsSourceDialog_;
CountyDialog* countyDialog_; CountyDialog* countyDialog_;
QFontDialog* fontDialog_; QFontDialog* fontDialog_;
@ -252,6 +259,8 @@ public:
settings::SettingsInterface<std::string> alertAudioLocationMethod_ {}; settings::SettingsInterface<std::string> alertAudioLocationMethod_ {};
settings::SettingsInterface<double> alertAudioLatitude_ {}; settings::SettingsInterface<double> alertAudioLatitude_ {};
settings::SettingsInterface<double> alertAudioLongitude_ {}; settings::SettingsInterface<double> alertAudioLongitude_ {};
settings::SettingsInterface<std::string> alertAudioRadarSite_ {};
settings::SettingsInterface<double> alertAudioRadius_ {};
settings::SettingsInterface<std::string> alertAudioCounty_ {}; settings::SettingsInterface<std::string> alertAudioCounty_ {};
std::unordered_map<awips::Phenomenon, settings::SettingsInterface<bool>> std::unordered_map<awips::Phenomenon, settings::SettingsInterface<bool>>
@ -339,6 +348,30 @@ void SettingsDialogImpl::ConnectSignals()
} }
}); });
QObject::connect(self_->ui->alertAudioRadarSiteSelectButton,
&QAbstractButton::clicked,
self_,
[this]() { alertAudioRadarSiteDialog_->show(); });
QObject::connect(alertAudioRadarSiteDialog_,
&RadarSiteDialog::accepted,
self_,
[this]()
{
std::string id =
alertAudioRadarSiteDialog_->radar_site();
std::shared_ptr<config::RadarSite> radarSite =
config::RadarSite::Get(id);
if (radarSite != nullptr)
{
self_->ui->alertAudioRadarSiteComboBox
->setCurrentText(QString::fromStdString(
RadarSiteLabel(radarSite)));
}
});
QObject::connect(self_->ui->gpsSourceSelectButton, QObject::connect(self_->ui->gpsSourceSelectButton,
&QAbstractButton::clicked, &QAbstractButton::clicked,
self_, self_,
@ -504,11 +537,18 @@ void SettingsDialogImpl::SetupGeneralTab()
const std::shared_ptr<config::RadarSite>& b) const std::shared_ptr<config::RadarSite>& b)
{ return a->id() < b->id(); }); { return a->id() < b->id(); });
// Add default and follow options to radar sites
self_->ui->alertAudioRadarSiteComboBox->addItem(
QString::fromStdString("default"));
self_->ui->alertAudioRadarSiteComboBox->addItem(
QString::fromStdString("follow"));
// Add sorted radar sites // Add sorted radar sites
for (std::shared_ptr<config::RadarSite>& radarSite : radarSites) for (std::shared_ptr<config::RadarSite>& radarSite : radarSites)
{ {
QString text = QString::fromStdString(RadarSiteLabel(radarSite)); QString text = QString::fromStdString(RadarSiteLabel(radarSite));
self_->ui->radarSiteComboBox->addItem(text); self_->ui->radarSiteComboBox->addItem(text);
self_->ui->alertAudioRadarSiteComboBox->addItem(text);
} }
defaultRadarSite_.SetSettingsVariable(generalSettings.default_radar_site()); defaultRadarSite_.SetSettingsVariable(generalSettings.default_radar_site());
@ -900,6 +940,12 @@ void SettingsDialogImpl::SetupAudioTab()
bool coordinateEntryEnabled = bool coordinateEntryEnabled =
locationMethod == types::LocationMethod::Fixed; locationMethod == types::LocationMethod::Fixed;
bool radarSiteEntryEnable =
locationMethod == types::LocationMethod::RadarSite;
bool radiusEntryEnable =
locationMethod == types::LocationMethod::Fixed ||
locationMethod == types::LocationMethod::Track ||
locationMethod == types::LocationMethod::RadarSite;
bool countyEntryEnabled = bool countyEntryEnabled =
locationMethod == types::LocationMethod::County; locationMethod == types::LocationMethod::County;
@ -912,6 +958,18 @@ void SettingsDialogImpl::SetupAudioTab()
self_->ui->resetAlertAudioLongitudeButton->setEnabled( self_->ui->resetAlertAudioLongitudeButton->setEnabled(
coordinateEntryEnabled); coordinateEntryEnabled);
self_->ui->alertAudioRadarSiteComboBox->setEnabled(
radarSiteEntryEnable);
self_->ui->alertAudioRadarSiteSelectButton->setEnabled(
radarSiteEntryEnable);
self_->ui->resetAlertAudioRadarSiteButton->setEnabled(
radarSiteEntryEnable);
self_->ui->alertAudioRadiusSpinBox->setEnabled(
radiusEntryEnable);
self_->ui->resetAlertAudioRadiusButton->setEnabled(
radiusEntryEnable);
self_->ui->alertAudioCountyLineEdit->setEnabled(countyEntryEnabled); self_->ui->alertAudioCountyLineEdit->setEnabled(countyEntryEnabled);
self_->ui->alertAudioCountySelectButton->setEnabled( self_->ui->alertAudioCountySelectButton->setEnabled(
countyEntryEnabled); countyEntryEnabled);
@ -983,6 +1041,64 @@ void SettingsDialogImpl::SetupAudioTab()
alertAudioLongitude_.SetResetButton( alertAudioLongitude_.SetResetButton(
self_->ui->resetAlertAudioLongitudeButton); self_->ui->resetAlertAudioLongitudeButton);
alertAudioRadarSite_.SetSettingsVariable(audioSettings.alert_radar_site());
alertAudioRadarSite_.SetMapFromValueFunction(
[](const std::string& id) -> std::string
{
// Get the radar site associated with the ID
std::shared_ptr<config::RadarSite> radarSite =
config::RadarSite::Get(id);
if (radarSite == nullptr)
{
// No radar site found, just return the ID
return id;
}
// Add location details to the radar site
return RadarSiteLabel(radarSite);
});
alertAudioRadarSite_.SetMapToValueFunction(
[](const std::string& text) -> std::string
{
// Find the position of location details
size_t pos = text.rfind(" (");
if (pos == std::string::npos)
{
// No location details found, just return the text
return text;
}
// Remove location details from the radar site
return text.substr(0, pos);
});
alertAudioRadarSite_.SetEditWidget(self_->ui->alertAudioRadarSiteComboBox);
alertAudioRadarSite_.SetResetButton(
self_->ui->resetAlertAudioRadarSiteButton);
UpdateAlertRadarDialogLocation(audioSettings.alert_radar_site().GetValue());
alertAudioRadius_.SetSettingsVariable(audioSettings.alert_radius());
alertAudioRadius_.SetEditWidget(self_->ui->alertAudioRadiusSpinBox);
alertAudioRadius_.SetResetButton(
self_->ui->resetAlertAudioRadiusButton);
alertAudioRadius_.SetUnitLabel(self_->ui->alertAudioRadiusUnitsLabel);
auto alertAudioRadiusUpdateUnits = [this](const std::string& newValue)
{
types::DistanceUnits radiusUnits =
types::GetDistanceUnitsFromName(newValue);
double radiusScale = types::GetDistanceUnitsScale(radiusUnits);
std::string abbreviation =
types::GetDistanceUnitsAbbreviation(radiusUnits);
alertAudioRadius_.SetUnit(radiusScale, abbreviation);
};
settings::UnitSettings::Instance()
.distance_units()
.RegisterValueStagedCallback(alertAudioRadiusUpdateUnits);
alertAudioRadiusUpdateUnits(
settings::UnitSettings::Instance().distance_units().GetValue());
auto& alertAudioPhenomena = types::GetAlertAudioPhenomena(); auto& alertAudioPhenomena = types::GetAlertAudioPhenomena();
auto alertAudioLayout = auto alertAudioLayout =
static_cast<QGridLayout*>(self_->ui->alertAudioGroupBox->layout()); static_cast<QGridLayout*>(self_->ui->alertAudioGroupBox->layout());
@ -1268,6 +1384,19 @@ void SettingsDialogImpl::UpdateRadarDialogLocation(const std::string& id)
} }
} }
void SettingsDialogImpl::UpdateAlertRadarDialogLocation(const std::string& id)
{
std::shared_ptr<config::RadarSite> radarSite = config::RadarSite::Get(id);
if (radarSite != nullptr)
{
alertAudioRadarSiteDialog_->HandleMapUpdate(radarSite->latitude(),
radarSite->longitude());
}
}
QFont SettingsDialogImpl::GetSelectedFont() QFont SettingsDialogImpl::GetSelectedFont()
{ {
std::string fontFamily = fontFamilies_.at(selectedFontCategory_) std::string fontFamily = fontFamilies_.at(selectedFontCategory_)

View file

@ -122,7 +122,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>3</number>
</property> </property>
<widget class="QWidget" name="general"> <widget class="QWidget" name="general">
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
@ -135,9 +135,9 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>-113</y> <y>0</y>
<width>511</width> <width>511</width>
<height>669</height> <height>647</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
@ -610,8 +610,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>98</width> <width>66</width>
<height>28</height> <height>18</height>
</rect> </rect>
</property> </property>
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
@ -691,45 +691,6 @@
<string>Alerts</string> <string>Alerts</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_10"> <layout class="QGridLayout" name="gridLayout_10">
<item row="2" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Latitude</string>
</property>
</widget>
</item>
<item row="1" column="6">
<widget class="QToolButton" name="resetAlertAudioLocationMethodButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../../scwx-qt.qrc">
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Longitude</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QToolButton" name="alertAudioSoundSelectButton">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Location Method</string>
</property>
</widget>
</item>
<item row="3" column="6"> <item row="3" column="6">
<widget class="QToolButton" name="resetAlertAudioLongitudeButton"> <widget class="QToolButton" name="resetAlertAudioLongitudeButton">
<property name="text"> <property name="text">
@ -741,11 +702,19 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="5"> <item row="5" column="1" colspan="2">
<widget class="QToolButton" name="alertAudioSoundStopButton"> <widget class="QDoubleSpinBox" name="alertAudioRadiusSpinBox">
<property name="icon"> <property name="decimals">
<iconset resource="../../../../scwx-qt.qrc"> <number>2</number>
<normaloff>:/res/icons/font-awesome-6/stop-solid.svg</normaloff>:/res/icons/font-awesome-6/stop-solid.svg</iconset> </property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>9999999999999.000000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property> </property>
</widget> </widget>
</item> </item>
@ -760,6 +729,23 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1" colspan="2">
<widget class="QComboBox" name="alertAudioLocationMethodComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_19">
<property name="text">
<string>County</string>
</property>
</widget>
</item>
<item row="0" column="6"> <item row="0" column="6">
<widget class="QToolButton" name="resetAlertAudioSoundButton"> <widget class="QToolButton" name="resetAlertAudioSoundButton">
<property name="text"> <property name="text">
@ -771,25 +757,10 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="4"> <item row="5" column="3">
<widget class="QToolButton" name="alertAudioSoundTestButton"> <widget class="QLabel" name="alertAudioRadiusUnitsLabel">
<property name="icon">
<iconset resource="../../../../scwx-qt.qrc">
<normaloff>:/res/icons/font-awesome-6/play-solid.svg</normaloff>:/res/icons/font-awesome-6/play-solid.svg</iconset>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_17">
<property name="text"> <property name="text">
<string>Sound</string> <string/>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_19">
<property name="text">
<string>County</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -809,6 +780,34 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="6">
<widget class="QToolButton" name="resetAlertAudioLocationMethodButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../../scwx-qt.qrc">
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QToolButton" name="alertAudioSoundSelectButton">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="7" column="3">
<widget class="QToolButton" name="alertAudioCountySelectButton">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QLineEdit" name="alertAudioSoundLineEdit"/>
</item>
<item row="2" column="1" colspan="2"> <item row="2" column="1" colspan="2">
<widget class="QDoubleSpinBox" name="alertAudioLatitudeSpinBox"> <widget class="QDoubleSpinBox" name="alertAudioLatitudeSpinBox">
<property name="decimals"> <property name="decimals">
@ -825,21 +824,16 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1" colspan="2"> <item row="0" column="5">
<widget class="QComboBox" name="alertAudioLocationMethodComboBox"> <widget class="QToolButton" name="alertAudioSoundStopButton">
<property name="sizePolicy"> <property name="icon">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"> <iconset resource="../../../../scwx-qt.qrc">
<horstretch>0</horstretch> <normaloff>:/res/icons/font-awesome-6/stop-solid.svg</normaloff>:/res/icons/font-awesome-6/stop-solid.svg</iconset>
<verstretch>0</verstretch>
</sizepolicy>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1" colspan="2"> <item row="5" column="6">
<widget class="QLineEdit" name="alertAudioSoundLineEdit"/> <widget class="QToolButton" name="resetAlertAudioRadiusButton">
</item>
<item row="4" column="6">
<widget class="QToolButton" name="resetAlertAudioCountyButton">
<property name="text"> <property name="text">
<string>...</string> <string>...</string>
</property> </property>
@ -849,14 +843,21 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="3"> <item row="2" column="0">
<widget class="QToolButton" name="alertAudioCountySelectButton"> <widget class="QLabel" name="label_14">
<property name="text"> <property name="text">
<string>...</string> <string>Latitude</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="2"> <item row="0" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Sound</string>
</property>
</widget>
</item>
<item row="7" column="2">
<widget class="QLabel" name="alertAudioCountyLabel"> <widget class="QLabel" name="alertAudioCountyLabel">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred"> <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
@ -869,7 +870,15 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1"> <item row="0" column="4">
<widget class="QToolButton" name="alertAudioSoundTestButton">
<property name="icon">
<iconset resource="../../../../scwx-qt.qrc">
<normaloff>:/res/icons/font-awesome-6/play-solid.svg</normaloff>:/res/icons/font-awesome-6/play-solid.svg</iconset>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLineEdit" name="alertAudioCountyLineEdit"> <widget class="QLineEdit" name="alertAudioCountyLineEdit">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"> <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@ -882,6 +891,66 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Location Method</string>
</property>
</widget>
</item>
<item row="7" column="6">
<widget class="QToolButton" name="resetAlertAudioCountyButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../../scwx-qt.qrc">
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="alertAudioRadiusLabel">
<property name="text">
<string>Radius</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Longitude</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_28">
<property name="text">
<string>Radar Site</string>
</property>
</widget>
</item>
<item row="4" column="3">
<widget class="QToolButton" name="alertAudioRadarSiteSelectButton">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="4" column="6">
<widget class="QToolButton" name="resetAlertAudioRadarSiteButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../../scwx-qt.qrc">
<normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</normaloff>:/res/icons/font-awesome-6/rotate-left-solid.svg</iconset>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<widget class="QComboBox" name="alertAudioRadarSiteComboBox"/>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View file

@ -5,7 +5,9 @@
#include <GeographicLib/Gnomonic.hpp> #include <GeographicLib/Gnomonic.hpp>
#include <geos/algorithm/PointLocation.h> #include <geos/algorithm/PointLocation.h>
#include <geos/operation/distance/DistanceOp.h>
#include <geos/geom/CoordinateSequence.h> #include <geos/geom/CoordinateSequence.h>
#include <geos/geom/GeometryFactory.h>
namespace scwx namespace scwx
{ {
@ -28,6 +30,40 @@ const ::GeographicLib::Geodesic& DefaultGeodesic()
return geodesic_; return geodesic_;
} }
bool GnomonicAreaContainsCenter(geos::geom::CoordinateSequence sequence)
{
// Cannot have an area with just two points
if (sequence.size() <= 2 ||
(sequence.size() == 3 && sequence.front() == sequence.back()))
{
return false;
}
bool areaContainsPoint = false;
geos::geom::CoordinateXY zero {};
// If the sequence is not a ring, add the first point again for closure
if (!sequence.isRing())
{
sequence.add(sequence.front(), false);
}
// The sequence should be a ring at this point, but make sure
if (sequence.isRing())
{
try
{
areaContainsPoint =
geos::algorithm::PointLocation::isInRing(zero, &sequence);
}
catch (const std::exception& ex)
{
logger_->warn("Invalid area sequence. {}", ex.what());
}
}
return areaContainsPoint;
}
bool AreaContainsPoint(const std::vector<common::Coordinate>& area, bool AreaContainsPoint(const std::vector<common::Coordinate>& area,
const common::Coordinate& point) const common::Coordinate& point)
{ {
@ -82,6 +118,12 @@ bool AreaContainsPoint(const std::vector<common::Coordinate>& area,
return areaContainsPoint; return areaContainsPoint;
} }
units::angle::degrees<double> units::angle::degrees<double>
GetAngle(double lat1, double lon1, double lat2, double lon2) GetAngle(double lat1, double lon1, double lat2, double lon2)
{ {
@ -137,6 +179,116 @@ GetDistance(double lat1, double lon1, double lat2, double lon2)
return units::length::meters<double> {distance}; return units::length::meters<double> {distance};
} }
/*
* Uses the gnomonic projection to determine if the area is in the radius.
*
* The basic algorithm is as follows:
* - Get a gnomonic projection centered on the point of the area
* - Find the point on the area which is closest to the center
* - Convert the closest point back to latitude and longitude.
* - Find the distance form the closest point to the point.
*
* The first property needed to make this work is that great circles become
* lines in the projection, which allows the area to be converted to strait
* lines. This is generally true for gnomic projections.
*
* The second property needed to make this work is that a point further away
* on the geodesic must be further away on the projection. This means that
* the closes point on the projection is also the closest point on the geodesic.
* This holds for spherical geodesics and is an approximation non spherical
* geodesics.
*
* This algorithm only works if the area is fully on the hemisphere centered
* on the point. Otherwise, this falls back to centroid based distances.
*
* If the point is inside the area, 0 is always returned.
*/
units::length::meters<double>
GetDistanceAreaPoint(const std::vector<common::Coordinate>& area,
const common::Coordinate& point)
{
// Ensure that the same geodesic is used here as is for the distance
// calculation
::GeographicLib::Gnomonic gnomonic =
::GeographicLib::Gnomonic(DefaultGeodesic());
geos::geom::CoordinateSequence sequence {};
double x;
double y;
bool useCentroid = false;
// Using a gnomonic projection with the test point as the center
// latitude/longitude, the projected test point will be at (0, 0)
geos::geom::CoordinateXY zero {};
// Create the area coordinate sequence using a gnomonic projection
for (auto& areaCoordinate : area)
{
gnomonic.Forward(point.latitude_,
point.longitude_,
areaCoordinate.latitude_,
areaCoordinate.longitude_,
x,
y);
// Check if the current point is in the hemisphere centered on the point
// if not, fall back to using centroid.
if (std::isnan(x) || std::isnan(y))
{
useCentroid = true;
}
sequence.add(x, y);
}
units::length::meters<double> distance;
if (useCentroid)
{
common::Coordinate centroid = common::GetCentroid(area);
distance = GetDistance(point.latitude_,
point.longitude_,
centroid.latitude_,
centroid.longitude_);
}
else if (GnomonicAreaContainsCenter(sequence))
{
distance = units::length::meters<double>(0);
}
else
{
// Get the closes point on the geometry
auto geometryFactory = geos::geom::GeometryFactory::getDefaultInstance();
auto lineString = geometryFactory->createLineString(sequence);
auto zeroPoint = geometryFactory->createPoint(zero);
std::unique_ptr<geos::geom::CoordinateSequence> closestPoints =
geos::operation::distance::DistanceOp::nearestPoints(lineString.get(),
zeroPoint.get());
double closestLat;
double closestLon;
gnomonic.Reverse(point.latitude_,
point.longitude_,
closestPoints->getX(0),
closestPoints->getY(0),
closestLat,
closestLon);
distance = GetDistance(point.latitude_,
point.longitude_,
closestLat,
closestLon);
}
return distance;
}
bool AreaInRangeOfPoint(const std::vector<common::Coordinate>& area,
const common::Coordinate& point,
const units::length::meters<double> distance)
{
return GetDistanceAreaPoint(area, point) <= distance;
}
} // namespace GeographicLib } // namespace GeographicLib
} // namespace util } // namespace util
} // namespace qt } // namespace qt

View file

@ -90,6 +90,37 @@ common::Coordinate GetCoordinate(const common::Coordinate& center,
units::length::meters<double> units::length::meters<double>
GetDistance(double lat1, double lon1, double lat2, double lon2); GetDistance(double lat1, double lon1, double lat2, double lon2);
/**
* Get the distance from an area to a point. If the area is less than a quarter
* radius of the Earth away, this is the closest distance between the area and
* the point. Otherwise it is the distance from the centroid of the area to the
* point. Finally, if the point is in the area, it is always 0.
*
* @param [in] area A vector of Coordinates representing the area
* @param [in] point The point to check against the area
*
* @return true if area is inside the radius of the point
*/
units::length::meters<double>
GetDistanceAreaPoint(const std::vector<common::Coordinate>& area,
const common::Coordinate& point);
/**
* Determine if an area/ring, oriented in either direction, is within a
* distance of a point. A point lying on the area boundary is considered to be
* inside the area, and thus always in range. Any part of the area being inside
* the radius counts as inside. Uses GetDistanceAreaPoint to get the distance.
*
* @param [in] area A vector of Coordinates representing the area
* @param [in] point The point to check against the area
* @param [in] distance The max distance in meters
*
* @return true if area is inside the radius of the point
*/
bool AreaInRangeOfPoint(const std::vector<common::Coordinate>& area,
const common::Coordinate& point,
const units::length::meters<double> distance);
} // namespace GeographicLib } // namespace GeographicLib
} // namespace util } // namespace util
} // namespace qt } // namespace qt

@ -1 +1 @@
Subproject commit 0ea32947d6a6e39a69d931b2827056e7bc58cbba Subproject commit 5115993596cfe6528b7a07c0b16dbd1ae16ce4df

View file

@ -0,0 +1,69 @@
#include <scwx/qt/util/geographic_lib.hpp>
#include <gtest/gtest.h>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/filtering_streambuf.hpp>
namespace scwx
{
namespace util
{
std::vector<common::Coordinate> area = {
common::Coordinate(37.0193692, -91.8778413),
common::Coordinate(36.9719180, -91.3006973),
common::Coordinate(36.7270831, -91.6815753),
};
TEST(geographic_lib, area_in_range_inside)
{
auto inside = common::Coordinate(36.9241584, -91.6425933);
bool value;
// inside is always true
value = scwx::qt::util::GeographicLib::AreaInRangeOfPoint(
area, inside, units::length::meters<double>(0));
EXPECT_EQ(value, true);
value = scwx::qt::util::GeographicLib::AreaInRangeOfPoint(
area, inside, units::length::meters<double>(1e6));
EXPECT_EQ(value, true);
}
TEST(geographic_lib, area_in_range_near)
{
auto near = common::Coordinate(36.8009181, -91.3922700);
bool value;
value = scwx::qt::util::GeographicLib::AreaInRangeOfPoint(
area, near, units::length::meters<double>(9000));
EXPECT_EQ(value, false);
value = scwx::qt::util::GeographicLib::AreaInRangeOfPoint(
area, near, units::length::meters<double>(10100));
EXPECT_EQ(value, true);
value = scwx::qt::util::GeographicLib::AreaInRangeOfPoint(
area, near, units::length::meters<double>(1e6));
EXPECT_EQ(value, true);
}
TEST(geographic_lib, area_in_range_far)
{
auto far = common::Coordinate(37.6481966, -94.2163834);
bool value;
value = scwx::qt::util::GeographicLib::AreaInRangeOfPoint(
area, far, units::length::meters<double>(9000));
EXPECT_EQ(value, false);
value = scwx::qt::util::GeographicLib::AreaInRangeOfPoint(
area, far, units::length::meters<double>(10100));
EXPECT_EQ(value, false);
value = scwx::qt::util::GeographicLib::AreaInRangeOfPoint(
area, far, units::length::meters<double>(100e3));
EXPECT_EQ(value, false);
value = scwx::qt::util::GeographicLib::AreaInRangeOfPoint(
area, far, units::length::meters<double>(300e3));
EXPECT_EQ(value, true);
}
} // namespace util
} // namespace scwx

View file

@ -28,7 +28,8 @@ set(SRC_QT_MAP_TESTS source/scwx/qt/map/map_provider.test.cpp)
set(SRC_QT_MODEL_TESTS source/scwx/qt/model/imgui_context_model.test.cpp) set(SRC_QT_MODEL_TESTS source/scwx/qt/model/imgui_context_model.test.cpp)
set(SRC_QT_SETTINGS_TESTS source/scwx/qt/settings/settings_container.test.cpp set(SRC_QT_SETTINGS_TESTS source/scwx/qt/settings/settings_container.test.cpp
source/scwx/qt/settings/settings_variable.test.cpp) source/scwx/qt/settings/settings_variable.test.cpp)
set(SRC_QT_UTIL_TESTS source/scwx/qt/util/q_file_input_stream.test.cpp) set(SRC_QT_UTIL_TESTS source/scwx/qt/util/q_file_input_stream.test.cpp
source/scwx/qt/util/geographic_lib.test.cpp)
set(SRC_UTIL_TESTS source/scwx/util/float.test.cpp set(SRC_UTIL_TESTS source/scwx/util/float.test.cpp
source/scwx/util/rangebuf.test.cpp source/scwx/util/rangebuf.test.cpp
source/scwx/util/streams.test.cpp source/scwx/util/streams.test.cpp