Merge pull request #97 from dpaulat/feature/radar-site-labels

Radar Site Labels
This commit is contained in:
Dan Paulat 2023-11-20 22:27:33 -06:00 committed by GitHub
commit 6e4501f0bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 268 additions and 16 deletions

View file

@ -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/overlay_layer.hpp
source/scwx/qt/map/placefile_layer.hpp source/scwx/qt/map/placefile_layer.hpp
source/scwx/qt/map/radar_product_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 set(SRC_MAP source/scwx/qt/map/alert_layer.cpp
source/scwx/qt/map/color_table_layer.cpp source/scwx/qt/map/color_table_layer.cpp
source/scwx/qt/map/draw_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/overlay_layer.cpp
source/scwx/qt/map/placefile_layer.cpp source/scwx/qt/map/placefile_layer.cpp
source/scwx/qt/map/radar_product_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 set(HDR_MODEL source/scwx/qt/model/alert_model.hpp
source/scwx/qt/model/alert_proxy_model.hpp source/scwx/qt/model/alert_proxy_model.hpp
source/scwx/qt/model/imgui_context_model.hpp source/scwx/qt/model/imgui_context_model.hpp

View file

@ -807,6 +807,18 @@ void MainWindowImpl::ConnectAnimationSignals()
&map::MapWidget::WidgetPainted, &map::MapWidget::WidgetPainted,
timelineManager_.get(), timelineManager_.get(),
[=, this]() { timelineManager_->ReceiveMapWidgetPainted(i); }); [=, 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();
});
} }
} }

View file

@ -11,6 +11,7 @@
#include <scwx/qt/map/placefile_layer.hpp> #include <scwx/qt/map/placefile_layer.hpp>
#include <scwx/qt/map/radar_product_layer.hpp> #include <scwx/qt/map/radar_product_layer.hpp>
#include <scwx/qt/map/radar_range_layer.hpp> #include <scwx/qt/map/radar_range_layer.hpp>
#include <scwx/qt/map/radar_site_layer.hpp>
#include <scwx/qt/model/imgui_context_model.hpp> #include <scwx/qt/model/imgui_context_model.hpp>
#include <scwx/qt/model/layer_model.hpp> #include <scwx/qt/model/layer_model.hpp>
#include <scwx/qt/settings/general_settings.hpp> #include <scwx/qt/settings/general_settings.hpp>
@ -164,6 +165,8 @@ public:
std::shared_ptr<QMapLibreGL::Map> map_; std::shared_ptr<QMapLibreGL::Map> map_;
std::list<std::string> layerList_; std::list<std::string> layerList_;
std::vector<std::shared_ptr<GenericLayer>> genericLayers_ {};
QStringList styleLayers_; QStringList styleLayers_;
types::LayerVector customLayers_; types::LayerVector customLayers_;
@ -186,6 +189,7 @@ public:
std::shared_ptr<OverlayLayer> overlayLayer_; std::shared_ptr<OverlayLayer> overlayLayer_;
std::shared_ptr<PlacefileLayer> placefileLayer_; std::shared_ptr<PlacefileLayer> placefileLayer_;
std::shared_ptr<ColorTableLayer> colorTableLayer_; std::shared_ptr<ColorTableLayer> colorTableLayer_;
std::shared_ptr<RadarSiteLayer> radarSiteLayer_ {nullptr};
std::list<std::shared_ptr<PlacefileLayer>> placefileLayers_ {}; std::list<std::shared_ptr<PlacefileLayer>> placefileLayers_ {};
@ -800,6 +804,7 @@ void MapWidgetImpl::AddLayers()
map_->removeLayer(id.c_str()); map_->removeLayer(id.c_str());
} }
layerList_.clear(); layerList_.clear();
genericLayers_.clear();
placefileLayers_.clear(); placefileLayers_.clear();
// Update custom layer list from model // Update custom layer list from model
@ -890,6 +895,16 @@ void MapWidgetImpl::AddLayer(types::LayerType type,
} }
break; break;
// Create the radar site layer
case types::InformationLayer::RadarSite:
radarSiteLayer_ = std::make_shared<RadarSiteLayer>(context_);
AddLayer(layerName, radarSiteLayer_, before);
connect(radarSiteLayer_.get(),
&RadarSiteLayer::RadarSiteSelected,
widget_,
&MapWidget::RadarSiteRequested);
break;
default: default:
break; break;
} }
@ -953,6 +968,7 @@ void MapWidgetImpl::AddLayer(const std::string& id,
map_->addCustomLayer(id.c_str(), std::move(pHost), before.c_str()); map_->addCustomLayer(id.c_str(), std::move(pHost), before.c_str());
layerList_.push_back(id); layerList_.push_back(id);
genericLayers_.push_back(layer);
} }
catch (const std::exception&) catch (const std::exception&)
{ {
@ -1198,10 +1214,8 @@ void MapWidgetImpl::RunMousePicking()
util::maplibre::LatLongToScreenCoordinate(coordinate); util::maplibre::LatLongToScreenCoordinate(coordinate);
// For each layer in reverse // For each layer in reverse
// TODO: All Generic Layers, not just Placefile Layers
bool itemPicked = false; bool itemPicked = false;
for (auto it = placefileLayers_.rbegin(); it != placefileLayers_.rend(); for (auto it = genericLayers_.rbegin(); it != genericLayers_.rend(); ++it)
++it)
{ {
// Run mouse picking for each layer // Run mouse picking for each layer
if ((*it)->RunMousePicking( if ((*it)->RunMousePicking(

View file

@ -147,6 +147,7 @@ signals:
double bearing, double bearing,
double pitch); double pitch);
void MapStyleChanged(const std::string& styleName); void MapStyleChanged(const std::string& styleName);
void RadarSiteRequested(const std::string& id);
void RadarSiteUpdated(std::shared_ptr<config::RadarSite> radarSite); void RadarSiteUpdated(std::shared_ptr<config::RadarSite> radarSite);
void RadarSweepUpdated(); void RadarSweepUpdated();
void RadarSweepNotUpdated(types::NoUpdateReason reason); void RadarSweepNotUpdated(types::NoUpdateReason reason);

View file

@ -0,0 +1,173 @@
#include <scwx/qt/map/radar_site_layer.hpp>
#include <scwx/qt/config/radar_site.hpp>
#include <scwx/qt/util/maplibre.hpp>
#include <scwx/qt/util/tooltip.hpp>
#include <scwx/common/geographic.hpp>
#include <scwx/util/logger.hpp>
// #include <GeographicLib/Geodesic.hpp>
#include <imgui.h>
#include <mbgl/util/constants.hpp>
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<config::RadarSite>& radarSite);
RadarSiteLayer* self_;
std::vector<std::shared_ptr<config::RadarSite>> 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<MapContext> context) :
DrawLayer(context), p(std::make_unique<Impl>(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<config::RadarSite>& 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

View file

@ -0,0 +1,40 @@
#pragma once
#include <scwx/qt/map/draw_layer.hpp>
namespace scwx
{
namespace qt
{
namespace map
{
class RadarSiteLayer : public DrawLayer
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(RadarSiteLayer)
public:
explicit RadarSiteLayer(std::shared_ptr<MapContext> 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<Impl> p;
};
} // namespace map
} // namespace qt
} // namespace scwx

View file

@ -39,6 +39,10 @@ static const QString kMimeFormat {"application/x.scwx-layer-model"};
static const std::vector<types::LayerInfo> kDefaultLayers_ { static const std::vector<types::LayerInfo> kDefaultLayers_ {
{types::LayerType::Information, types::InformationLayer::MapOverlay, false}, {types::LayerType::Information, types::InformationLayer::MapOverlay, false},
{types::LayerType::Information, types::InformationLayer::ColorTable, 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::Data, types::DataLayer::RadarRange, true},
{types::LayerType::Alert, awips::Phenomenon::Tornado, true}, {types::LayerType::Alert, awips::Phenomenon::Tornado, true},
{types::LayerType::Alert, awips::Phenomenon::SnowSquall, true}, {types::LayerType::Alert, awips::Phenomenon::SnowSquall, true},
@ -53,6 +57,10 @@ static const std::vector<types::LayerInfo> kDefaultLayers_ {
static const std::vector<types::LayerInfo> kImmovableLayers_ { static const std::vector<types::LayerInfo> kImmovableLayers_ {
{types::LayerType::Information, types::InformationLayer::MapOverlay, false}, {types::LayerType::Information, types::InformationLayer::MapOverlay, false},
{types::LayerType::Information, types::InformationLayer::ColorTable, 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::MapSymbology, false},
{types::LayerType::Map, types::MapLayer::MapUnderlay, false}, {types::LayerType::Map, types::MapLayer::MapUnderlay, false},
}; };
@ -235,13 +243,13 @@ void LayerModel::Impl::ValidateLayerSettings(types::LayerVector& layers)
// Validate immovable layers // Validate immovable layers
std::vector<types::LayerVector::iterator> immovableIterators {}; std::vector<types::LayerVector::iterator> immovableIterators {};
types::LayerVector::iterator colorTableIterator {}; types::LayerVector::iterator radarSiteIterator {};
types::LayerVector::iterator mapSymbologyIterator {}; types::LayerVector::iterator mapSymbologyIterator {};
types::LayerVector::iterator mapUnderlayIterator {}; types::LayerVector::iterator mapUnderlayIterator {};
for (auto& immovableLayer : kImmovableLayers_) for (auto& immovableLayer : kImmovableLayers_)
{ {
// Set the default displayed state for a layer that is not found // Set the default displayed state for a layer that is not found
std::array<bool, kMapCount_> displayed {true, true, true, true}; std::array<bool, kMapCount_> displayed = immovableLayer.displayed_;
// Find the immovable layer // Find the immovable layer
auto it = std::find_if(layers.begin(), auto it = std::find_if(layers.begin(),
@ -285,8 +293,8 @@ void LayerModel::Impl::ValidateLayerSettings(types::LayerVector& layers)
{ {
switch (std::get<types::InformationLayer>(it->description_)) switch (std::get<types::InformationLayer>(it->description_))
{ {
case types::InformationLayer::ColorTable: case types::InformationLayer::RadarSite:
colorTableIterator = it; radarSiteIterator = it;
break; break;
default: default:
@ -330,10 +338,10 @@ void LayerModel::Impl::ValidateLayerSettings(types::LayerVector& layers)
if (it == layers.end()) 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 // otherwise, insert after the previous data layer
types::LayerVector::iterator insertPosition = types::LayerVector::iterator insertPosition =
dataIterators.empty() ? colorTableIterator + 1 : dataIterators.empty() ? radarSiteIterator + 1 :
dataIterators.back() + 1; dataIterators.back() + 1;
it = it =
layers.insert(insertPosition, {types::LayerType::Data, dataLayer}); layers.insert(insertPosition, {types::LayerType::Data, dataLayer});
@ -398,7 +406,7 @@ void LayerModel::ResetLayers()
types::LayerVector newLayers {}; types::LayerVector newLayers {};
newLayers.assign(kDefaultLayers_.cbegin(), kDefaultLayers_.cend()); newLayers.assign(kDefaultLayers_.cbegin(), kDefaultLayers_.cend());
auto colorTableIterator = std::find_if( auto radarSiteIterator = std::find_if(
newLayers.begin(), newLayers.begin(),
newLayers.end(), newLayers.end(),
[](const types::LayerInfo& layerInfo) [](const types::LayerInfo& layerInfo)
@ -406,7 +414,7 @@ void LayerModel::ResetLayers()
return std::holds_alternative<types::InformationLayer>( return std::holds_alternative<types::InformationLayer>(
layerInfo.description_) && layerInfo.description_) &&
std::get<types::InformationLayer>(layerInfo.description_) == std::get<types::InformationLayer>(layerInfo.description_) ==
types::InformationLayer::ColorTable; types::InformationLayer::RadarSite;
}); });
// Add all existing placefile layers // Add all existing placefile layers
@ -415,7 +423,7 @@ void LayerModel::ResetLayers()
if (it->type_ == types::LayerType::Placefile) if (it->type_ == types::LayerType::Placefile)
{ {
newLayers.insert( newLayers.insert(
colorTableIterator + 1, radarSiteIterator + 1,
{it->type_, it->description_, it->movable_, it->displayed_}); {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) void LayerModel::Impl::AddPlacefile(const std::string& name)
{ {
// Insert after color table // Insert after radar site
auto insertPosition = std::find_if( auto insertPosition = std::find_if(
layers_.begin(), layers_.begin(),
layers_.end(), layers_.end(),
@ -1016,7 +1024,7 @@ void LayerModel::Impl::AddPlacefile(const std::string& name)
return std::holds_alternative<types::InformationLayer>( return std::holds_alternative<types::InformationLayer>(
layerInfo.description_) && layerInfo.description_) &&
std::get<types::InformationLayer>(layerInfo.description_) == std::get<types::InformationLayer>(layerInfo.description_) ==
types::InformationLayer::ColorTable; types::InformationLayer::RadarSite;
}); });
if (insertPosition != layers_.end()) if (insertPosition != layers_.end())
{ {

View file

@ -27,6 +27,7 @@ static const std::unordered_map<DataLayer, std::string> dataLayerName_ {
static const std::unordered_map<InformationLayer, std::string> static const std::unordered_map<InformationLayer, std::string>
informationLayerName_ {{InformationLayer::MapOverlay, "Map Overlay"}, informationLayerName_ {{InformationLayer::MapOverlay, "Map Overlay"},
{InformationLayer::RadarSite, "Radar Sites"},
{InformationLayer::ColorTable, "Color Table"}, {InformationLayer::ColorTable, "Color Table"},
{InformationLayer::Unknown, "?"}}; {InformationLayer::Unknown, "?"}};

View file

@ -41,6 +41,7 @@ typedef scwx::util::
enum class InformationLayer enum class InformationLayer
{ {
MapOverlay, MapOverlay,
RadarSite,
ColorTable, ColorTable,
Unknown Unknown
}; };