mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-11-04 09:00:05 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			326 lines
		
	
	
	
		
			8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			326 lines
		
	
	
	
		
			8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#include <scwx/qt/config/radar_site.hpp>
 | 
						|
#include <scwx/qt/util/geographic_lib.hpp>
 | 
						|
#include <scwx/qt/util/json.hpp>
 | 
						|
#include <scwx/common/sites.hpp>
 | 
						|
#include <scwx/util/logger.hpp>
 | 
						|
 | 
						|
#include <chrono>
 | 
						|
#include <shared_mutex>
 | 
						|
#include <unordered_map>
 | 
						|
 | 
						|
#include <boost/json.hpp>
 | 
						|
 | 
						|
#if (__cpp_lib_chrono < 201907L)
 | 
						|
#   include <date/date.h>
 | 
						|
#endif
 | 
						|
 | 
						|
namespace scwx
 | 
						|
{
 | 
						|
namespace qt
 | 
						|
{
 | 
						|
namespace config
 | 
						|
{
 | 
						|
 | 
						|
static const std::string logPrefix_ = "scwx::qt::config::radar_site";
 | 
						|
static const auto        logger_    = scwx::util::Logger::Create(logPrefix_);
 | 
						|
 | 
						|
static const std::string defaultRadarSiteFile_ =
 | 
						|
   ":/res/config/radar_sites.json";
 | 
						|
 | 
						|
static const std::unordered_map<std::string, std::string> typeNameMap_ {
 | 
						|
   {"wsr88d", "WSR-88D"}, {"tdwr", "TDWR"}, {"?", "?"}};
 | 
						|
 | 
						|
static std::unordered_map<std::string, std::shared_ptr<RadarSite>>
 | 
						|
                                                    radarSiteMap_;
 | 
						|
static std::unordered_map<std::string, std::string> siteIdMap_;
 | 
						|
static std::shared_mutex                            siteMutex_;
 | 
						|
 | 
						|
static bool ValidateJsonEntry(const boost::json::object& o);
 | 
						|
 | 
						|
class RadarSiteImpl
 | 
						|
{
 | 
						|
public:
 | 
						|
   explicit RadarSiteImpl() {}
 | 
						|
   ~RadarSiteImpl() {}
 | 
						|
 | 
						|
   std::string type_ {};
 | 
						|
   std::string id_ {};
 | 
						|
   double      latitude_ {0.0};
 | 
						|
   double      longitude_ {0.0};
 | 
						|
   std::string country_ {};
 | 
						|
   std::string state_ {};
 | 
						|
   std::string place_ {};
 | 
						|
   std::string tzName_ {};
 | 
						|
 | 
						|
   const scwx::util::time_zone* timeZone_ {nullptr};
 | 
						|
};
 | 
						|
 | 
						|
RadarSite::RadarSite() : p(std::make_unique<RadarSiteImpl>()) {}
 | 
						|
RadarSite::~RadarSite() = default;
 | 
						|
 | 
						|
RadarSite::RadarSite(RadarSite&&) noexcept            = default;
 | 
						|
RadarSite& RadarSite::operator=(RadarSite&&) noexcept = default;
 | 
						|
 | 
						|
std::string RadarSite::type() const
 | 
						|
{
 | 
						|
   return p->type_;
 | 
						|
}
 | 
						|
 | 
						|
std::string RadarSite::type_name() const
 | 
						|
{
 | 
						|
   auto it = typeNameMap_.find(p->type_);
 | 
						|
   if (it != typeNameMap_.cend())
 | 
						|
   {
 | 
						|
      return it->second;
 | 
						|
   }
 | 
						|
   else
 | 
						|
   {
 | 
						|
      return typeNameMap_.at("?");
 | 
						|
   }
 | 
						|
}
 | 
						|
 | 
						|
std::string RadarSite::id() const
 | 
						|
{
 | 
						|
   return p->id_;
 | 
						|
}
 | 
						|
 | 
						|
double RadarSite::latitude() const
 | 
						|
{
 | 
						|
   return p->latitude_;
 | 
						|
}
 | 
						|
 | 
						|
double RadarSite::longitude() const
 | 
						|
{
 | 
						|
   return p->longitude_;
 | 
						|
}
 | 
						|
 | 
						|
std::string RadarSite::country() const
 | 
						|
{
 | 
						|
   return p->country_;
 | 
						|
}
 | 
						|
 | 
						|
std::string RadarSite::state() const
 | 
						|
{
 | 
						|
   return p->state_;
 | 
						|
}
 | 
						|
 | 
						|
std::string RadarSite::place() const
 | 
						|
{
 | 
						|
   return p->place_;
 | 
						|
}
 | 
						|
 | 
						|
std::string RadarSite::location_name() const
 | 
						|
{
 | 
						|
   std::string locationName;
 | 
						|
 | 
						|
   if (p->country_ == "USA")
 | 
						|
   {
 | 
						|
      locationName = fmt::format("{}, {}", p->place_, p->state_);
 | 
						|
   }
 | 
						|
   else if (std::all_of(p->state_.cbegin(),
 | 
						|
                        p->state_.cend(),
 | 
						|
                        [](char c) { return std::isdigit(c); }))
 | 
						|
   {
 | 
						|
      locationName = fmt::format("{}, {}", p->place_, p->country_);
 | 
						|
   }
 | 
						|
   else
 | 
						|
   {
 | 
						|
      locationName =
 | 
						|
         fmt::format("{}, {}, {}", p->place_, p->state_, p->country_);
 | 
						|
   }
 | 
						|
 | 
						|
   return locationName;
 | 
						|
}
 | 
						|
 | 
						|
std::string RadarSite::tz_name() const
 | 
						|
{
 | 
						|
   return p->tzName_;
 | 
						|
}
 | 
						|
 | 
						|
const scwx::util::time_zone* RadarSite::time_zone() const
 | 
						|
{
 | 
						|
   return p->timeZone_;
 | 
						|
}
 | 
						|
 | 
						|
std::shared_ptr<RadarSite> RadarSite::Get(const std::string& id)
 | 
						|
{
 | 
						|
   std::shared_lock           lock(siteMutex_);
 | 
						|
   std::shared_ptr<RadarSite> radarSite = nullptr;
 | 
						|
 | 
						|
   if (radarSiteMap_.contains(id))
 | 
						|
   {
 | 
						|
      radarSite = radarSiteMap_.at(id);
 | 
						|
   }
 | 
						|
 | 
						|
   return radarSite;
 | 
						|
}
 | 
						|
 | 
						|
std::vector<std::shared_ptr<RadarSite>> RadarSite::GetAll()
 | 
						|
{
 | 
						|
   std::shared_lock                        lock(siteMutex_);
 | 
						|
   std::vector<std::shared_ptr<RadarSite>> radarSites;
 | 
						|
 | 
						|
   radarSites.reserve(radarSiteMap_.size());
 | 
						|
 | 
						|
   for (const auto& site : radarSiteMap_)
 | 
						|
   {
 | 
						|
      radarSites.push_back(site.second);
 | 
						|
   }
 | 
						|
 | 
						|
   return radarSites;
 | 
						|
}
 | 
						|
 | 
						|
std::shared_ptr<RadarSite> RadarSite::FindNearest(
 | 
						|
   double latitude, double longitude, std::optional<std::string> type)
 | 
						|
{
 | 
						|
   std::shared_lock lock(siteMutex_);
 | 
						|
 | 
						|
   double distanceInMeters;
 | 
						|
 | 
						|
   std::shared_ptr<RadarSite> nearestRadarSite = nullptr;
 | 
						|
   double                     nearestDistance  = 0.0;
 | 
						|
 | 
						|
   for (const auto& site : radarSiteMap_)
 | 
						|
   {
 | 
						|
      auto& radarSite = site.second;
 | 
						|
 | 
						|
      // If the type filter doesn't match, skip
 | 
						|
      if (type.has_value() && radarSite->type() != type)
 | 
						|
      {
 | 
						|
         continue;
 | 
						|
      }
 | 
						|
 | 
						|
      // Calculate distance to radar site
 | 
						|
      util::GeographicLib::DefaultGeodesic().Inverse(latitude,
 | 
						|
                                                     longitude,
 | 
						|
                                                     radarSite->latitude(),
 | 
						|
                                                     radarSite->longitude(),
 | 
						|
                                                     distanceInMeters);
 | 
						|
 | 
						|
      // If the radar site is the closer, record it as the closest
 | 
						|
      if (nearestRadarSite == nullptr || distanceInMeters < nearestDistance)
 | 
						|
      {
 | 
						|
         nearestRadarSite = radarSite;
 | 
						|
         nearestDistance  = distanceInMeters;
 | 
						|
      }
 | 
						|
   }
 | 
						|
 | 
						|
   return nearestRadarSite;
 | 
						|
}
 | 
						|
 | 
						|
std::string GetRadarIdFromSiteId(const std::string& siteId)
 | 
						|
{
 | 
						|
   std::shared_lock lock(siteMutex_);
 | 
						|
   std::string      id = "???";
 | 
						|
 | 
						|
   if (siteIdMap_.contains(siteId))
 | 
						|
   {
 | 
						|
      id = siteIdMap_.at(siteId);
 | 
						|
   }
 | 
						|
 | 
						|
   return id;
 | 
						|
}
 | 
						|
 | 
						|
void RadarSite::Initialize()
 | 
						|
{
 | 
						|
   static bool initialized_ = false;
 | 
						|
 | 
						|
   if (!initialized_)
 | 
						|
   {
 | 
						|
      ReadConfig(defaultRadarSiteFile_);
 | 
						|
      initialized_ = true;
 | 
						|
   }
 | 
						|
}
 | 
						|
 | 
						|
size_t RadarSite::ReadConfig(const std::string& path)
 | 
						|
{
 | 
						|
   logger_->info("Loading radar sites from \"{}\"...", path);
 | 
						|
 | 
						|
   bool   dataValid  = true;
 | 
						|
   size_t sitesAdded = 0;
 | 
						|
 | 
						|
   boost::json::value j = util::json::ReadJsonFile(path);
 | 
						|
 | 
						|
   dataValid = j.is_array();
 | 
						|
 | 
						|
   if (dataValid)
 | 
						|
   {
 | 
						|
      std::unique_lock lock(siteMutex_);
 | 
						|
 | 
						|
      for (auto& v : j.as_array())
 | 
						|
      {
 | 
						|
         auto& o = v.as_object();
 | 
						|
 | 
						|
         if (!ValidateJsonEntry(o))
 | 
						|
         {
 | 
						|
            logger_->info("Incorrect format: {}", boost::json::serialize(v));
 | 
						|
         }
 | 
						|
         else
 | 
						|
         {
 | 
						|
            std::shared_ptr<RadarSite> site = std::make_shared<RadarSite>();
 | 
						|
 | 
						|
            site->p->type_ = boost::json::value_to<std::string>(o.at("type"));
 | 
						|
            site->p->id_   = boost::json::value_to<std::string>(o.at("id"));
 | 
						|
            site->p->latitude_  = boost::json::value_to<double>(o.at("lat"));
 | 
						|
            site->p->longitude_ = boost::json::value_to<double>(o.at("lon"));
 | 
						|
            site->p->country_ =
 | 
						|
               boost::json::value_to<std::string>(o.at("country"));
 | 
						|
            site->p->state_ = boost::json::value_to<std::string>(o.at("state"));
 | 
						|
            site->p->place_ = boost::json::value_to<std::string>(o.at("place"));
 | 
						|
            site->p->tzName_ = boost::json::value_to<std::string>(o.at("tz"));
 | 
						|
 | 
						|
            try
 | 
						|
            {
 | 
						|
#if (__cpp_lib_chrono >= 201907L)
 | 
						|
               using namespace std::chrono;
 | 
						|
#else
 | 
						|
               using namespace date;
 | 
						|
#endif
 | 
						|
 | 
						|
               site->p->timeZone_ = get_tzdb().locate_zone(site->p->tzName_);
 | 
						|
            }
 | 
						|
            catch (const std::runtime_error&)
 | 
						|
            {
 | 
						|
               logger_->warn(
 | 
						|
                  "{} unknown time zone: {}", site->p->id_, site->p->tzName_);
 | 
						|
            }
 | 
						|
 | 
						|
            if (!radarSiteMap_.contains(site->p->id_))
 | 
						|
            {
 | 
						|
               radarSiteMap_[site->p->id_] = site;
 | 
						|
               ++sitesAdded;
 | 
						|
            }
 | 
						|
 | 
						|
            std::string siteId = common::GetSiteId(site->p->id_);
 | 
						|
 | 
						|
            if (!siteIdMap_.contains(siteId))
 | 
						|
            {
 | 
						|
               siteIdMap_[siteId] = site->p->id_;
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
               logger_->warn("Site ID conflict: {} and {}",
 | 
						|
                             siteIdMap_.at(siteId),
 | 
						|
                             site->p->id_);
 | 
						|
            }
 | 
						|
         }
 | 
						|
      }
 | 
						|
   }
 | 
						|
 | 
						|
   return sitesAdded;
 | 
						|
}
 | 
						|
 | 
						|
static bool ValidateJsonEntry(const boost::json::object& o)
 | 
						|
{
 | 
						|
   return (o.contains("type") && o.at("type").is_string() &&       //
 | 
						|
           o.contains("id") && o.at("id").is_string() &&           //
 | 
						|
           o.contains("lat") && o.at("lat").is_double() &&         //
 | 
						|
           o.contains("lon") && o.at("lon").is_double() &&         //
 | 
						|
           o.contains("country") && o.at("country").is_string() && //
 | 
						|
           o.contains("state") && o.at("state").is_string() &&     //
 | 
						|
           o.contains("place") && o.at("place").is_string());
 | 
						|
}
 | 
						|
 | 
						|
} // namespace config
 | 
						|
} // namespace qt
 | 
						|
} // namespace scwx
 |