#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 { namespace qt { namespace 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 QMapLibreGL::Settings& settings) : id_ {id}, uuid_ {boost::uuids::random_generator()()}, context_ {std::make_shared()}, widget_ {widget}, settings_(settings), map_(), layerList_ {}, imGuiRendererInitialized_ {false}, radarProductManager_ {nullptr}, radarProductLayer_ {nullptr}, alertLayer_ {std::make_shared(context_)}, overlayLayer_ {nullptr}, placefileLayer_ {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} { auto& generalSettings = settings::GeneralSettings::Instance(); SetRadarSite(generalSettings.default_radar_site().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(); ImGui_ImplQt_RegisterWidget(widget_); // Set Map Provider Details mapProvider_ = GetMapProvider(generalSettings.map_provider().GetValue()); ConnectSignals(); } ~MapWidgetImpl() { // 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, std::shared_ptr layer, const std::string& before = {}); void AddLayers(); void AddPlacefileLayer(const std::string& placefileName, const std::string& before); void ConnectSignals(); void ImGuiCheckFonts(); void InitializeNewRadarProductView(const std::string& colorPalette); void RadarProductManagerConnect(); void RadarProductManagerDisconnect(); void RadarProductViewConnect(); void RadarProductViewDisconnect(); void RunMousePicking(); void SetRadarSite(const std::string& radarSite); void UpdateLoadedStyle(); bool UpdateStoredMapParameters(); std::string FindMapSymbologyLayer(); 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_; MapWidget* widget_; MapProvider mapProvider_; QMapLibreGL::Settings settings_; std::shared_ptr map_; std::list layerList_; std::vector> genericLayers_ {}; QStringList styleLayers_; types::LayerVector customLayers_; ImGuiContext* imGuiContext_; std::string imGuiContextName_; bool imGuiRendererInitialized_; std::uint64_t imGuiFontsBuildCount_ {}; std::shared_ptr layerModel_ { model::LayerModel::Instance()}; std::shared_ptr placefileManager_ { manager::PlacefileManager::Instance()}; std::shared_ptr radarProductManager_; std::shared_ptr colorTable_; std::shared_ptr radarProductLayer_; std::shared_ptr alertLayer_; std::shared_ptr overlayLayer_; std::shared_ptr placefileLayer_; std::shared_ptr colorTableLayer_; std::shared_ptr radarSiteLayer_ {nullptr}; std::list> placefileLayers_ {}; bool autoRefreshEnabled_; bool autoUpdateEnabled_; common::Level2Product selectedLevel2Product_; bool hasMouse_ {false}; bool lastItemPicked_ {false}; QPointF lastPos_ {}; QPointF lastGlobalPos_ {}; std::size_t currentStyleIndex_; const MapStyle* currentStyle_; std::string initialStyleName_ {}; uint64_t frameDraws_; double prevLatitude_; double prevLongitude_; double prevZoom_; double prevBearing_; double prevPitch_; public slots: void Update(); }; MapWidget::MapWidget(std::size_t id, const QMapLibreGL::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); ImGui_ImplQt_RegisterWidget(this); } MapWidget::~MapWidget() { // Make sure we have a valid context so we can delete the QMapLibreGL. makeCurrent(); } void MapWidgetImpl::ConnectSignals() { connect(placefileManager_.get(), &manager::PlacefileManager::PlacefileUpdated, widget_, [this]() { widget_->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(); }); } common::Level3ProductCategoryMap MapWidget::GetAvailableLevel3Categories() { if (p->radarProductManager_ != nullptr) { return p->radarProductManager_->GetAvailableLevel3Categories(); } else { return {}; } } float MapWidget::GetElevation() const { auto radarProductView = p->context_->radar_product_view(); if (radarProductView != nullptr) { return radarProductView->elevation(); } else { return 0.0f; } } std::vector MapWidget::GetElevationCuts() const { auto radarProductView = p->context_->radar_product_view(); if (radarProductView != nullptr) { return radarProductView->GetElevationCuts(); } else { return {}; } } 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->GetSelectedTime(); } return time; } std::uint16_t MapWidget::GetVcp() const { auto radarProductView = p->context_->radar_product_view(); if (radarProductView != nullptr) { return radarProductView->vcp(); } else { return 0u; } } 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 (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_); 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()); p->Update(); // Select products from new site if (radarProductView != nullptr) { radarProductView->set_radar_product_manager(p->radarProductManager_); SelectRadarProduct(radarProductView->GetRadarProductGroup(), radarProductView->GetRadarProductName(), 0, radarProductView->selected_time(), false); } 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(); // 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; 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_); } } } void MapWidget::SetAutoUpdate(bool enabled) { p->autoUpdateEnabled_ = 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& mapProviderInfo = GetMapProviderInfo(p->mapProvider_); auto& styles = mapProviderInfo.mapStyles_; 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_); p->map_->setStyleUrl(styles[i].url_.c_str()); if (++p->currentStyleIndex_ == styles.size()) { p->currentStyleIndex_ = 0; } break; } } } qreal MapWidget::pixelRatio() { return devicePixelRatioF(); } void MapWidget::changeStyle() { const auto& mapProviderInfo = GetMapProviderInfo(p->mapProvider_); auto& styles = mapProviderInfo.mapStyles_; p->currentStyle_ = &styles[p->currentStyleIndex_]; logger_->debug("Updating style: {}", styles[p->currentStyleIndex_].name_); p->map_->setStyleUrl(styles[p->currentStyleIndex_].url_.c_str()); 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()); } std::string MapWidgetImpl::FindMapSymbologyLayer() { std::string before = "ferry"; for (const QString& qlayer : styleLayers_) { const std::string layer = qlayer.toStdString(); // Draw below layers defined in map style auto it = std::find_if(currentStyle_->drawBelow_.cbegin(), currentStyle_->drawBelow_.cend(), [&layer](const std::string& styleLayer) -> bool { // Perform case-insensitive matching RE2 re {"(?i)" + styleLayer}; return RE2::FullMatch(layer, re); }); if (it != currentStyle_->drawBelow_.cend()) { before = layer; break; } } return before; } 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 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 (auto it = customLayers_.crbegin(); it != customLayers_.crend(); ++it) { if (it->type_ == types::LayerType::Map) { // Style-defined map layers switch (std::get(it->description_)) { // Subsequent layers are drawn underneath the map symbology layer case types::MapLayer::MapUnderlay: before = FindMapSymbologyLayer(); break; // Subsequent layers are drawn after all style-defined layers case types::MapLayer::MapSymbology: before = ""; break; default: break; } } else if (it->displayed_[id_]) { // If the layer is displayed for the current map, add it AddLayer(it->type_, it->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(context_); AddLayer(layerName, radarProductLayer_, before); } } else if (type == types::LayerType::Alert) { // Add the alert layer for the phenomenon auto newLayers = alertLayer_->AddLayers( std::get(description), before); layerList_.insert(layerList_.end(), newLayers.cbegin(), newLayers.cend()); } 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(context_); 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(context_); AddLayer(layerName, colorTableLayer_, before); } break; // Create the radar site layer case types::InformationLayer::RadarSite: radarSiteLayer_ = std::make_shared(context_); AddLayer(layerName, radarSiteLayer_, before); connect(radarSiteLayer_.get(), &RadarSiteLayer::RadarSiteSelected, widget_, &MapWidget::RadarSiteRequested); break; default: break; } } else if (type == types::LayerType::Data) { switch (std::get(description)) { // 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(context_, 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_, [this]() { widget_->update(); }); } std::string MapWidgetImpl::GetPlacefileLayerName(const std::string& placefileName) { return types::GetLayerName(types::LayerType::Placefile, placefileName); } void MapWidgetImpl::AddLayer(const std::string& id, std::shared_ptr layer, const std::string& before) { // QMapLibreGL::addCustomLayer will take ownership of the std::unique_ptr std::unique_ptr pHost = std::make_unique(layer); 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_, [this]() { widget_->update(); }); } catch (const std::exception&) { // When dragging and dropping, a temporary duplicate layer exists } } void MapWidget::enterEvent(QEnterEvent* /* ev */) { p->hasMouse_ = true; } void MapWidget::leaveEvent(QEvent* /* ev */) { p->hasMouse_ = false; } void MapWidget::keyPressEvent(QKeyEvent* ev) { switch (ev->key()) { case Qt::Key_S: changeStyle(); break; default: break; } ev->accept(); } void MapWidget::mousePressEvent(QMouseEvent* ev) { p->lastPos_ = ev->position(); p->lastGlobalPos_ = ev->globalPosition(); if (ev->type() == QEvent::MouseButtonPress) { if (ev->buttons() == (Qt::LeftButton | Qt::RightButton)) { changeStyle(); } } if (ev->type() == QEvent::MouseButtonDblClick) { if (ev->buttons() == Qt::LeftButton) { p->map_->scaleBy(2.0, p->lastPos_); } else if (ev->buttons() == Qt::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::LeftButton) { p->map_->moveBy(delta); } else if (ev->buttons() == Qt::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->context_->gl().initializeOpenGLFunctions(); // 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_ImplOpenGL3_Init(); p->imGuiFontsBuildCount_ = manager::FontManager::Instance().imgui_fonts_build_count(); p->imGuiRendererInitialized_ = true; p->map_.reset( new QMapLibreGL::Map(nullptr, p->settings_, size(), pixelRatio())); p->context_->set_map(p->map_); connect(p->map_.get(), &QMapLibreGL::Map::needsRendering, p.get(), &MapWidgetImpl::Update); // 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(), &QMapLibreGL::Map::mapChanged, this, &MapWidget::mapChanged); } void MapWidget::paintGL() { auto defaultFont = manager::FontManager::Instance().GetImGuiFont( types::FontCategory::Default); p->frameDraws_++; // Setup ImGui Frame ImGui::SetCurrentContext(p->imGuiContext_); // Lock ImGui font atlas prior to new ImGui frame std::shared_lock imguiFontAtlasLock { manager::FontManager::Instance().imgui_font_atlas_mutex()}; // Start ImGui Frame ImGui_ImplQt_NewFrame(this); ImGui_ImplOpenGL3_NewFrame(); p->ImGuiCheckFonts(); ImGui::NewFrame(); // Set default font ImGui::PushFont(defaultFont->font()); // Update pixel ratio p->context_->set_pixel_ratio(pixelRatio()); // Render QMapLibreGL Map p->map_->resize(size()); p->map_->setFramebufferObject(defaultFramebufferObject(), size() * pixelRatio()); p->map_->render(); // 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(); } 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(); } imGuiFontsBuildCount_ = currentImGuiFontsBuildCount; } void MapWidgetImpl::RunMousePicking() { const QMapLibreGL::CustomLayerRenderParameters params = context_->render_parameters(); auto coordinate = map_->coordinateForPixel(lastPos_); auto mouseScreenCoordinate = util::maplibre::LatLongToScreenCoordinate(coordinate); // For each layer in reverse bool itemPicked = false; for (auto it = genericLayers_.rbegin(); it != genericLayers_.rend(); ++it) { // Run mouse picking for each layer if ((*it)->RunMousePicking(params, lastPos_, lastGlobalPos_, mouseScreenCoordinate, {coordinate.first, coordinate.second})) { // If a draw item was picked, don't process additional layers itemPicked = true; break; } } // If no draw item was picked, hide the tooltip if (!itemPicked) { util::tooltip::Hide(); } lastItemPicked_ = itemPicked; } void MapWidget::mapChanged(QMapLibreGL::Map::MapChange mapChange) { switch (mapChange) { case QMapLibreGL::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::Level3ProductsChanged, this, [this]() { Q_EMIT widget_->Level3ProductsChanged(); }); connect( radarProductManager_.get(), &manager::RadarProductManager::NewDataAvailable, this, [this](common::RadarProductGroup group, const std::string& product, std::chrono::system_clock::time_point latestTime) { if (autoRefreshEnabled_ && context_->radar_product_group() == group && (group == common::RadarProductGroup::Level2 || context_->radar_product() == product)) { // Create file request std::shared_ptr request = std::make_shared(); // File request callback if (autoUpdateEnabled_) { connect( request.get(), &request::NexradFileRequest::RequestComplete, this, [=, this](std::shared_ptr request) { // Select loaded record auto record = request->radar_product_record(); // Validate record, and verify current map context still // displays product if (record != nullptr && context_->radar_product_group() == group && (group == common::RadarProductGroup::Level2 || context_->radar_product() == product)) { widget_->SelectRadarProduct(record); } }); } // Load file boost::asio::post( threadPool_, [=, this]() { if (group == common::RadarProductGroup::Level2) { radarProductManager_->LoadLevel2Data(latestTime, request); } else { radarProductManager_->LoadLevel3Data( product, latestTime, request); } }); } }, Qt::QueuedConnection); } } void MapWidgetImpl::RadarProductManagerDisconnect() { if (radarProductManager_ != nullptr) { disconnect(radarProductManager_.get(), &manager::RadarProductManager::NewDataAvailable, this, nullptr); } } void MapWidgetImpl::InitializeNewRadarProductView( const std::string& colorPalette) { boost::asio::post(threadPool_, [=, this]() { auto radarProductView = context_->radar_product_view(); std::string colorTableFile = settings::PaletteSettings::Instance() .palette(colorPalette) .GetValue(); if (!colorTableFile.empty()) { std::unique_ptr colorTableStream = util::OpenFile(colorTableFile); std::shared_ptr colorTable = common::ColorTable::Load(*colorTableStream); radarProductView->LoadColorTable(colorTable); } radarProductView->Initialize(); }); if (map_ != nullptr) { AddLayers(); } } void MapWidgetImpl::RadarProductViewConnect() { auto radarProductView = context_->radar_product_view(); if (radarProductView != nullptr) { connect( radarProductView.get(), &view::RadarProductView::ColorTableUpdated, this, [this]() { widget_->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::ColorTableUpdated, this, nullptr); disconnect(radarProductView.get(), &view::RadarProductView::SweepComputed, this, nullptr); disconnect(radarProductView.get(), &view::RadarProductView::SweepNotComputed, widget_, nullptr); } } void MapWidgetImpl::SetRadarSite(const std::string& 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); // Connect signals to new RadarProductManager RadarProductManagerConnect(); radarProductManager_->UpdateAvailableProducts(); } } void MapWidgetImpl::Update() { widget_->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; } } // namespace map } // namespace qt } // namespace scwx #include "map_widget.moc"