mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-30 14:20:05 +00:00
Merge pull request #250 from AdenKoperczak/audio_location_methods
Radar Site Audio Location Method and Radius
This commit is contained in:
commit
7b41184fbb
23 changed files with 802 additions and 120 deletions
|
|
@ -1466,6 +1466,7 @@ void MainWindowImpl::UpdateRadarSite()
|
|||
timelineManager_->SetRadarSite("?");
|
||||
}
|
||||
|
||||
alertManager_->SetRadarSite(radarSite);
|
||||
placefileManager_->SetRadarSite(radarSite);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
#include <scwx/qt/types/location_types.hpp>
|
||||
#include <scwx/qt/util/geographic_lib.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/thread_pool.hpp>
|
||||
|
|
@ -74,6 +76,8 @@ public:
|
|||
PositionManager::Instance()};
|
||||
std::shared_ptr<TextEventManager> textEventManager_ {
|
||||
TextEventManager::Instance()};
|
||||
|
||||
std::shared_ptr<config::RadarSite> radarSite_ {};
|
||||
};
|
||||
|
||||
AlertManager::AlertManager() : p(std::make_unique<Impl>(this)) {}
|
||||
|
|
@ -100,6 +104,33 @@ common::Coordinate AlertManager::Impl::CurrentCoordinate(
|
|||
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;
|
||||
}
|
||||
|
|
@ -118,6 +149,8 @@ void AlertManager::Impl::HandleAlert(const types::TextEventKey& key,
|
|||
audioSettings.alert_location_method().GetValue());
|
||||
common::Coordinate currentCoordinate = CurrentCoordinate(locationMethod);
|
||||
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);
|
||||
|
||||
|
|
@ -145,13 +178,16 @@ void AlertManager::Impl::HandleAlert(const types::TextEventKey& key,
|
|||
bool activeAtLocation = (locationMethod == types::LocationMethod::All);
|
||||
|
||||
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
|
||||
auto alertCoordinates = segment->codedLocation_->coordinates();
|
||||
|
||||
activeAtLocation = util::GeographicLib::AreaContainsPoint(
|
||||
alertCoordinates, currentCoordinate);
|
||||
activeAtLocation = util::GeographicLib::AreaInRangeOfPoint(
|
||||
alertCoordinates,
|
||||
currentCoordinate,
|
||||
alertRadius);
|
||||
}
|
||||
else if (locationMethod == types::LocationMethod::County)
|
||||
{
|
||||
|
|
@ -183,6 +219,27 @@ void AlertManager::Impl::UpdateLocationTracking(
|
|||
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()
|
||||
{
|
||||
static std::weak_ptr<AlertManager> alertManagerReference_ {};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/qt/config/radar_site.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
|
|
@ -20,6 +22,7 @@ public:
|
|||
explicit AlertManager();
|
||||
~AlertManager();
|
||||
|
||||
void SetRadarSite(const std::shared_ptr<config::RadarSite>& radarSite);
|
||||
static std::shared_ptr<AlertManager> Instance();
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
#include <scwx/qt/model/alert_model.hpp>
|
||||
#include <scwx/qt/config/county_database.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/unit_types.hpp>
|
||||
#include <scwx/qt/util/geographic_lib.hpp>
|
||||
#include <scwx/common/geographic.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
#include <scwx/util/strings.hpp>
|
||||
#include <scwx/util/time.hpp>
|
||||
|
||||
|
||||
#include <format>
|
||||
|
||||
#include <QApplication>
|
||||
|
|
@ -73,7 +76,6 @@ public:
|
|||
double,
|
||||
types::TextEventHash<types::TextEventKey>>
|
||||
distanceMap_;
|
||||
scwx::common::DistanceType distanceDisplay_;
|
||||
scwx::common::Coordinate previousPosition_;
|
||||
};
|
||||
|
||||
|
|
@ -182,18 +184,19 @@ QVariant AlertModel::data(const QModelIndex& index, int role) const
|
|||
case static_cast<int>(Column::Distance):
|
||||
if (role == Qt::DisplayRole)
|
||||
{
|
||||
if (p->distanceDisplay_ == scwx::common::DistanceType::Miles)
|
||||
{
|
||||
return QString("%1 mi").arg(
|
||||
static_cast<uint32_t>(p->distanceMap_.at(textEventKey) *
|
||||
scwx::common::kMilesPerMeter));
|
||||
}
|
||||
else
|
||||
{
|
||||
return QString("%1 km").arg(
|
||||
static_cast<uint32_t>(p->distanceMap_.at(textEventKey) *
|
||||
scwx::common::kKilometersPerMeter));
|
||||
}
|
||||
const std::string distanceUnitName =
|
||||
settings::UnitSettings::Instance().distance_units().GetValue();
|
||||
types::DistanceUnits distanceUnits =
|
||||
types::GetDistanceUnitsFromName(distanceUnitName);
|
||||
double distanceScale = types::GetDistanceUnitsScale(distanceUnits);
|
||||
std::string abbreviation =
|
||||
types::GetDistanceUnitsAbbreviation(distanceUnits);
|
||||
|
||||
return QString("%1 %2")
|
||||
.arg(static_cast<uint32_t>(p->distanceMap_.at(textEventKey) *
|
||||
scwx::common::kKilometersPerMeter *
|
||||
distanceScale))
|
||||
.arg(QString::fromStdString(abbreviation));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -419,7 +422,6 @@ AlertModelImpl::AlertModelImpl() :
|
|||
textEventKeys_ {},
|
||||
geodesic_(util::GeographicLib::DefaultGeodesic()),
|
||||
distanceMap_ {},
|
||||
distanceDisplay_ {scwx::common::DistanceType::Miles},
|
||||
previousPosition_ {}
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
#include <scwx/qt/model/radar_site_model.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/unit_types.hpp>
|
||||
#include <scwx/qt/util/geographic_lib.hpp>
|
||||
#include <scwx/qt/util/json.hpp>
|
||||
#include <scwx/common/geographic.hpp>
|
||||
|
|
@ -36,7 +38,6 @@ public:
|
|||
radarSites_ {},
|
||||
geodesic_(util::GeographicLib::DefaultGeodesic()),
|
||||
distanceMap_ {},
|
||||
distanceDisplay_ {scwx::common::DistanceType::Miles},
|
||||
previousPosition_ {}
|
||||
{
|
||||
// Get all loaded radar sites
|
||||
|
|
@ -64,7 +65,6 @@ public:
|
|||
const GeographicLib::Geodesic& geodesic_;
|
||||
|
||||
std::unordered_map<std::string, double> distanceMap_;
|
||||
scwx::common::DistanceType distanceDisplay_;
|
||||
scwx::common::Coordinate previousPosition_;
|
||||
|
||||
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):
|
||||
if (role == Qt::DisplayRole)
|
||||
{
|
||||
if (p->distanceDisplay_ == scwx::common::DistanceType::Miles)
|
||||
{
|
||||
return QString("%1 mi").arg(
|
||||
static_cast<uint32_t>(p->distanceMap_.at(site->id()) *
|
||||
scwx::common::kMilesPerMeter));
|
||||
}
|
||||
else
|
||||
{
|
||||
return QString("%1 km").arg(
|
||||
static_cast<uint32_t>(p->distanceMap_.at(site->id()) *
|
||||
scwx::common::kKilometersPerMeter));
|
||||
}
|
||||
const std::string distanceUnitName =
|
||||
settings::UnitSettings::Instance().distance_units().GetValue();
|
||||
types::DistanceUnits distanceUnits =
|
||||
types::GetDistanceUnitsFromName(distanceUnitName);
|
||||
double distanceScale = types::GetDistanceUnitsScale(distanceUnits);
|
||||
std::string abbreviation =
|
||||
types::GetDistanceUnitsAbbreviation(distanceUnits);
|
||||
|
||||
return QString("%1 %2")
|
||||
.arg(static_cast<uint32_t>(p->distanceMap_.at(site->id()) *
|
||||
scwx::common::kKilometersPerMeter *
|
||||
distanceScale))
|
||||
.arg(QString::fromStdString(abbreviation));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -37,12 +37,17 @@ public:
|
|||
alertLocationMethod_.SetDefault(defaultAlertLocationMethodValue);
|
||||
alertLatitude_.SetDefault(0.0);
|
||||
alertLongitude_.SetDefault(0.0);
|
||||
alertRadius_.SetDefault(0.0);
|
||||
alertRadarSite_.SetDefault("default");
|
||||
ignoreMissingCodecs_.SetDefault(false);
|
||||
|
||||
alertLatitude_.SetMinimum(-90.0);
|
||||
alertLatitude_.SetMaximum(90.0);
|
||||
alertLongitude_.SetMinimum(-180.0);
|
||||
alertLongitude_.SetMaximum(180.0);
|
||||
alertRadius_.SetMinimum(0.0);
|
||||
alertRadius_.SetMaximum(9999999999);
|
||||
|
||||
|
||||
alertLocationMethod_.SetValidator(
|
||||
SCWX_SETTINGS_ENUM_VALIDATOR(types::LocationMethod,
|
||||
|
|
@ -86,6 +91,8 @@ public:
|
|||
SettingsVariable<std::string> alertLocationMethod_ {"alert_location_method"};
|
||||
SettingsVariable<double> alertLatitude_ {"alert_latitude"};
|
||||
SettingsVariable<double> alertLongitude_ {"alert_longitude"};
|
||||
SettingsVariable<std::string> alertRadarSite_ {"alert_radar_site"};
|
||||
SettingsVariable<double> alertRadius_ {"alert_radius"};
|
||||
SettingsVariable<std::string> alertCounty_ {"alert_county"};
|
||||
SettingsVariable<bool> ignoreMissingCodecs_ {"ignore_missing_codecs"};
|
||||
|
||||
|
|
@ -101,6 +108,8 @@ AudioSettings::AudioSettings() :
|
|||
&p->alertLocationMethod_,
|
||||
&p->alertLatitude_,
|
||||
&p->alertLongitude_,
|
||||
&p->alertRadarSite_,
|
||||
&p->alertRadius_,
|
||||
&p->alertCounty_,
|
||||
&p->ignoreMissingCodecs_});
|
||||
RegisterVariables(p->variables_);
|
||||
|
|
@ -133,6 +142,16 @@ SettingsVariable<double>& AudioSettings::alert_longitude() const
|
|||
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
|
||||
{
|
||||
return p->alertCounty_;
|
||||
|
|
@ -166,6 +185,8 @@ bool operator==(const AudioSettings& lhs, const AudioSettings& rhs)
|
|||
lhs.p->alertLocationMethod_ == rhs.p->alertLocationMethod_ &&
|
||||
lhs.p->alertLatitude_ == rhs.p->alertLatitude_ &&
|
||||
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->alertEnabled_ == rhs.p->alertEnabled_);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ public:
|
|||
SettingsVariable<std::string>& alert_location_method() const;
|
||||
SettingsVariable<double>& alert_latitude() 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<bool>& alert_enabled(awips::Phenomenon phenomenon) const;
|
||||
SettingsVariable<bool>& ignore_missing_codecs() const;
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ public:
|
|||
|
||||
void UpdateEditWidget();
|
||||
void UpdateResetButton();
|
||||
void UpdateUnitLabel();
|
||||
|
||||
SettingsInterface<T>* self_;
|
||||
|
||||
|
|
@ -49,9 +50,14 @@ public:
|
|||
std::unique_ptr<QObject> context_ {std::make_unique<QObject>()};
|
||||
QWidget* editWidget_ {nullptr};
|
||||
QAbstractButton* resetButton_ {nullptr};
|
||||
QLabel* unitLabel_ {nullptr};
|
||||
|
||||
std::function<std::string(const T&)> mapFromValue_ {nullptr};
|
||||
std::function<T(const std::string&)> mapToValue_ {nullptr};
|
||||
|
||||
double unitScale_ {1};
|
||||
std::optional<std::string> unitAbbreviation_ {};
|
||||
bool unitEnabled_ {false};
|
||||
};
|
||||
|
||||
template<class T>
|
||||
|
|
@ -381,6 +387,11 @@ void SettingsInterface<T>::SetEditWidget(QWidget* widget)
|
|||
p->context_.get(),
|
||||
[this](double d)
|
||||
{
|
||||
if (p->unitEnabled_)
|
||||
{
|
||||
d = d / p->unitScale_;
|
||||
}
|
||||
|
||||
const T value = p->variable_->GetValue();
|
||||
const std::optional<T> staged = p->variable_->GetStaged();
|
||||
|
||||
|
|
@ -448,6 +459,11 @@ void SettingsInterface<T>::SetResetButton(QAbstractButton* button)
|
|||
p->UpdateResetButton();
|
||||
}
|
||||
}
|
||||
template<class T>
|
||||
void SettingsInterface<T>::SetUnitLabel(QLabel* label)
|
||||
{
|
||||
p->unitLabel_ = label;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void SettingsInterface<T>::SetMapFromValueFunction(
|
||||
|
|
@ -463,6 +479,17 @@ void SettingsInterface<T>::SetMapToValueFunction(
|
|||
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 U>
|
||||
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>)
|
||||
{
|
||||
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>
|
||||
void SettingsInterface<T>::Impl::UpdateResetButton()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class QLabel;
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace qt
|
||||
|
|
@ -91,6 +93,13 @@ public:
|
|||
*/
|
||||
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
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
|
|
|
|||
|
|
@ -26,16 +26,20 @@ public:
|
|||
types::GetOtherUnitsName(types::OtherUnits::Default);
|
||||
std::string defaultSpeedUnitsValue =
|
||||
types::GetSpeedUnitsName(types::SpeedUnits::Knots);
|
||||
std::string defaultDistanceUnitsValue =
|
||||
types::GetDistanceUnitsName(types::DistanceUnits::Miles);
|
||||
|
||||
boost::to_lower(defaultAccumulationUnitsValue);
|
||||
boost::to_lower(defaultEchoTopsUnitsValue);
|
||||
boost::to_lower(defaultOtherUnitsValue);
|
||||
boost::to_lower(defaultSpeedUnitsValue);
|
||||
boost::to_lower(defaultDistanceUnitsValue);
|
||||
|
||||
accumulationUnits_.SetDefault(defaultAccumulationUnitsValue);
|
||||
echoTopsUnits_.SetDefault(defaultEchoTopsUnitsValue);
|
||||
otherUnits_.SetDefault(defaultOtherUnitsValue);
|
||||
speedUnits_.SetDefault(defaultSpeedUnitsValue);
|
||||
distanceUnits_.SetDefault(defaultDistanceUnitsValue);
|
||||
|
||||
accumulationUnits_.SetValidator(
|
||||
SCWX_SETTINGS_ENUM_VALIDATOR(types::AccumulationUnits,
|
||||
|
|
@ -53,6 +57,10 @@ public:
|
|||
SCWX_SETTINGS_ENUM_VALIDATOR(types::SpeedUnits,
|
||||
types::SpeedUnitsIterator(),
|
||||
types::GetSpeedUnitsName));
|
||||
distanceUnits_.SetValidator(
|
||||
SCWX_SETTINGS_ENUM_VALIDATOR(types::DistanceUnits,
|
||||
types::DistanceUnitsIterator(),
|
||||
types::GetDistanceUnitsName));
|
||||
}
|
||||
|
||||
~Impl() {}
|
||||
|
|
@ -61,6 +69,7 @@ public:
|
|||
SettingsVariable<std::string> echoTopsUnits_ {"echo_tops_units"};
|
||||
SettingsVariable<std::string> otherUnits_ {"other_units"};
|
||||
SettingsVariable<std::string> speedUnits_ {"speed_units"};
|
||||
SettingsVariable<std::string> distanceUnits_ {"distance_units"};
|
||||
};
|
||||
|
||||
UnitSettings::UnitSettings() :
|
||||
|
|
@ -69,7 +78,8 @@ UnitSettings::UnitSettings() :
|
|||
RegisterVariables({&p->accumulationUnits_,
|
||||
&p->echoTopsUnits_,
|
||||
&p->otherUnits_,
|
||||
&p->speedUnits_});
|
||||
&p->speedUnits_,
|
||||
&p->distanceUnits_});
|
||||
SetDefaults();
|
||||
}
|
||||
UnitSettings::~UnitSettings() = default;
|
||||
|
|
@ -97,6 +107,11 @@ SettingsVariable<std::string>& UnitSettings::speed_units() const
|
|||
return p->speedUnits_;
|
||||
}
|
||||
|
||||
SettingsVariable<std::string>& UnitSettings::distance_units() const
|
||||
{
|
||||
return p->distanceUnits_;
|
||||
}
|
||||
|
||||
UnitSettings& UnitSettings::Instance()
|
||||
{
|
||||
static UnitSettings generalSettings_;
|
||||
|
|
@ -108,7 +123,8 @@ bool operator==(const UnitSettings& lhs, const UnitSettings& rhs)
|
|||
return (lhs.p->accumulationUnits_ == rhs.p->accumulationUnits_ &&
|
||||
lhs.p->echoTopsUnits_ == rhs.p->echoTopsUnits_ &&
|
||||
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
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ public:
|
|||
SettingsVariable<std::string>& echo_tops_units() const;
|
||||
SettingsVariable<std::string>& other_units() const;
|
||||
SettingsVariable<std::string>& speed_units() const;
|
||||
SettingsVariable<std::string>& distance_units() const;
|
||||
|
||||
static UnitSettings& Instance();
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ namespace types
|
|||
static const std::unordered_map<LocationMethod, std::string>
|
||||
locationMethodName_ {{LocationMethod::Fixed, "Fixed"},
|
||||
{LocationMethod::Track, "Track"},
|
||||
{LocationMethod::RadarSite, "Radar Site"},
|
||||
{LocationMethod::County, "County"},
|
||||
{LocationMethod::All, "All"},
|
||||
{LocationMethod::Unknown, "?"}};
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ enum class LocationMethod
|
|||
{
|
||||
Fixed,
|
||||
Track,
|
||||
RadarSite,
|
||||
County,
|
||||
All,
|
||||
Unknown
|
||||
|
|
|
|||
|
|
@ -89,12 +89,35 @@ static const std::unordered_map<SpeedUnits, float> speedUnitsScale_ {
|
|||
{SpeedUnits::User, 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,
|
||||
GetAccumulationUnitsFromName,
|
||||
accumulationUnitsName_)
|
||||
SCWX_GET_ENUM(EchoTopsUnits, GetEchoTopsUnitsFromName, echoTopsUnitsName_)
|
||||
SCWX_GET_ENUM(OtherUnits, GetOtherUnitsFromName, otherUnitsName_)
|
||||
SCWX_GET_ENUM(SpeedUnits, GetSpeedUnitsFromName, speedUnitsName_)
|
||||
SCWX_GET_ENUM(DistanceUnits, GetDistanceUnitsFromName, distanceUnitsName_)
|
||||
|
||||
const std::string& GetAccumulationUnitsAbbreviation(AccumulationUnits units)
|
||||
{
|
||||
|
|
@ -146,6 +169,21 @@ float GetSpeedUnitsScale(SpeedUnits 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 qt
|
||||
} // namespace scwx
|
||||
|
|
|
|||
|
|
@ -56,6 +56,17 @@ typedef scwx::util::
|
|||
Iterator<SpeedUnits, SpeedUnits::KilometersPerHour, SpeedUnits::User>
|
||||
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& GetAccumulationUnitsName(AccumulationUnits units);
|
||||
AccumulationUnits GetAccumulationUnitsFromName(const std::string& name);
|
||||
|
|
@ -74,6 +85,11 @@ const std::string& GetSpeedUnitsName(SpeedUnits units);
|
|||
SpeedUnits GetSpeedUnitsFromName(const std::string& name);
|
||||
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 qt
|
||||
} // namespace scwx
|
||||
|
|
|
|||
|
|
@ -102,6 +102,16 @@ public:
|
|||
types::GetSpeedUnitsName);
|
||||
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);
|
||||
otherComboBox->setSizePolicy(QSizePolicy::Expanding,
|
||||
QSizePolicy::Preferred);
|
||||
|
|
@ -127,6 +137,7 @@ public:
|
|||
settings::SettingsInterface<std::string> echoTopsUnits_ {};
|
||||
settings::SettingsInterface<std::string> otherUnits_ {};
|
||||
settings::SettingsInterface<std::string> speedUnits_ {};
|
||||
settings::SettingsInterface<std::string> distanceUnits_ {};
|
||||
};
|
||||
|
||||
UnitSettingsWidget::UnitSettingsWidget(QWidget* parent) :
|
||||
|
|
|
|||
|
|
@ -14,12 +14,14 @@
|
|||
#include <scwx/qt/settings/palette_settings.hpp>
|
||||
#include <scwx/qt/settings/settings_interface.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/font_types.hpp>
|
||||
#include <scwx/qt/types/location_types.hpp>
|
||||
#include <scwx/qt/types/qt_types.hpp>
|
||||
#include <scwx/qt/types/text_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/radar_site_dialog.hpp>
|
||||
#include <scwx/qt/ui/serial_port_dialog.hpp>
|
||||
|
|
@ -103,6 +105,7 @@ public:
|
|||
explicit SettingsDialogImpl(SettingsDialog* self) :
|
||||
self_ {self},
|
||||
radarSiteDialog_ {new RadarSiteDialog(self)},
|
||||
alertAudioRadarSiteDialog_ {new RadarSiteDialog(self)},
|
||||
gpsSourceDialog_ {new SerialPortDialog(self)},
|
||||
countyDialog_ {new CountyDialog(self)},
|
||||
fontDialog_ {new QFontDialog(self)},
|
||||
|
|
@ -134,6 +137,8 @@ public:
|
|||
&alertAudioLocationMethod_,
|
||||
&alertAudioLatitude_,
|
||||
&alertAudioLongitude_,
|
||||
&alertAudioRadarSite_,
|
||||
&alertAudioRadius_,
|
||||
&alertAudioCounty_,
|
||||
&hoverTextWrap_,
|
||||
&tooltipMethod_,
|
||||
|
|
@ -175,6 +180,7 @@ public:
|
|||
|
||||
void ShowColorDialog(QLineEdit* lineEdit, QFrame* frame = nullptr);
|
||||
void UpdateRadarDialogLocation(const std::string& id);
|
||||
void UpdateAlertRadarDialogLocation(const std::string& id);
|
||||
|
||||
QFont GetSelectedFont();
|
||||
void SelectFontCategory(types::FontCategory fontCategory);
|
||||
|
|
@ -199,6 +205,7 @@ public:
|
|||
|
||||
SettingsDialog* self_;
|
||||
RadarSiteDialog* radarSiteDialog_;
|
||||
RadarSiteDialog* alertAudioRadarSiteDialog_;
|
||||
SerialPortDialog* gpsSourceDialog_;
|
||||
CountyDialog* countyDialog_;
|
||||
QFontDialog* fontDialog_;
|
||||
|
|
@ -252,6 +259,8 @@ public:
|
|||
settings::SettingsInterface<std::string> alertAudioLocationMethod_ {};
|
||||
settings::SettingsInterface<double> alertAudioLatitude_ {};
|
||||
settings::SettingsInterface<double> alertAudioLongitude_ {};
|
||||
settings::SettingsInterface<std::string> alertAudioRadarSite_ {};
|
||||
settings::SettingsInterface<double> alertAudioRadius_ {};
|
||||
settings::SettingsInterface<std::string> alertAudioCounty_ {};
|
||||
|
||||
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,
|
||||
&QAbstractButton::clicked,
|
||||
self_,
|
||||
|
|
@ -504,11 +537,18 @@ void SettingsDialogImpl::SetupGeneralTab()
|
|||
const std::shared_ptr<config::RadarSite>& b)
|
||||
{ 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
|
||||
for (std::shared_ptr<config::RadarSite>& radarSite : radarSites)
|
||||
{
|
||||
QString text = QString::fromStdString(RadarSiteLabel(radarSite));
|
||||
self_->ui->radarSiteComboBox->addItem(text);
|
||||
self_->ui->alertAudioRadarSiteComboBox->addItem(text);
|
||||
}
|
||||
|
||||
defaultRadarSite_.SetSettingsVariable(generalSettings.default_radar_site());
|
||||
|
|
@ -900,6 +940,12 @@ void SettingsDialogImpl::SetupAudioTab()
|
|||
|
||||
bool coordinateEntryEnabled =
|
||||
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 =
|
||||
locationMethod == types::LocationMethod::County;
|
||||
|
||||
|
|
@ -912,6 +958,18 @@ void SettingsDialogImpl::SetupAudioTab()
|
|||
self_->ui->resetAlertAudioLongitudeButton->setEnabled(
|
||||
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->alertAudioCountySelectButton->setEnabled(
|
||||
countyEntryEnabled);
|
||||
|
|
@ -983,6 +1041,64 @@ void SettingsDialogImpl::SetupAudioTab()
|
|||
alertAudioLongitude_.SetResetButton(
|
||||
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 alertAudioLayout =
|
||||
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()
|
||||
{
|
||||
std::string fontFamily = fontFamilies_.at(selectedFontCategory_)
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@
|
|||
</sizepolicy>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
<number>3</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="general">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
|
|
@ -135,9 +135,9 @@
|
|||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>-113</y>
|
||||
<y>0</y>
|
||||
<width>511</width>
|
||||
<height>669</height>
|
||||
<height>647</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
|
|
@ -610,8 +610,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>98</width>
|
||||
<height>28</height>
|
||||
<width>66</width>
|
||||
<height>18</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
|
|
@ -691,45 +691,6 @@
|
|||
<string>Alerts</string>
|
||||
</property>
|
||||
<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">
|
||||
<widget class="QToolButton" name="resetAlertAudioLongitudeButton">
|
||||
<property name="text">
|
||||
|
|
@ -741,11 +702,19 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="5">
|
||||
<widget class="QToolButton" name="alertAudioSoundStopButton">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/stop-solid.svg</normaloff>:/res/icons/font-awesome-6/stop-solid.svg</iconset>
|
||||
<item row="5" column="1" colspan="2">
|
||||
<widget class="QDoubleSpinBox" name="alertAudioRadiusSpinBox">
|
||||
<property name="decimals">
|
||||
<number>2</number>
|
||||
</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>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
@ -760,6 +729,23 @@
|
|||
</property>
|
||||
</widget>
|
||||
</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">
|
||||
<widget class="QToolButton" name="resetAlertAudioSoundButton">
|
||||
<property name="text">
|
||||
|
|
@ -771,25 +757,10 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<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="0" column="0">
|
||||
<widget class="QLabel" name="label_17">
|
||||
<item row="5" column="3">
|
||||
<widget class="QLabel" name="alertAudioRadiusUnitsLabel">
|
||||
<property name="text">
|
||||
<string>Sound</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_19">
|
||||
<property name="text">
|
||||
<string>County</string>
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
@ -809,6 +780,34 @@
|
|||
</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="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">
|
||||
<widget class="QDoubleSpinBox" name="alertAudioLatitudeSpinBox">
|
||||
<property name="decimals">
|
||||
|
|
@ -825,21 +824,16 @@
|
|||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
<item row="0" column="5">
|
||||
<widget class="QToolButton" name="alertAudioSoundStopButton">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../scwx-qt.qrc">
|
||||
<normaloff>:/res/icons/font-awesome-6/stop-solid.svg</normaloff>:/res/icons/font-awesome-6/stop-solid.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<widget class="QLineEdit" name="alertAudioSoundLineEdit"/>
|
||||
</item>
|
||||
<item row="4" column="6">
|
||||
<widget class="QToolButton" name="resetAlertAudioCountyButton">
|
||||
<item row="5" column="6">
|
||||
<widget class="QToolButton" name="resetAlertAudioRadiusButton">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
|
|
@ -849,14 +843,21 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="3">
|
||||
<widget class="QToolButton" name="alertAudioCountySelectButton">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
<string>Latitude</string>
|
||||
</property>
|
||||
</widget>
|
||||
</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">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
|
|
@ -869,7 +870,15 @@
|
|||
</property>
|
||||
</widget>
|
||||
</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">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
|
|
@ -882,6 +891,66 @@
|
|||
</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="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>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@
|
|||
|
||||
#include <GeographicLib/Gnomonic.hpp>
|
||||
#include <geos/algorithm/PointLocation.h>
|
||||
#include <geos/operation/distance/DistanceOp.h>
|
||||
#include <geos/geom/CoordinateSequence.h>
|
||||
#include <geos/geom/GeometryFactory.h>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
|
|
@ -28,6 +30,40 @@ const ::GeographicLib::Geodesic& DefaultGeodesic()
|
|||
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,
|
||||
const common::Coordinate& point)
|
||||
{
|
||||
|
|
@ -82,6 +118,12 @@ bool AreaContainsPoint(const std::vector<common::Coordinate>& area,
|
|||
return areaContainsPoint;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
units::angle::degrees<double>
|
||||
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};
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 util
|
||||
} // namespace qt
|
||||
|
|
|
|||
|
|
@ -90,6 +90,37 @@ common::Coordinate GetCoordinate(const common::Coordinate& center,
|
|||
units::length::meters<double>
|
||||
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 util
|
||||
} // namespace qt
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 0ea32947d6a6e39a69d931b2827056e7bc58cbba
|
||||
Subproject commit 5115993596cfe6528b7a07c0b16dbd1ae16ce4df
|
||||
69
test/source/scwx/qt/util/geographic_lib.test.cpp
Normal file
69
test/source/scwx/qt/util/geographic_lib.test.cpp
Normal 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
|
||||
|
|
@ -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_SETTINGS_TESTS source/scwx/qt/settings/settings_container.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
|
||||
source/scwx/util/rangebuf.test.cpp
|
||||
source/scwx/util/streams.test.cpp
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue