mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 20:50:06 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			268 lines
		
	
	
	
		
			8.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			268 lines
		
	
	
	
		
			8.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include <scwx/qt/manager/alert_manager.hpp>
 | |
| #include <scwx/qt/manager/media_manager.hpp>
 | |
| #include <scwx/qt/manager/position_manager.hpp>
 | |
| #include <scwx/qt/manager/text_event_manager.hpp>
 | |
| #include <scwx/qt/settings/audio_settings.hpp>
 | |
| #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>
 | |
| #include <boost/uuid/random_generator.hpp>
 | |
| #include <QGeoPositionInfo>
 | |
| 
 | |
| namespace scwx
 | |
| {
 | |
| namespace qt
 | |
| {
 | |
| namespace manager
 | |
| {
 | |
| 
 | |
| static const std::string logPrefix_ = "scwx::qt::manager::alert_manager";
 | |
| static const auto        logger_    = scwx::util::Logger::Create(logPrefix_);
 | |
| 
 | |
| class AlertManager::Impl
 | |
| {
 | |
| public:
 | |
|    explicit Impl(AlertManager* self) : self_ {self}
 | |
|    {
 | |
|       settings::AudioSettings& audioSettings =
 | |
|          settings::AudioSettings::Instance();
 | |
| 
 | |
|       UpdateLocationTracking(audioSettings.alert_location_method().GetValue());
 | |
| 
 | |
|       audioSettings.alert_location_method().RegisterValueChangedCallback(
 | |
|          [this](const std::string& value) { UpdateLocationTracking(value); });
 | |
| 
 | |
|       QObject::connect(
 | |
|          textEventManager_.get(),
 | |
|          &manager::TextEventManager::AlertUpdated,
 | |
|          self_,
 | |
|          [this](const types::TextEventKey& key, size_t messageIndex)
 | |
|          {
 | |
|             boost::asio::post(threadPool_,
 | |
|                               [=, this]()
 | |
|                               {
 | |
|                                  try
 | |
|                                  {
 | |
|                                     HandleAlert(key, messageIndex);
 | |
|                                  }
 | |
|                                  catch (const std::exception& ex)
 | |
|                                  {
 | |
|                                     logger_->error(ex.what());
 | |
|                                  }
 | |
|                               });
 | |
|          });
 | |
|    }
 | |
| 
 | |
|    ~Impl() { threadPool_.join(); }
 | |
| 
 | |
|    common::Coordinate
 | |
|         CurrentCoordinate(types::LocationMethod locationMethod) const;
 | |
|    void HandleAlert(const types::TextEventKey& key, size_t messageIndex) const;
 | |
|    void UpdateLocationTracking(const std::string& value) const;
 | |
| 
 | |
|    boost::asio::thread_pool threadPool_ {1u};
 | |
| 
 | |
|    AlertManager* self_;
 | |
| 
 | |
|    boost::uuids::uuid uuid_ {boost::uuids::random_generator()()};
 | |
| 
 | |
|    std::shared_ptr<MediaManager>    mediaManager_ {MediaManager::Instance()};
 | |
|    std::shared_ptr<PositionManager> positionManager_ {
 | |
|       PositionManager::Instance()};
 | |
|    std::shared_ptr<TextEventManager> textEventManager_ {
 | |
|       TextEventManager::Instance()};
 | |
| 
 | |
|    std::shared_ptr<config::RadarSite> radarSite_ {};
 | |
| };
 | |
| 
 | |
| AlertManager::AlertManager() : p(std::make_unique<Impl>(this)) {}
 | |
| AlertManager::~AlertManager() = default;
 | |
| 
 | |
| common::Coordinate AlertManager::Impl::CurrentCoordinate(
 | |
|    types::LocationMethod locationMethod) const
 | |
| {
 | |
|    settings::AudioSettings& audioSettings = settings::AudioSettings::Instance();
 | |
|    common::Coordinate       coordinate {};
 | |
| 
 | |
|    if (locationMethod == types::LocationMethod::Fixed)
 | |
|    {
 | |
|       coordinate.latitude_  = audioSettings.alert_latitude().GetValue();
 | |
|       coordinate.longitude_ = audioSettings.alert_longitude().GetValue();
 | |
|    }
 | |
|    else if (locationMethod == types::LocationMethod::Track)
 | |
|    {
 | |
|       QGeoPositionInfo position = positionManager_->position();
 | |
|       if (position.isValid())
 | |
|       {
 | |
|          QGeoCoordinate trackedCoordinate = position.coordinate();
 | |
|          coordinate.latitude_             = trackedCoordinate.latitude();
 | |
|          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;
 | |
| }
 | |
| 
 | |
| void AlertManager::Impl::HandleAlert(const types::TextEventKey& key,
 | |
|                                      size_t messageIndex) const
 | |
| {
 | |
|    // Skip alert if there are more messages to be processed
 | |
|    if (messageIndex + 1 < textEventManager_->message_count(key))
 | |
|    {
 | |
|       return;
 | |
|    }
 | |
| 
 | |
|    settings::AudioSettings& audioSettings = settings::AudioSettings::Instance();
 | |
|    types::LocationMethod    locationMethod = types::GetLocationMethod(
 | |
|       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());
 | |
|    std::string alertWFO = audioSettings.alert_wfo().GetValue();
 | |
| 
 | |
|    auto message = textEventManager_->message_list(key).at(messageIndex);
 | |
| 
 | |
|    for (auto& segment : message->segments())
 | |
|    {
 | |
|       if (!segment->codedLocation_.has_value())
 | |
|       {
 | |
|          continue;
 | |
|       }
 | |
| 
 | |
|       auto&             vtec       = segment->header_->vtecString_.front();
 | |
|       auto              action     = vtec.pVtec_.action();
 | |
|       awips::Phenomenon phenomenon = vtec.pVtec_.phenomenon();
 | |
|       auto              eventEnd   = vtec.pVtec_.event_end();
 | |
|       bool alertActive             = (action != awips::PVtec::Action::Canceled);
 | |
| 
 | |
|       // If the event has ended or is inactive, or if the alert is not enabled,
 | |
|       // skip it
 | |
|       if (eventEnd < std::chrono::system_clock::now() || !alertActive ||
 | |
|           !audioSettings.alert_enabled(phenomenon).GetValue())
 | |
|       {
 | |
|          continue;
 | |
|       }
 | |
| 
 | |
|       bool activeAtLocation = (locationMethod == types::LocationMethod::All);
 | |
| 
 | |
|       if (locationMethod == types::LocationMethod::Fixed ||
 | |
|           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::AreaInRangeOfPoint(
 | |
|             alertCoordinates, currentCoordinate, alertRadius);
 | |
|       }
 | |
|       else if (locationMethod == types::LocationMethod::County)
 | |
|       {
 | |
|          // Determine if the alert contains the current county
 | |
|          auto fipsIds = segment->header_->ugc_.fips_ids();
 | |
|          auto it = std::find(fipsIds.cbegin(), fipsIds.cend(), alertCounty);
 | |
|          activeAtLocation = it != fipsIds.cend();
 | |
|       }
 | |
|       else if (locationMethod == types::LocationMethod::WFO)
 | |
|       {
 | |
|          std::string wfoId = vtec.pVtec_.office_id();
 | |
| 
 | |
|          activeAtLocation = wfoId == alertWFO;
 | |
|       }
 | |
| 
 | |
|       if (activeAtLocation)
 | |
|       {
 | |
|          logger_->info("Alert active at current location: {} {}.{} {}",
 | |
|                        vtec.pVtec_.office_id(),
 | |
|                        awips::GetPhenomenonCode(vtec.pVtec_.phenomenon()),
 | |
|                        awips::PVtec::GetActionCode(vtec.pVtec_.action()),
 | |
|                        vtec.pVtec_.event_tracking_number());
 | |
| 
 | |
|          mediaManager_->Play(audioSettings.alert_sound_file().GetValue());
 | |
|       }
 | |
|    }
 | |
| }
 | |
| 
 | |
| void AlertManager::Impl::UpdateLocationTracking(
 | |
|    const std::string& locationMethodName) const
 | |
| {
 | |
|    types::LocationMethod locationMethod =
 | |
|       types::GetLocationMethod(locationMethodName);
 | |
|    bool locationEnabled = locationMethod == types::LocationMethod::Track;
 | |
|    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_ {};
 | |
|    static std::mutex                  instanceMutex_ {};
 | |
| 
 | |
|    std::unique_lock lock(instanceMutex_);
 | |
| 
 | |
|    std::shared_ptr<AlertManager> alertManager = alertManagerReference_.lock();
 | |
| 
 | |
|    if (alertManager == nullptr)
 | |
|    {
 | |
|       alertManager           = std::make_shared<AlertManager>();
 | |
|       alertManagerReference_ = alertManager;
 | |
|    }
 | |
| 
 | |
|    return alertManager;
 | |
| }
 | |
| 
 | |
| } // namespace manager
 | |
| } // namespace qt
 | |
| } // namespace scwx
 | 
