diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 787382a3..650d94f7 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -102,7 +102,8 @@ set(HDR_MAP source/scwx/qt/map/alert_layer.hpp source/scwx/qt/map/overlay_layer.hpp source/scwx/qt/map/placefile_layer.hpp source/scwx/qt/map/radar_product_layer.hpp - source/scwx/qt/map/radar_range_layer.hpp) + source/scwx/qt/map/radar_range_layer.hpp + source/scwx/qt/map/radar_site_layer.hpp) set(SRC_MAP source/scwx/qt/map/alert_layer.cpp source/scwx/qt/map/color_table_layer.cpp source/scwx/qt/map/draw_layer.cpp @@ -114,7 +115,8 @@ set(SRC_MAP source/scwx/qt/map/alert_layer.cpp source/scwx/qt/map/overlay_layer.cpp source/scwx/qt/map/placefile_layer.cpp source/scwx/qt/map/radar_product_layer.cpp - source/scwx/qt/map/radar_range_layer.cpp) + source/scwx/qt/map/radar_range_layer.cpp + source/scwx/qt/map/radar_site_layer.cpp) set(HDR_MODEL source/scwx/qt/model/alert_model.hpp source/scwx/qt/model/alert_proxy_model.hpp source/scwx/qt/model/imgui_context_model.hpp diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index bd97c031..d07ca41c 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -807,6 +807,18 @@ void MainWindowImpl::ConnectAnimationSignals() &map::MapWidget::WidgetPainted, timelineManager_.get(), [=, this]() { timelineManager_->ReceiveMapWidgetPainted(i); }); + connect(maps_[i], + &map::MapWidget::RadarSiteRequested, + this, + [this](const std::string& id) + { + for (map::MapWidget* map : maps_) + { + map->SelectRadarSite(id); + } + + UpdateRadarSite(); + }); } } diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 5b2a2e8a..7c543bdd 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -164,6 +165,8 @@ class MapWidgetImpl : public QObject std::shared_ptr map_; std::list layerList_; + std::vector> genericLayers_ {}; + QStringList styleLayers_; types::LayerVector customLayers_; @@ -186,6 +189,7 @@ class MapWidgetImpl : public QObject std::shared_ptr overlayLayer_; std::shared_ptr placefileLayer_; std::shared_ptr colorTableLayer_; + std::shared_ptr radarSiteLayer_ {nullptr}; std::list> placefileLayers_ {}; @@ -800,6 +804,7 @@ void MapWidgetImpl::AddLayers() map_->removeLayer(id.c_str()); } layerList_.clear(); + genericLayers_.clear(); placefileLayers_.clear(); // Update custom layer list from model @@ -890,6 +895,16 @@ void MapWidgetImpl::AddLayer(types::LayerType type, } 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; } @@ -953,6 +968,7 @@ void MapWidgetImpl::AddLayer(const std::string& id, map_->addCustomLayer(id.c_str(), std::move(pHost), before.c_str()); layerList_.push_back(id); + genericLayers_.push_back(layer); } catch (const std::exception&) { @@ -1198,10 +1214,8 @@ void MapWidgetImpl::RunMousePicking() util::maplibre::LatLongToScreenCoordinate(coordinate); // For each layer in reverse - // TODO: All Generic Layers, not just Placefile Layers bool itemPicked = false; - for (auto it = placefileLayers_.rbegin(); it != placefileLayers_.rend(); - ++it) + for (auto it = genericLayers_.rbegin(); it != genericLayers_.rend(); ++it) { // Run mouse picking for each layer if ((*it)->RunMousePicking( diff --git a/scwx-qt/source/scwx/qt/map/map_widget.hpp b/scwx-qt/source/scwx/qt/map/map_widget.hpp index c130c3e6..1f863df3 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.hpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.hpp @@ -147,6 +147,7 @@ private slots: double bearing, double pitch); void MapStyleChanged(const std::string& styleName); + void RadarSiteRequested(const std::string& id); void RadarSiteUpdated(std::shared_ptr radarSite); void RadarSweepUpdated(); void RadarSweepNotUpdated(types::NoUpdateReason reason); diff --git a/scwx-qt/source/scwx/qt/map/radar_site_layer.cpp b/scwx-qt/source/scwx/qt/map/radar_site_layer.cpp new file mode 100644 index 00000000..92d8a1f6 --- /dev/null +++ b/scwx-qt/source/scwx/qt/map/radar_site_layer.cpp @@ -0,0 +1,173 @@ +#include +#include +#include +#include +#include +#include + +// #include +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace map +{ + +static const std::string logPrefix_ = "scwx::qt::map::radar_site_layer"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class RadarSiteLayer::Impl +{ +public: + explicit Impl(RadarSiteLayer* self) : self_ {self} {} + ~Impl() = default; + + void RenderRadarSite(const QMapLibreGL::CustomLayerRenderParameters& params, + std::shared_ptr& radarSite); + + RadarSiteLayer* self_; + + std::vector> radarSites_ {}; + + glm::vec2 mapScreenCoordLocation_ {}; + float mapScale_ {1.0f}; + float mapBearingCos_ {1.0f}; + float mapBearingSin_ {0.0f}; + float halfWidth_ {}; + float halfHeight_ {}; + + std::string hoverText_ {}; +}; + +RadarSiteLayer::RadarSiteLayer(std::shared_ptr context) : + DrawLayer(context), p(std::make_unique(this)) +{ +} + +RadarSiteLayer::~RadarSiteLayer() = default; + +void RadarSiteLayer::Initialize() +{ + logger_->debug("Initialize()"); + + p->radarSites_ = config::RadarSite::GetAll(); +} + +void RadarSiteLayer::Render( + const QMapLibreGL::CustomLayerRenderParameters& params) +{ + gl::OpenGLFunctions& gl = context()->gl(); + + context()->set_render_parameters(params); + + // Update map screen coordinate and scale information + p->mapScreenCoordLocation_ = util::maplibre::LatLongToScreenCoordinate( + {params.latitude, params.longitude}); + p->mapScale_ = std::pow(2.0, params.zoom) * mbgl::util::tileSize_D / + mbgl::util::DEGREES_MAX; + p->mapBearingCos_ = cosf(params.bearing * common::kDegreesToRadians); + p->mapBearingSin_ = sinf(params.bearing * common::kDegreesToRadians); + p->halfWidth_ = params.width * 0.5f; + p->halfHeight_ = params.height * 0.5f; + + p->hoverText_.clear(); + + // Radar site ImGui windows shouldn't have padding + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2 {0.0f, 0.0f}); + + for (auto& radarSite : p->radarSites_) + { + p->RenderRadarSite(params, radarSite); + } + + ImGui::PopStyleVar(); + + SCWX_GL_CHECK_ERROR(); +} + +void RadarSiteLayer::Impl::RenderRadarSite( + const QMapLibreGL::CustomLayerRenderParameters& params, + std::shared_ptr& radarSite) +{ + const std::string windowName = fmt::format("radar-site-{}", radarSite->id()); + + const auto screenCoordinates = + (util::maplibre::LatLongToScreenCoordinate( + {radarSite->latitude(), radarSite->longitude()}) - + mapScreenCoordLocation_) * + mapScale_; + + // Rotate text according to map rotation + float rotatedX = screenCoordinates.x; + float rotatedY = screenCoordinates.y; + if (params.bearing != 0.0) + { + rotatedX = screenCoordinates.x * mapBearingCos_ - + screenCoordinates.y * mapBearingSin_; + rotatedY = screenCoordinates.x * mapBearingSin_ + + screenCoordinates.y * mapBearingCos_; + } + + // Convert screen to ImGui coordinates + float x = rotatedX + halfWidth_; + float y = params.height - (rotatedY + halfHeight_); + + // Setup window to hold text + ImGui::SetNextWindowPos( + ImVec2 {x, y}, ImGuiCond_Always, ImVec2 {0.5f, 0.5f}); + if (ImGui::Begin(windowName.c_str(), + nullptr, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_AlwaysAutoResize)) + { + // Render text + if (ImGui::Button(radarSite->id().c_str())) + { + Q_EMIT self_->RadarSiteSelected(radarSite->id()); + } + + // Store hover text for mouse picking pass + if (ImGui::IsItemHovered()) + { + hoverText_ = + fmt::format("{} ({})\n{}\n{}, {}", + radarSite->id(), + radarSite->type_name(), + radarSite->location_name(), + common::GetLatitudeString(radarSite->latitude()), + common::GetLongitudeString(radarSite->longitude())); + } + + // End window + ImGui::End(); + } +} + +void RadarSiteLayer::Deinitialize() +{ + logger_->debug("Deinitialize()"); + + p->radarSites_.clear(); +} + +bool RadarSiteLayer::RunMousePicking( + const QMapLibreGL::CustomLayerRenderParameters& /* params */, + const QPointF& /* mouseLocalPos */, + const QPointF& mouseGlobalPos, + const glm::vec2& /* mouseCoords */) +{ + if (!p->hoverText_.empty()) + { + util::tooltip::Show(p->hoverText_, mouseGlobalPos); + return true; + } + + return false; +} + +} // namespace map +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/map/radar_site_layer.hpp b/scwx-qt/source/scwx/qt/map/radar_site_layer.hpp new file mode 100644 index 00000000..639be3b6 --- /dev/null +++ b/scwx-qt/source/scwx/qt/map/radar_site_layer.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include + +namespace scwx +{ +namespace qt +{ +namespace map +{ + +class RadarSiteLayer : public DrawLayer +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(RadarSiteLayer) + +public: + explicit RadarSiteLayer(std::shared_ptr context); + ~RadarSiteLayer(); + + void Initialize() override final; + void Render(const QMapLibreGL::CustomLayerRenderParameters&) override final; + void Deinitialize() override final; + + bool RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, + const QPointF& mouseLocalPos, + const QPointF& mouseGlobalPos, + const glm::vec2& mouseCoords) override final; + +signals: + void RadarSiteSelected(const std::string& id); + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace map +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/model/layer_model.cpp b/scwx-qt/source/scwx/qt/model/layer_model.cpp index a153f4f2..0e545d96 100644 --- a/scwx-qt/source/scwx/qt/model/layer_model.cpp +++ b/scwx-qt/source/scwx/qt/model/layer_model.cpp @@ -39,6 +39,10 @@ static const QString kMimeFormat {"application/x.scwx-layer-model"}; static const std::vector kDefaultLayers_ { {types::LayerType::Information, types::InformationLayer::MapOverlay, false}, {types::LayerType::Information, types::InformationLayer::ColorTable, false}, + {types::LayerType::Information, + types::InformationLayer::RadarSite, + false, + {false, false, false, false}}, {types::LayerType::Data, types::DataLayer::RadarRange, true}, {types::LayerType::Alert, awips::Phenomenon::Tornado, true}, {types::LayerType::Alert, awips::Phenomenon::SnowSquall, true}, @@ -53,6 +57,10 @@ static const std::vector kDefaultLayers_ { static const std::vector kImmovableLayers_ { {types::LayerType::Information, types::InformationLayer::MapOverlay, false}, {types::LayerType::Information, types::InformationLayer::ColorTable, false}, + {types::LayerType::Information, + types::InformationLayer::RadarSite, + false, + {false, false, false, false}}, {types::LayerType::Map, types::MapLayer::MapSymbology, false}, {types::LayerType::Map, types::MapLayer::MapUnderlay, false}, }; @@ -235,13 +243,13 @@ void LayerModel::Impl::ValidateLayerSettings(types::LayerVector& layers) // Validate immovable layers std::vector immovableIterators {}; - types::LayerVector::iterator colorTableIterator {}; + types::LayerVector::iterator radarSiteIterator {}; types::LayerVector::iterator mapSymbologyIterator {}; types::LayerVector::iterator mapUnderlayIterator {}; for (auto& immovableLayer : kImmovableLayers_) { // Set the default displayed state for a layer that is not found - std::array displayed {true, true, true, true}; + std::array displayed = immovableLayer.displayed_; // Find the immovable layer auto it = std::find_if(layers.begin(), @@ -285,8 +293,8 @@ void LayerModel::Impl::ValidateLayerSettings(types::LayerVector& layers) { switch (std::get(it->description_)) { - case types::InformationLayer::ColorTable: - colorTableIterator = it; + case types::InformationLayer::RadarSite: + radarSiteIterator = it; break; default: @@ -330,10 +338,10 @@ void LayerModel::Impl::ValidateLayerSettings(types::LayerVector& layers) if (it == layers.end()) { - // If this is the first data layer, insert after the color table layer, + // If this is the first data layer, insert after the radar site layer, // otherwise, insert after the previous data layer types::LayerVector::iterator insertPosition = - dataIterators.empty() ? colorTableIterator + 1 : + dataIterators.empty() ? radarSiteIterator + 1 : dataIterators.back() + 1; it = layers.insert(insertPosition, {types::LayerType::Data, dataLayer}); @@ -398,7 +406,7 @@ void LayerModel::ResetLayers() types::LayerVector newLayers {}; newLayers.assign(kDefaultLayers_.cbegin(), kDefaultLayers_.cend()); - auto colorTableIterator = std::find_if( + auto radarSiteIterator = std::find_if( newLayers.begin(), newLayers.end(), [](const types::LayerInfo& layerInfo) @@ -406,7 +414,7 @@ void LayerModel::ResetLayers() return std::holds_alternative( layerInfo.description_) && std::get(layerInfo.description_) == - types::InformationLayer::ColorTable; + types::InformationLayer::RadarSite; }); // Add all existing placefile layers @@ -415,7 +423,7 @@ void LayerModel::ResetLayers() if (it->type_ == types::LayerType::Placefile) { newLayers.insert( - colorTableIterator + 1, + radarSiteIterator + 1, {it->type_, it->description_, it->movable_, it->displayed_}); } } @@ -1007,7 +1015,7 @@ void LayerModel::Impl::HandlePlacefileUpdate(const std::string& name, void LayerModel::Impl::AddPlacefile(const std::string& name) { - // Insert after color table + // Insert after radar site auto insertPosition = std::find_if( layers_.begin(), layers_.end(), @@ -1016,7 +1024,7 @@ void LayerModel::Impl::AddPlacefile(const std::string& name) return std::holds_alternative( layerInfo.description_) && std::get(layerInfo.description_) == - types::InformationLayer::ColorTable; + types::InformationLayer::RadarSite; }); if (insertPosition != layers_.end()) { diff --git a/scwx-qt/source/scwx/qt/types/layer_types.cpp b/scwx-qt/source/scwx/qt/types/layer_types.cpp index 0f7e1db8..d935cd42 100644 --- a/scwx-qt/source/scwx/qt/types/layer_types.cpp +++ b/scwx-qt/source/scwx/qt/types/layer_types.cpp @@ -27,6 +27,7 @@ static const std::unordered_map dataLayerName_ { static const std::unordered_map informationLayerName_ {{InformationLayer::MapOverlay, "Map Overlay"}, + {InformationLayer::RadarSite, "Radar Sites"}, {InformationLayer::ColorTable, "Color Table"}, {InformationLayer::Unknown, "?"}}; diff --git a/scwx-qt/source/scwx/qt/types/layer_types.hpp b/scwx-qt/source/scwx/qt/types/layer_types.hpp index 6a00d03b..1309b648 100644 --- a/scwx-qt/source/scwx/qt/types/layer_types.hpp +++ b/scwx-qt/source/scwx/qt/types/layer_types.hpp @@ -41,6 +41,7 @@ typedef scwx::util:: enum class InformationLayer { MapOverlay, + RadarSite, ColorTable, Unknown };