mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-30 23:00:04 +00:00
383 lines
10 KiB
C++
383 lines
10 KiB
C++
#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/common/geographic.hpp>
|
|
#include <scwx/util/json.hpp>
|
|
#include <scwx/util/logger.hpp>
|
|
|
|
#include <filesystem>
|
|
|
|
#include <boost/json.hpp>
|
|
#include <boost/algorithm/string.hpp>
|
|
#include <QIcon>
|
|
#include <QStandardPaths>
|
|
|
|
namespace scwx
|
|
{
|
|
namespace qt
|
|
{
|
|
namespace model
|
|
{
|
|
|
|
static const std::string logPrefix_ = "scwx::qt::model::radar_site_model";
|
|
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
|
|
|
|
static constexpr int kFirstColumn =
|
|
static_cast<int>(RadarSiteModel::Column::SiteId);
|
|
static constexpr int kLastColumn =
|
|
static_cast<int>(RadarSiteModel::Column::Preset);
|
|
static constexpr int kNumColumns = kLastColumn - kFirstColumn + 1;
|
|
|
|
class RadarSiteModelImpl
|
|
{
|
|
public:
|
|
explicit RadarSiteModelImpl() :
|
|
radarSites_ {},
|
|
geodesic_(util::GeographicLib::DefaultGeodesic()),
|
|
distanceMap_ {},
|
|
previousPosition_ {}
|
|
{
|
|
// Get all loaded radar sites
|
|
std::vector<std::shared_ptr<config::RadarSite>> radarSites =
|
|
config::RadarSite::GetAll();
|
|
|
|
// Setup radar site list
|
|
for (auto& site : radarSites)
|
|
{
|
|
distanceMap_[site->id()] = 0.0;
|
|
radarSites_.emplace_back(std::move(site));
|
|
}
|
|
}
|
|
~RadarSiteModelImpl() = default;
|
|
|
|
void InitializePresets();
|
|
void ReadPresets();
|
|
void WritePresets();
|
|
|
|
QList<std::shared_ptr<config::RadarSite>> radarSites_;
|
|
std::unordered_set<std::string> presets_ {};
|
|
|
|
std::string presetsPath_ {};
|
|
|
|
const GeographicLib::Geodesic& geodesic_;
|
|
|
|
std::unordered_map<std::string, double> distanceMap_;
|
|
scwx::common::Coordinate previousPosition_;
|
|
|
|
QIcon starIcon_ {":/res/icons/font-awesome-6/star-solid.svg"};
|
|
|
|
bool presetsRead_ {false};
|
|
};
|
|
|
|
RadarSiteModel::RadarSiteModel(QObject* parent) :
|
|
QAbstractTableModel(parent), p(std::make_unique<RadarSiteModelImpl>())
|
|
{
|
|
p->InitializePresets();
|
|
p->ReadPresets();
|
|
}
|
|
|
|
RadarSiteModel::~RadarSiteModel()
|
|
{
|
|
// Write presets on shutdown
|
|
p->WritePresets();
|
|
};
|
|
|
|
std::unordered_set<std::string> RadarSiteModel::presets() const
|
|
{
|
|
return p->presets_;
|
|
}
|
|
|
|
void RadarSiteModelImpl::InitializePresets()
|
|
{
|
|
std::string appDataPath {
|
|
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)
|
|
.toStdString()};
|
|
|
|
if (!std::filesystem::exists(appDataPath))
|
|
{
|
|
if (!std::filesystem::create_directories(appDataPath))
|
|
{
|
|
logger_->error("Unable to create application data directory: \"{}\"",
|
|
appDataPath);
|
|
}
|
|
}
|
|
|
|
presetsPath_ = appDataPath + "/radar-presets.json";
|
|
}
|
|
|
|
void RadarSiteModelImpl::ReadPresets()
|
|
{
|
|
logger_->info("Reading presets");
|
|
|
|
boost::json::value presetsJson = nullptr;
|
|
|
|
// Determine if presets exists
|
|
if (std::filesystem::exists(presetsPath_))
|
|
{
|
|
presetsJson = scwx::util::json::ReadJsonFile(presetsPath_);
|
|
}
|
|
|
|
// If presets was successfully read
|
|
if (presetsJson != nullptr && presetsJson.is_array())
|
|
{
|
|
auto& presetsArray = presetsJson.as_array();
|
|
for (auto& presetsEntry : presetsArray)
|
|
{
|
|
if (presetsEntry.is_string())
|
|
{
|
|
// Get radar site ID from JSON value
|
|
std::string preset =
|
|
boost::json::value_to<std::string>(presetsEntry);
|
|
boost::to_upper(preset);
|
|
|
|
// Find the preset in the list of radar sites
|
|
auto it = std::find_if(
|
|
radarSites_.cbegin(),
|
|
radarSites_.cend(),
|
|
[&preset](const std::shared_ptr<config::RadarSite>& radarSite)
|
|
{ return (radarSite->id() == preset); });
|
|
|
|
// If a match, add to the presets
|
|
if (it != radarSites_.cend())
|
|
{
|
|
presets_.insert(preset);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
presetsRead_ = true;
|
|
}
|
|
|
|
void RadarSiteModelImpl::WritePresets()
|
|
{
|
|
if (!presetsRead_)
|
|
{
|
|
return;
|
|
}
|
|
logger_->info("Saving presets");
|
|
|
|
auto presetsJson = boost::json::value_from(presets_);
|
|
scwx::util::json::WriteJsonFile(presetsPath_, presetsJson);
|
|
}
|
|
|
|
int RadarSiteModel::rowCount(const QModelIndex& parent) const
|
|
{
|
|
return parent.isValid() ? 0 : p->radarSites_.size();
|
|
}
|
|
|
|
int RadarSiteModel::columnCount(const QModelIndex& parent) const
|
|
{
|
|
return parent.isValid() ? 0 : kNumColumns;
|
|
}
|
|
|
|
QVariant RadarSiteModel::data(const QModelIndex& index, int role) const
|
|
{
|
|
if (!index.isValid() || index.row() < 0 ||
|
|
index.row() >= p->radarSites_.size())
|
|
{
|
|
return QVariant();
|
|
}
|
|
|
|
const auto& site = p->radarSites_.at(index.row());
|
|
|
|
if (role == Qt::DisplayRole || role == types::SortRole)
|
|
{
|
|
switch (index.column())
|
|
{
|
|
case static_cast<int>(Column::SiteId):
|
|
return QString::fromStdString(site->id());
|
|
case static_cast<int>(Column::Place):
|
|
return QString::fromStdString(site->place());
|
|
case static_cast<int>(Column::State):
|
|
return QString::fromStdString(site->state());
|
|
case static_cast<int>(Column::Country):
|
|
return QString::fromStdString(site->country());
|
|
case static_cast<int>(Column::Latitude):
|
|
if (role == Qt::DisplayRole)
|
|
{
|
|
return QString::fromStdString(
|
|
scwx::common::GetLatitudeString(site->latitude()));
|
|
}
|
|
else
|
|
{
|
|
return site->latitude();
|
|
}
|
|
case static_cast<int>(Column::Longitude):
|
|
if (role == Qt::DisplayRole)
|
|
{
|
|
return QString::fromStdString(
|
|
scwx::common::GetLongitudeString(site->longitude()));
|
|
}
|
|
else
|
|
{
|
|
return site->longitude();
|
|
}
|
|
case static_cast<int>(Column::Type):
|
|
return QString::fromStdString(site->type_name());
|
|
case static_cast<int>(Column::Distance):
|
|
if (role == Qt::DisplayRole)
|
|
{
|
|
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
|
|
{
|
|
return p->distanceMap_.at(site->id());
|
|
}
|
|
case static_cast<int>(Column::Preset):
|
|
if (role == types::SortRole)
|
|
{
|
|
return QVariant(p->presets_.contains(site->id()));
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else if (role == Qt::DecorationRole)
|
|
{
|
|
switch (index.column())
|
|
{
|
|
case static_cast<int>(Column::Preset):
|
|
if (p->presets_.contains(site->id()))
|
|
{
|
|
return p->starIcon_;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
QVariant RadarSiteModel::headerData(int section,
|
|
Qt::Orientation orientation,
|
|
int role) const
|
|
{
|
|
if (role == Qt::DisplayRole)
|
|
{
|
|
if (orientation == Qt::Horizontal)
|
|
{
|
|
switch (section)
|
|
{
|
|
case static_cast<int>(Column::SiteId):
|
|
return tr("Site ID");
|
|
case static_cast<int>(Column::Place):
|
|
return tr("Place");
|
|
case static_cast<int>(Column::State):
|
|
return tr("State");
|
|
case static_cast<int>(Column::Country):
|
|
return tr("Country");
|
|
case static_cast<int>(Column::Latitude):
|
|
return tr("Latitude");
|
|
case static_cast<int>(Column::Longitude):
|
|
return tr("Longitude");
|
|
case static_cast<int>(Column::Type):
|
|
return tr("Type");
|
|
case static_cast<int>(Column::Distance):
|
|
return tr("Distance");
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (role == Qt::DecorationRole)
|
|
{
|
|
if (orientation == Qt::Horizontal)
|
|
{
|
|
switch (section)
|
|
{
|
|
case static_cast<int>(Column::Preset):
|
|
return p->starIcon_;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
void RadarSiteModel::HandleMapUpdate(double latitude, double longitude)
|
|
{
|
|
logger_->trace("Handle map update: {}, {}", latitude, longitude);
|
|
|
|
double distanceInMeters;
|
|
|
|
for (const auto& site : p->radarSites_)
|
|
{
|
|
p->geodesic_.Inverse(latitude,
|
|
longitude,
|
|
site->latitude(),
|
|
site->longitude(),
|
|
distanceInMeters);
|
|
p->distanceMap_[site->id()] = distanceInMeters;
|
|
}
|
|
|
|
QModelIndex topLeft = createIndex(0, static_cast<int>(Column::Distance));
|
|
QModelIndex bottomRight =
|
|
createIndex(rowCount() - 1, static_cast<int>(Column::Distance));
|
|
|
|
Q_EMIT dataChanged(topLeft, bottomRight);
|
|
}
|
|
|
|
void RadarSiteModel::TogglePreset(int row)
|
|
{
|
|
if (row >= 0 && row < p->radarSites_.size())
|
|
{
|
|
std::string siteId = p->radarSites_.at(row)->id();
|
|
bool isPreset = false;
|
|
|
|
// Attempt to erase the radar site from presets
|
|
if (p->presets_.erase(siteId) == 0)
|
|
{
|
|
// If the radar site did not exist, add it
|
|
p->presets_.insert(siteId);
|
|
isPreset = true;
|
|
}
|
|
|
|
QModelIndex index = createIndex(row, static_cast<int>(Column::Preset));
|
|
Q_EMIT dataChanged(index, index);
|
|
|
|
Q_EMIT PresetToggled(siteId, isPreset);
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<RadarSiteModel> RadarSiteModel::Instance()
|
|
{
|
|
static std::weak_ptr<RadarSiteModel> radarSiteModelReference_ {};
|
|
static std::mutex instanceMutex_ {};
|
|
|
|
std::unique_lock lock(instanceMutex_);
|
|
|
|
std::shared_ptr<RadarSiteModel> radarSiteModel =
|
|
radarSiteModelReference_.lock();
|
|
|
|
if (radarSiteModel == nullptr)
|
|
{
|
|
radarSiteModel = std::make_shared<RadarSiteModel>();
|
|
radarSiteModelReference_ = radarSiteModel;
|
|
}
|
|
|
|
return radarSiteModel;
|
|
}
|
|
|
|
} // namespace model
|
|
} // namespace qt
|
|
} // namespace scwx
|