#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace scwx::qt::map { static const std::string logPrefix_ = "scwx::qt::map::map_widget"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class MapWidgetImpl : public QObject { Q_OBJECT public: explicit MapWidgetImpl(MapWidget* widget, std::size_t id, const QMapLibre::Settings& settings) : id_ {id}, uuid_ {boost::uuids::random_generator()()}, widget_ {widget}, settings_(settings), map_(), layerList_ {}, imGuiRendererInitialized_ {false}, radarProductManager_ {nullptr}, radarProductLayer_ {nullptr}, overlayLayer_ {nullptr}, placefileLayer_ {nullptr}, markerLayer_ {nullptr}, colorTableLayer_ {nullptr}, autoRefreshEnabled_ {true}, autoUpdateEnabled_ {true}, selectedLevel2Product_ {common::Level2Product::Unknown}, currentStyleIndex_ {0}, currentStyle_ {nullptr}, frameDraws_(0), prevLatitude_ {0.0}, prevLongitude_ {0.0}, prevZoom_ {0.0}, prevBearing_ {0.0}, prevPitch_ {0.0}, tiltsToIndices_ {} { // Create views auto overlayProductView = std::make_shared(); overlayProductView->SetAutoRefresh(autoRefreshEnabled_); overlayProductView->SetAutoUpdate(autoUpdateEnabled_); // Initialize AlertLayerHandler map::AlertLayer::InitializeHandler(); auto& generalSettings = settings::GeneralSettings::Instance(); auto& mapSettings = settings::MapSettings::Instance(); // Initialize context context_->set_map_provider( GetMapProvider(generalSettings.map_provider().GetValue())); context_->set_overlay_product_view(overlayProductView); context_->set_widget(widget); // Initialize map data SetRadarSite(generalSettings.default_radar_site().GetValue()); smoothingEnabled_ = mapSettings.smoothing_enabled(id).GetValue(); // Create ImGui Context static size_t currentMapId_ {0u}; imGuiContextName_ = fmt::format("Map {}", ++currentMapId_); imGuiContext_ = model::ImGuiContextModel::Instance().CreateContext(imGuiContextName_); // Initialize ImGui Qt backend ImGui_ImplQt_Init(); InitializeCustomStyles(); } ~MapWidgetImpl() { DeinitializeCustomStyles(); // Set ImGui Context ImGui::SetCurrentContext(imGuiContext_); // Shutdown ImGui Context if (imGuiRendererInitialized_) { ImGui_ImplOpenGL3_Shutdown(); } ImGui_ImplQt_Shutdown(); // Destroy ImGui Context model::ImGuiContextModel::Instance().DestroyContext(imGuiContextName_); threadPool_.join(); } void AddLayer(types::LayerType type, types::LayerDescription description, const std::string& before = {}); void AddLayer(const std::string& id, const std::shared_ptr& layer, const std::string& before = {}); void AddLayers(); void AddPlacefileLayer(const std::string& placefileName, const std::string& before); void ConnectMapSignals(); void ConnectSignals(); void DeinitializeCustomStyles() const; void HandleHotkeyPressed(types::Hotkey hotkey, bool isAutoRepeat); void HandleHotkeyReleased(types::Hotkey hotkey); void HandleHotkeyUpdates(); void HandlePinchGesture(QPinchGesture* gesture); void ImGuiCheckFonts(); void InitializeCustomStyles(); void InitializeNewRadarProductView(const std::string& colorPalette); void RadarProductManagerConnect(); void RadarProductManagerDisconnect(); void RadarProductViewConnect(); void RadarProductViewDisconnect(); void RunMousePicking(); void SelectNearestRadarSite(double latitude, double longitude, std::optional type); void SetRadarSite(const std::string& radarSite, bool checkProductAvailability = false); void UpdateLoadedStyle(); bool UpdateStoredMapParameters(); void CheckLevel3Availability(); common::Level2Product GetLevel2ProductOrDefault(const std::string& productName) const; static std::string GetPlacefileLayerName(const std::string& placefileName); boost::asio::thread_pool threadPool_ {1u}; std::size_t id_; boost::uuids::uuid uuid_; std::shared_ptr context_ {std::make_shared()}; std::shared_ptr glContext_ { std::make_shared()}; MapWidget* widget_; QMapLibre::Settings settings_; std::shared_ptr map_; std::list layerList_; std::vector> genericLayers_ {}; const std::vector emptyStyles_ {}; std::vector customStyles_ { MapStyle {.name_ {"Custom"}, .url_ {}, .drawBelow_ {}}}; QStringList styleLayers_; boost::uuids::uuid customStyleUrlChangedCallbackId_ {}; boost::uuids::uuid customStyleDrawBelowChangedCallbackId_ {}; ImGuiContext* imGuiContext_; std::string imGuiContextName_; bool imGuiRendererInitialized_; std::uint64_t imGuiFontsBuildCount_ {}; std::shared_ptr layerModel_ { model::LayerModel::Instance()}; ui::EditMarkerDialog* editMarkerDialog_ {nullptr}; std::shared_ptr hotkeyManager_ { manager::HotkeyManager::Instance()}; std::shared_ptr placefileManager_ { manager::PlacefileManager::Instance()}; std::shared_ptr radarProductManager_; std::shared_ptr radarProductLayer_; std::shared_ptr overlayLayer_; std::shared_ptr overlayProductLayer_ {nullptr}; std::shared_ptr placefileLayer_; std::shared_ptr markerLayer_; std::shared_ptr colorTableLayer_; std::shared_ptr radarSiteLayer_ {nullptr}; std::list> placefileLayers_ {}; bool autoRefreshEnabled_; bool autoUpdateEnabled_; bool smoothingEnabled_ {false}; common::Level2Product selectedLevel2Product_; bool hasMouse_ {false}; bool isPainting_ {false}; bool lastItemPicked_ {false}; QPointF lastPos_ {}; QPointF lastGlobalPos_ {}; std::size_t currentStyleIndex_; const MapStyle* currentStyle_; std::string initialStyleName_ {}; Qt::KeyboardModifiers lastKeyboardModifiers_ { Qt::KeyboardModifier::NoModifier}; std::weak_ptr weakPickedEventHandler_ {}; uint64_t frameDraws_; double prevLatitude_; double prevLongitude_; double prevZoom_; double prevBearing_; double prevPitch_; std::set activeHotkeys_ {}; std::chrono::system_clock::time_point prevHotkeyTime_ {}; bool productAvailabilityCheckNeeded_ {false}; bool productAvailabilityUpdated_ {false}; bool productAvailabilityProductSelected_ {false}; std::unordered_map tiltsToIndices_; size_t currentTiltIndex_ {0}; public slots: void Update(); }; MapWidget::MapWidget(std::size_t id, const QMapLibre::Settings& settings) : p(std::make_unique(this, id, settings)) { if (settings::GeneralSettings::Instance().anti_aliasing_enabled().GetValue()) { QSurfaceFormat surfaceFormat = QSurfaceFormat::defaultFormat(); surfaceFormat.setSamples(4); setFormat(surfaceFormat); } setFocusPolicy(Qt::StrongFocus); grabGesture(Qt::GestureType::PinchGesture); ImGui_ImplQt_RegisterWidget(this); // Qt parent deals with memory management // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) p->editMarkerDialog_ = new ui::EditMarkerDialog(this); p->ConnectSignals(); } MapWidget::~MapWidget() { // Make sure we have a valid context so we can delete the QMapLibre. makeCurrent(); } void MapWidgetImpl::InitializeCustomStyles() { auto& generalSettings = settings::GeneralSettings::Instance(); auto& customStyleUrl = generalSettings.custom_style_url(); auto& customStyleDrawLayer = generalSettings.custom_style_draw_layer(); auto& customStyle = customStyles_.at(0); customStyle.url_ = customStyleUrl.GetValue(); customStyle.drawBelow_.push_back(customStyleDrawLayer.GetValue()); customStyleUrlChangedCallbackId_ = customStyleUrl.RegisterValueChangedCallback( [this](const std::string& url) { customStyles_[0].url_ = url; }); customStyleDrawBelowChangedCallbackId_ = customStyleDrawLayer.RegisterValueChangedCallback( [this](const std::string& drawLayer) { if (!drawLayer.empty()) { customStyles_[0].drawBelow_ = {drawLayer}; } else { customStyles_[0].drawBelow_.clear(); } }); } void MapWidgetImpl::DeinitializeCustomStyles() const { auto& generalSettings = settings::GeneralSettings::Instance(); auto& customStyleUrl = generalSettings.custom_style_url(); auto& customStyleDrawLayer = generalSettings.custom_style_draw_layer(); customStyleUrl.UnregisterValueChangedCallback( customStyleUrlChangedCallbackId_); customStyleDrawLayer.UnregisterValueChangedCallback( customStyleDrawBelowChangedCallbackId_); } void MapWidgetImpl::ConnectMapSignals() { connect(map_.get(), &QMapLibre::Map::needsRendering, this, &MapWidgetImpl::Update); connect(map_.get(), &QMapLibre::Map::copyrightsChanged, this, [this](const QString& copyrightsHtml) { QTextDocument document {}; document.setHtml(copyrightsHtml); // HTML cannot currently be included in ImGui windows. Where // links can't be included, remove "Improve this map". std::string copyrights {document.toPlainText().toStdString()}; boost::erase_all(copyrights, "Improve this map"); boost::trim_right(copyrights); context_->set_map_copyrights(copyrights); }); } void MapWidgetImpl::ConnectSignals() { connect(placefileManager_.get(), &manager::PlacefileManager::PlacefileUpdated, widget_, static_cast(&QWidget::update)); // When the layer model changes, update the layers connect(layerModel_.get(), &QAbstractItemModel::dataChanged, widget_, [this](const QModelIndex& topLeft, const QModelIndex& bottomRight, const QList& /* roles */) { static const int enabledColumn = static_cast(model::LayerModel::Column::Enabled); const int displayColumn = static_cast(model::LayerModel::Column::DisplayMap1) + static_cast(id_); // Update layers if the displayed or enabled state of the layer // has changed if ((topLeft.column() <= displayColumn && displayColumn <= bottomRight.column()) || (topLeft.column() <= enabledColumn && enabledColumn <= bottomRight.column())) { AddLayers(); } }); connect(layerModel_.get(), &QAbstractItemModel::modelReset, widget_, [this]() { AddLayers(); }); connect(layerModel_.get(), &QAbstractItemModel::rowsInserted, widget_, [this](const QModelIndex& /* parent */, // int /* first */, int /* last */) { AddLayers(); }); connect(layerModel_.get(), &QAbstractItemModel::rowsMoved, widget_, [this](const QModelIndex& /* sourceParent */, int /* sourceStart */, int /* sourceEnd */, const QModelIndex& /* destinationParent */, int /* destinationRow */) { AddLayers(); }); connect(layerModel_.get(), &QAbstractItemModel::rowsRemoved, widget_, [this](const QModelIndex& /* parent */, // int /* first */, int /* last */) { AddLayers(); }); connect(hotkeyManager_.get(), &manager::HotkeyManager::HotkeyPressed, this, &MapWidgetImpl::HandleHotkeyPressed); connect(hotkeyManager_.get(), &manager::HotkeyManager::HotkeyReleased, this, &MapWidgetImpl::HandleHotkeyReleased); connect(widget_, &MapWidget::RadarSiteUpdated, widget_, [this](const std::shared_ptr&) { productAvailabilityProductSelected_ = true; CheckLevel3Availability(); }); } void MapWidgetImpl::HandleHotkeyPressed(types::Hotkey hotkey, bool isAutoRepeat) { Q_UNUSED(isAutoRepeat); switch (hotkey) { case types::Hotkey::AddLocationMarker: if (hasMouse_) { auto coordinate = map_->coordinateForPixel(lastPos_); editMarkerDialog_->setup(coordinate.first, coordinate.second); editMarkerDialog_->show(); } break; case types::Hotkey::ChangeMapStyle: if (context_->settings().isActive_) { widget_->changeStyle(); } break; case types::Hotkey::CopyCursorCoordinates: if (hasMouse_) { QClipboard* clipboard = QGuiApplication::clipboard(); auto coordinate = map_->coordinateForPixel(lastPos_); std::string text = fmt::format("{}, {}", coordinate.first, coordinate.second); clipboard->setText(QString::fromStdString(text)); } break; case types::Hotkey::CopyMapCoordinates: if (context_->settings().isActive_) { QClipboard* clipboard = QGuiApplication::clipboard(); auto coordinate = map_->coordinate(); std::string text = fmt::format("{}, {}", coordinate.first, coordinate.second); clipboard->setText(QString::fromStdString(text)); } break; default: break; } activeHotkeys_.insert(hotkey); } void MapWidgetImpl::HandleHotkeyReleased(types::Hotkey hotkey) { // Erase the hotkey from the active set regardless of whether this is the // active map activeHotkeys_.erase(hotkey); } void MapWidgetImpl::HandleHotkeyUpdates() { using namespace std::chrono_literals; static constexpr float kMapPanFactor = 0.2f; static constexpr float kMapRotateFactor = 0.2f; static constexpr double kMapScaleFactor = 1000.0; std::chrono::system_clock::time_point hotkeyTime = std::chrono::system_clock::now(); std::chrono::milliseconds hotkeyElapsed = std::min(std::chrono::duration_cast( hotkeyTime - prevHotkeyTime_), 100ms); prevHotkeyTime_ = hotkeyTime; if (!context_->settings().isActive_) { // Don't attempt to handle a hotkey if this is not the active map return; } for (auto& hotkey : activeHotkeys_) { switch (hotkey) { case types::Hotkey::MapPanUp: { QPointF delta {0.0f, kMapPanFactor * hotkeyElapsed.count()}; map_->moveBy(delta); break; } case types::Hotkey::MapPanDown: { QPointF delta {0.0f, -kMapPanFactor * hotkeyElapsed.count()}; map_->moveBy(delta); break; } case types::Hotkey::MapPanLeft: { QPointF delta {kMapPanFactor * hotkeyElapsed.count(), 0.0f}; map_->moveBy(delta); break; } case types::Hotkey::MapPanRight: { QPointF delta {-kMapPanFactor * hotkeyElapsed.count(), 0.0f}; map_->moveBy(delta); break; } case types::Hotkey::MapRotateClockwise: { QPointF delta {-kMapRotateFactor * hotkeyElapsed.count(), 0.0f}; map_->rotateBy({}, delta); break; } case types::Hotkey::MapRotateCounterclockwise: { QPointF delta {kMapRotateFactor * hotkeyElapsed.count(), 0.0f}; map_->rotateBy({}, delta); break; } case types::Hotkey::MapZoomIn: { auto widgetSize = widget_->size(); QPointF center = {widgetSize.width() * 0.5f, widgetSize.height() * 0.5f}; double scale = std::pow(2.0, hotkeyElapsed.count() / kMapScaleFactor); map_->scaleBy(scale, center); break; } case types::Hotkey::MapZoomOut: { auto widgetSize = widget_->size(); QPointF center = {widgetSize.width() * 0.5f, widgetSize.height() * 0.5f}; double scale = 1.0 / std::pow(2.0, hotkeyElapsed.count() / kMapScaleFactor); map_->scaleBy(scale, center); break; } default: break; } } } void MapWidgetImpl::HandlePinchGesture(QPinchGesture* gesture) { if (gesture->changeFlags() & QPinchGesture::ChangeFlag::ScaleFactorChanged) { map_->scaleBy(gesture->scaleFactor(), widget_->mapFromGlobal(gesture->centerPoint())); } } common::Level3ProductCategoryMap MapWidget::GetAvailableLevel3Categories() { if (p->radarProductManager_ != nullptr) { return p->radarProductManager_->GetAvailableLevel3Categories(); } else { return {}; } } std::optional MapWidget::GetElevation() const { auto radarProductView = p->context_->radar_product_view(); if (radarProductView != nullptr) { return radarProductView->elevation(); } else { return {}; } } std::vector MapWidget::GetElevationCuts() const { auto radarProductView = p->context_->radar_product_view(); if (radarProductView != nullptr) { return radarProductView->GetElevationCuts(); } else { return {}; } } std::optional MapWidget::GetIncomingLevel2Elevation() const { return p->radarProductManager_->incoming_level_2_elevation(); } common::Level2Product MapWidgetImpl::GetLevel2ProductOrDefault(const std::string& productName) const { common::Level2Product level2Product = common::GetLevel2Product(productName); if (level2Product == common::Level2Product::Unknown) { auto radarProductView = context_->radar_product_view(); if (radarProductView != nullptr) { level2Product = common::GetLevel2Product(radarProductView->GetRadarProductName()); } } if (level2Product == common::Level2Product::Unknown) { if (selectedLevel2Product_ != common::Level2Product::Unknown) { level2Product = selectedLevel2Product_; } else { level2Product = common::Level2Product::Reflectivity; } } return level2Product; } std::vector MapWidget::GetLevel3Products() { if (p->radarProductManager_ != nullptr) { return p->radarProductManager_->GetLevel3Products(); } else { return {}; } } std::string MapWidget::GetMapStyle() const { if (p->currentStyle_ != nullptr) { return p->currentStyle_->name_; } else { return "?"; } } common::RadarProductGroup MapWidget::GetRadarProductGroup() const { auto radarProductView = p->context_->radar_product_view(); if (radarProductView != nullptr) { return radarProductView->GetRadarProductGroup(); } else { return common::RadarProductGroup::Unknown; } } std::string MapWidget::GetRadarProductName() const { auto radarProductView = p->context_->radar_product_view(); if (radarProductView != nullptr) { return radarProductView->GetRadarProductName(); } else { return "?"; } } std::shared_ptr MapWidget::GetRadarSite() const { std::shared_ptr radarSite = nullptr; if (p->radarProductManager_ != nullptr) { radarSite = p->radarProductManager_->radar_site(); } return radarSite; } std::chrono::system_clock::time_point MapWidget::GetSelectedTime() const { auto radarProductView = p->context_->radar_product_view(); std::chrono::system_clock::time_point time; // If there is an active radar product view if (radarProductView != nullptr) { // Select the time associated with the active radar product time = radarProductView->selected_time(); } return time; } std::uint16_t MapWidget::GetVcp() const { auto radarProductView = p->context_->radar_product_view(); if (radarProductView != nullptr) { return radarProductView->vcp(); } else { return 0u; } } bool MapWidget::GetRadarWireframeEnabled() const { return p->context_->settings().radarWireframeEnabled_; } void MapWidget::SetRadarWireframeEnabled(bool wireframeEnabled) { p->context_->settings().radarWireframeEnabled_ = wireframeEnabled; QMetaObject::invokeMethod( this, static_cast(&QWidget::update)); } bool MapWidget::GetSmoothingEnabled() const { return p->smoothingEnabled_; } void MapWidget::SetSmoothingEnabled(bool smoothingEnabled) { p->smoothingEnabled_ = smoothingEnabled; auto radarProductView = p->context_->radar_product_view(); if (radarProductView != nullptr) { radarProductView->set_smoothing_enabled(smoothingEnabled); radarProductView->Update(); } } void MapWidget::SelectElevation(float elevation) { auto radarProductView = p->context_->radar_product_view(); if (radarProductView != nullptr) { radarProductView->SelectElevation(elevation); radarProductView->Update(); } } void MapWidget::SelectRadarProduct(common::RadarProductGroup group, const std::string& product, std::int16_t productCode, std::chrono::system_clock::time_point time, bool update) { bool radarProductViewCreated = false; auto radarProductView = p->context_->radar_product_view(); std::string productName {product}; // Validate level 2 product, set to default if invalid if (group == common::RadarProductGroup::Level2) { common::Level2Product level2Product = p->GetLevel2ProductOrDefault(productName); productName = common::GetLevel2Name(level2Product); p->selectedLevel2Product_ = level2Product; } if (group == common::RadarProductGroup::Level3 && productCode == 0) { productCode = common::GetLevel3ProductCodeByAwipsId(productName); } if (group == common::RadarProductGroup::Level3) { const auto& tiltIndex = p->tiltsToIndices_.find(productName); p->currentTiltIndex_ = tiltIndex != p->tiltsToIndices_.cend() ? tiltIndex->second : 0; } else { p->currentTiltIndex_ = 0; } if (radarProductView == nullptr || radarProductView->GetRadarProductGroup() != group || (radarProductView->GetRadarProductGroup() == common::RadarProductGroup::Level2 && radarProductView->GetRadarProductName() != productName) || p->context_->radar_product_code() != productCode) { p->RadarProductViewDisconnect(); radarProductView = view::RadarProductViewFactory::Create( group, productName, productCode, p->radarProductManager_); radarProductView->set_smoothing_enabled(p->smoothingEnabled_); p->context_->set_radar_product_view(radarProductView); p->RadarProductViewConnect(); radarProductViewCreated = true; } else { radarProductView->SelectProduct(productName); } p->context_->set_radar_product_group(group); p->context_->set_radar_product(productName); p->context_->set_radar_product_code(productCode); if (radarProductView != nullptr) { // Select the time associated with the request radarProductView->SelectTime(time); if (radarProductViewCreated) { const std::string palette = (group == common::RadarProductGroup::Level2) ? common::GetLevel2Palette(common::GetLevel2Product(productName)) : common::GetLevel3Palette(productCode); p->InitializeNewRadarProductView(palette); } else if (update) { radarProductView->Update(); } } if (p->autoRefreshEnabled_) { p->radarProductManager_->EnableRefresh( group, productName, true, p->uuid_); } } void MapWidget::SelectRadarProduct( std::shared_ptr record) { const std::string radarId = record->radar_id(); common::RadarProductGroup group = record->radar_product_group(); const std::string product = record->radar_product(); std::chrono::system_clock::time_point time = record->time(); int16_t productCode = record->product_code(); logger_->debug("SelectRadarProduct: {}, {}, {}, {}", radarId, common::GetRadarProductGroupName(group), product, scwx::util::TimeString(time)); p->SetRadarSite(radarId); SelectRadarProduct(group, product, productCode, time); } void MapWidget::SelectRadarSite(const std::string& id, bool updateCoordinates) { logger_->debug("Selecting radar site: {}", id); std::shared_ptr radarSite = config::RadarSite::Get(id); SelectRadarSite(radarSite, updateCoordinates); } void MapWidget::SelectRadarSite(std::shared_ptr radarSite, bool updateCoordinates) { // Verify radar site is valid and has changed if (radarSite != nullptr && (p->radarProductManager_ == nullptr || radarSite->id() != p->radarProductManager_->radar_site()->id())) { auto radarProductView = p->context_->radar_product_view(); if (updateCoordinates) { p->map_->setCoordinate( {radarSite->latitude(), radarSite->longitude()}); } p->SetRadarSite(radarSite->id(), true); p->Update(); // Select products from new site if (radarProductView != nullptr) { radarProductView->set_radar_product_manager(p->radarProductManager_); } p->AddLayers(); // TODO: Disable refresh from old site Q_EMIT RadarSiteUpdated(radarSite); } } void MapWidget::SelectTime(std::chrono::system_clock::time_point time) { auto radarProductView = p->context_->radar_product_view(); // Update other views p->context_->overlay_product_view()->SelectTime(time); // If there is an active radar product view if (radarProductView != nullptr) { // Select the time associated with the active radar product radarProductView->SelectTime(time); // Trigger an update of the radar product view radarProductView->Update(); } } void MapWidget::SetActive(bool isActive) { p->context_->settings().isActive_ = isActive; QMetaObject::invokeMethod( this, static_cast(&QWidget::update)); } void MapWidget::SetAutoRefresh(bool enabled) { if (p->autoRefreshEnabled_ != enabled) { p->autoRefreshEnabled_ = enabled; auto radarProductView = p->context_->radar_product_view(); if (p->autoRefreshEnabled_ && radarProductView != nullptr) { p->radarProductManager_->EnableRefresh( radarProductView->GetRadarProductGroup(), radarProductView->GetRadarProductName(), true, p->uuid_); } p->context_->overlay_product_view()->SetAutoRefresh(enabled); } } void MapWidget::SetAutoUpdate(bool enabled) { p->autoUpdateEnabled_ = enabled; p->context_->overlay_product_view()->SetAutoUpdate(enabled); } void MapWidget::SetMapLocation(double latitude, double longitude, bool updateRadarSite) { if (p->map_ != nullptr && (p->prevLatitude_ != latitude || p->prevLongitude_ != longitude)) { // Update the map location p->map_->setCoordinate({latitude, longitude}); // If the radar site should be updated based on the new location if (updateRadarSite) { // Find the nearest WSR-88D radar std::shared_ptr nearestRadarSite = config::RadarSite::FindNearest(latitude, longitude, "wsr88d"); // If found, select it if (nearestRadarSite != nullptr) { SelectRadarSite(nearestRadarSite->id(), false); } } } } void MapWidget::SetMapParameters( double latitude, double longitude, double zoom, double bearing, double pitch) { if (p->map_ != nullptr && (p->prevLatitude_ != latitude || p->prevLongitude_ != longitude || p->prevZoom_ != zoom || p->prevBearing_ != bearing || p->prevPitch_ != pitch)) { p->map_->setCoordinateZoom({latitude, longitude}, zoom); p->map_->setBearing(bearing); p->map_->setPitch(pitch); } } void MapWidget::SetInitialMapStyle(const std::string& styleName) { p->initialStyleName_ = styleName; } void MapWidget::SetMapStyle(const std::string& styleName) { const auto mapProvider = p->context_->map_provider(); const auto& mapProviderInfo = GetMapProviderInfo(mapProvider); auto& fixedStyles = mapProviderInfo.mapStyles_; auto styles = boost::join(fixedStyles, p->customStyles_[0].IsValid() ? p->customStyles_ : p->emptyStyles_); if (p->currentStyleIndex_ >= styles.size()) { p->currentStyleIndex_ = 0; } for (size_t i = 0u; i < styles.size(); ++i) { if (styles[i].name_ == styleName) { p->currentStyleIndex_ = i; p->currentStyle_ = &styles[i]; logger_->debug("Updating style: {}", styles[i].name_); util::maplibre::SetMapStyleUrl(p->context_, styles[i].url_); if (++p->currentStyleIndex_ >= styles.size()) { p->currentStyleIndex_ = 0; } break; } } } void MapWidget::UpdateMouseCoordinate(const common::Coordinate& coordinate) { if (p->context_->mouse_coordinate() != coordinate) { auto& generalSettings = settings::GeneralSettings::Instance(); p->context_->set_mouse_coordinate(coordinate); auto keyboardModifiers = QGuiApplication::keyboardModifiers(); if (generalSettings.cursor_icon_always_on().GetValue() || keyboardModifiers != Qt::KeyboardModifier::NoModifier || keyboardModifiers != p->lastKeyboardModifiers_) { QMetaObject::invokeMethod( this, static_cast(&QWidget::update)); } p->lastKeyboardModifiers_ = keyboardModifiers; } } qreal MapWidget::pixelRatio() { return devicePixelRatioF(); } void MapWidget::changeStyle() { const auto mapProvider = p->context_->map_provider(); const auto& mapProviderInfo = GetMapProviderInfo(mapProvider); auto& fixedStyles = mapProviderInfo.mapStyles_; auto styles = boost::join(fixedStyles, p->customStyles_[0].IsValid() ? p->customStyles_ : p->emptyStyles_); if (p->currentStyleIndex_ >= styles.size()) { p->currentStyleIndex_ = 0; } p->currentStyle_ = &styles[p->currentStyleIndex_]; logger_->debug("Updating style: {}", styles[p->currentStyleIndex_].name_); util::maplibre::SetMapStyleUrl(p->context_, styles[p->currentStyleIndex_].url_); if (++p->currentStyleIndex_ >= styles.size()) { p->currentStyleIndex_ = 0; } Q_EMIT MapStyleChanged(p->currentStyle_->name_); } void MapWidget::DumpLayerList() const { logger_->info("Layers: {}", p->map_->layerIds().join(", ").toStdString()); } void MapWidgetImpl::AddLayers() { if (styleLayers_.isEmpty()) { // Skip if the map has not yet been initialized return; } logger_->debug("Add Layers"); // Clear custom layers for (const std::string& id : layerList_) { map_->removeLayer(id.c_str()); } layerList_.clear(); genericLayers_.clear(); placefileLayers_.clear(); // Update custom layer list from model types::LayerVector customLayers = model::LayerModel::Instance()->GetLayers(); // Start by drawing layers before any style-defined layers std::string before = styleLayers_.front().toStdString(); // Loop through each custom layer in reverse order for (const auto& customLayer : std::ranges::reverse_view(customLayers)) { if (customLayer.type_ == types::LayerType::Map) { // Style-defined map layers switch (std::get(customLayer.description_)) { // Subsequent layers are drawn underneath the map symbology layer case types::MapLayer::MapUnderlay: before = util::maplibre::FindMapSymbologyLayer( styleLayers_, currentStyle_->drawBelow_); break; // Subsequent layers are drawn after all style-defined layers case types::MapLayer::MapSymbology: before = ""; break; default: break; } } // id_ is always < 4, so this is safe // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) else if (customLayer.displayed_[id_]) { // If the layer is displayed for the current map, add it AddLayer(customLayer.type_, customLayer.description_, before); } } } void MapWidgetImpl::AddLayer(types::LayerType type, types::LayerDescription description, const std::string& before) { std::string layerName = types::GetLayerName(type, description); auto radarProductView = context_->radar_product_view(); if (type == types::LayerType::Radar) { // If there is a radar product view, create the radar product layer if (radarProductView != nullptr) { radarProductLayer_ = std::make_shared(glContext_); AddLayer(layerName, radarProductLayer_, before); } } else if (type == types::LayerType::Alert) { auto phenomenon = std::get(description); std::shared_ptr alertLayer = std::make_shared(glContext_, phenomenon); AddLayer(fmt::format("alert.{}", awips::GetPhenomenonCode(phenomenon)), alertLayer, before); connect(alertLayer.get(), &AlertLayer::AlertSelected, widget_, &MapWidget::AlertSelected); } else if (type == types::LayerType::Placefile) { // If the placefile is enabled, add the placefile layer std::string placefileName = std::get(description); if (placefileManager_->placefile_enabled(placefileName)) { AddPlacefileLayer(placefileName, before); } } else if (type == types::LayerType::Information) { switch (std::get(description)) { // Create the map overlay layer case types::InformationLayer::MapOverlay: overlayLayer_ = std::make_shared(glContext_); AddLayer(layerName, overlayLayer_, before); break; // If there is a radar product view, create the color table layer case types::InformationLayer::ColorTable: if (radarProductView != nullptr) { colorTableLayer_ = std::make_shared(glContext_); AddLayer(layerName, colorTableLayer_, before); } break; // Create the radar site layer case types::InformationLayer::RadarSite: radarSiteLayer_ = std::make_shared(glContext_); AddLayer(layerName, radarSiteLayer_, before); connect( radarSiteLayer_.get(), &RadarSiteLayer::RadarSiteSelected, this, [this](const std::string& id) { auto& generalSettings = settings::GeneralSettings::Instance(); widget_->RadarSiteRequested( id, generalSettings.center_on_radar_selection().GetValue()); }); break; // Create the location marker layer case types::InformationLayer::Markers: markerLayer_ = std::make_shared(glContext_); AddLayer(layerName, markerLayer_, before); break; default: break; } } else if (type == types::LayerType::Data) { switch (std::get(description)) { // If there is a radar product view, create the overlay product layer case types::DataLayer::OverlayProduct: if (radarProductView != nullptr) { overlayProductLayer_ = std::make_shared(glContext_); AddLayer(layerName, overlayProductLayer_, before); } break; // If there is a radar product view, create the radar range layer case types::DataLayer::RadarRange: if (radarProductView != nullptr) { std::shared_ptr radarSite = radarProductManager_->radar_site(); RadarRangeLayer::Add( map_, radarProductView->range(), {radarSite->latitude(), radarSite->longitude()}, QString::fromStdString(before)); layerList_.push_back(types::GetLayerName(type, description)); } break; default: break; } } } void MapWidgetImpl::AddPlacefileLayer(const std::string& placefileName, const std::string& before) { std::shared_ptr placefileLayer = std::make_shared(glContext_, placefileName); placefileLayers_.push_back(placefileLayer); AddLayer(GetPlacefileLayerName(placefileName), placefileLayer, before); // When the layer updates, trigger a map widget update connect(placefileLayer.get(), &PlacefileLayer::DataReloaded, widget_, static_cast(&QWidget::update)); } std::string MapWidgetImpl::GetPlacefileLayerName(const std::string& placefileName) { return types::GetLayerName(types::LayerType::Placefile, placefileName); } void MapWidgetImpl::AddLayer(const std::string& id, const std::shared_ptr& layer, const std::string& before) { // QMapLibre::addCustomLayer will take ownership of the std::unique_ptr std::unique_ptr pHost = std::make_unique(layer, context_); try { map_->addCustomLayer(id.c_str(), std::move(pHost), before.c_str()); layerList_.push_back(id); genericLayers_.push_back(layer); connect(layer.get(), &GenericLayer::NeedsRendering, widget_, static_cast(&QWidget::update)); } catch (const std::exception&) { // When dragging and dropping, a temporary duplicate layer exists } } bool MapWidget::event(QEvent* e) { if (e->type() == QEvent::Type::Paint && p->isPainting_) { logger_->error("Recursive paint event ignored"); // Ignore recursive paint events return true; } auto pickedEventHandler = p->weakPickedEventHandler_.lock(); if (pickedEventHandler != nullptr && pickedEventHandler->event_ != nullptr) { pickedEventHandler->event_(e); } pickedEventHandler.reset(); switch (e->type()) { case QEvent::Type::Gesture: // QEvent is always a QGestureEvent // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) gestureEvent(static_cast(e)); break; default: break; } return QOpenGLWidget::event(e); } void MapWidget::enterEvent(QEnterEvent* /* ev */) { p->hasMouse_ = true; } void MapWidget::leaveEvent(QEvent* /* ev */) { p->hasMouse_ = false; } void MapWidget::keyPressEvent(QKeyEvent* ev) { if (p->hotkeyManager_->HandleKeyPress(ev)) { ev->accept(); } } void MapWidget::keyReleaseEvent(QKeyEvent* ev) { if (p->hotkeyManager_->HandleKeyRelease(ev)) { ev->accept(); } } void MapWidget::gestureEvent(QGestureEvent* ev) { if (QGesture* pinch = ev->gesture(Qt::PinchGesture)) { // QGesture is always a QPinchGesture // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) p->HandlePinchGesture(static_cast(pinch)); } } void MapWidget::mousePressEvent(QMouseEvent* ev) { p->lastPos_ = ev->position(); p->lastGlobalPos_ = ev->globalPosition(); if (ev->type() == QEvent::Type::MouseButtonPress) { if (ev->buttons() == (Qt::MouseButton::LeftButton | Qt::MouseButton::RightButton)) { changeStyle(); } else if (ev->buttons() == Qt::MouseButton::MiddleButton) { // Select nearest WSR-88D radar on middle click auto coordinate = p->map_->coordinateForPixel(p->lastPos_); p->SelectNearestRadarSite( coordinate.first, coordinate.second, "wsr88d"); } } if (ev->type() == QEvent::Type::MouseButtonDblClick) { if (ev->buttons() == Qt::MouseButton::LeftButton) { p->map_->scaleBy(2.0, p->lastPos_); } else if (ev->buttons() == Qt::MouseButton::RightButton) { p->map_->scaleBy(0.5, p->lastPos_); } } ev->accept(); } void MapWidget::mouseMoveEvent(QMouseEvent* ev) { QPointF delta = ev->position() - p->lastPos_; if (!delta.isNull()) { if (ev->buttons() == Qt::MouseButton::LeftButton) { p->map_->moveBy(delta); } else if (ev->buttons() == Qt::MouseButton::RightButton) { p->map_->rotateBy(p->lastPos_, ev->position()); } } p->lastPos_ = ev->position(); p->lastGlobalPos_ = ev->globalPosition(); ev->accept(); } void MapWidget::wheelEvent(QWheelEvent* ev) { if (ev->angleDelta().y() == 0) { return; } float factor = ev->angleDelta().y() / 1200.; if (ev->angleDelta().y() < 0) { factor = factor > -1 ? factor : 1 / factor; } p->map_->scaleBy(1 + factor, ev->position()); ev->accept(); } void MapWidget::initializeGL() { logger_->debug("initializeGL()"); makeCurrent(); p->glContext_->Initialize(); // Lock ImGui font atlas prior to new ImGui frame std::shared_lock imguiFontAtlasLock { manager::FontManager::Instance().imgui_font_atlas_mutex()}; // Initialize ImGui OpenGL3 backend ImGui::SetCurrentContext(p->imGuiContext_); ImGui_ImplQt_RegisterWidget(this); ImGui_ImplOpenGL3_Init(); p->imGuiFontsBuildCount_ = manager::FontManager::Instance().imgui_fonts_build_count(); p->imGuiRendererInitialized_ = true; p->map_.reset( new QMapLibre::Map(nullptr, p->settings_, size(), pixelRatio())); p->context_->set_map(p->map_); p->ConnectMapSignals(); // Set default location to radar site std::shared_ptr radarSite = p->radarProductManager_->radar_site(); p->map_->setCoordinateZoom({radarSite->latitude(), radarSite->longitude()}, 7); p->UpdateStoredMapParameters(); Q_EMIT MapParametersChanged(p->prevLatitude_, p->prevLongitude_, p->prevZoom_, p->prevBearing_, p->prevPitch_); // Update style if (p->initialStyleName_.empty()) { changeStyle(); } else { SetMapStyle(p->initialStyleName_); } connect( p->map_.get(), &QMapLibre::Map::mapChanged, this, &MapWidget::mapChanged); } void MapWidget::paintGL() { p->isPainting_ = true; auto defaultFont = manager::FontManager::Instance().GetImGuiFont( types::FontCategory::Default); p->frameDraws_++; p->glContext_->StartFrame(); // Handle hotkey updates p->HandleHotkeyUpdates(); // Lock ImGui font atlas prior to new ImGui frame std::shared_lock imguiFontAtlasLock { manager::FontManager::Instance().imgui_font_atlas_mutex()}; // Check ImGui fonts ImGui::SetCurrentContext(p->imGuiContext_); p->ImGuiCheckFonts(); // Update pixel ratio p->context_->set_pixel_ratio(pixelRatio()); // Render QMapLibre Map p->map_->resize(size()); p->map_->setOpenGLFramebufferObject(defaultFramebufferObject(), size() * pixelRatio()); p->map_->render(); // ImGui tool tip code // Setup ImGui Frame ImGui::SetCurrentContext(p->imGuiContext_); // Start ImGui Frame ImGui_ImplQt_NewFrame(this); ImGui_ImplOpenGL3_NewFrame(); ImGui::NewFrame(); // Set default font ImGui::PushFont(defaultFont->font()); // Perform mouse picking if (p->hasMouse_) { p->RunMousePicking(); } else if (p->lastItemPicked_) { // Hide the tooltip when losing focus util::tooltip::Hide(); p->lastItemPicked_ = false; } // Pop default font ImGui::PopFont(); // Render ImGui Frame ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); // Unlock ImGui font atlas after rendering imguiFontAtlasLock.unlock(); // Paint complete Q_EMIT WidgetPainted(); p->isPainting_ = false; } void MapWidgetImpl::ImGuiCheckFonts() { // Update ImGui Fonts if required std::uint64_t currentImGuiFontsBuildCount = manager::FontManager::Instance().imgui_fonts_build_count(); if (imGuiFontsBuildCount_ != currentImGuiFontsBuildCount || !model::ImGuiContextModel::Instance().font_atlas()->IsBuilt()) { ImGui_ImplOpenGL3_DestroyFontsTexture(); ImGui_ImplOpenGL3_CreateFontsTexture(); } static bool haveLogged = false; if (!model::ImGuiContextModel::Instance().font_atlas()->IsBuilt() && !haveLogged) { logger_->error("ImGui font atlas could not be built."); haveLogged = true; } imGuiFontsBuildCount_ = currentImGuiFontsBuildCount; } void MapWidgetImpl::RunMousePicking() { const QMapLibre::CustomLayerRenderParameters params = { .width = static_cast(widget_->size().width()), .height = static_cast(widget_->size().height()), .latitude = map_->coordinate().first, .longitude = map_->coordinate().second, .zoom = map_->zoom(), .bearing = map_->bearing(), .pitch = map_->pitch(), .fieldOfView = 0}; auto coordinate = map_->coordinateForPixel(lastPos_); auto mouseScreenCoordinate = util::maplibre::LatLongToScreenCoordinate(coordinate); // For each layer in reverse bool itemPicked = false; std::shared_ptr eventHandler = nullptr; for (auto it = genericLayers_.rbegin(); it != genericLayers_.rend(); ++it) { // Run mouse picking for each layer if ((*it)->RunMousePicking(context_, params, lastPos_, lastGlobalPos_, mouseScreenCoordinate, {coordinate.first, coordinate.second}, eventHandler)) { // If a draw item was picked, don't process additional layers itemPicked = true; break; } } // If no draw item was picked, hide the tooltip auto prevPickedEventHandler = weakPickedEventHandler_.lock(); if (!itemPicked) { util::tooltip::Hide(); if (prevPickedEventHandler != nullptr) { // Send leave event to picked event handler if (prevPickedEventHandler->event_ != nullptr) { QEvent event(QEvent::Type::Leave); prevPickedEventHandler->event_(&event); } // Reset picked event handler weakPickedEventHandler_.reset(); } } else if (eventHandler != nullptr) { // If the event handler changed if (prevPickedEventHandler != eventHandler) { // Send leave event to old event handler if (prevPickedEventHandler != nullptr && prevPickedEventHandler->event_ != nullptr) { QEvent event(QEvent::Type::Leave); prevPickedEventHandler->event_(&event); } // Send enter event to new event handler if (eventHandler->event_ != nullptr) { QEvent event(QEvent::Type::Enter); eventHandler->event_(&event); } // Store picked event handler weakPickedEventHandler_ = eventHandler; } eventHandler.reset(); } if (prevPickedEventHandler != nullptr) { prevPickedEventHandler.reset(); } Q_EMIT widget_->MouseCoordinateChanged( {coordinate.first, coordinate.second}); lastItemPicked_ = itemPicked; } void MapWidget::mapChanged(QMapLibre::Map::MapChange mapChange) { switch (mapChange) { case QMapLibre::Map::MapChangeDidFinishLoadingStyle: p->UpdateLoadedStyle(); p->AddLayers(); break; default: break; } } void MapWidgetImpl::UpdateLoadedStyle() { styleLayers_ = map_->layerIds(); } void MapWidgetImpl::RadarProductManagerConnect() { if (radarProductManager_ != nullptr) { connect(radarProductManager_.get(), &manager::RadarProductManager::IncomingLevel2ElevationChanged, this, [this](std::optional incomingElevation) { Q_EMIT widget_->IncomingLevel2ElevationChanged( incomingElevation); }); connect(radarProductManager_.get(), &manager::RadarProductManager::Level3ProductsChanged, this, [this]() { const common::Level3ProductCategoryMap& categoryMap = widget_->GetAvailableLevel3Categories(); tiltsToIndices_.clear(); for (const auto& category : categoryMap) { for (const auto& product : category.second) { for (size_t tiltIndex = 0; tiltIndex < product.second.size(); tiltIndex++) { tiltsToIndices_.emplace(product.second[tiltIndex], tiltIndex); } } } productAvailabilityUpdated_ = true; CheckLevel3Availability(); Q_EMIT widget_->Level3ProductsChanged(); }); connect( radarProductManager_.get(), &manager::RadarProductManager::NewDataAvailable, this, [this](common::RadarProductGroup group, const std::string& product, bool isChunks, std::chrono::system_clock::time_point latestTime) { if (autoRefreshEnabled_ && context_->radar_product_group() == group && (group == common::RadarProductGroup::Level2 || context_->radar_product() == product)) { if (isChunks && autoUpdateEnabled_) { // Level 2 products may have multiple time points, // ensure the latest is selected widget_->SelectRadarProduct(group, product); } else { // Create file request const std::shared_ptr request = std::make_shared( radarProductManager_->radar_id()); // File request callback if (autoUpdateEnabled_) { connect( request.get(), &request::NexradFileRequest::RequestComplete, this, [group, product, this]( const std::shared_ptr& request) { // Select loaded record auto record = request->radar_product_record(); // Validate record, and verify current map context // still displays site and product if (record != nullptr && radarProductManager_ != nullptr && radarProductManager_->radar_id() == request->current_radar_site() && context_->radar_product_group() == group && (group == common::RadarProductGroup::Level2 || context_->radar_product() == product)) { if (group == common::RadarProductGroup::Level2) { // Level 2 products may have multiple time // points, ensure the latest is selected widget_->SelectRadarProduct(group, product); } else { widget_->SelectRadarProduct(record); } } }); } // Load file boost::asio::post( threadPool_, [group, latestTime, request, product, this]() { try { if (group == common::RadarProductGroup::Level2) { radarProductManager_->LoadLevel2Data(latestTime, request); } else { radarProductManager_->LoadLevel3Data( product, latestTime, request); } } catch (const std::exception& ex) { logger_->error(ex.what()); } }); } } }, Qt::QueuedConnection); } } void MapWidgetImpl::RadarProductManagerDisconnect() { if (radarProductManager_ != nullptr) { disconnect(radarProductManager_.get(), &manager::RadarProductManager::NewDataAvailable, this, nullptr); disconnect(radarProductManager_.get(), &manager::RadarProductManager::IncomingLevel2ElevationChanged, this, nullptr); } } void MapWidgetImpl::InitializeNewRadarProductView( const std::string& colorPalette) { boost::asio::post( threadPool_, [colorPalette, this]() { try { auto radarProductView = context_->radar_product_view(); auto& paletteSetting = settings::PaletteSettings::Instance().palette(colorPalette); std::string colorTableFile = paletteSetting.GetValue(); if (colorTableFile.empty()) { colorTableFile = paletteSetting.GetDefault(); } std::unique_ptr colorTableStream = util::OpenFile(colorTableFile); if (colorTableStream->fail()) { logger_->warn("Could not open color table {}", colorTableFile); colorTableStream = util::OpenFile(paletteSetting.GetDefault()); } std::shared_ptr colorTable = common::ColorTable::Load(*colorTableStream); if (!colorTable->IsValid()) { logger_->warn("Could not load color table {}", colorTableFile); colorTableStream = util::OpenFile(paletteSetting.GetDefault()); colorTable = common::ColorTable::Load(*colorTableStream); } radarProductView->LoadColorTable(colorTable); radarProductView->Initialize(); } catch (const std::exception& ex) { logger_->error(ex.what()); } }); if (map_ != nullptr) { AddLayers(); } } void MapWidgetImpl::RadarProductViewConnect() { auto radarProductView = context_->radar_product_view(); if (radarProductView != nullptr) { connect(radarProductView.get(), &view::RadarProductView::ColorTableLutUpdated, widget_, static_cast(&QWidget::update), Qt::QueuedConnection); connect( radarProductView.get(), &view::RadarProductView::SweepComputed, this, [=, this]() { std::shared_ptr radarSite = radarProductManager_->radar_site(); RadarRangeLayer::Update( map_, radarProductView->range(), {radarSite->latitude(), radarSite->longitude()}); widget_->update(); Q_EMIT widget_->RadarSweepUpdated(); }, Qt::QueuedConnection); connect(radarProductView.get(), &view::RadarProductView::SweepNotComputed, widget_, &MapWidget::RadarSweepNotUpdated); } } void MapWidgetImpl::RadarProductViewDisconnect() { auto radarProductView = context_->radar_product_view(); if (radarProductView != nullptr) { disconnect(radarProductView.get(), &view::RadarProductView::ColorTableLutUpdated, widget_, nullptr); disconnect(radarProductView.get(), &view::RadarProductView::SweepComputed, this, nullptr); disconnect(radarProductView.get(), &view::RadarProductView::SweepNotComputed, widget_, nullptr); } } void MapWidgetImpl::SelectNearestRadarSite(double latitude, double longitude, std::optional type) { auto radarSite = config::RadarSite::FindNearest(latitude, longitude, type); if (radarSite != nullptr) { Q_EMIT widget_->RadarSiteRequested(radarSite->id(), false); } } void MapWidgetImpl::SetRadarSite(const std::string& radarSite, bool checkProductAvailability) { // Set the radar site in the context context_->set_radar_site(config::RadarSite::Get(radarSite)); // Check if radar site has changed if (radarProductManager_ == nullptr || radarSite != radarProductManager_->radar_site()->id()) { // Disconnect signals from old RadarProductManager RadarProductManagerDisconnect(); // Set new RadarProductManager radarProductManager_ = manager::RadarProductManager::Instance(radarSite); // Update views context_->overlay_product_view()->set_radar_product_manager( radarProductManager_); // Connect signals to new RadarProductManager RadarProductManagerConnect(); // Once the available products are loaded, check to make sure the current // one is available productAvailabilityCheckNeeded_ = checkProductAvailability; productAvailabilityUpdated_ = false; productAvailabilityProductSelected_ = false; radarProductManager_->UpdateAvailableProducts(); } } void MapWidgetImpl::Update() { QMetaObject::invokeMethod( widget_, static_cast(&QWidget::update)); if (UpdateStoredMapParameters()) { Q_EMIT widget_->MapParametersChanged( prevLatitude_, prevLongitude_, prevZoom_, prevBearing_, prevPitch_); } } bool MapWidgetImpl::UpdateStoredMapParameters() { bool changed = false; double newLatitude = map_->latitude(); double newLongitude = map_->longitude(); double newZoom = map_->zoom(); double newBearing = map_->bearing(); double newPitch = map_->pitch(); if (prevLatitude_ != newLatitude || // prevLongitude_ != newLongitude || // prevZoom_ != newZoom || // prevBearing_ != newBearing || // prevPitch_ != newPitch) { prevLatitude_ = newLatitude; prevLongitude_ = newLongitude; prevZoom_ = newZoom; prevBearing_ = newBearing; prevPitch_ = newPitch; changed = true; } return changed; } void MapWidgetImpl::CheckLevel3Availability() { /* * productAvailabilityCheckNeeded_ Only do this when it is indicated that it * is needed (mostly on radar site change). This is mainly to avoid potential * recursion with SelectRadarProduct calls. * * productAvailabilityUpdated_ Only update once the product availability * has been updated * * productAvailabilityProductSelected_ Only update once the radar site is * fully selected */ if (!(productAvailabilityCheckNeeded_ && productAvailabilityUpdated_ && productAvailabilityProductSelected_)) { return; } productAvailabilityCheckNeeded_ = false; // Get radar product view for fallback and level2 selection auto radarProductView = context_->radar_product_view(); if (radarProductView == nullptr) { return; } // Only do this for level3 products if (widget_->GetRadarProductGroup() != common::RadarProductGroup::Level3) { widget_->SelectRadarProduct(radarProductView->GetRadarProductGroup(), radarProductView->GetRadarProductName(), 0, radarProductView->selected_time(), false); return; } const common::Level3ProductCategoryMap& categoryMap = widget_->GetAvailableLevel3Categories(); const std::string& productTilt = context_->radar_product(); const std::string& productName = common::GetLevel3ProductByAwipsId(productTilt); const common::Level3ProductCategory productCategory = common::GetLevel3CategoryByProduct(productName); if (productCategory == common::Level3ProductCategory::Unknown) { // Default to the same as already selected widget_->SelectRadarProduct(radarProductView->GetRadarProductGroup(), radarProductView->GetRadarProductName(), 0, radarProductView->selected_time(), false); return; } const auto& availableProductsIt = categoryMap.find(productCategory); // Has no products in this category, do not change categories if (availableProductsIt == categoryMap.cend()) { // Default to the same as already selected widget_->SelectRadarProduct(radarProductView->GetRadarProductGroup(), radarProductView->GetRadarProductName(), 0, radarProductView->selected_time(), false); return; } const auto& availableProducts = availableProductsIt->second; const auto& availableTiltsIt = availableProducts.find(productName); const auto& availableTilts = availableTiltsIt == availableProducts.cend() ? // Does not have the same product, but has others in the same category. // Switch to the default product and tilt in this category. availableProducts.at(common::GetLevel3ProductByAwipsId( common::GetLevel3CategoryDefaultProduct(productCategory, categoryMap))) : // Has the same product availableTiltsIt->second; // Try to match the tilt to the last tilt. if (currentTiltIndex_ < availableTilts.size()) { widget_->SelectRadarProduct(common::RadarProductGroup::Level3, availableTilts[currentTiltIndex_], 0, widget_->GetSelectedTime()); } else if (availableTilts.size() > 0) { widget_->SelectRadarProduct(common::RadarProductGroup::Level3, availableTilts[availableTilts.size() - 1], 0, widget_->GetSelectedTime()); } else { // No tilts available in this case, default to the same as already // selected widget_->SelectRadarProduct(radarProductView->GetRadarProductGroup(), radarProductView->GetRadarProductName(), 0, radarProductView->selected_time(), false); } } } // namespace scwx::qt::map #include "map_widget.moc"