diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 34347be2..ca2a7f36 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,7 @@ jobs: msvc_version: 2022 qt_version: 6.5.0 qt_arch: win64_msvc2019_64 + qt_modules: qtimageformats qt_tools: '' conan_arch: x86_64 conan_compiler: Visual Studio @@ -46,6 +47,7 @@ jobs: compiler: gcc qt_version: 6.5.0 qt_arch: gcc_64 + qt_modules: qtimageformats qt_tools: '' conan_arch: x86_64 conan_compiler: gcc @@ -74,6 +76,7 @@ jobs: with: version: ${{ matrix.qt_version }} arch: ${{ matrix.qt_arch }} + modules: ${{ matrix.qt_modules }} tools: ${{ matrix.qt_tools }} - name: Setup MSVC diff --git a/.gitmodules b/.gitmodules index 472835da..7828c19a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "external/mapbox-gl-native"] path = external/mapbox-gl-native - url = https://github.com/maplibre/maplibre-gl-native.git + url = https://github.com/dpaulat/maplibre-gl-native.git [submodule "external/cmake-conan"] path = external/cmake-conan url = https://github.com/conan-io/cmake-conan.git diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md index 30ec5f63..889246f4 100644 --- a/ACKNOWLEDGEMENTS.md +++ b/ACKNOWLEDGEMENTS.md @@ -15,7 +15,7 @@ Supercell Wx uses code from the following dependencies: | [bzip2](https://sourceware.org/bzip2/) | [bzip2 and libbzip2 License v1.0.6](https://spdx.org/licenses/bzip2-1.0.6.html) | | [cmake-conan](https://github.com/conan-io/cmake-conan) | [MIT License](https://spdx.org/licenses/MIT.html) | | [cpr](https://github.com/libcpr/cpr) | [MIT License](https://spdx.org/licenses/MIT.html) | -| [CSS Color Parser](https://github.com/deanm/css-color-parser-js) | [MIT License](https://spdx.org/licenses/MIT.html) | Ported to C++ for MapLibre GL Native | +| [CSS Color Parser](https://github.com/deanm/css-color-parser-js) | [MIT License](https://spdx.org/licenses/MIT.html) | Ported to C++ for MapLibre Native | | [Date](https://github.com/HowardHinnant/date) | [MIT License](https://spdx.org/licenses/MIT.html) | | [Dear ImGui](https://github.com/ocornut/imgui) | [MIT License](https://spdx.org/licenses/MIT.html) | | [FreeType](https://freetype.org/) | [Freetype Project License](https://spdx.org/licenses/FTL.html) | @@ -29,8 +29,8 @@ Supercell Wx uses code from the following dependencies: | [libiconv](https://www.gnu.org/software/libiconv/) | [GNU Lesser General Public License v2.1 or later](https://spdx.org/licenses/LGPL-2.1-or-later.html) | | [libpng](http://libpng.org/pub/png/libpng.html) | [PNG Reference Library version 2](https://spdx.org/licenses/libpng-2.0.html) | | [libxml2](http://xmlsoft.org/) | [MIT License](https://spdx.org/licenses/MIT.html) | -| [MapLibre GL Native](https://maplibre.org/projects/maplibre-native/) | [BSD 2-Clause "Simplified" License](https://spdx.org/licenses/BSD-2-Clause.html) | -| [nunicode](https://bitbucket.org/alekseyt/nunicode/src/master/) | [MIT License](https://spdx.org/licenses/MIT.html) | Modified for MapLibre GL Native | +| [MapLibre Native](https://maplibre.org/projects/maplibre-native/) | [BSD 2-Clause "Simplified" License](https://spdx.org/licenses/BSD-2-Clause.html) | +| [nunicode](https://bitbucket.org/alekseyt/nunicode/src/master/) | [MIT License](https://spdx.org/licenses/MIT.html) | Modified for MapLibre Native | | [OpenSSL](https://www.openssl.org/) | [OpenSSL License](https://spdx.org/licenses/OpenSSL.html) | | [Qt](https://www.qt.io/) | [GNU Lesser General Public License v3.0 only](https://spdx.org/licenses/LGPL-3.0-only.html) | Qt Core, Qt GUI, Qt Network, Qt OpenGL, Qt SQL, Qt SVG, Qt Widgets
Additional Licenses: https://doc.qt.io/qt-6/licenses-used-in-qt.html | | [spdlog](https://github.com/gabime/spdlog) | [MIT License](https://spdx.org/licenses/MIT.html) | diff --git a/external/mapbox-gl-native b/external/mapbox-gl-native index e12c4d6b..fbb06ff5 160000 --- a/external/mapbox-gl-native +++ b/external/mapbox-gl-native @@ -1 +1 @@ -Subproject commit e12c4d6be450a163b38548f72e72f4080fc71dbb +Subproject commit fbb06ff53e74d3a81b434b84fff1a5dfe4b2d3c7 diff --git a/external/mapbox-gl-native.cmake b/external/mapbox-gl-native.cmake index 910a6849..c48900c1 100644 --- a/external/mapbox-gl-native.cmake +++ b/external/mapbox-gl-native.cmake @@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.20) set(PROJECT_NAME scwx-mbgl) set(gtest_disable_pthreads ON) -set(MBGL_WITH_QT ON) -set(MBGL_QT_WITH_INTERNAL_ICU ON) +set(MLN_WITH_QT ON) +set(MLN_QT_WITH_INTERNAL_ICU ON) add_subdirectory(mapbox-gl-native) find_package(ZLIB) diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 15d09a62..32d900ce 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -78,6 +78,7 @@ set(HDR_MAP source/scwx/qt/map/alert_layer.hpp source/scwx/qt/map/generic_layer.hpp source/scwx/qt/map/layer_wrapper.hpp source/scwx/qt/map/map_context.hpp + source/scwx/qt/map/map_provider.hpp source/scwx/qt/map/map_settings.hpp source/scwx/qt/map/map_widget.hpp source/scwx/qt/map/overlay_layer.hpp @@ -89,6 +90,7 @@ set(SRC_MAP source/scwx/qt/map/alert_layer.cpp source/scwx/qt/map/generic_layer.cpp source/scwx/qt/map/layer_wrapper.cpp source/scwx/qt/map/map_context.cpp + source/scwx/qt/map/map_provider.cpp source/scwx/qt/map/map_widget.cpp source/scwx/qt/map/overlay_layer.cpp source/scwx/qt/map/radar_product_layer.cpp diff --git a/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp b/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp index c15b1f6d..b2c92279 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp @@ -58,12 +58,14 @@ void DrawItem::UseDefaultProjection( static glm::vec2 LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate) { + static constexpr double RAD2DEG_D = 180.0 / M_PI; + double latitude = std::clamp( coordinate.first, -mbgl::util::LATITUDE_MAX, mbgl::util::LATITUDE_MAX); glm::vec2 screen { mbgl::util::LONGITUDE_MAX + coordinate.second, -(mbgl::util::LONGITUDE_MAX - - mbgl::util::RAD2DEG_D * + RAD2DEG_D * std::log(std::tan(M_PI / 4.0 + latitude * M_PI / mbgl::util::DEGREES_MAX)))}; return screen; diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index 4b87d60b..3b392f01 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -74,10 +75,18 @@ public: elevationButtonsChanged_ {false}, resizeElevationButtons_ {false} { + mapProvider_ = + map::GetMapProvider(manager::SettingsManager::general_settings() + .map_provider() + .GetValue()); + const map::MapProviderInfo& mapProviderInfo = + map::GetMapProviderInfo(mapProvider_); + std::string appDataPath { QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) .toStdString()}; - std::string cacheDbPath {appDataPath + "/mbgl-cache.db"}; + std::string cacheDbPath {appDataPath + "/" + + mapProviderInfo.cacheDbName_}; if (!std::filesystem::exists(appDataPath)) { @@ -89,12 +98,10 @@ public: } } - std::string mapboxApiKey = manager::SettingsManager::general_settings() - .mapbox_api_key() - .GetValue(); + std::string mapProviderApiKey = map::GetMapProviderApiKey(mapProvider_); - settings_.resetToTemplate(QMapLibreGL::Settings::MapboxSettings); - settings_.setApiKey(QString {mapboxApiKey.c_str()}); + settings_.resetToTemplate(mapProviderInfo.settingsTemplate_); + settings_.setApiKey(QString {mapProviderApiKey.c_str()}); settings_.setCacheDatabasePath(QString {cacheDbPath.c_str()}); settings_.setCacheDatabaseMaximumSize(20 * 1024 * 1024); } @@ -105,6 +112,7 @@ public: void ConnectMapSignals(); void ConnectOtherSignals(); void HandleFocusChange(QWidget* focused); + void PopulateMapStyles(); void SelectElevation(map::MapWidget* mapWidget, float elevation); void SelectRadarProduct(map::MapWidget* mapWidget, common::RadarProductGroup group, @@ -113,6 +121,7 @@ public: void SetActiveMap(map::MapWidget* mapWidget); void UpdateAvailableLevel3Products(); void UpdateElevationSelection(float elevation); + void UpdateMapStyle(const std::string& styleName); void UpdateRadarProductSelection(common::RadarProductGroup group, const std::string& product); void UpdateRadarProductSettings(); @@ -121,6 +130,7 @@ public: MainWindow* mainWindow_; QMapLibreGL::Settings settings_; + map::MapProvider mapProvider_; map::MapWidget* activeMap_; ui::Level2ProductsWidget* level2ProductsWidget_; @@ -245,6 +255,7 @@ MainWindow::MainWindow(QWidget* parent) : 0); } + p->PopulateMapStyles(); p->ConnectMapSignals(); p->ConnectOtherSignals(); p->HandleFocusChange(p->activeMap_); @@ -586,6 +597,11 @@ void MainWindowImpl::ConnectMapSignals() }, Qt::QueuedConnection); + connect(mapWidget, + &map::MapWidget::MapStyleChanged, + this, + &MainWindowImpl::UpdateMapStyle); + connect( mapWidget, &map::MapWidget::RadarSweepUpdated, @@ -624,6 +640,11 @@ void MainWindowImpl::ConnectOtherSignals() &QApplication::focusChanged, mainWindow_, [this](QWidget* /*old*/, QWidget* now) { HandleFocusChange(now); }); + connect(mainWindow_->ui->mapStyleComboBox, + &QComboBox::currentTextChanged, + mainWindow_, + [&](const QString& text) + { activeMap_->SetMapStyle(text.toStdString()); }); connect(level2ProductsWidget_, &ui::Level2ProductsWidget::RadarProductSelected, mainWindow_, @@ -696,6 +717,7 @@ void MainWindowImpl::HandleFocusChange(QWidget* focused) { SetActiveMap(mapWidget); UpdateAvailableLevel3Products(); + UpdateMapStyle(mapWidget->GetMapStyle()); UpdateRadarProductSelection(mapWidget->GetRadarProductGroup(), mapWidget->GetRadarProductName()); UpdateRadarProductSettings(); @@ -704,6 +726,16 @@ void MainWindowImpl::HandleFocusChange(QWidget* focused) } } +void MainWindowImpl::PopulateMapStyles() +{ + const auto& mapProviderInfo = map::GetMapProviderInfo(mapProvider_); + for (const auto& mapStyle : mapProviderInfo.mapStyles_) + { + mainWindow_->ui->mapStyleComboBox->addItem( + QString::fromStdString(mapStyle.name_)); + } +} + void MainWindowImpl::SelectElevation(map::MapWidget* mapWidget, float elevation) { if (mapWidget == activeMap_) @@ -767,6 +799,16 @@ void MainWindowImpl::UpdateMapParameters( } } +void MainWindowImpl::UpdateMapStyle(const std::string& styleName) +{ + int index = mainWindow_->ui->mapStyleComboBox->findText( + QString::fromStdString(styleName)); + if (index != -1) + { + mainWindow_->ui->mapStyleComboBox->setCurrentIndex(index); + } +} + void MainWindowImpl::UpdateRadarProductSelection( common::RadarProductGroup group, const std::string& product) { diff --git a/scwx-qt/source/scwx/qt/main/main_window.ui b/scwx-qt/source/scwx/qt/main/main_window.ui index 9a66af52..0d5c5489 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.ui +++ b/scwx-qt/source/scwx/qt/main/main_window.ui @@ -184,6 +184,25 @@ + + + + Map Settings + + + + + + Map Style + + + + + + + + + diff --git a/scwx-qt/source/scwx/qt/manager/settings_manager.cpp b/scwx-qt/source/scwx/qt/manager/settings_manager.cpp index be161245..c11d4f71 100644 --- a/scwx-qt/source/scwx/qt/manager/settings_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/settings_manager.cpp @@ -1,10 +1,12 @@ #include +#include #include #include #include #include +#include #include #include @@ -23,6 +25,7 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); static boost::json::value ConvertSettingsToJson(); static void GenerateDefaultSettings(); static bool LoadSettings(const boost::json::object& settingsJson); +static void ValidateSettings(); static bool initialized_ {false}; static std::string settingsPath_ {}; @@ -46,6 +49,7 @@ void Initialize() initialized_ = true; ReadSettings(settingsPath_); + ValidateSettings(); } void ReadSettings(const std::string& settingsPath) @@ -137,6 +141,47 @@ static bool LoadSettings(const boost::json::object& settingsJson) return jsonDirty; } +static void ValidateSettings() +{ + logger_->debug("Validating settings"); + + bool settingsChanged = false; + + auto& generalSettings = general_settings(); + + // Validate map provider + std::string mapProviderName = generalSettings.map_provider().GetValue(); + std::string mapboxApiKey = generalSettings.mapbox_api_key().GetValue(); + std::string maptilerApiKey = generalSettings.maptiler_api_key().GetValue(); + + map::MapProvider mapProvider = map::GetMapProvider(mapProviderName); + std::string mapApiKey = map::GetMapProviderApiKey(mapProvider); + + if (mapApiKey == "?") + { + for (map::MapProvider newProvider : map::MapProviderIterator()) + { + if (mapProvider != newProvider && + map::GetMapProviderApiKey(newProvider).size() > 1) + { + logger_->info( + "Setting Map Provider to {} based on API key settings", + map::GetMapProviderName(newProvider)); + + std::string newProviderName {GetMapProviderName(newProvider)}; + boost::to_lower(newProviderName); + generalSettings.map_provider().SetValue(newProviderName); + settingsChanged = true; + } + } + } + + if (settingsChanged) + { + SaveSettings(); + } +} + } // namespace SettingsManager } // namespace manager } // namespace qt diff --git a/scwx-qt/source/scwx/qt/map/map_provider.cpp b/scwx-qt/source/scwx/qt/map/map_provider.cpp new file mode 100644 index 00000000..5850cd79 --- /dev/null +++ b/scwx-qt/source/scwx/qt/map/map_provider.cpp @@ -0,0 +1,127 @@ +#include +#include + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace map +{ + +static const std::unordered_map mapProviderName_ { + {MapProvider::Mapbox, "Mapbox"}, + {MapProvider::MapTiler, "MapTiler"}, + {MapProvider::Unknown, "?"}}; + +// Draw below tunnels, ferries and roads +static const std::vector mapboxDrawBelow_ { + "tunnel.*", "ferry.*", "road.*"}; + +static const std::unordered_map mapProviderInfo_ { + {MapProvider::Mapbox, + MapProviderInfo { + .mapProvider_ {MapProvider::Mapbox}, + .cacheDbName_ {"mbgl-cache.db"}, + .settingsTemplate_ { + QMapLibreGL::Settings::SettingsTemplate::MapboxSettings}, + .mapStyles_ {{.name_ {"Streets"}, + .url_ {"mapbox://styles/mapbox/streets-v11"}, + .drawBelow_ {mapboxDrawBelow_}}, + {.name_ {"Outdoors"}, + .url_ {"mapbox://styles/mapbox/outdoors-v11"}, + .drawBelow_ {mapboxDrawBelow_}}, + {.name_ {"Light"}, + .url_ {"mapbox://styles/mapbox/light-v10"}, + .drawBelow_ {mapboxDrawBelow_}}, + {.name_ {"Dark"}, + .url_ {"mapbox://styles/mapbox/dark-v10"}, + .drawBelow_ {mapboxDrawBelow_}}, + {.name_ {"Satellite"}, + .url_ {"mapbox://styles/mapbox/satellite-v9"}, + .drawBelow_ {mapboxDrawBelow_}}, + {.name_ {"Satellite Streets"}, + .url_ {"mapbox://styles/mapbox/satellite-streets-v11"}, + .drawBelow_ {mapboxDrawBelow_}}}}}, + {MapProvider::MapTiler, + MapProviderInfo { + .mapProvider_ {MapProvider::MapTiler}, + .cacheDbName_ {"maptiler-cache.db"}, + .settingsTemplate_ { + QMapLibreGL::Settings::SettingsTemplate::MapTilerSettings}, + .mapStyles_ {{.name_ {"Satellite"}, + .url_ {"maptiler://maps/hybrid"}, + .drawBelow_ {"tunnel"}}, + {.name_ {"Streets"}, + .url_ {"maptiler://maps/streets-v2"}, + .drawBelow_ {"aeroway"}}, + {.name_ {"Basic"}, + .url_ {"maptiler://maps/basic-v2"}, + .drawBelow_ {"railway_transit_tunnel"}}, + {.name_ {"Bright"}, + .url_ {"maptiler://maps/bright-v2"}, + .drawBelow_ {"ferry"}}, + {.name_ {"Outdoor"}, + .url_ {"maptiler://maps/outdoor-v2"}, + .drawBelow_ {"aeroway_runway"}}, + {.name_ {"Topo"}, + .url_ {"maptiler://maps/topo-v2"}, + .drawBelow_ {"aeroway_runway"}}, + {.name_ {"Winter"}, + .url_ {"maptiler://maps/winter-v2"}, + .drawBelow_ {"aeroway_runway"}}}}}, + {MapProvider::Unknown, MapProviderInfo {}}}; + +MapProvider GetMapProvider(const std::string& name) +{ + auto result = + std::find_if(mapProviderName_.cbegin(), + mapProviderName_.cend(), + [&](const std::pair& pair) -> bool + { return boost::iequals(pair.second, name); }); + + if (result != mapProviderName_.cend()) + { + return result->first; + } + else + { + return MapProvider::Unknown; + } +} + +std::string GetMapProviderName(MapProvider mapProvider) +{ + return mapProviderName_.at(mapProvider); +} + +std::string GetMapProviderApiKey(MapProvider mapProvider) +{ + switch (mapProvider) + { + case MapProvider::Mapbox: + return manager::SettingsManager::general_settings() + .mapbox_api_key() + .GetValue(); + + case MapProvider::MapTiler: + return manager::SettingsManager::general_settings() + .maptiler_api_key() + .GetValue(); + + default: + return "?"; + } +} + +const MapProviderInfo& GetMapProviderInfo(MapProvider mapProvider) +{ + return mapProviderInfo_.at(mapProvider); +} + +} // namespace map +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/map/map_provider.hpp b/scwx-qt/source/scwx/qt/map/map_provider.hpp new file mode 100644 index 00000000..7e6bb491 --- /dev/null +++ b/scwx-qt/source/scwx/qt/map/map_provider.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace map +{ + +enum class MapProvider +{ + Mapbox, + MapTiler, + Unknown +}; +typedef scwx::util:: + Iterator + MapProviderIterator; + +struct MapStyle +{ + std::string name_; + std::string url_; + std::vector drawBelow_; +}; + +struct MapProviderInfo +{ + MapProvider mapProvider_ {MapProvider::Unknown}; + std::string cacheDbName_ {}; + QMapLibreGL::Settings::SettingsTemplate settingsTemplate_ {}; + std::vector mapStyles_ {}; +}; + +MapProvider GetMapProvider(const std::string& name); +std::string GetMapProviderName(MapProvider mapProvider); +std::string GetMapProviderApiKey(MapProvider mapProvider); +const MapProviderInfo& GetMapProviderInfo(MapProvider mapProvider); + +} // namespace map +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 5de29c5e..40eb40f0 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -15,6 +16,8 @@ #include #include +#include + #include #include #include @@ -39,20 +42,6 @@ namespace map static const std::string logPrefix_ = "scwx::qt::map::map_widget"; static const auto logger_ = scwx::util::Logger::Create(logPrefix_); -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 @@ -77,6 +66,7 @@ public: selectedTime_ {}, lastPos_(), currentStyleIndex_ {0}, + currentStyle_ {nullptr}, frameDraws_(0), prevLatitude_ {0.0}, prevLongitude_ {0.0}, @@ -84,9 +74,10 @@ public: prevBearing_ {0.0}, prevPitch_ {0.0} { - SetRadarSite(scwx::qt::manager::SettingsManager::general_settings() - .default_radar_site() - .GetValue()); + auto& generalSettings = + scwx::qt::manager::SettingsManager::general_settings(); + + SetRadarSite(generalSettings.default_radar_site().GetValue()); // Create ImGui Context static size_t currentMapId_ {0u}; @@ -97,6 +88,9 @@ public: // Initialize ImGui Qt backend ImGui_ImplQt_Init(); ImGui_ImplQt_RegisterWidget(widget_); + + // Set Map Provider Details + mapProvider_ = GetMapProvider(generalSettings.map_provider().GetValue()); } ~MapWidgetImpl() @@ -134,6 +128,7 @@ public: std::shared_ptr context_; MapWidget* widget_; + MapProvider mapProvider_; QMapLibreGL::Settings settings_; std::shared_ptr map_; std::list layerList_; @@ -156,8 +151,9 @@ public: common::Level2Product selectedLevel2Product_; std::chrono::system_clock::time_point selectedTime_; - QPointF lastPos_; - uint8_t currentStyleIndex_; + QPointF lastPos_; + std::size_t currentStyleIndex_; + const MapStyle* currentStyle_; uint64_t frameDraws_; @@ -268,6 +264,18 @@ std::vector MapWidget::GetLevel3Products() } } +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(); @@ -538,6 +546,32 @@ void MapWidget::SetMapParameters( } } +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(); @@ -545,16 +579,21 @@ qreal MapWidget::pixelRatio() void MapWidget::changeStyle() { - auto& styles = mapboxStyles_; + const auto& mapProviderInfo = GetMapProviderInfo(p->mapProvider_); + auto& styles = mapProviderInfo.mapStyles_; - p->map_->setStyleUrl(styles[p->currentStyleIndex_].first.c_str()); - setWindowTitle(QString("Mapbox GL: ") + - styles[p->currentStyleIndex_].second.c_str()); + 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; } + + emit MapStyleChanged(p->currentStyle_->name_); } void MapWidget::AddLayers() @@ -578,15 +617,26 @@ void MapWidget::AddLayers() std::shared_ptr radarSite = p->radarProductManager_->radar_site(); + const auto& mapStyle = *p->currentStyle_; + std::string before = "ferry"; - for (const QString& layer : p->map_->layerIds()) + for (const QString& qlayer : p->map_->layerIds()) { - // Draw below tunnels, ferries and roads - if (layer.startsWith("tunnel") || layer.startsWith("ferry") || - layer.startsWith("road")) + const std::string layer = qlayer.toStdString(); + + // Draw below layers defined in map style + auto it = std::find_if(mapStyle.drawBelow_.cbegin(), + mapStyle.drawBelow_.cend(), + [&layer](const std::string& styleLayer) -> bool + { + std::regex re {styleLayer}; + return std::regex_match(layer, re); + }); + + if (it != mapStyle.drawBelow_.cend()) { - before = layer.toStdString(); + before = layer; break; } } @@ -740,16 +790,8 @@ void MapWidget::initializeGL() p->prevBearing_, p->prevPitch_); - QString styleUrl = qgetenv("MAPBOX_STYLE_URL"); - if (styleUrl.isEmpty()) - { - changeStyle(); - } - else - { - p->map_->setStyleUrl(styleUrl); - setWindowTitle(QString("Mapbox GL: ") + styleUrl); - } + // Update style + changeStyle(); connect(p->map_.get(), &QMapLibreGL::Map::mapChanged, diff --git a/scwx-qt/source/scwx/qt/map/map_widget.hpp b/scwx-qt/source/scwx/qt/map/map_widget.hpp index 104c280f..fbb5746e 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.hpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.hpp @@ -38,6 +38,7 @@ public: float GetElevation() const; std::vector GetElevationCuts() const; std::vector GetLevel3Products(); + std::string GetMapStyle() const; common::RadarProductGroup GetRadarProductGroup() const; std::string GetRadarProductName() const; std::shared_ptr GetRadarSite() const; @@ -98,6 +99,7 @@ public: double zoom, double bearing, double pitch); + void SetMapStyle(const std::string& styleName); private: void changeStyle(); @@ -129,6 +131,7 @@ signals: double zoom, double bearing, double pitch); + void MapStyleChanged(const std::string& styleName); void RadarSweepUpdated(); }; diff --git a/scwx-qt/source/scwx/qt/map/radar_product_layer.cpp b/scwx-qt/source/scwx/qt/map/radar_product_layer.cpp index f5310f1e..514b7e5c 100644 --- a/scwx-qt/source/scwx/qt/map/radar_product_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/radar_product_layer.cpp @@ -359,12 +359,14 @@ void RadarProductLayer::UpdateColorTable() static glm::vec2 LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate) { + static constexpr double RAD2DEG_D = 180.0 / M_PI; + double latitude = std::clamp( coordinate.first, -mbgl::util::LATITUDE_MAX, mbgl::util::LATITUDE_MAX); glm::vec2 screen { mbgl::util::LONGITUDE_MAX + coordinate.second, -(mbgl::util::LONGITUDE_MAX - - mbgl::util::RAD2DEG_D * + RAD2DEG_D * std::log(std::tan(M_PI / 4.0 + latitude * M_PI / mbgl::util::DEGREES_MAX)))}; return screen; diff --git a/scwx-qt/source/scwx/qt/settings/general_settings.cpp b/scwx-qt/source/scwx/qt/settings/general_settings.cpp index 55f74a00..19c97f8a 100644 --- a/scwx-qt/source/scwx/qt/settings/general_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/general_settings.cpp @@ -1,5 +1,10 @@ #include #include +#include + +#include + +#include namespace scwx { @@ -20,7 +25,9 @@ public: fontSizes_.SetDefault({16}); gridWidth_.SetDefault(1); gridHeight_.SetDefault(1); + mapProvider_.SetDefault("maptiler"); mapboxApiKey_.SetDefault("?"); + maptilerApiKey_.SetDefault("?"); updateNotificationsEnabled_.SetDefault(true); fontSizes_.SetElementMinimum(1); @@ -31,8 +38,29 @@ public: gridWidth_.SetMaximum(2); gridHeight_.SetMinimum(1); gridHeight_.SetMaximum(2); + mapProvider_.SetValidator( + [](const std::string& value) + { + for (map::MapProvider mapProvider : map::MapProviderIterator()) + { + // If the value is equal to a lower case map provider name + std::string mapProviderName = + map::GetMapProviderName(mapProvider); + boost::to_lower(mapProviderName); + if (value == mapProviderName) + { + // Regard as a match, valid + return true; + } + } + + // No match found, invalid + return false; + }); mapboxApiKey_.SetValidator([](const std::string& value) { return !value.empty(); }); + maptilerApiKey_.SetValidator([](const std::string& value) + { return !value.empty(); }); } ~GeneralSettingsImpl() {} @@ -42,7 +70,9 @@ public: SettingsContainer> fontSizes_ {"font_sizes"}; SettingsVariable gridWidth_ {"grid_width"}; SettingsVariable gridHeight_ {"grid_height"}; + SettingsVariable mapProvider_ {"map_provider"}; SettingsVariable mapboxApiKey_ {"mapbox_api_key"}; + SettingsVariable maptilerApiKey_ {"maptiler_api_key"}; SettingsVariable updateNotificationsEnabled_ {"update_notifications"}; }; @@ -54,7 +84,9 @@ GeneralSettings::GeneralSettings() : &p->fontSizes_, &p->gridWidth_, &p->gridHeight_, + &p->mapProvider_, &p->mapboxApiKey_, + &p->maptilerApiKey_, &p->updateNotificationsEnabled_}); SetDefaults(); } @@ -90,11 +122,21 @@ SettingsVariable& GeneralSettings::grid_width() const return p->gridWidth_; } +SettingsVariable& GeneralSettings::map_provider() const +{ + return p->mapProvider_; +} + SettingsVariable& GeneralSettings::mapbox_api_key() const { return p->mapboxApiKey_; } +SettingsVariable& GeneralSettings::maptiler_api_key() const +{ + return p->maptilerApiKey_; +} + SettingsVariable& GeneralSettings::update_notifications_enabled() const { return p->updateNotificationsEnabled_; @@ -107,7 +149,9 @@ bool operator==(const GeneralSettings& lhs, const GeneralSettings& rhs) lhs.p->fontSizes_ == rhs.p->fontSizes_ && lhs.p->gridWidth_ == rhs.p->gridWidth_ && lhs.p->gridHeight_ == rhs.p->gridHeight_ && + lhs.p->mapProvider_ == rhs.p->mapProvider_ && lhs.p->mapboxApiKey_ == rhs.p->mapboxApiKey_ && + lhs.p->maptilerApiKey_ == rhs.p->maptilerApiKey_ && lhs.p->updateNotificationsEnabled_ == rhs.p->updateNotificationsEnabled_); } diff --git a/scwx-qt/source/scwx/qt/settings/general_settings.hpp b/scwx-qt/source/scwx/qt/settings/general_settings.hpp index 48757e28..a565f09e 100644 --- a/scwx-qt/source/scwx/qt/settings/general_settings.hpp +++ b/scwx-qt/source/scwx/qt/settings/general_settings.hpp @@ -32,7 +32,9 @@ public: SettingsContainer>& font_sizes() const; SettingsVariable& grid_height() const; SettingsVariable& grid_width() const; + SettingsVariable& map_provider() const; SettingsVariable& mapbox_api_key() const; + SettingsVariable& maptiler_api_key() const; SettingsVariable& update_notifications_enabled() const; friend bool operator==(const GeneralSettings& lhs, diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp index b8250d7f..5793f6bb 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -12,6 +13,7 @@ #include #include +#include #include #include #include @@ -83,7 +85,9 @@ public: &fontSizes_, &gridWidth_, &gridHeight_, + &mapProvider_, &mapboxApiKey_, + &mapTilerApiKey_, &updateNotificationsEnabled_, &debugEnabled_}} { @@ -137,7 +141,9 @@ public: settings::SettingsInterface> fontSizes_ {}; settings::SettingsInterface gridWidth_ {}; settings::SettingsInterface gridHeight_ {}; + settings::SettingsInterface mapProvider_ {}; settings::SettingsInterface mapboxApiKey_ {}; + settings::SettingsInterface mapTilerApiKey_ {}; settings::SettingsInterface updateNotificationsEnabled_ {}; settings::SettingsInterface debugEnabled_ {}; @@ -313,10 +319,46 @@ void SettingsDialogImpl::SetupGeneralTab() gridHeight_.SetEditWidget(self_->ui->gridHeightSpinBox); gridHeight_.SetResetButton(self_->ui->resetGridHeightButton); + for (const auto& mapProvider : map::MapProviderIterator()) + { + self_->ui->mapProviderComboBox->addItem( + QString::fromStdString(map::GetMapProviderName(mapProvider))); + } + + mapProvider_.SetSettingsVariable(generalSettings.map_provider()); + mapProvider_.SetMapFromValueFunction( + [](const std::string& text) -> std::string + { + for (map::MapProvider mapProvider : map::MapProviderIterator()) + { + if (boost::iequals(text, map::GetMapProviderName(mapProvider))) + { + // Return map provider label + return GetMapProviderName(mapProvider); + } + } + + // Map provider label not found, return unknown + return "?"; + }); + mapProvider_.SetMapToValueFunction( + [](std::string text) -> std::string + { + // Convert label to lower case and return + boost::to_lower(text); + return text; + }); + mapProvider_.SetEditWidget(self_->ui->mapProviderComboBox); + mapProvider_.SetResetButton(self_->ui->resetMapProviderButton); + mapboxApiKey_.SetSettingsVariable(generalSettings.mapbox_api_key()); mapboxApiKey_.SetEditWidget(self_->ui->mapboxApiKeyLineEdit); mapboxApiKey_.SetResetButton(self_->ui->resetMapboxApiKeyButton); + mapTilerApiKey_.SetSettingsVariable(generalSettings.maptiler_api_key()); + mapTilerApiKey_.SetEditWidget(self_->ui->mapTilerApiKeyLineEdit); + mapTilerApiKey_.SetResetButton(self_->ui->resetMapTilerApiKeyButton); + updateNotificationsEnabled_.SetSettingsVariable( generalSettings.update_notifications_enabled()); updateNotificationsEnabled_.SetEditWidget( diff --git a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui index f021c230..9a748feb 100644 --- a/scwx-qt/source/scwx/qt/ui/settings_dialog.ui +++ b/scwx-qt/source/scwx/qt/ui/settings_dialog.ui @@ -109,43 +109,14 @@ 0 - - - - Default Radar Site - - + + - - - - Grid Height - - - - - - - - + + ... - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - - - - - - - ... - - - - :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg - @@ -159,11 +130,41 @@ - - + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + - - + + + + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + Font Sizes + + + + + + + MapTiler API Key + + @@ -176,13 +177,13 @@ - - + + - - + + - Mapbox API Key + Grid Height @@ -197,16 +198,36 @@ - - + + - - + + + + + - Font Sizes + ... + + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + + + + + + + Mapbox API Key + + + + + + @@ -214,11 +235,32 @@ - - + + + + Default Radar Site + + + + + + + Map Provider + + + + + + + + ... + + + :/res/icons/font-awesome-6/rotate-left-solid.svg:/res/icons/font-awesome-6/rotate-left-solid.svg + diff --git a/test/data b/test/data index 5b507378..938b0240 160000 --- a/test/data +++ b/test/data @@ -1 +1 @@ -Subproject commit 5b5073780fe44e55eb4c33799036683b28ffd2bd +Subproject commit 938b0240e51aff37530adc679a11d0e65e5e96ce