mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-11-04 06:20:06 +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/qt/util/json.hpp>
 | 
						|
#include <scwx/common/geographic.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 = 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_);
 | 
						|
   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
 |