#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] "; typedef std::pair MapStyle; // clang-format off static const MapStyle streets { "mapbox://styles/mapbox/streets-v11", "Streets"}; static const MapStyle outdoors { "mapbox://styles/mapbox/outdoors-v11", "Outdoors"}; static const MapStyle light { "mapbox://styles/mapbox/light-v10", "Light"}; static const MapStyle dark { "mapbox://styles/mapbox/dark-v10", "Dark" }; static const MapStyle satellite { "mapbox://styles/mapbox/satellite-v9", "Satellite" }; static const MapStyle satelliteStreets { "mapbox://styles/mapbox/satellite-streets-v11", "Satellite Streets" }; // clang-format on static const std::array mapboxStyles_ = { {streets, outdoors, light, dark, satellite, satelliteStreets}}; class MapWidgetImpl : public QObject { Q_OBJECT public: explicit MapWidgetImpl(MapWidget* widget, const QMapboxGLSettings& settings) : context_ {std::make_shared()}, widget_ {widget}, settings_(settings), map_(), layerList_ {}, radarProductManager_ {manager::RadarProductManager::Instance("KLSX")}, radarProductLayer_ {nullptr}, overlayLayer_ {nullptr}, colorTableLayer_ {nullptr}, selectedLevel2Product_ {common::Level2Product::Unknown}, selectedTime_ {}, lastPos_(), currentStyleIndex_ {0}, frameDraws_(0), prevLatitude_ {0.0}, prevLongitude_ {0.0}, prevZoom_ {0.0}, prevBearing_ {0.0}, prevPitch_ {0.0} { } ~MapWidgetImpl() = default; void AddLayer(const std::string& id, std::shared_ptr layer, const std::string& before = {}); bool UpdateStoredMapParameters(); common::Level2Product GetLevel2ProductOrDefault(const std::string& productName) const; std::shared_ptr context_; MapWidget* widget_; QMapboxGLSettings settings_; std::shared_ptr map_; std::list layerList_; std::shared_ptr radarProductManager_; std::shared_ptr colorTable_; std::shared_ptr radarProductLayer_; std::shared_ptr overlayLayer_; std::shared_ptr colorTableLayer_; common::Level2Product selectedLevel2Product_; std::chrono::system_clock::time_point selectedTime_; QPointF lastPos_; uint8_t currentStyleIndex_; uint64_t frameDraws_; double prevLatitude_; double prevLongitude_; double prevZoom_; double prevBearing_; double prevPitch_; public slots: void Update(); }; MapWidget::MapWidget(const QMapboxGLSettings& settings) : p(std::make_unique(this, settings)) { setFocusPolicy(Qt::StrongFocus); } MapWidget::~MapWidget() { // Make sure we have a valid context so we // can delete the QMapboxGL. makeCurrent(); } float MapWidget::GetElevation() const { if (p->context_->radarProductView_ != nullptr) { return p->context_->radarProductView_->elevation(); } else { return 0.0f; } } std::vector MapWidget::GetElevationCuts() const { if (p->context_->radarProductView_ != nullptr) { return p->context_->radarProductView_->GetElevationCuts(); } else { return {}; } } common::Level2Product MapWidgetImpl::GetLevel2ProductOrDefault(const std::string& productName) const { common::Level2Product level2Product = common::GetLevel2Product(productName); if (level2Product == common::Level2Product::Unknown) { if (context_->radarProductView_ != nullptr) { common::Level2Product level2Product = common::GetLevel2Product( context_->radarProductView_->GetRadarProductName()); } } if (level2Product == common::Level2Product::Unknown) { if (selectedLevel2Product_ != common::Level2Product::Unknown) { level2Product = selectedLevel2Product_; } else { level2Product = common::Level2Product::Reflectivity; } } return level2Product; } common::RadarProductGroup MapWidget::GetRadarProductGroup() const { if (p->context_->radarProductView_ != nullptr) { return p->context_->radarProductView_->GetRadarProductGroup(); } else { return common::RadarProductGroup::Unknown; } } std::string MapWidget::GetRadarProductName() const { if (p->context_->radarProductView_ != nullptr) { return p->context_->radarProductView_->GetRadarProductName(); } else { return "?"; } } uint16_t MapWidget::GetVcp() const { if (p->context_->radarProductView_ != nullptr) { return p->context_->radarProductView_->vcp(); } else { return 0; } } void MapWidget::SelectElevation(float elevation) { if (p->context_->radarProductView_ != nullptr) { p->context_->radarProductView_->SelectElevation(elevation); p->context_->radarProductView_->Update(); } } void MapWidget::SelectRadarProduct(common::Level2Product product) { float currentElevation = 0.0f; std::shared_ptr& radarProductView = p->context_->radarProductView_; if (p->context_->radarProductView_ != nullptr) { currentElevation = p->context_->radarProductView_->elevation(); } radarProductView = view::RadarProductViewFactory::Create( product, currentElevation, p->radarProductManager_); radarProductView->SelectTime(p->selectedTime_); p->selectedLevel2Product_ = product; connect( radarProductView.get(), &view::RadarProductView::ColorTableUpdated, this, [&]() { update(); }, Qt::QueuedConnection); connect( radarProductView.get(), &view::RadarProductView::SweepComputed, this, [&]() { std::shared_ptr radarSite = p->radarProductManager_->radar_site(); RadarRangeLayer::Update( p->map_, radarProductView->range(), {radarSite->latitude(), radarSite->longitude()}); update(); emit RadarSweepUpdated(); }, Qt::QueuedConnection); util::async( [=]() { radarProductView->Initialize(); std::string colorTableFile = manager::SettingsManager::palette_settings()->palette( common::GetLevel2Palette(product)); if (!colorTableFile.empty()) { std::shared_ptr colorTable = common::ColorTable::Load(colorTableFile); radarProductView->LoadColorTable(colorTable); } }); if (p->map_ != nullptr) { AddLayers(); } } void MapWidget::SelectRadarProduct(const std::string& radarId, common::RadarProductGroup group, const std::string& product, std::chrono::system_clock::time_point time) { BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "SelectRadarProduct(" << radarId << ", " << common::GetRadarProductGroupName(group) << ", " << product << ", " << util::TimeString(time) << ")"; p->radarProductManager_ = manager::RadarProductManager::Instance(radarId); p->selectedTime_ = time; if (group == common::RadarProductGroup::Level2) { common::Level2Product level2Product = p->GetLevel2ProductOrDefault(product); SelectRadarProduct(level2Product); } } void MapWidget::SetActive(bool isActive) { p->context_->settings_.isActive_ = isActive; update(); } void MapWidget::SetMapParameters( double latitude, double longitude, double zoom, double bearing, double pitch) { p->map_->setCoordinateZoom({latitude, longitude}, zoom); p->map_->setBearing(bearing); p->map_->setPitch(pitch); } qreal MapWidget::pixelRatio() { return devicePixelRatioF(); } void MapWidget::changeStyle() { auto& styles = mapboxStyles_; p->map_->setStyleUrl(styles[p->currentStyleIndex_].first.c_str()); setWindowTitle(QString("Mapbox GL: ") + styles[p->currentStyleIndex_].second.c_str()); if (++p->currentStyleIndex_ == styles.size()) { p->currentStyleIndex_ = 0; } } void MapWidget::AddLayers() { // Clear custom layers for (const std::string& id : p->layerList_) { p->map_->removeLayer(id.c_str()); } p->layerList_.clear(); if (p->context_->radarProductView_ != nullptr) { p->radarProductLayer_ = std::make_shared(p->context_); p->colorTableLayer_ = std::make_shared(p->context_); std::shared_ptr radarSite = p->radarProductManager_->radar_site(); std::string before = "ferry"; for (const QString& layer : p->map_->layerIds()) { // Draw below tunnels, ferries and roads if (layer.startsWith("tunnel") || layer.startsWith("ferry") || layer.startsWith("road")) { before = layer.toStdString(); break; } } p->AddLayer("radar", p->radarProductLayer_, before); RadarRangeLayer::Add(p->map_, p->context_->radarProductView_->range(), {radarSite->latitude(), radarSite->longitude()}); p->AddLayer("colorTable", p->colorTableLayer_); } p->overlayLayer_ = std::make_shared(p->context_); p->AddLayer("overlay", p->overlayLayer_); } void MapWidgetImpl::AddLayer(const std::string& id, std::shared_ptr layer, const std::string& before) { // QMapboxGL::addCustomLayer will take ownership of the std::unique_ptr std::unique_ptr pHost = std::make_unique(layer); map_->addCustomLayer(id.c_str(), std::move(pHost), before.c_str()); layerList_.push_back(id); } void MapWidget::keyPressEvent(QKeyEvent* ev) { switch (ev->key()) { case Qt::Key_S: changeStyle(); break; case Qt::Key_L: { for (const QString& layer : p->map_->layerIds()) { qDebug() << "Layer: " << layer; } } break; default: break; } ev->accept(); } void MapWidget::mousePressEvent(QMouseEvent* ev) { p->lastPos_ = ev->position(); 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 && ev->modifiers() & Qt::ShiftModifier) { p->map_->pitchBy(delta.y()); } else 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(); 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() { makeCurrent(); p->context_->gl_.initializeOpenGLFunctions(); p->map_.reset(new QMapboxGL(nullptr, p->settings_, size(), pixelRatio())); connect(p->map_.get(), &QMapboxGL::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()}, 9); p->UpdateStoredMapParameters(); QString styleUrl = qgetenv("MAPBOX_STYLE_URL"); if (styleUrl.isEmpty()) { changeStyle(); } else { p->map_->setStyleUrl(styleUrl); setWindowTitle(QString("Mapbox GL: ") + styleUrl); } connect(p->map_.get(), &QMapboxGL::mapChanged, this, &MapWidget::mapChanged); } void MapWidget::paintGL() { p->frameDraws_++; p->map_->resize(size()); p->map_->setFramebufferObject(defaultFramebufferObject(), size() * pixelRatio()); p->map_->render(); } void MapWidget::mapChanged(QMapboxGL::MapChange mapChange) { switch (mapChange) { case QMapboxGL::MapChangeDidFinishLoadingStyle: AddLayers(); break; } } void MapWidgetImpl::Update() { widget_->update(); if (UpdateStoredMapParameters()) { 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"