From 001fc89e510626d21b30e13f36a34e7a9ce618fd Mon Sep 17 00:00:00 2001 From: Matthias Frei Date: Wed, 14 Jan 2026 15:53:27 +0100 Subject: [PATCH 1/3] Preparation for "crs" support for vector tiles; explicit representation of raster tile source description --- shared/public/RasterVectorLayerDescription.h | 69 +++++++---------- shared/public/VectorLayerDescription.h | 3 +- shared/public/VectorMapSourceDescription.h | 2 +- .../tiled/vector/Tiled2dMapVectorLayer.cpp | 6 +- .../Tiled2dMapVectorLayerParserHelper.cpp | 77 ++++++++----------- .../Tiled2dMapVectorRasterSubLayerConfig.h | 35 +++------ .../tiled/wmts/WmtsTiled2dMapLayerConfig.cpp | 1 - 7 files changed, 75 insertions(+), 118 deletions(-) diff --git a/shared/public/RasterVectorLayerDescription.h b/shared/public/RasterVectorLayerDescription.h index 73f694ac2..a5db49098 100644 --- a/shared/public/RasterVectorLayerDescription.h +++ b/shared/public/RasterVectorLayerDescription.h @@ -11,7 +11,7 @@ #pragma once #include "VectorLayerDescription.h" -#include "Color.h" +#include "VectorMapSourceDescription.h" #include "RasterShaderStyle.h" #include "FeatureValueEvaluator.h" @@ -127,68 +127,53 @@ class RasterVectorStyle { FeatureValueEvaluator blendModeEvaluator; }; +struct RasterVectorMapSourceDescription : public VectorMapSourceDescription { + bool maskTiles; + + RasterVectorMapSourceDescription(std::string identifier, + std::string url, + int minZoom, + int maxZoom, + std::optional<::RectCoord> bounds, + std::optional zoomLevelScaleFactor, + std::optional adaptScaleToScreen, + std::optional numDrawPreviousLayers, + std::optional underzoom, + std::optional overzoom, + std::optional> levels, + bool maskTiles) : + VectorMapSourceDescription(identifier, url, minZoom, maxZoom, bounds, zoomLevelScaleFactor, adaptScaleToScreen, numDrawPreviousLayers, underzoom, overzoom, levels), + maskTiles(maskTiles) {} +}; + class RasterVectorLayerDescription: public VectorLayerDescription { public: VectorLayerType getType() override { return VectorLayerType::raster; }; - std::string url; + std::shared_ptr source; RasterVectorStyle style; - bool adaptScaleToScreen; - int32_t numDrawPreviousLayers; - bool maskTiles; - double zoomLevelScaleFactor; - bool overzoom; - bool underzoom; - std::optional<::RectCoord> bounds; - std::optional coordinateReferenceSystem; - std::optional> levels; RasterVectorLayerDescription(std::string identifier, - std::string source, + std::shared_ptr source, int minZoom, int maxZoom, - int sourceMinZoom, - int sourceMaxZoom, - std::string url, std::shared_ptr filter, - RasterVectorStyle style, - bool adaptScaleToScreen, - int32_t numDrawPreviousLayers, - bool maskTiles, - double zoomLevelScaleFactor, std::optional renderPassIndex, std::shared_ptr interactable, - bool underzoom, - bool overzoom, - std::optional<::RectCoord> bounds, - std::optional coordinateReferenceSystem, - std::optional> levels) : - VectorLayerDescription(identifier, source, "", minZoom, maxZoom, sourceMinZoom, sourceMaxZoom, filter, renderPassIndex, interactable, false, false), - style(style), url(url), underzoom(underzoom), overzoom(overzoom), adaptScaleToScreen(adaptScaleToScreen), numDrawPreviousLayers(numDrawPreviousLayers), - maskTiles(maskTiles), zoomLevelScaleFactor(zoomLevelScaleFactor), bounds(bounds), coordinateReferenceSystem(coordinateReferenceSystem), levels(levels) {}; - + RasterVectorStyle style) + : VectorLayerDescription(identifier, source->identifier, "", minZoom, maxZoom, source->minZoom, source->maxZoom, filter, renderPassIndex, interactable, false, false) + , source(source) + , style(style) {} std::unique_ptr clone() override { return std::make_unique(identifier, source, minZoom, maxZoom, - sourceMinZoom, - sourceMaxZoom, - url, filter, - style, - adaptScaleToScreen, - numDrawPreviousLayers, - maskTiles, - zoomLevelScaleFactor, renderPassIndex, interactable ? interactable->clone() : nullptr, - underzoom, - overzoom, - bounds, - coordinateReferenceSystem, - levels); + style); } virtual UsedKeysCollection getUsedKeys() const override { diff --git a/shared/public/VectorLayerDescription.h b/shared/public/VectorLayerDescription.h index 684bd9213..543b9fa71 100644 --- a/shared/public/VectorLayerDescription.h +++ b/shared/public/VectorLayerDescription.h @@ -11,7 +11,8 @@ #pragma once #include "Value.h" -#include +#include +#include enum VectorLayerType { background, raster, line, polygon, symbol, custom diff --git a/shared/public/VectorMapSourceDescription.h b/shared/public/VectorMapSourceDescription.h index 20081a43e..251d953e7 100644 --- a/shared/public/VectorMapSourceDescription.h +++ b/shared/public/VectorMapSourceDescription.h @@ -11,7 +11,6 @@ #pragma once #include "VectorLayerDescription.h" -#include "Color.h" #include "GeoJsonTypes.h" #include "RectCoord.h" @@ -28,6 +27,7 @@ class VectorMapSourceDescription { std::optional underzoom; std::optional overzoom; std::optional> levels; + std::optional coordinateReferenceSystem; VectorMapSourceDescription(std::string identifier, std::string vectorUrl, diff --git a/shared/src/map/layers/tiled/vector/Tiled2dMapVectorLayer.cpp b/shared/src/map/layers/tiled/vector/Tiled2dMapVectorLayer.cpp index f21365023..02d64d02b 100644 --- a/shared/src/map/layers/tiled/vector/Tiled2dMapVectorLayer.cpp +++ b/shared/src/map/layers/tiled/vector/Tiled2dMapVectorLayer.cpp @@ -332,10 +332,8 @@ void Tiled2dMapVectorLayer::initializeVectorLayer() { break; } case raster: { - auto rasterSubLayerConfig = customZoomInfo.has_value() ? std::make_shared( - std::static_pointer_cast(layerDesc), is3d,*customZoomInfo) - : std::make_shared( - std::static_pointer_cast(layerDesc), is3d); + auto rasterSubLayerConfig = std::make_shared( + std::dynamic_pointer_cast(layerDesc), is3d, customZoomInfo); auto sourceMailbox = std::make_shared(mapInterface->getScheduler()); auto sourceActor = Actor(sourceMailbox, diff --git a/shared/src/map/layers/tiled/vector/Tiled2dMapVectorLayerParserHelper.cpp b/shared/src/map/layers/tiled/vector/Tiled2dMapVectorLayerParserHelper.cpp index ae0d45e59..b828c8ee0 100644 --- a/shared/src/map/layers/tiled/vector/Tiled2dMapVectorLayerParserHelper.cpp +++ b/shared/src/map/layers/tiled/vector/Tiled2dMapVectorLayerParserHelper.cpp @@ -73,7 +73,7 @@ Tiled2dMapVectorLayerParserResult Tiled2dMapVectorLayerParserHelper::parseStyleJ std::vector> layers; - std::map> rasterLayerMap; + std::map> rasterSourceMap; std::map> geojsonSources; @@ -174,28 +174,22 @@ Tiled2dMapVectorLayerParserResult Tiled2dMapVectorLayerParserHelper::parseStyleJ maxZoom = json.value("maxzoom", 22); } - - RasterVectorStyle style = RasterVectorStyle(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); - rasterLayerMap[key] = std::make_shared(layerName, - key, - 0, - 24, - minZoom, - maxZoom, - url, - nullptr, - style, - adaptScaleToScreen, - numDrawPreviousLayers, - maskTiles, - zoomLevelScaleFactor, - std::nullopt, - nullptr, - underzoom, - overzoom, - bounds, - coordinateReferenceSystem, - levels); + // XXX: coordinateReferenceSystem + // XXX: maskTiles + rasterSourceMap[key] = std::make_shared( + key, + url, + minZoom, + maxZoom, + bounds, + adaptScaleToScreen, + numDrawPreviousLayers, + zoomLevelScaleFactor, + underzoom, + overzoom, + levels, + maskTiles + ); } else if (type == "vector" && val["url"].is_string()) { auto result = LoaderHelper::loadData(replaceUrlParams(val["url"].get(), sourceUrlParams), std::nullopt, loaders); @@ -361,8 +355,8 @@ Tiled2dMapVectorLayerParserResult Tiled2dMapVectorLayerParserHelper::parseStyleJ interactable); layers.push_back(layerDesc); - } else if (val["type"] == "raster" && rasterLayerMap.count(val["source"]) != 0) { - auto layer = rasterLayerMap[val["source"]]; + } else if (val["type"] == "raster" && rasterSourceMap.count(val["source"]) != 0) { + const auto &source = rasterSourceMap[val["source"]]; RasterVectorStyle style = RasterVectorStyle(parser.parseValue(val["paint"]["raster-opacity"]), parser.parseValue(val["paint"]["raster-brightness-min"]), parser.parseValue(val["paint"]["raster-brightness-max"]), @@ -373,30 +367,21 @@ Tiled2dMapVectorLayerParserResult Tiled2dMapVectorLayerParserHelper::parseStyleJ blendMode); std::shared_ptr filter = parser.parseValue(val["filter"]); - bool underzoom = layer->underzoom && !val.contains("minzoom"); - bool overzoom = layer->overzoom && !val.contains("maxzoom"); + // TODO + [[maybe_unused]] + bool underzoom = source->underzoom && !val.contains("minzoom"); + [[maybe_unused]] + bool overzoom = source->overzoom && !val.contains("maxzoom"); - auto newLayer = std::make_shared(val["id"], - val["source"], - val.value("minzoom", layer->minZoom), - val.value("maxzoom", layer->maxZoom), - layer->sourceMinZoom, - layer->sourceMaxZoom, - layer->url, + auto layer = std::make_shared(val["id"], + source, + val.value("minzoom", source->minZoom), + val.value("maxzoom", source->maxZoom), filter, - style, - layer->adaptScaleToScreen, - layer->numDrawPreviousLayers, - layer->maskTiles, - layer->zoomLevelScaleFactor, - layer->renderPassIndex, + renderPassIndex, interactable, - underzoom, - overzoom, - layer->bounds, - layer->coordinateReferenceSystem, - layer->levels); - layers.push_back(newLayer); + style); + layers.push_back(layer); } else if (val["type"] == "line") { std::shared_ptr filter = parser.parseValue(val["filter"]); diff --git a/shared/src/map/layers/tiled/vector/Tiled2dMapVectorRasterSubLayerConfig.h b/shared/src/map/layers/tiled/vector/Tiled2dMapVectorRasterSubLayerConfig.h index 0dca53544..420b017bc 100644 --- a/shared/src/map/layers/tiled/vector/Tiled2dMapVectorRasterSubLayerConfig.h +++ b/shared/src/map/layers/tiled/vector/Tiled2dMapVectorRasterSubLayerConfig.h @@ -24,33 +24,22 @@ class Tiled2dMapVectorRasterSubLayerConfig : public Tiled2dMapVectorLayerConfig Tiled2dMapVectorRasterSubLayerConfig(const std::shared_ptr &layerDescription, const bool is3d, const std::optional &customZoomInfo = std::nullopt) - : Tiled2dMapVectorLayerConfig( - std::make_shared(layerDescription->source, layerDescription->url, layerDescription->sourceMinZoom, - layerDescription->sourceMaxZoom, layerDescription->bounds, - layerDescription->zoomLevelScaleFactor, - layerDescription->adaptScaleToScreen, - layerDescription->numDrawPreviousLayers, - layerDescription->underzoom, - layerDescription->overzoom, - layerDescription->levels), is3d), + : Tiled2dMapVectorLayerConfig(std::static_pointer_cast(layerDescription->source), is3d), description(layerDescription) { if (customZoomInfo.has_value()) { - zoomInfo = Tiled2dMapZoomInfo(customZoomInfo->zoomLevelScaleFactor * description->zoomLevelScaleFactor, - std::max(customZoomInfo->numDrawPreviousLayers, description->numDrawPreviousLayers), - 0, - customZoomInfo->adaptScaleToScreen || description->adaptScaleToScreen, - customZoomInfo->maskTile || description->maskTiles, - customZoomInfo->underzoom && description->underzoom, - customZoomInfo->overzoom && description->overzoom); - } else { - zoomInfo = Tiled2dMapZoomInfo(description->zoomLevelScaleFactor, description->numDrawPreviousLayers, 0, - description->adaptScaleToScreen, description->maskTiles, description->underzoom, - description->overzoom); + // zoomInfo is already initialized in super from the source-description + zoomInfo.zoomLevelScaleFactor *= customZoomInfo->zoomLevelScaleFactor; + zoomInfo.numDrawPreviousLayers = std::max(customZoomInfo->numDrawPreviousLayers, zoomInfo.numDrawPreviousLayers); + zoomInfo.numDrawPreviousOrLaterTLayers = 0; + zoomInfo.adaptScaleToScreen |= customZoomInfo->adaptScaleToScreen; + zoomInfo.maskTile |= customZoomInfo->maskTile; + zoomInfo.underzoom &= customZoomInfo->underzoom; + zoomInfo.overzoom &= customZoomInfo->overzoom; } - if (description->coordinateReferenceSystem == "EPSG:4326") { - customConfig = std::make_shared(layerDescription->source, - layerDescription->url, + if (description->source->coordinateReferenceSystem == "EPSG:4326") { + customConfig = std::make_shared(layerDescription->source->identifier, + layerDescription->source->vectorUrl, zoomInfo, layerDescription->sourceMinZoom, layerDescription->sourceMaxZoom); diff --git a/shared/src/map/layers/tiled/wmts/WmtsTiled2dMapLayerConfig.cpp b/shared/src/map/layers/tiled/wmts/WmtsTiled2dMapLayerConfig.cpp index 4f91171f6..58c750737 100644 --- a/shared/src/map/layers/tiled/wmts/WmtsTiled2dMapLayerConfig.cpp +++ b/shared/src/map/layers/tiled/wmts/WmtsTiled2dMapLayerConfig.cpp @@ -11,7 +11,6 @@ #include "WmtsTiled2dMapLayerConfig.h" #include "Tiled2dMapVectorSettings.h" -#include "Logger.h" #include WmtsTiled2dMapLayerConfig::WmtsTiled2dMapLayerConfig(const WmtsLayerDescription &description, From c10fe1c8839e7d88d90be4091d05fef2b348a32b Mon Sep 17 00:00:00 2001 From: Matthias Frei Date: Tue, 20 Jan 2026 08:22:31 +0100 Subject: [PATCH 2/3] Support "crs" for vector source in style/tile.json Reorganize implementation of layer configs. Combine redundant implementation of default (webmercator), WebMercator again and Epsg4326 cases. For raster sources, enabling EPSG:4326 was already supported before. Now also support EPSG:2056 and EPSG:21871. Support specifying in field "crs" in addition to previously supported "metadata"."crs". --- shared/public/RasterVectorLayerDescription.h | 25 ++- shared/public/Tiled2dMapVectorLayerConfig.h | 154 ++++-------------- shared/public/VectorMapSourceDescription.h | 31 +++- .../coordinates/CoordinateSystemFactory.cpp | 4 +- .../tiled/DefaultTiled2dMapLayerConfigs.cpp | 21 +-- .../tiled/Epsg2056Tiled2dMapLayerConfig.cpp | 83 ++++++++++ .../tiled/Epsg2056Tiled2dMapLayerConfig.h | 32 ++++ .../tiled/Epsg21781Tiled2dMapLayerConfig.cpp | 64 ++++++++ .../tiled/Epsg21781Tiled2dMapLayerConfig.h | 32 ++++ .../tiled/Epsg3857Tiled2dMapLayerConfig.cpp | 62 +++++++ .../tiled/Epsg3857Tiled2dMapLayerConfig.h | 35 ++++ .../tiled/Epsg4326Tiled2dMapLayerConfig.cpp | 95 +++-------- .../tiled/Epsg4326Tiled2dMapLayerConfig.h | 53 ++---- .../tiled/IrregularTiled2dMapLayerConfig.cpp | 72 ++++++++ .../tiled/IrregularTiled2dMapLayerConfig.h | 46 ++++++ .../tiled/RegularTiled2dMapLayerConfig.cpp | 48 ++++++ .../tiled/RegularTiled2dMapLayerConfig.h | 53 ++++++ .../tiled/Tiled2dMapVectorLayerConfig.cpp | 53 ++++++ .../WebMercatorTiled2dMapLayerConfig.cpp | 87 ---------- .../tiled/WebMercatorTiled2dMapLayerConfig.h | 53 ------ .../Tiled2dMapVectorGeoJSONLayerConfig.h | 47 ------ .../tiled/vector/Tiled2dMapVectorLayer.cpp | 31 +++- .../Tiled2dMapVectorLayerParserHelper.cpp | 68 ++++---- .../Tiled2dMapVectorRasterSubLayerConfig.h | 110 ------------- shared/test/TestTileSource.cpp | 26 ++- 25 files changed, 784 insertions(+), 601 deletions(-) create mode 100644 shared/src/map/layers/tiled/Epsg2056Tiled2dMapLayerConfig.cpp create mode 100644 shared/src/map/layers/tiled/Epsg2056Tiled2dMapLayerConfig.h create mode 100644 shared/src/map/layers/tiled/Epsg21781Tiled2dMapLayerConfig.cpp create mode 100644 shared/src/map/layers/tiled/Epsg21781Tiled2dMapLayerConfig.h create mode 100644 shared/src/map/layers/tiled/Epsg3857Tiled2dMapLayerConfig.cpp create mode 100644 shared/src/map/layers/tiled/Epsg3857Tiled2dMapLayerConfig.h create mode 100644 shared/src/map/layers/tiled/IrregularTiled2dMapLayerConfig.cpp create mode 100644 shared/src/map/layers/tiled/IrregularTiled2dMapLayerConfig.h create mode 100644 shared/src/map/layers/tiled/RegularTiled2dMapLayerConfig.cpp create mode 100644 shared/src/map/layers/tiled/RegularTiled2dMapLayerConfig.h create mode 100644 shared/src/map/layers/tiled/Tiled2dMapVectorLayerConfig.cpp delete mode 100644 shared/src/map/layers/tiled/WebMercatorTiled2dMapLayerConfig.cpp delete mode 100644 shared/src/map/layers/tiled/WebMercatorTiled2dMapLayerConfig.h delete mode 100644 shared/src/map/layers/tiled/vector/Tiled2dMapVectorGeoJSONLayerConfig.h delete mode 100644 shared/src/map/layers/tiled/vector/Tiled2dMapVectorRasterSubLayerConfig.h diff --git a/shared/public/RasterVectorLayerDescription.h b/shared/public/RasterVectorLayerDescription.h index a5db49098..f0bee23b8 100644 --- a/shared/public/RasterVectorLayerDescription.h +++ b/shared/public/RasterVectorLayerDescription.h @@ -44,7 +44,7 @@ class RasterVectorStyle { UsedKeysCollection getUsedKeys() const { UsedKeysCollection usedKeys; - std::shared_ptr values[] = { + std::shared_ptr values[] = { rasterOpacityEvaluator.getValue(), rasterBrightnessMinEvaluator.getValue(), rasterBrightnessMaxEvaluator.getValue(), @@ -68,7 +68,7 @@ class RasterVectorStyle { static const BlendMode defaultValue = BlendMode::NORMAL; return blendModeEvaluator.getResult(context, defaultValue).value; } - + RasterShaderStyle getRasterStyle(const EvaluationContext &context) { return { (float) getRasterOpacity(context), @@ -85,17 +85,17 @@ class RasterVectorStyle { double defaultValue = 1.0; return rasterOpacityEvaluator.getResult(context, defaultValue).value; } - + double getRasterBrightnessMin(const EvaluationContext &context) { double defaultValue = 0.0; return rasterBrightnessMinEvaluator.getResult(context, defaultValue).value; } - + double getRasterBrightnessMax(const EvaluationContext &context) { double defaultValue = 1.0; return rasterBrightnessMaxEvaluator.getResult(context, defaultValue).value; } - + double getRasterContrast(const EvaluationContext &context) { double defaultValue = 0.0; return rasterContrastEvaluator.getResult(context, defaultValue).value; @@ -129,7 +129,7 @@ class RasterVectorStyle { struct RasterVectorMapSourceDescription : public VectorMapSourceDescription { bool maskTiles; - + RasterVectorMapSourceDescription(std::string identifier, std::string url, int minZoom, @@ -141,9 +141,16 @@ struct RasterVectorMapSourceDescription : public VectorMapSourceDescription { std::optional underzoom, std::optional overzoom, std::optional> levels, - bool maskTiles) : - VectorMapSourceDescription(identifier, url, minZoom, maxZoom, bounds, zoomLevelScaleFactor, adaptScaleToScreen, numDrawPreviousLayers, underzoom, overzoom, levels), - maskTiles(maskTiles) {} + std::optional coordinateReferenceSystem, + bool maskTiles) + : VectorMapSourceDescription(identifier, url, minZoom, maxZoom, bounds, zoomLevelScaleFactor, adaptScaleToScreen, numDrawPreviousLayers, underzoom, overzoom, levels, coordinateReferenceSystem) + , maskTiles(maskTiles) {} + + virtual Tiled2dMapZoomInfo getZoomInfo(bool is3d) const { + auto zoomInfo = VectorMapSourceDescription::getZoomInfo(is3d); + zoomInfo.maskTile = maskTiles; + return zoomInfo; + } }; class RasterVectorLayerDescription: public VectorLayerDescription { diff --git a/shared/public/Tiled2dMapVectorLayerConfig.h b/shared/public/Tiled2dMapVectorLayerConfig.h index 019b5b43d..c407154bd 100644 --- a/shared/public/Tiled2dMapVectorLayerConfig.h +++ b/shared/public/Tiled2dMapVectorLayerConfig.h @@ -9,145 +9,63 @@ */ #pragma once +#include "RectCoord.h" #include "Tiled2dMapLayerConfig.h" -#include "Tiled2dMapZoomInfo.h" -#include "Tiled2dMapZoomLevelInfo.h" -#include "VectorMapSourceDescription.h" -#include "VectorLayerDescription.h" -#include "CoordinateSystemIdentifiers.h" #include "Tiled2dMapVectorSettings.h" -#include "Logger.h" +#include "Tiled2dMapZoomInfo.h" +#include +#include +#include +/** +* Abstract base class for different layer configurations. +*/ class Tiled2dMapVectorLayerConfig : public Tiled2dMapLayerConfig { public: - Tiled2dMapVectorLayerConfig(const std::shared_ptr &sourceDescription, - const Tiled2dMapZoomInfo &zoomInfo) - : sourceDescription(sourceDescription), zoomInfo(zoomInfo) {} - - Tiled2dMapVectorLayerConfig(const std::shared_ptr &sourceDescription, - const bool is3d) - : sourceDescription(sourceDescription), - zoomInfo(Tiled2dMapZoomInfo( - sourceDescription->zoomLevelScaleFactor ? *sourceDescription->zoomLevelScaleFactor : (is3d ? 0.75 : 1.0), - sourceDescription->numDrawPreviousLayers ? *sourceDescription->numDrawPreviousLayers : 0, - 0, - sourceDescription->adaptScaleToScreen ? *sourceDescription->adaptScaleToScreen : false, - true, - sourceDescription->underzoom ? *sourceDescription->underzoom : false, - sourceDescription->overzoom ? *sourceDescription->overzoom : true)) {} - - ~Tiled2dMapVectorLayerConfig() {} - - int32_t getCoordinateSystemIdentifier() override { - return epsg3857Id; - } - - std::string getTileUrl(int32_t x, int32_t y, int32_t t, int32_t zoom) override { - std::string url = sourceDescription->vectorUrl; - size_t epsg3857Index = url.find("{bbox-epsg-3857}", 0); - if (epsg3857Index != std::string::npos) { - const auto zoomLevelInfos = getDefaultEpsg3857ZoomLevels(zoom, zoom, std::nullopt); - const Tiled2dMapZoomLevelInfo &zoomLevelInfo = zoomLevelInfos.at(0); - RectCoord layerBounds = zoomLevelInfo.bounds; - const double tileWidth = zoomLevelInfo.tileWidthLayerSystemUnits; + virtual double getZoomIdentifier(double zoom) = 0; + virtual double getZoomFactorAtIdentifier(double zoomIdentifier) = 0; - const bool leftToRight = layerBounds.topLeft.x < layerBounds.bottomRight.x; - const bool topToBottom = layerBounds.topLeft.y < layerBounds.bottomRight.y; - const double tileWidthAdj = leftToRight ? tileWidth : -tileWidth; - const double tileHeightAdj = topToBottom ? tileWidth : -tileWidth; - - const double boundsLeft = layerBounds.topLeft.x; - const double boundsTop = layerBounds.topLeft.y; - - const Coord topLeft = Coord(epsg3857Id, x * tileWidthAdj + boundsLeft, y * tileHeightAdj + boundsTop, 0); - const Coord bottomRight = Coord(epsg3857Id, topLeft.x + tileWidthAdj, topLeft.y + tileHeightAdj, 0); - - std::string boxString = std::to_string(topLeft.x) + "," + std::to_string(bottomRight.y) + "," + std::to_string(bottomRight.x) + "," + std::to_string(topLeft.y); - url = url.replace(epsg3857Index, 16, boxString); - return url; +public: + Tiled2dMapVectorLayerConfig( + std::string layerName, + std::string urlFormat, + const std::optional &bounds, + const Tiled2dMapZoomInfo &zoomInfo, + const std::vector &levels + ); - } - size_t zoomIndex = url.find("{z}", 0); - if (zoomIndex == std::string::npos) throw std::invalid_argument("Layer url \'" + url + "\' has no valid format!"); - url = url.replace(zoomIndex, 3, std::to_string(zoom)); - size_t xIndex = url.find("{x}", 0); - if (xIndex == std::string::npos) throw std::invalid_argument("Layer url \'" + url + "\' has no valid format!"); - url = url.replace(xIndex, 3, std::to_string(x)); - size_t yIndex = url.find("{y}", 0); - if (yIndex == std::string::npos) throw std::invalid_argument("Layer url \'" + url + "\' has no valid format!"); - return url.replace(yIndex, 3, std::to_string(y)); - } + virtual ~Tiled2dMapVectorLayerConfig() = default; - std::vector getZoomLevelInfos() override { - return getDefaultEpsg3857ZoomLevels(sourceDescription->minZoom, sourceDescription->maxZoom, sourceDescription->levels); +public: + virtual std::string getLayerName() override { + return layerName; } - std::vector getVirtualZoomLevelInfos() override { - return getDefaultEpsg3857ZoomLevels(0, sourceDescription->minZoom - 1, sourceDescription->levels); - }; + virtual std::string getTileUrl(int32_t x, int32_t y, int32_t t, int32_t zoom) override; - Tiled2dMapZoomInfo getZoomInfo() override { + virtual Tiled2dMapZoomInfo getZoomInfo() override { return zoomInfo; } - std::string getLayerName() override { - return sourceDescription->identifier; + std::optional<::RectCoord> getBounds() override { + return bounds; } - std::optional getVectorSettings() override { + virtual std::optional getVectorSettings() override { return std::nullopt; } - virtual double getZoomIdentifier(double zoom) { - return std::max(0.0, std::round(log(baseValueZoom * zoomInfo.zoomLevelScaleFactor / zoom) / log(2) * 100) / 100); - } - - virtual double getZoomFactorAtIdentifier(double zoomIdentifier) { - double factor = pow(2, zoomIdentifier); - return baseValueZoom * zoomInfo.zoomLevelScaleFactor / factor; - } - - std::optional<::RectCoord> getBounds() override { - if (sourceDescription) { - return sourceDescription->bounds; - } - else { - return std::nullopt; - } - } - - static std::vector getDefaultEpsg3857ZoomLevels(int minZoom, int maxZoom, const std::optional> &levels) { - std::vector infos; - if (levels.has_value()) { - for (const auto &level : levels.value()) { - double factor = pow(2, level); - double zoom = baseValueZoom / factor; - double width = baseValueWidth / factor; - infos.push_back(Tiled2dMapZoomLevelInfo(zoom, width, factor, factor, 1, level, epsg3857Bounds)); - } - } else { - for (int i = minZoom; i <= maxZoom; i++) { - double factor = pow(2, i); - double zoom = baseValueZoom / factor; - double width = baseValueWidth / factor; - infos.push_back(Tiled2dMapZoomLevelInfo(zoom, width, factor, factor, 1, i, epsg3857Bounds)); - } - } - return infos; - } +public: + // Helper to initialize `zoomInfo` + static Tiled2dMapZoomInfo defaultMapZoomInfo(); + // Helper to initalize `levels` from min/max zoom levels value + static std::vector generateLevelsFromMinMax(int minZoomLevel, int maxZoomLevel); protected: - std::shared_ptr sourceDescription; + std::string layerName; + std::string urlFormat; Tiled2dMapZoomInfo zoomInfo; - - static constexpr double baseValueZoom = 500000000.0; - static constexpr double baseValueWidth = 40075016.0; - static const inline int32_t epsg3857Id = CoordinateSystemIdentifiers::EPSG3857(); - static const inline RectCoord epsg3857Bounds = RectCoord( - Coord(epsg3857Id, -20037508.34, 20037508.34, 0.0), - Coord(epsg3857Id, 20037508.34, -20037508.34, 0.0) - ); - - + std::optional bounds; + std::vector levels; // zoom level indices (kept sorted ascending) }; diff --git a/shared/public/VectorMapSourceDescription.h b/shared/public/VectorMapSourceDescription.h index 251d953e7..3aaf59e05 100644 --- a/shared/public/VectorMapSourceDescription.h +++ b/shared/public/VectorMapSourceDescription.h @@ -10,9 +10,11 @@ #pragma once -#include "VectorLayerDescription.h" #include "GeoJsonTypes.h" #include "RectCoord.h" +#include "Tiled2dMapVectorLayerConfig.h" +#include "Tiled2dMapZoomInfo.h" +#include "VectorLayerDescription.h" class VectorMapSourceDescription { public: @@ -27,7 +29,7 @@ class VectorMapSourceDescription { std::optional underzoom; std::optional overzoom; std::optional> levels; - std::optional coordinateReferenceSystem; + std::optional coordinateReferenceSystem; VectorMapSourceDescription(std::string identifier, std::string vectorUrl, @@ -39,10 +41,31 @@ class VectorMapSourceDescription { std::optional numDrawPreviousLayers, std::optional underzoom, std::optional overzoom, - std::optional> levels) : + std::optional> levels, + std::optional coordinateReferenceSystem) : identifier(identifier), vectorUrl(vectorUrl), minZoom(minZoom), maxZoom(maxZoom), bounds(bounds), adaptScaleToScreen(adaptScaleToScreen), numDrawPreviousLayers(numDrawPreviousLayers), - zoomLevelScaleFactor(zoomLevelScaleFactor), underzoom(underzoom), overzoom(overzoom), levels(levels) {} + zoomLevelScaleFactor(zoomLevelScaleFactor), underzoom(underzoom), overzoom(overzoom), levels(levels), + coordinateReferenceSystem(coordinateReferenceSystem) {} + + virtual Tiled2dMapZoomInfo getZoomInfo(bool is3d) const { + return Tiled2dMapZoomInfo( + zoomLevelScaleFactor ? *zoomLevelScaleFactor : (is3d ? 0.75 : 1.0), + numDrawPreviousLayers ? *numDrawPreviousLayers : 0, + 0, + adaptScaleToScreen ? *adaptScaleToScreen : false, + true, + underzoom ? *underzoom : false, + overzoom ? *overzoom : true); + }; + + std::vector getZoomLevels() { + if(levels) { + return *levels; + } else { + return Tiled2dMapVectorLayerConfig::generateLevelsFromMinMax(minZoom, maxZoom); + } + } }; struct SpriteSourceDescription { diff --git a/shared/src/map/coordinates/CoordinateSystemFactory.cpp b/shared/src/map/coordinates/CoordinateSystemFactory.cpp index 40f26e92a..51a965698 100644 --- a/shared/src/map/coordinates/CoordinateSystemFactory.cpp +++ b/shared/src/map/coordinates/CoordinateSystemFactory.cpp @@ -17,8 +17,8 @@ ::MapCoordinateSystem CoordinateSystemFactory::getEpsg2056System() { return MapCoordinateSystem(CoordinateSystemIdentifiers::EPSG2056(), - RectCoord(Coord(CoordinateSystemIdentifiers::EPSG2056(), 2485000.0, 1300000.0, 0), - Coord(CoordinateSystemIdentifiers::EPSG2056(), 2840000.0, 1070000.0, 0)), + RectCoord(Coord(CoordinateSystemIdentifiers::EPSG2056(), 2420000.0, 1350000.0, 0), + Coord(CoordinateSystemIdentifiers::EPSG2056(), 2900000.0, 1030000.0, 0)), 1.0); } diff --git a/shared/src/map/layers/tiled/DefaultTiled2dMapLayerConfigs.cpp b/shared/src/map/layers/tiled/DefaultTiled2dMapLayerConfigs.cpp index 3a2df1559..7bbda7f3b 100644 --- a/shared/src/map/layers/tiled/DefaultTiled2dMapLayerConfigs.cpp +++ b/shared/src/map/layers/tiled/DefaultTiled2dMapLayerConfigs.cpp @@ -5,24 +5,21 @@ // Created by Nicolas Märki on 13.02.2024. // -#ifndef DefaultTiled2dMapLayerConfigs_h -#define DefaultTiled2dMapLayerConfigs_h - #include "DefaultTiled2dMapLayerConfigs.h" -#include "WebMercatorTiled2dMapLayerConfig.h" +#include "Epsg3857Tiled2dMapLayerConfig.h" #include "Epsg4326Tiled2dMapLayerConfig.h" std::shared_ptr DefaultTiled2dMapLayerConfigs::webMercator(const std::string & layerName, const std::string & urlFormat) { - return std::make_shared(layerName, urlFormat); + return std::make_shared(layerName, urlFormat); } std::shared_ptr DefaultTiled2dMapLayerConfigs::webMercatorCustom(const std::string &layerName, const std::string &urlFormat, const std::optional & zoomInfo, int32_t minZoomLevel, int32_t maxZoomLevel) { - if (zoomInfo.has_value()) { - return std::make_shared(layerName, urlFormat, zoomInfo.value(), minZoomLevel, maxZoomLevel); - } - return std::make_shared(layerName, urlFormat, minZoomLevel, maxZoomLevel); + return std::make_shared( + layerName, urlFormat, std::nullopt, + zoomInfo.value_or(Tiled2dMapVectorLayerConfig::defaultMapZoomInfo()), + Tiled2dMapVectorLayerConfig::generateLevelsFromMinMax(minZoomLevel, maxZoomLevel)); } std::shared_ptr @@ -33,7 +30,7 @@ DefaultTiled2dMapLayerConfigs::epsg4326(const std::string &layerName, const std: std::shared_ptr DefaultTiled2dMapLayerConfigs::epsg4326Custom(const std::string &layerName, const std::string &urlFormat, const Tiled2dMapZoomInfo &zoomInfo, int32_t minZoomLevel, int32_t maxZoomLevel) { - return std::make_shared(layerName, urlFormat, zoomInfo, minZoomLevel, maxZoomLevel); + return std::make_shared( + layerName, urlFormat, std::nullopt, zoomInfo, + Tiled2dMapVectorLayerConfig::generateLevelsFromMinMax(minZoomLevel, maxZoomLevel)); } - -#endif /* DefaultTiled2dMapLayerConfigs_h */ diff --git a/shared/src/map/layers/tiled/Epsg2056Tiled2dMapLayerConfig.cpp b/shared/src/map/layers/tiled/Epsg2056Tiled2dMapLayerConfig.cpp new file mode 100644 index 000000000..d858c15ba --- /dev/null +++ b/shared/src/map/layers/tiled/Epsg2056Tiled2dMapLayerConfig.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2026 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +#include "RectCoord.h" +#include "CoordinateSystemIdentifiers.h" +#include "Epsg2056Tiled2dMapLayerConfig.h" + +Epsg2056Tiled2dMapLayerConfig::Epsg2056Tiled2dMapLayerConfig( + std::string layerName, + std::string urlFormat) + : IrregularTiled2dMapLayerConfig( + layerName, urlFormat, std::nullopt, + defaultMapZoomInfo(), + generateLevelsFromMinMax(0, 28), + swisstopoZoomLevelInfos()) +{} + +Epsg2056Tiled2dMapLayerConfig::Epsg2056Tiled2dMapLayerConfig( + std::string layerName, + std::string urlFormat, + const std::optional &bounds, + const Tiled2dMapZoomInfo &zoomInfo, + const std::vector &levels) + : IrregularTiled2dMapLayerConfig( + layerName, urlFormat, bounds, zoomInfo, levels, swisstopoZoomLevelInfos()) +{} + +static Tiled2dMapZoomLevelInfo makeZoomLevelInfo( + const Coord &topLeft, double zoom, float tileWidthLayerSystemUnits, + int32_t numTilesX, int32_t numTilesY, int32_t numTilesT, int32_t zoomLevelIdentifier) +{ + const auto bounds = RectCoord( + topLeft, + Coord(topLeft.systemIdentifier, + topLeft.x + tileWidthLayerSystemUnits * numTilesX, + topLeft.y - tileWidthLayerSystemUnits * numTilesY, 0)); + + return Tiled2dMapZoomLevelInfo(zoom, tileWidthLayerSystemUnits, numTilesX, + numTilesY, numTilesT, zoomLevelIdentifier, + bounds); +} + +std::vector Epsg2056Tiled2dMapLayerConfig::swisstopoZoomLevelInfos() { + const Coord topLeft{CoordinateSystemIdentifiers::EPSG2056(), 2420000.0, 1350000.0, 0.f}; + return { + makeZoomLevelInfo(topLeft, 14285714.2857, 1024000, 1, 1, 1, 0), + makeZoomLevelInfo(topLeft, 13392857.1429, 960000, 1, 1, 1, 1), + makeZoomLevelInfo(topLeft, 12500000.0, 896000, 1, 1, 1, 2), + makeZoomLevelInfo(topLeft, 11607142.8571, 832000, 1, 1, 1, 3), + makeZoomLevelInfo(topLeft, 10714285.7143, 768000, 1, 1, 1, 4), + makeZoomLevelInfo(topLeft, 9821428.57143, 704000, 1, 1, 1, 5), + makeZoomLevelInfo(topLeft, 8928571.42857, 640000, 1, 1, 1, 6), + makeZoomLevelInfo(topLeft, 8035714.28571, 576000, 1, 1, 1, 7), + makeZoomLevelInfo(topLeft, 7142857.14286, 512000, 1, 1, 1, 8), + makeZoomLevelInfo(topLeft, 6250000.0, 448000, 2, 1, 1, 9), + makeZoomLevelInfo(topLeft, 5357142.85714, 384000, 2, 1, 1, 10), + makeZoomLevelInfo(topLeft, 4464285.71429, 320000, 2, 1, 1, 11), + makeZoomLevelInfo(topLeft, 3571428.57143, 256000, 2, 2, 1, 12), + makeZoomLevelInfo(topLeft, 2678571.42857, 192000, 3, 2, 1, 13), + makeZoomLevelInfo(topLeft, 2321428.57143, 166400, 3, 2, 1, 14), + makeZoomLevelInfo(topLeft, 1785714.28571, 128000, 4, 3, 1, 15), + makeZoomLevelInfo(topLeft, 892857.142857, 64000, 8, 5, 1, 16), + makeZoomLevelInfo(topLeft, 357142.857143, 25600, 19, 13, 1, 17), + makeZoomLevelInfo(topLeft, 178571.428571, 12800, 38, 25, 1, 18), + makeZoomLevelInfo(topLeft, 71428.5714286, 5120, 94, 63, 1, 19), + makeZoomLevelInfo(topLeft, 35714.2857143, 2560, 188, 125, 1, 20), + makeZoomLevelInfo(topLeft, 17857.1428571, 1280, 375, 250, 1, 21), + makeZoomLevelInfo(topLeft, 8928.57142857, 640, 750, 500, 1, 22), + makeZoomLevelInfo(topLeft, 7142.85714286, 512, 938, 625, 1, 23), + makeZoomLevelInfo(topLeft, 5357.14285714, 384, 1250, 834, 1, 24), + makeZoomLevelInfo(topLeft, 3571.42857143, 256, 1875, 1250, 1, 25), + makeZoomLevelInfo(topLeft, 1785.71428571, 128, 3750, 2500, 1, 26), + makeZoomLevelInfo(topLeft, 892.857142857, 64, 7500, 5000, 1, 27), + makeZoomLevelInfo(topLeft, 357.142857143, 25.6, 18750, 12500, 1, 28) + }; +} diff --git a/shared/src/map/layers/tiled/Epsg2056Tiled2dMapLayerConfig.h b/shared/src/map/layers/tiled/Epsg2056Tiled2dMapLayerConfig.h new file mode 100644 index 000000000..15b8a7f47 --- /dev/null +++ b/shared/src/map/layers/tiled/Epsg2056Tiled2dMapLayerConfig.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +#pragma once + +#include "IrregularTiled2dMapLayerConfig.h" +#include "Tiled2dMapZoomInfo.h" + +class Epsg2056Tiled2dMapLayerConfig : public IrregularTiled2dMapLayerConfig { +public: + Epsg2056Tiled2dMapLayerConfig( + std::string layerName, + std::string urlFormat + ); + + Epsg2056Tiled2dMapLayerConfig( + std::string layerName, + std::string urlFormat, + const std::optional &bounds, + const Tiled2dMapZoomInfo &zoomInfo, + const std::vector &levels + ); + + static std::vector swisstopoZoomLevelInfos(); +}; diff --git a/shared/src/map/layers/tiled/Epsg21781Tiled2dMapLayerConfig.cpp b/shared/src/map/layers/tiled/Epsg21781Tiled2dMapLayerConfig.cpp new file mode 100644 index 000000000..1f28264bd --- /dev/null +++ b/shared/src/map/layers/tiled/Epsg21781Tiled2dMapLayerConfig.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2026 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +#include "Epsg21781Tiled2dMapLayerConfig.h" +#include "CoordinateSystemIdentifiers.h" +#include "Epsg2056Tiled2dMapLayerConfig.h" +#include "RectCoord.h" + +Epsg21781Tiled2dMapLayerConfig::Epsg21781Tiled2dMapLayerConfig( + std::string layerName, + std::string urlFormat) + : IrregularTiled2dMapLayerConfig( + layerName, urlFormat, std::nullopt, + defaultMapZoomInfo(), + generateLevelsFromMinMax(0, 28), + swisstopoZoomLevelInfos()) +{} + +Epsg21781Tiled2dMapLayerConfig::Epsg21781Tiled2dMapLayerConfig( + std::string layerName, + std::string urlFormat, + const std::optional &bounds, + const Tiled2dMapZoomInfo &zoomInfo, + const std::vector &levels) + : IrregularTiled2dMapLayerConfig( + layerName, urlFormat, bounds, zoomInfo, levels, swisstopoZoomLevelInfos()) +{} + +static Tiled2dMapZoomLevelInfo makeZoomLevelInfo( + const Coord &topLeft, double zoom, float tileWidthLayerSystemUnits, + int32_t numTilesX, int32_t numTilesY, int32_t numTilesT, int32_t zoomLevelIdentifier) +{ + const auto bounds = RectCoord( + topLeft, + Coord(topLeft.systemIdentifier, + topLeft.x + tileWidthLayerSystemUnits * numTilesX, + topLeft.y - tileWidthLayerSystemUnits * numTilesY, 0)); + + return Tiled2dMapZoomLevelInfo(zoom, tileWidthLayerSystemUnits, numTilesX, + numTilesY, numTilesT, zoomLevelIdentifier, + bounds); +} + +std::vector Epsg21781Tiled2dMapLayerConfig::swisstopoZoomLevelInfos() { + // same zoom level pyramid as for 2056, just different coordinate origin. + const Coord topLeft{CoordinateSystemIdentifiers::EPSG21781(), 420000.0, 350000.0, 0.}; + + std::vector result; + const auto epsg2056ZoomLevelInfos = Epsg2056Tiled2dMapLayerConfig::swisstopoZoomLevelInfos(); + for(const auto &other : epsg2056ZoomLevelInfos) { + result.push_back(makeZoomLevelInfo( + topLeft, other.zoom, other.tileWidthLayerSystemUnits, + other.numTilesX, other.numTilesY, other.numTilesT, + other.zoomLevelIdentifier)); + } + return result; +} diff --git a/shared/src/map/layers/tiled/Epsg21781Tiled2dMapLayerConfig.h b/shared/src/map/layers/tiled/Epsg21781Tiled2dMapLayerConfig.h new file mode 100644 index 000000000..3c80627e0 --- /dev/null +++ b/shared/src/map/layers/tiled/Epsg21781Tiled2dMapLayerConfig.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +#pragma once + +#include "IrregularTiled2dMapLayerConfig.h" +#include "Tiled2dMapZoomInfo.h" + +class Epsg21781Tiled2dMapLayerConfig : public IrregularTiled2dMapLayerConfig { +public: + Epsg21781Tiled2dMapLayerConfig( + std::string layerName, + std::string urlFormat + ); + + Epsg21781Tiled2dMapLayerConfig( + std::string layerName, + std::string urlFormat, + const std::optional &bounds, + const Tiled2dMapZoomInfo &zoomInfo, + const std::vector &levels + ); + + static std::vector swisstopoZoomLevelInfos(); +}; diff --git a/shared/src/map/layers/tiled/Epsg3857Tiled2dMapLayerConfig.cpp b/shared/src/map/layers/tiled/Epsg3857Tiled2dMapLayerConfig.cpp new file mode 100644 index 000000000..4656a4470 --- /dev/null +++ b/shared/src/map/layers/tiled/Epsg3857Tiled2dMapLayerConfig.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2026 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +#include "Epsg3857Tiled2dMapLayerConfig.h" +#include "CoordinateSystemFactory.h" + +const double Epsg3857Tiled2dMapLayerConfig::BASE_ZOOM = 559082264.029; + +Epsg3857Tiled2dMapLayerConfig::Epsg3857Tiled2dMapLayerConfig( + std::string layerName, + std::string urlFormat) + : RegularTiled2dMapLayerConfig( + layerName, urlFormat, std::nullopt, + defaultMapZoomInfo(), + generateLevelsFromMinMax(0, 20), + CoordinateSystemFactory::getEpsg3857System(), + BASE_ZOOM) +{} + +Epsg3857Tiled2dMapLayerConfig::Epsg3857Tiled2dMapLayerConfig( + std::string layerName, + std::string urlFormat, + const std::optional &bounds, + const Tiled2dMapZoomInfo &zoomInfo, + const std::vector &levels) + : RegularTiled2dMapLayerConfig( + layerName, urlFormat, bounds, zoomInfo, levels, + CoordinateSystemFactory::getEpsg3857System(), BASE_ZOOM) +{} + +std::string Epsg3857Tiled2dMapLayerConfig::getTileUrl(int32_t x, int32_t y, int32_t t, int32_t zoom) { + std::string url = urlFormat; + size_t epsg3857Index = url.find("{bbox-epsg-3857}", 0); + if (epsg3857Index != std::string::npos) { + const auto &zoomLevelInfo = getRegularZoomLevelInfo(zoom); + RectCoord levelBounds = zoomLevelInfo.bounds; + const double tileWidth = zoomLevelInfo.tileWidthLayerSystemUnits; + + const bool leftToRight = levelBounds.topLeft.x < levelBounds.bottomRight.x; + const bool topToBottom = levelBounds.topLeft.y < levelBounds.bottomRight.y; + const double tileWidthAdj = leftToRight ? tileWidth : -tileWidth; + const double tileHeightAdj = topToBottom ? tileWidth : -tileWidth; + + const double boundsLeft = levelBounds.topLeft.x; + const double boundsTop = levelBounds.topLeft.y; + + const Coord topLeft = Coord(coordinateSystem.identifier, x * tileWidthAdj + boundsLeft, y * tileHeightAdj + boundsTop, 0); + const Coord bottomRight = Coord(coordinateSystem.identifier, topLeft.x + tileWidthAdj, topLeft.y + tileHeightAdj, 0); + + std::string boxString = std::to_string(topLeft.x) + "," + std::to_string(bottomRight.y) + "," + std::to_string(bottomRight.x) + "," + std::to_string(topLeft.y); + url = url.replace(epsg3857Index, 16, boxString); + return url; + } + return RegularTiled2dMapLayerConfig::getTileUrl(x, y, t, zoom); +} diff --git a/shared/src/map/layers/tiled/Epsg3857Tiled2dMapLayerConfig.h b/shared/src/map/layers/tiled/Epsg3857Tiled2dMapLayerConfig.h new file mode 100644 index 000000000..68b7dd7ff --- /dev/null +++ b/shared/src/map/layers/tiled/Epsg3857Tiled2dMapLayerConfig.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2026 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +#pragma once + +#include "RegularTiled2dMapLayerConfig.h" +#include "Tiled2dMapZoomInfo.h" + +class Epsg3857Tiled2dMapLayerConfig : public RegularTiled2dMapLayerConfig { +public: + Epsg3857Tiled2dMapLayerConfig( + std::string layerName, + std::string urlFormat + ); + + Epsg3857Tiled2dMapLayerConfig( + std::string layerName, + std::string urlFormat, + const std::optional &bounds, + const Tiled2dMapZoomInfo &zoomInfo, + const std::vector &levels + ); + + virtual std::string getTileUrl(int32_t x, int32_t y, int32_t t, int32_t zoom) override; + +private: + const static double BASE_ZOOM; +}; diff --git a/shared/src/map/layers/tiled/Epsg4326Tiled2dMapLayerConfig.cpp b/shared/src/map/layers/tiled/Epsg4326Tiled2dMapLayerConfig.cpp index 20781a0e6..125a3604e 100644 --- a/shared/src/map/layers/tiled/Epsg4326Tiled2dMapLayerConfig.cpp +++ b/shared/src/map/layers/tiled/Epsg4326Tiled2dMapLayerConfig.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Ubique Innovation AG + * Copyright (c) 2026 Ubique Innovation AG * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -9,77 +9,28 @@ */ #include "Epsg4326Tiled2dMapLayerConfig.h" -#include "Tiled2dMapVectorSettings.h" -#include "CoordinateSystemIdentifiers.h" -#include -#include +#include "CoordinateSystemFactory.h" -const RectCoord Epsg4326Tiled2dMapLayerConfig::EPSG_4326_BOUNDS = RectCoord(Coord(CoordinateSystemIdentifiers::EPSG4326(), -180.0, 90.0, 0.0), - Coord(CoordinateSystemIdentifiers::EPSG4326(), 180.0, -90.0, 0.0)); const double Epsg4326Tiled2dMapLayerConfig::BASE_ZOOM = 500000000.0; -const double Epsg4326Tiled2dMapLayerConfig::BASE_WIDTH = 360.0; -Epsg4326Tiled2dMapLayerConfig::Epsg4326Tiled2dMapLayerConfig(std::string layerName, std::string urlFormat) - : layerName(layerName), urlFormat(urlFormat) - {} - -Epsg4326Tiled2dMapLayerConfig::Epsg4326Tiled2dMapLayerConfig(std::string layerName, std::string urlFormat, - const Tiled2dMapZoomInfo &zoomInfo, int32_t minZoomLevel, - int32_t maxZoomLevel) - : layerName(layerName), urlFormat(urlFormat), zoomInfo(zoomInfo), minZoomLevel(minZoomLevel), maxZoomLevel(maxZoomLevel) {} - -int32_t Epsg4326Tiled2dMapLayerConfig::getCoordinateSystemIdentifier() { return CoordinateSystemIdentifiers::EPSG4326(); } - -std::string Epsg4326Tiled2dMapLayerConfig::getTileUrl(int32_t x, int32_t y, int32_t t, int32_t zoom) { - std::string url = urlFormat; - size_t zoomIndex = url.find("{z}", 0); - if (zoomIndex == std::string::npos) - throw std::invalid_argument("Layer url \'" + url + "\' has no valid format!"); - url = url.replace(zoomIndex, 3, std::to_string(zoom)); - size_t xIndex = url.find("{x}", 0); - if (xIndex == std::string::npos) - throw std::invalid_argument("Layer url \'" + url + "\' has no valid format!"); - url = url.replace(xIndex, 3, std::to_string(x)); - size_t yIndex = url.find("{y}", 0); - if (yIndex == std::string::npos) - throw std::invalid_argument("Layer url \'" + url + "\' has no valid format!"); - return url.replace(yIndex, 3, std::to_string(y)); -} - -std::string Epsg4326Tiled2dMapLayerConfig::getLayerName() { return layerName; } - -std::vector Epsg4326Tiled2dMapLayerConfig::getZoomLevelInfos() { - std::vector levels; - levels.reserve(maxZoomLevel - minZoomLevel + 1); - for (int32_t i = minZoomLevel; i <= maxZoomLevel; ++i) { - levels.emplace_back(getZoomLevelInfo(i)); - } - return levels; -} - -std::vector Epsg4326Tiled2dMapLayerConfig::getVirtualZoomLevelInfos() { - std::vector levels; - levels.reserve(minZoomLevel); - for (int32_t i = 0; i < minZoomLevel; ++i) { - levels.emplace_back(getZoomLevelInfo(i)); - } - return levels; -} - -Tiled2dMapZoomInfo Epsg4326Tiled2dMapLayerConfig::getZoomInfo() { - return zoomInfo; -} - -std::optional Epsg4326Tiled2dMapLayerConfig::getVectorSettings() { - return std::nullopt; -} - -std::optional<::RectCoord> Epsg4326Tiled2dMapLayerConfig::getBounds() { - return EPSG_4326_BOUNDS; -} - -Tiled2dMapZoomLevelInfo Epsg4326Tiled2dMapLayerConfig::getZoomLevelInfo(int32_t zoomLevel) { - double tileCount = std::pow(2.0,zoomLevel); - double zoom = BASE_ZOOM / tileCount; - return {zoom, (float) (BASE_WIDTH / tileCount), (int32_t) tileCount, (int32_t) tileCount, 1, zoomLevel, EPSG_4326_BOUNDS}; -} +Epsg4326Tiled2dMapLayerConfig::Epsg4326Tiled2dMapLayerConfig( + std::string layerName, + std::string urlFormat) + : RegularTiled2dMapLayerConfig( + layerName, urlFormat, std::nullopt, + defaultMapZoomInfo(), + generateLevelsFromMinMax(0, 20), + CoordinateSystemFactory::getEpsg4326System(), + BASE_ZOOM) +{} + +Epsg4326Tiled2dMapLayerConfig::Epsg4326Tiled2dMapLayerConfig( + std::string layerName, + std::string urlFormat, + const std::optional &bounds, + const Tiled2dMapZoomInfo &zoomInfo, + const std::vector &levels) + : RegularTiled2dMapLayerConfig( + layerName, urlFormat, bounds, zoomInfo, levels, + CoordinateSystemFactory::getEpsg4326System(), BASE_ZOOM) +{} diff --git a/shared/src/map/layers/tiled/Epsg4326Tiled2dMapLayerConfig.h b/shared/src/map/layers/tiled/Epsg4326Tiled2dMapLayerConfig.h index c9b2ed3d0..99e2e25b7 100644 --- a/shared/src/map/layers/tiled/Epsg4326Tiled2dMapLayerConfig.h +++ b/shared/src/map/layers/tiled/Epsg4326Tiled2dMapLayerConfig.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Ubique Innovation AG + * Copyright (c) 2026 Ubique Innovation AG * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -10,44 +10,23 @@ #pragma once -#include "Tiled2dMapLayerConfig.h" +#include "RegularTiled2dMapLayerConfig.h" #include "Tiled2dMapZoomInfo.h" -#include "Tiled2dMapZoomLevelInfo.h" -#include "WmtsLayerDescription.h" - -class Epsg4326Tiled2dMapLayerConfig : public Tiled2dMapLayerConfig { - public: - Epsg4326Tiled2dMapLayerConfig(std::string layerName, std::string urlFormat); - - Epsg4326Tiled2dMapLayerConfig(std::string layerName, std::string urlFormat, const Tiled2dMapZoomInfo &zoomInfo, int32_t minZoomLevel, int32_t maxZoomLevel); - - virtual int32_t getCoordinateSystemIdentifier() override; - - virtual std::string getTileUrl(int32_t x, int32_t y, int32_t t, int32_t zoom) override; - - virtual std::string getLayerName() override; - - virtual std::vector getZoomLevelInfos() override; - - virtual std::vector getVirtualZoomLevelInfos() override; - - virtual Tiled2dMapZoomInfo getZoomInfo() override; - - virtual std::optional getVectorSettings() override; - - std::optional<::RectCoord> getBounds() override; +class Epsg4326Tiled2dMapLayerConfig : public RegularTiled2dMapLayerConfig { +public: + Epsg4326Tiled2dMapLayerConfig( + std::string layerName, + std::string urlFormat + ); + + Epsg4326Tiled2dMapLayerConfig( + std::string layerName, + std::string urlFormat, + const std::optional &bounds, + const Tiled2dMapZoomInfo &zoomInfo, + const std::vector &levels + ); private: - Tiled2dMapZoomLevelInfo getZoomLevelInfo(int32_t zoomLevel); - - const static RectCoord EPSG_4326_BOUNDS; const static double BASE_ZOOM; - const static double BASE_WIDTH; - - std::string layerName; - std::string urlFormat; - - int32_t minZoomLevel = 0; - int32_t maxZoomLevel = 20; - Tiled2dMapZoomInfo zoomInfo = Tiled2dMapZoomInfo(1.0, 0, 0, true, false, true, true); }; diff --git a/shared/src/map/layers/tiled/IrregularTiled2dMapLayerConfig.cpp b/shared/src/map/layers/tiled/IrregularTiled2dMapLayerConfig.cpp new file mode 100644 index 000000000..bab210de1 --- /dev/null +++ b/shared/src/map/layers/tiled/IrregularTiled2dMapLayerConfig.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2026 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +#include "IrregularTiled2dMapLayerConfig.h" + +std::vector IrregularTiled2dMapLayerConfig::getZoomLevelInfos() { + std::vector result; + result.reserve(levels.size()); + for(int level : levels) { + result.push_back(zoomLevelInfos[level]); + } + return result; +} + +std::vector IrregularTiled2dMapLayerConfig::getVirtualZoomLevelInfos() { + const int minZoomLevel = levels.front(); + std::vector result(zoomLevelInfos.begin(), zoomLevelInfos.begin() + minZoomLevel); + return result; + }; + +double IrregularTiled2dMapLayerConfig::getZoomIdentifier(double zoom) { + const int minZoomLevel = levels.front(); + const int maxZoomLevel = levels.back(); + + Tiled2dMapZoomLevelInfo prevZoom = zoomLevelInfos.back(); + + for (auto it = zoomLevelInfos.rbegin() + maxZoomLevel; it != zoomLevelInfos.rend() + minZoomLevel; ++it) { + if ((*it).zoom <= zoom) { + prevZoom = *it; + } else { + // Linear Interpolation + auto y0 = prevZoom.zoomLevelIdentifier; + auto x0 = prevZoom.zoom; + auto y1 = (*it).zoomLevelIdentifier; + auto x1 = (*it).zoom; + + return y0 + (zoom - x0) * (y1 - y0) / (x1 - x0); + } + } + + return prevZoom.zoomLevelIdentifier; +} + +double IrregularTiled2dMapLayerConfig::getZoomFactorAtIdentifier(double zoomIdentifier) { + const int minZoomLevel = levels.front(); + const int maxZoomLevel = levels.back(); + + Tiled2dMapZoomLevelInfo prevZoom = zoomLevelInfos.back(); + + for (auto it = zoomLevelInfos.begin() + minZoomLevel; it != zoomLevelInfos.end() + maxZoomLevel; ++it) { + if ((*it).zoomLevelIdentifier <= zoomIdentifier) { + prevZoom = *it; + } else { + // Linear Interpolation + auto y0 = prevZoom.zoom; + auto x0 = prevZoom.zoomLevelIdentifier; + auto y1 = (*it).zoom; + auto x1 = (*it).zoomLevelIdentifier; + + return y0 + (zoomIdentifier - x0) * (y1 - y0) / (x1 - x0); + } + } + + return prevZoom.zoom; +} diff --git a/shared/src/map/layers/tiled/IrregularTiled2dMapLayerConfig.h b/shared/src/map/layers/tiled/IrregularTiled2dMapLayerConfig.h new file mode 100644 index 000000000..2b3cc706f --- /dev/null +++ b/shared/src/map/layers/tiled/IrregularTiled2dMapLayerConfig.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2026 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ +#pragma once + +#include "Tiled2dMapVectorLayerConfig.h" +#include "Tiled2dMapZoomLevelInfo.h" + +/** Base class for Tiled2dMapVectorLayerConfig implementations for _irregular_ + * tile pyramids, i.e. for tile systems that do not follow a simple power-of-2 + * relation between zoom levels, but are defined with a table of fixed zoom + * levels. + */ +class IrregularTiled2dMapLayerConfig : public Tiled2dMapVectorLayerConfig { +public: + IrregularTiled2dMapLayerConfig( + std::string layerName, + std::string urlFormat, + const std::optional &bounds, + const Tiled2dMapZoomInfo &zoomInfo, + const std::vector &levels, + const std::vector &zoomLevelInfos + ) + : Tiled2dMapVectorLayerConfig(layerName, urlFormat, bounds, zoomInfo, levels) + , zoomLevelInfos(zoomLevelInfos) + {} + + virtual int32_t getCoordinateSystemIdentifier() override { + return zoomLevelInfos.front().bounds.topLeft.systemIdentifier; + } + + virtual std::vector getZoomLevelInfos() override; + virtual std::vector getVirtualZoomLevelInfos() override; + + virtual double getZoomIdentifier(double zoom) override; + virtual double getZoomFactorAtIdentifier(double zoomIdentifier) override; + +protected: + std::vector zoomLevelInfos; +}; diff --git a/shared/src/map/layers/tiled/RegularTiled2dMapLayerConfig.cpp b/shared/src/map/layers/tiled/RegularTiled2dMapLayerConfig.cpp new file mode 100644 index 000000000..a3d17563d --- /dev/null +++ b/shared/src/map/layers/tiled/RegularTiled2dMapLayerConfig.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2026 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +#include "RegularTiled2dMapLayerConfig.h" +#include + +std::vector RegularTiled2dMapLayerConfig::getZoomLevelInfos() { + std::vector result; + result.reserve(levels.size()); + for(int level : levels) { + result.push_back(getRegularZoomLevelInfo(level)); + } + return result; +} + +std::vector RegularTiled2dMapLayerConfig::getVirtualZoomLevelInfos() { + int minZoomLevel = levels.front(); + std::vector result; + result.reserve(minZoomLevel); + for(int level = 0; level < minZoomLevel; level++) { + result.push_back(getRegularZoomLevelInfo(level)); + } + return result; +}; + +double RegularTiled2dMapLayerConfig::getZoomIdentifier(double zoom) { + return std::max(0.0, std::round(log(baseZoom * zoomInfo.zoomLevelScaleFactor / zoom) / log(2) * 100) / 100); +} + +double RegularTiled2dMapLayerConfig::getZoomFactorAtIdentifier(double zoomIdentifier) { + double factor = pow(2, zoomIdentifier); + return baseZoom * zoomInfo.zoomLevelScaleFactor / factor; +} + +Tiled2dMapZoomLevelInfo RegularTiled2dMapLayerConfig::getRegularZoomLevelInfo(int zoomLevel) { + const double baseValueWidth = (coordinateSystem.bounds.bottomRight.x - coordinateSystem.bounds.topLeft.x); + double factor = pow(2, zoomLevel); + double zoom = baseZoom / factor; + double width = baseValueWidth / factor; + return Tiled2dMapZoomLevelInfo(zoom, width, factor, factor, 1, zoomLevel, coordinateSystem.bounds); +} diff --git a/shared/src/map/layers/tiled/RegularTiled2dMapLayerConfig.h b/shared/src/map/layers/tiled/RegularTiled2dMapLayerConfig.h new file mode 100644 index 000000000..857f1ba68 --- /dev/null +++ b/shared/src/map/layers/tiled/RegularTiled2dMapLayerConfig.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2026 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ +#pragma once + +#include "Tiled2dMapVectorLayerConfig.h" +#include "Tiled2dMapZoomLevelInfo.h" +#include "MapCoordinateSystem.h" + +/** Base class for Tiled2dMapVectorLayerConfig implementations for _regular_ + * tile pyramids, i.e. for tile systems with a simple power-of-2 relation + * between zoom levels. + */ +class RegularTiled2dMapLayerConfig : public Tiled2dMapVectorLayerConfig { +public: + RegularTiled2dMapLayerConfig( + std::string layerName, + std::string urlFormat, + const std::optional &bounds, + const Tiled2dMapZoomInfo &zoomInfo, + const std::vector &levels, + const MapCoordinateSystem &coordinateSystem, + double baseZoom) + : Tiled2dMapVectorLayerConfig(layerName, urlFormat, bounds, zoomInfo, levels) + , coordinateSystem(coordinateSystem) + , baseZoom(baseZoom) + {} + + virtual int32_t getCoordinateSystemIdentifier() override { + return coordinateSystem.identifier; + } + + virtual std::vector getZoomLevelInfos() override; + + virtual std::vector getVirtualZoomLevelInfos() override; + + virtual double getZoomIdentifier(double zoom) override; + + virtual double getZoomFactorAtIdentifier(double zoomIdentifier) override; + +protected: + Tiled2dMapZoomLevelInfo getRegularZoomLevelInfo(int zoomLevel); + +protected: + double baseZoom; + MapCoordinateSystem coordinateSystem; +}; diff --git a/shared/src/map/layers/tiled/Tiled2dMapVectorLayerConfig.cpp b/shared/src/map/layers/tiled/Tiled2dMapVectorLayerConfig.cpp new file mode 100644 index 000000000..8b42f0ac1 --- /dev/null +++ b/shared/src/map/layers/tiled/Tiled2dMapVectorLayerConfig.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2026 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +#include "Tiled2dMapVectorLayerConfig.h" +#include +#include +#include + +Tiled2dMapVectorLayerConfig::Tiled2dMapVectorLayerConfig( + std::string layerName, + std::string urlFormat, + const std::optional &bounds, + const Tiled2dMapZoomInfo &zoomInfo, + const std::vector &levels_ +) + : layerName(layerName) + , urlFormat(urlFormat) + , bounds(bounds) + , zoomInfo(zoomInfo) + , levels(levels_) +{ + std::sort(levels.begin(), levels.end()); +} + +std::string Tiled2dMapVectorLayerConfig::getTileUrl(int32_t x, int32_t y, int32_t t, int32_t zoom) { + std::string url = urlFormat; + size_t zoomIndex = url.find("{z}", 0); + if (zoomIndex == std::string::npos) throw std::invalid_argument("Layer url \'" + url + "\' has no valid format!"); + url = url.replace(zoomIndex, 3, std::to_string(zoom)); + size_t xIndex = url.find("{x}", 0); + if (xIndex == std::string::npos) throw std::invalid_argument("Layer url \'" + url + "\' has no valid format!"); + url = url.replace(xIndex, 3, std::to_string(x)); + size_t yIndex = url.find("{y}", 0); + if (yIndex == std::string::npos) throw std::invalid_argument("Layer url \'" + url + "\' has no valid format!"); + return url.replace(yIndex, 3, std::to_string(y)); +} + +Tiled2dMapZoomInfo Tiled2dMapVectorLayerConfig::defaultMapZoomInfo() { + return Tiled2dMapZoomInfo(1.0, 0, 0, true, false, true, true); +} + +std::vector Tiled2dMapVectorLayerConfig::generateLevelsFromMinMax(int minZoomLevel, int maxZoomLevel) { + std::vector levels(maxZoomLevel - minZoomLevel + 1); + std::iota(levels.begin(), levels.end(), minZoomLevel); + return levels; +} diff --git a/shared/src/map/layers/tiled/WebMercatorTiled2dMapLayerConfig.cpp b/shared/src/map/layers/tiled/WebMercatorTiled2dMapLayerConfig.cpp deleted file mode 100644 index a9bb90b34..000000000 --- a/shared/src/map/layers/tiled/WebMercatorTiled2dMapLayerConfig.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2021 Ubique Innovation AG - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * SPDX-License-Identifier: MPL-2.0 - */ - -#include "WebMercatorTiled2dMapLayerConfig.h" -#include "Tiled2dMapVectorSettings.h" -#include "CoordinateSystemIdentifiers.h" -#include -#include - - -const RectCoord WebMercatorTiled2dMapLayerConfig::WEB_MERCATOR_BOUNDS = RectCoord(Coord(CoordinateSystemIdentifiers::EPSG3857(), -20037508.34, 20037508.34, 0.0), - Coord(CoordinateSystemIdentifiers::EPSG3857(), 20037508.34, -20037508.34, 0.0)); -const double WebMercatorTiled2dMapLayerConfig::BASE_ZOOM = 559082264.029; -const double WebMercatorTiled2dMapLayerConfig::BASE_WIDTH = 40075016; - -WebMercatorTiled2dMapLayerConfig::WebMercatorTiled2dMapLayerConfig(std::string layerName, std::string urlFormat, int32_t minZoomLevel, - int32_t maxZoomLevel) - : layerName(layerName), urlFormat(urlFormat), minZoomLevel(minZoomLevel), maxZoomLevel(maxZoomLevel) - {} - -WebMercatorTiled2dMapLayerConfig::WebMercatorTiled2dMapLayerConfig(std::string layerName, std::string urlFormat, - const Tiled2dMapZoomInfo &zoomInfo, int32_t minZoomLevel, - int32_t maxZoomLevel) - : layerName(layerName), urlFormat(urlFormat), zoomInfo(zoomInfo), minZoomLevel(minZoomLevel), maxZoomLevel(maxZoomLevel) {} - -int32_t WebMercatorTiled2dMapLayerConfig::getCoordinateSystemIdentifier() { return CoordinateSystemIdentifiers::EPSG3857(); } - -std::string WebMercatorTiled2dMapLayerConfig::getTileUrl(int32_t x, int32_t y, int32_t t, int32_t zoom) { - std::string url = urlFormat; - size_t zoomIndex = url.find("{z}", 0); - if (zoomIndex == std::string::npos) - throw std::invalid_argument("Layer url \'" + url + "\' has no valid format!"); - url = url.replace(zoomIndex, 3, std::to_string(zoom)); - size_t xIndex = url.find("{x}", 0); - if (xIndex == std::string::npos) - throw std::invalid_argument("Layer url \'" + url + "\' has no valid format!"); - url = url.replace(xIndex, 3, std::to_string(x)); - size_t yIndex = url.find("{y}", 0); - if (yIndex == std::string::npos) - throw std::invalid_argument("Layer url \'" + url + "\' has no valid format!"); - return url.replace(yIndex, 3, std::to_string(y)); -} - -std::string WebMercatorTiled2dMapLayerConfig::getLayerName() { return layerName; } - -std::vector WebMercatorTiled2dMapLayerConfig::getZoomLevelInfos() { - std::vector levels; - levels.reserve(maxZoomLevel - minZoomLevel + 1); - for (int32_t i = minZoomLevel; i <= maxZoomLevel; ++i) { - levels.emplace_back(getZoomLevelInfo(i)); - } - return levels; -} - -std::vector WebMercatorTiled2dMapLayerConfig::getVirtualZoomLevelInfos() { - std::vector levels; - levels.reserve(minZoomLevel); - for (int32_t i = 0; i < minZoomLevel; ++i) { - levels.emplace_back(getZoomLevelInfo(i)); - } - return levels; -}; - -Tiled2dMapZoomInfo WebMercatorTiled2dMapLayerConfig::getZoomInfo() { - return zoomInfo; -} - -std::optional WebMercatorTiled2dMapLayerConfig::getVectorSettings() { - return std::nullopt; -} - -std::optional<::RectCoord> WebMercatorTiled2dMapLayerConfig::getBounds() { - return WEB_MERCATOR_BOUNDS; -} - -Tiled2dMapZoomLevelInfo WebMercatorTiled2dMapLayerConfig::getZoomLevelInfo(int32_t zoomLevel) { - double tileCount = std::pow(2.0,zoomLevel); - double zoom = BASE_ZOOM / tileCount; - return {zoom, (float) (BASE_WIDTH / tileCount), (int32_t) tileCount, (int32_t) tileCount, 1, zoomLevel, WEB_MERCATOR_BOUNDS}; -} diff --git a/shared/src/map/layers/tiled/WebMercatorTiled2dMapLayerConfig.h b/shared/src/map/layers/tiled/WebMercatorTiled2dMapLayerConfig.h deleted file mode 100644 index b7e0dc02d..000000000 --- a/shared/src/map/layers/tiled/WebMercatorTiled2dMapLayerConfig.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2021 Ubique Innovation AG - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * SPDX-License-Identifier: MPL-2.0 - */ - -#pragma once - -#include "Tiled2dMapLayerConfig.h" -#include "Tiled2dMapZoomInfo.h" -#include "Tiled2dMapZoomLevelInfo.h" -#include "WmtsLayerDescription.h" - -class WebMercatorTiled2dMapLayerConfig : public Tiled2dMapLayerConfig { - public: - WebMercatorTiled2dMapLayerConfig(std::string layerName, std::string urlFormat, int32_t minZoomLevel = 0, int32_t maxZoomLevel = 20); - - WebMercatorTiled2dMapLayerConfig(std::string layerName, std::string urlFormat, const Tiled2dMapZoomInfo &zoomInfo, int32_t minZoomLevel, int32_t maxZoomLevel); - - virtual int32_t getCoordinateSystemIdentifier() override; - - virtual std::string getTileUrl(int32_t x, int32_t y, int32_t t, int32_t zoom) override; - - virtual std::string getLayerName() override; - - virtual std::vector getZoomLevelInfos() override; - - virtual std::vector getVirtualZoomLevelInfos() override; - - virtual Tiled2dMapZoomInfo getZoomInfo() override; - - virtual std::optional getVectorSettings() override; - - std::optional<::RectCoord> getBounds() override; - -private: - Tiled2dMapZoomLevelInfo getZoomLevelInfo(int32_t zoomLevel); - - const static RectCoord WEB_MERCATOR_BOUNDS; - const static double BASE_ZOOM; - const static double BASE_WIDTH; - - std::string layerName; - std::string urlFormat; - - int32_t minZoomLevel = 0; - int32_t maxZoomLevel = 20; - Tiled2dMapZoomInfo zoomInfo = Tiled2dMapZoomInfo(1.0, 0, 0, true, false, true, true); -}; diff --git a/shared/src/map/layers/tiled/vector/Tiled2dMapVectorGeoJSONLayerConfig.h b/shared/src/map/layers/tiled/vector/Tiled2dMapVectorGeoJSONLayerConfig.h deleted file mode 100644 index 322f49ef3..000000000 --- a/shared/src/map/layers/tiled/vector/Tiled2dMapVectorGeoJSONLayerConfig.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2021 Ubique Innovation AG - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * SPDX-License-Identifier: MPL-2.0 - */ - -#pragma once -#include "Tiled2dMapVectorLayerConfig.h" - -class Tiled2dMapVectorGeoJSONLayerConfig : public Tiled2dMapVectorLayerConfig { -public: - Tiled2dMapVectorGeoJSONLayerConfig(const std::string &sourceName, const std::weak_ptr geoJSON, const Tiled2dMapZoomInfo &zoomInfo = Tiled2dMapZoomInfo(1.0, 0, 0, false, true, false, true)) - : Tiled2dMapVectorLayerConfig(nullptr, zoomInfo), geoJSON(geoJSON), sourceName(sourceName) {} - - ~Tiled2dMapVectorGeoJSONLayerConfig() {} - - std::string getTileUrl(int32_t x, int32_t y, int32_t t, int32_t zoom) override { - static std::string empty = ""; - return empty; - } - - std::vector getZoomLevelInfos() override { - int maxZoom = 0; - int minZoom = 0; - if (auto geoJSON = this->geoJSON.lock()){ - minZoom = geoJSON->getMinZoom(); - maxZoom = geoJSON->getMaxZoom(); - } - return getDefaultEpsg3857ZoomLevels(minZoom, maxZoom, std::nullopt); - } - - std::vector getVirtualZoomLevelInfos() override { - return {}; - } - - std::string getLayerName() override { - return sourceName; - } - -protected: - std::weak_ptr geoJSON; - const std::string sourceName; -}; diff --git a/shared/src/map/layers/tiled/vector/Tiled2dMapVectorLayer.cpp b/shared/src/map/layers/tiled/vector/Tiled2dMapVectorLayer.cpp index 02d64d02b..d8c428d93 100644 --- a/shared/src/map/layers/tiled/vector/Tiled2dMapVectorLayer.cpp +++ b/shared/src/map/layers/tiled/vector/Tiled2dMapVectorLayer.cpp @@ -19,7 +19,6 @@ #include "BackgroundVectorLayerDescription.h" #include "VectorTileGeometryHandler.h" #include "Tiled2dMapVectorBackgroundSubLayer.h" -#include "Tiled2dMapVectorRasterSubLayerConfig.h" #include "Polygon2dInterface.h" #include "MapCameraInterface.h" #include "QuadMaskObject.h" @@ -45,10 +44,14 @@ #include "Tiled2dMapVectorReadyManager.h" #include "Tiled2dVectorGeoJsonSource.h" #include "Tiled2dMapVectorStyleParser.h" -#include "Tiled2dMapVectorGeoJSONLayerConfig.h" #include "GeoJsonVTFactory.h" #include "VectorLayerFeatureCoordInfo.h" +#include "Epsg2056Tiled2dMapLayerConfig.h" +#include "Epsg21781Tiled2dMapLayerConfig.h" +#include "Epsg3857Tiled2dMapLayerConfig.h" +#include "Epsg4326Tiled2dMapLayerConfig.h" + Tiled2dMapVectorLayer::Tiled2dMapVectorLayer(const std::string &layerName, const std::optional &remoteStyleJsonUrl, const std::vector> &loaders, @@ -237,14 +240,26 @@ Tiled2dMapVectorLayer::getLayerConfig(const std::shared_ptr(source, *customZoomInfo) - : std::make_shared(source, mapInterface->is3d()); + const std::optional crs = source->coordinateReferenceSystem; + const auto &zoomInfo = customZoomInfo.has_value() ? *customZoomInfo : source->getZoomInfo(mapInterface->is3d()); + if(!crs.has_value() || *crs == CoordinateSystemIdentifiers::EPSG3857()) { + return std::make_shared(source->identifier, source->vectorUrl, source->bounds, zoomInfo, source->getZoomLevels()); + } else if(*crs == CoordinateSystemIdentifiers::EPSG4326()){ + return std::make_shared(source->identifier, source->vectorUrl, source->bounds, zoomInfo, source->getZoomLevels()); + } else if(*crs == CoordinateSystemIdentifiers::EPSG2056()){ + return std::static_pointer_cast(std::make_shared(source->identifier, source->vectorUrl, source->bounds, zoomInfo, source->getZoomLevels())); + } else if(*crs == CoordinateSystemIdentifiers::EPSG21781()){ + return std::make_shared(source->identifier, source->vectorUrl, source->bounds, zoomInfo, source->getZoomLevels()); + } + // layer will be ignored + return nullptr; } std::shared_ptr Tiled2dMapVectorLayer::getGeoJSONLayerConfig(const std::string &sourceName, const std::shared_ptr &source) { - return customZoomInfo.has_value() ? std::make_shared(sourceName, source, *customZoomInfo) - : std::make_shared(sourceName, source); + const auto levels = Tiled2dMapVectorLayerConfig::generateLevelsFromMinMax(source->getMinZoom(), source->getMaxZoom()); + auto zoomInfo = customZoomInfo.has_value() ? *customZoomInfo : Tiled2dMapZoomInfo(1.0, 0, 0, false, true, false, true); + return std::make_shared(sourceName, "", std::nullopt, zoomInfo, levels); } void Tiled2dMapVectorLayer::setMapDescription(const std::shared_ptr &mapDescription) { @@ -296,7 +311,6 @@ void Tiled2dMapVectorLayer::initializeVectorLayer() { if (!mapInterface) { return; } - bool is3d = mapInterface->is3d(); std::shared_ptr selfMailbox = mailbox; if (!mailbox) { @@ -332,8 +346,7 @@ void Tiled2dMapVectorLayer::initializeVectorLayer() { break; } case raster: { - auto rasterSubLayerConfig = std::make_shared( - std::dynamic_pointer_cast(layerDesc), is3d, customZoomInfo); + auto rasterSubLayerConfig = getLayerConfig(std::dynamic_pointer_cast(layerDesc)->source); auto sourceMailbox = std::make_shared(mapInterface->getScheduler()); auto sourceActor = Actor(sourceMailbox, diff --git a/shared/src/map/layers/tiled/vector/Tiled2dMapVectorLayerParserHelper.cpp b/shared/src/map/layers/tiled/vector/Tiled2dMapVectorLayerParserHelper.cpp index b828c8ee0..6da9439cf 100644 --- a/shared/src/map/layers/tiled/vector/Tiled2dMapVectorLayerParserHelper.cpp +++ b/shared/src/map/layers/tiled/vector/Tiled2dMapVectorLayerParserHelper.cpp @@ -92,8 +92,8 @@ Tiled2dMapVectorLayerParserResult Tiled2dMapVectorLayerParserHelper::parseStyleJ bool maskTiles = true; double zoomLevelScaleFactor = 1.0; - bool overzoom = val.contains("overzoom") ? tileJsons["overzoom"].get() : true; - bool underzoom = val.contains("underzoom") ? tileJsons["underzoom"].get() : false; + bool overzoom = val.contains("overzoom") ? val["overzoom"].get() : true; + bool underzoom = val.contains("underzoom") ? val["underzoom"].get() : false; std::optional> levels; int minZoom = std::numeric_limits::max(); @@ -111,9 +111,14 @@ Tiled2dMapVectorLayerParserResult Tiled2dMapVectorLayerParserHelper::parseStyleJ minZoom = val.value("minzoom", 0); maxZoom = val.value("maxzoom", 22); } + std::optional coordinateReferenceSystem; + if(val.contains("crs") && val["crs"].is_string()) { + coordinateReferenceSystem = CoordinateSystemIdentifiers::fromCrsIdentifier(val["crs"].get()); + } else if(val["metadata"].is_object() && val["metadata"].contains("crs") && val["metadata"]["crs"].is_string()) { + coordinateReferenceSystem = CoordinateSystemIdentifiers::fromCrsIdentifier(val["metadata"]["crs"].get()); + } std::optional<::RectCoord> bounds; - std::optional coordinateReferenceSystem; if (val["tiles"].is_array()) { auto str = val.dump(); @@ -135,9 +140,6 @@ Tiled2dMapVectorLayerParserResult Tiled2dMapVectorLayerParserHelper::parseStyleJ } } - if(val["metadata"].is_object() && val["metadata"].contains("crs") && val["metadata"]["crs"].is_string()) { - coordinateReferenceSystem = val["metadata"]["crs"].get(); - } } else if (val["url"].is_string()) { auto result = LoaderHelper::loadData(replaceUrlParams(val["url"].get(), sourceUrlParams), std::nullopt, loaders); @@ -166,30 +168,26 @@ Tiled2dMapVectorLayerParserResult Tiled2dMapVectorLayerParserHelper::parseStyleJ } } - if(json["metadata"].is_object() && json["metadata"].contains("crs") && json["metadata"]["crs"].is_string()) { - coordinateReferenceSystem = json["metadata"]["crs"].get(); - } minZoom = json.value("minzoom", 0); maxZoom = json.value("maxzoom", 22); } - // XXX: coordinateReferenceSystem - // XXX: maskTiles rasterSourceMap[key] = std::make_shared( - key, - url, - minZoom, - maxZoom, - bounds, - adaptScaleToScreen, - numDrawPreviousLayers, - zoomLevelScaleFactor, - underzoom, - overzoom, - levels, - maskTiles - ); + key, + url, + minZoom, + maxZoom, + bounds, + zoomLevelScaleFactor, + adaptScaleToScreen, + numDrawPreviousLayers, + underzoom, + overzoom, + levels, + coordinateReferenceSystem, + maskTiles + ); } else if (type == "vector" && val["url"].is_string()) { auto result = LoaderHelper::loadData(replaceUrlParams(val["url"].get(), sourceUrlParams), std::nullopt, loaders); @@ -269,6 +267,10 @@ Tiled2dMapVectorLayerParserResult Tiled2dMapVectorLayerParserHelper::parseStyleJ minZoom = tileJson.value("minzoom", 0); maxZoom = tileJson.value("maxzoom", 22); } + std::optional coordinateReferenceSystem; + if(tileJson.contains("crs") && tileJson["crs"].is_string()) { + coordinateReferenceSystem = CoordinateSystemIdentifiers::fromCrsIdentifier(tileJson["crs"].get()); + } sourceDescriptions.push_back( std::make_shared(identifier, @@ -281,7 +283,9 @@ Tiled2dMapVectorLayerParserResult Tiled2dMapVectorLayerParserHelper::parseStyleJ numDrawPreviousLayers, underzoom, overzoom, - levels)); + levels, + coordinateReferenceSystem + )); } @@ -374,13 +378,13 @@ Tiled2dMapVectorLayerParserResult Tiled2dMapVectorLayerParserHelper::parseStyleJ bool overzoom = source->overzoom && !val.contains("maxzoom"); auto layer = std::make_shared(val["id"], - source, - val.value("minzoom", source->minZoom), - val.value("maxzoom", source->maxZoom), - filter, - renderPassIndex, - interactable, - style); + source, + val.value("minzoom", source->minZoom), + val.value("maxzoom", source->maxZoom), + filter, + renderPassIndex, + interactable, + style); layers.push_back(layer); } else if (val["type"] == "line") { diff --git a/shared/src/map/layers/tiled/vector/Tiled2dMapVectorRasterSubLayerConfig.h b/shared/src/map/layers/tiled/vector/Tiled2dMapVectorRasterSubLayerConfig.h deleted file mode 100644 index 420b017bc..000000000 --- a/shared/src/map/layers/tiled/vector/Tiled2dMapVectorRasterSubLayerConfig.h +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (c) 2021 Ubique Innovation AG - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * SPDX-License-Identifier: MPL-2.0 - */ -#pragma once - -#include "VectorLayerDescription.h" -#include "RasterVectorLayerDescription.h" -#include "Tiled2dMapVectorLayerConfig.h" -#include "Tiled2dMapZoomInfo.h" -#include "CoordinateSystemIdentifiers.h" -#include "Tiled2dMapZoomLevelInfo.h" -#include "Logger.h" -#include "Tiled2dMapVectorSettings.h" -#include "Epsg4326Tiled2dMapLayerConfig.h" - -class Tiled2dMapVectorRasterSubLayerConfig : public Tiled2dMapVectorLayerConfig { -public: - Tiled2dMapVectorRasterSubLayerConfig(const std::shared_ptr &layerDescription, - const bool is3d, - const std::optional &customZoomInfo = std::nullopt) - : Tiled2dMapVectorLayerConfig(std::static_pointer_cast(layerDescription->source), is3d), - description(layerDescription) { - if (customZoomInfo.has_value()) { - // zoomInfo is already initialized in super from the source-description - zoomInfo.zoomLevelScaleFactor *= customZoomInfo->zoomLevelScaleFactor; - zoomInfo.numDrawPreviousLayers = std::max(customZoomInfo->numDrawPreviousLayers, zoomInfo.numDrawPreviousLayers); - zoomInfo.numDrawPreviousOrLaterTLayers = 0; - zoomInfo.adaptScaleToScreen |= customZoomInfo->adaptScaleToScreen; - zoomInfo.maskTile |= customZoomInfo->maskTile; - zoomInfo.underzoom &= customZoomInfo->underzoom; - zoomInfo.overzoom &= customZoomInfo->overzoom; - } - - if (description->source->coordinateReferenceSystem == "EPSG:4326") { - customConfig = std::make_shared(layerDescription->source->identifier, - layerDescription->source->vectorUrl, - zoomInfo, - layerDescription->sourceMinZoom, - layerDescription->sourceMaxZoom); - } - } - - int32_t getCoordinateSystemIdentifier() override { - if (customConfig) { - return customConfig->getCoordinateSystemIdentifier(); - } - return Tiled2dMapVectorLayerConfig::getCoordinateSystemIdentifier(); - } - - std::string getTileUrl(int32_t x, int32_t y, int32_t t, int32_t zoom) override { - if (customConfig) { - return customConfig->getTileUrl(x,y,t,zoom); - } - return Tiled2dMapVectorLayerConfig::getTileUrl(x,y,t,zoom); - } - - std::string getLayerName() override { - if (customConfig) { - return customConfig->getLayerName(); - } - return Tiled2dMapVectorLayerConfig::getLayerName(); - } - - std::vector getZoomLevelInfos() override { - if (customConfig) { - return customConfig->getZoomLevelInfos(); - } - return Tiled2dMapVectorLayerConfig::getZoomLevelInfos(); - } - - std::vector getVirtualZoomLevelInfos() override { - if (customConfig) { - return customConfig->getVirtualZoomLevelInfos(); - } - return Tiled2dMapVectorLayerConfig::getVirtualZoomLevelInfos(); - } - - Tiled2dMapZoomInfo getZoomInfo() override { - if (customConfig) { - return customConfig->getZoomInfo(); - } - return Tiled2dMapVectorLayerConfig::getZoomInfo(); - } - - std::optional getVectorSettings() override { - if (customConfig) { - return customConfig->getVectorSettings(); - } - return Tiled2dMapVectorLayerConfig::getVectorSettings(); - } - - std::optional<::RectCoord> getBounds() override { - if (customConfig) { - return customConfig->getBounds(); - } - return Tiled2dMapVectorLayerConfig::getBounds(); - } - - -private: - std::shared_ptr description; - - std::shared_ptr customConfig; -}; diff --git a/shared/test/TestTileSource.cpp b/shared/test/TestTileSource.cpp index dc87cf455..0e28fe3e1 100644 --- a/shared/test/TestTileSource.cpp +++ b/shared/test/TestTileSource.cpp @@ -1,9 +1,9 @@ #include "CoordinateSystemFactory.h" #include "DataLoaderResult.h" +#include "Epsg3857Tiled2dMapLayerConfig.h" #include "LoaderInterface.h" #include "TextureLoaderResult.h" #include "Tiled2dMapSource.h" -#include "WebMercatorTiled2dMapLayerConfig.h" #include "helper/TestScheduler.h" #include "Tiled2dMapSourceImpl.h" @@ -219,9 +219,18 @@ class BlockingTestLoader : public LoaderInterface { std::list blockedLoads; }; +static std::shared_ptr createTestLayerConfig() { + auto zoomInfo = Tiled2dMapVectorLayerConfig::defaultMapZoomInfo(); + zoomInfo.adaptScaleToScreen = false; // Important, otherwise the onVisibleBoundsChanged does not pick up the (in the tests) expected zoom level. + return std::make_shared("mock", "test-data://tile/{z}/{x}/{y}", + std::nullopt, + zoomInfo, + Tiled2dMapVectorLayerConfig::generateLevelsFromMinMax(0, 20) + ); +} + TEST_CASE("VectorTileSource") { - auto layerConfig = std::make_shared( - "mock", "{z}/{x}/{y}", Tiled2dMapZoomInfo(1.0, 0, 0, false, true, false, true), 0, 20); + auto layerConfig = createTestLayerConfig(); auto scheduler = std::make_shared(); std::shared_ptr source = std::make_shared( layerConfig, scheduler, std::vector>{std::make_shared()}); @@ -273,11 +282,10 @@ static std::unordered_map generateDummyData(const std: */ TEST_CASE("Tiled2dMapSource slow fallback does not block local loads") { - auto layerConfig = std::make_shared( - "mock", "test-data://tile/{z}/{x}/{y}", Tiled2dMapZoomInfo(1.0, 0, 0, false, true, false, true), 0, 20); + auto layerConfig = createTestLayerConfig(); // Load the entire world. - auto rect = *layerConfig->getBounds(); + auto rect = CoordinateSystemFactory::getEpsg3857System().bounds; auto zoomLevelInfos = layerConfig->getZoomLevelInfos(); // Try with different zoom levels; that's 1, 16, 256 or 1024 tiles const int z = GENERATE(0, 2, 4, 5); @@ -320,6 +328,7 @@ TEST_CASE("Tiled2dMapSource slow fallback does not block local loads") { // Complete all "local" loads scheduler->drain(); + CAPTURE(z); REQUIRE(source->numLoadingOrQueued() == expectedTiles.size()); while (localLoader->unblockAll()) { scheduler->drain(); @@ -347,11 +356,10 @@ TEST_CASE("Tiled2dMapSource slow fallback does not block local loads") { TEST_CASE("Tiled2dMapSource error load retry") { - auto layerConfig = std::make_shared( - "mock", "test-data://tile/{z}/{x}/{y}", Tiled2dMapZoomInfo(1.0, 0, 0, false, true, false, true), 0, 20); + auto layerConfig = createTestLayerConfig(); // Load the entire world at zoom level 3 - auto rect = *layerConfig->getBounds(); + auto rect = CoordinateSystemFactory::getEpsg3857System().bounds; auto zoomLevelInfos = layerConfig->getZoomLevelInfos(); const int z = 3; std::vector expectedTiles = {{rect, 0, 0, 0, 0, int(zoomLevelInfos[0].zoom)}}; From dbb791ba7b1e6d003cebbad8542efcaaaddb40bc Mon Sep 17 00:00:00 2001 From: Matthias Frei Date: Fri, 23 Jan 2026 17:39:10 +0100 Subject: [PATCH 3/3] Update golden images, small differences for geojson changed tile pyramid For the EPSG 3857 / WebMercator tile pyramid we now consistently use the base zoom value 559082264.029 instead of 500000000.0. Previously. both values where used in different places. Note: the webmercator tile pyramid is specified as urn:ogc:def:wkss:OGC:1.0:GoogleMapsCompatible in OGC WMTS Simple Profile, with the used base zoom value as ScaleDenominator of TileMatrix 0. --- ...MapRendererTest_testTiler_tile_7_66_44.png | Bin 10654 -> 10656 bytes ...MapRendererTest_testTiler_tile_7_66_45.png | Bin 19065 -> 18996 bytes ...MapRendererTest_testTiler_tile_7_67_44.png | Bin 11403 -> 11396 bytes ...MapRendererTest_testTiler_tile_7_67_45.png | Bin 20451 -> 20472 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/jvm/src/test/resources/golden/OffscreenMapRendererTest_testTiler_tile_7_66_44.png b/jvm/src/test/resources/golden/OffscreenMapRendererTest_testTiler_tile_7_66_44.png index 19ac578e4a2b45a9542bc3987ac00ed7433ca8bb..5b41c571dcc0efa94c3151a3c0e55aad64a2eeb9 100644 GIT binary patch literal 10656 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 bLN=6bnv6(%3Vp^u+w_;YwOOUfohSbXX64ls literal 10654 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 Ln${XW_~(BBLoEjI diff --git a/jvm/src/test/resources/golden/OffscreenMapRendererTest_testTiler_tile_7_66_45.png b/jvm/src/test/resources/golden/OffscreenMapRendererTest_testTiler_tile_7_66_45.png index 6b01b7b7a16e9625c5f5bdcb906096e636375aac..9ee8f54226d7d8722c34e4dc6da2e643b73de99b 100644 GIT binary patch literal 18996 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 literal 19065 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 SC50A&%=C2ib6Mw<&;$Un{hWmW diff --git a/jvm/src/test/resources/golden/OffscreenMapRendererTest_testTiler_tile_7_67_44.png b/jvm/src/test/resources/golden/OffscreenMapRendererTest_testTiler_tile_7_67_44.png index dcbbdf3342a9d272d59ab55a22bbb88d4947dc5e..2380aae75091ef8b1667e91c9950b566db1975ce 100644 GIT binary patch literal 11396 zcmeHt=U)@s+V)VSDM(RD=tWQzkrI$*5R@WKil9^#fdB&1d)bOoLlNmhkWfWHq=XKN zG^v3=q=WPpdWYnh?6c2%&hr<%Umm`M->kJ}&D?XB>%OiP@laQto(@6>006zF#ytZ7 zAP4^?2WY6kpIsm9F#z1q(!6)q$j5Shg4*NJqtUjlvgqgP_iuc9^I(IIPe*>>gTQOP zt4_*{OqVX5&*l@n8m{r^-q(JA!&;5xn+*rL8l|Bdf!PP?-TMVuLJ8w1#Nxdh{No?z z+Sc6mN9K8n;6DHN_kR}nKMVZ-wg4Z!@6g;Wd^;Hsf{cVFcgq;|DL$u5uNThme2JGt zr<-*n+8V&LprI-XffASlC1m)2b4gKm-z;zaA_YskZ(Fi#gT1EVmvMq9NgNY8yA4i# zOP7`*+{P6l@9o;2s>%;Tjt2)Vjp}~_oOpUJ<5xz=@mMY2LQgrnm%orPh0xQjTX18pUk&;rkVJC_9|~jI{iNGlryg;-qT3&9Y4GSs~dr1<@%xfskrEp^xahe2rJUvp6gS^R*jFiBU2ZQ zXM@B+V{uHp#CD*es2*DuXY6hD)^_vpr;WE93TAuqP<47Z$Dy2tF|vZu9Lp$+#hB1# zjwa!YE2Fg~Nf$H26?w;YTZbpcaT^V<6y4C*o{n#v2LN*$4sGATA#F*2?Z+PXq9K7j zE4UYp&k`CxHohACwVpOn8hiajRZlG#8octH{-BOKL{twKXeR7uM`;HZ`ls%n| zbUG6KP>{B}cfTmH_-yd|dV6KXt>v7%0PrfYKw@|_09vt;;;YuRhrN9Q+0TWQPAI)N zT`KHr8r`CY_AX0KB&xEU)E)#c&gzK-NH|SbGqJ3oorWcXTi*}WF973buNKIWIz-!& z64~z`Bu536xoLVh`!|CJaIlBBOt$14lU_zgN5yQw$2T|t;FNy8yWO@1b0KhQqJDV0 z&!W!5u&IwK)(P8qe{tL<%D7fysdvq}rR3cS@?a`{h;W*&Hl_2%t4CqE)~=hKg@=XX zWVV<7BGC{mzm%!T++{he;+pdJ z+8(I6KE;mph8MFUMqh{}GQYfS>&`rEfb1EnB@ib$3791-1xEbp8%yF+ z1Ha$82Pz`uNXHI!M5WsOrY%qp0H*ce%$dR?+~;AOXQi^1c#BVWdxQ@bx-5S>(hwc) zqiFqdvT}n*p%05F#&L()LqMAQ*P#Fx84d*GRFX_W=2`ug&Q(ePNG@#9e^dKnHO&I@ z$D4C{E?k~HOrZBCcNA@7j%HAr)^uZAawI*kOYK%ii6MqfX%F#;PtBfXNPyK5FdS7D3;4>=wG?n%wbDySJwc9d^Isin#0Xb zET_d}dqqHatNU9oCw#k|SCr>ir`lLCO_MF6jxx$sIC4nVBdFA>g(f73U)# zd2vo+xx`dIR=?<_>*2r6=q0MAMZ0%fnUm>kuzKS%=m@jPGsM#9cSIP$51aFWi$JWy zge#Zw^G0J2g1X_CRMnZ)If_~Py;|`np7v7|FOjc-n?`*UJe^^s#8dBfw3 zAMT7PAoqE5>xj7v4-Uv4hTRoB$5f{8I=Z}hWwsGawM`x<4!eLBqGjDl?lAn2nBW3K zW@`qYX%9@05yLH$C*eE%J(v5L?^&q2Q)NowSIL0Q>wfza9c#BB1eeKN3vYD1&bsgX zl*>hU9L#6KRXj);6#nwKfD=O59U^YFmFTewIBeD1AK&2Il9ptUZTr?w z-t_SzWvJR)bw~AeHShp{{jkMFn_n9;q4sj}H{hhYrmoGAu-gy>$C9z1_Shv< zx=*jm;k(u|-3s){lyv*`JN$~pnaVI*MbR;Q!(0?Pnc99%05HHBt0pa$-(of~Ik6Ns^FlX3`x%} zwVQ6=)ntZhCug!POSM-^k?l^zD}Y66Wf~KnHw4!M)~&M?ymbD!qk)@FcVs!Ed6#``NF7z$J$*3BC_vAr}eTldh0zqZ`qyc{ltOv$mL075Fhqxf}- zXhnCQ(u&zcN52>l`&IA?Ekp~A`@rgY@Y8+%J&v!j=~>+5VdeHBGT(%F0@7jA{wM~ufitT2 zEfaa4>|cteg&`)x6iA?{YY`Kdw+EMeTDCI3Qh$86^e%*{pM$#A4EG(Dpre&Aw6xx2 zx9c)WyOs3iKyk@Zt42=io%!s7j%5MPe(~qyomx%29nqV;=TXYjT~Aek7StY3kjzQ| zTxHfwEmlG_bzF^`qR0JJ={cY|cn7|DdL^wspPOLnxv`s?97y05j;SztuO- zo|`?cw^@U3wxJ<+r($`$mpsS1$)!f&vjUzTqMV)=-q)3r0~(Hskovip;c=_vt*UN( zdx8|HnOmhi0@hy^gff($3{dBPMnswtN(DQgk#&zKIU9AEjC}j8E8pVUZlP; z`OPmMVztib1HDK;5Et}F>enH|^?}cC#22+q`?D-|be}}cUn^ER4Vu~6S z7O=PHJ(zT3wBDhegkn~2P@t`|XyaGdbAF=e0A>d1V?1HB<< zFSC7r-u<(5d=6*Rrg<`N6|uqukqnues$119f!}TlID_al?bs9bkMDRP#s$* zedx9t4lFj=^_iJ(bP>j1t?n3i9Oqq59GLJuC-sv{vUa~Qjzu3**+2Y~W5@RTS`csK zg78w=TLx888`#dVQ@W>7`}7ntSWCuk{P<)G~t^qIa0(q?aD4=(RB0C-btH%9uXEh27Hov)>(LvCpI= ze$PO_LGiq?=p$A7FG&|IHPv%W2SrR*0+((!)Vl`fBx`A1s+5bby1LKZ8F1^=V{IMZ4cV}3m1a5L0P0n0;GOOV@1^~J(VLKZH(kiDYK?+#U$27tm; zbqco{->j!4fAl=zM*}CQI|Mhq%`%#vS`$IktcM9_PYG@cO@cFRk>^%-?VLWM+H}2Z z9oDe+8p+EgT=+^ucj-$oVm@!xQb}z&_+Y_mCsdc9qv7x{OyB&&pQ+WU*Vzczh00!+ z)nh#FQ~@KH+zuJ?Q_GMz3myOiQi#iiH4ULggjJ~iDPQQ@*FBW#mIXqjm0Px*-?++Q zEb{gTakWQI`3#gN!yS6ErX@+O(o&!a@o+_(u6#C_X?U*^xBde1Hy@3lP#E1kz4O~XUc zJbe{w!!vs%@QAHyJ0=X-56-Dgb#&hu{8gO+oGE2s!v7*nEkJX%p*tV!?fgxYpfsI0T!_MC6Zs@I7C^2{FPokC}b5gZB0Wy1s1qaz*)PbSs-2+s+ zA@WFD71R&Z5X~|ky&P3s=j+s(4%r#MF21X+Xw(6BYV^kyQ5n+b+U_)2FQlT>#xAYe zdGW0oMGJ2 za97T~ip11YJAJL_S~~kE%O;>A2!&+Fp9^dx6vgUEk2X!qB<$Kz02=48uN>3O=rPwF z!uP)1FZW~@XSnEiGepS?m{#lnq=z6XxE zZF8lMzop4=4jdG#Lri0NUsRmEGTbS9@@%(TX{$`*R9x%G16_*T801C{hBy z+d8gIYnSlC&%KRM=QnG=VUNN`$vdtbu7M4Sfgp_3xaCdpB18k~p0<5sGHmOC$n6!4 z$iJ7{+FRh<$$7TT59A2Q&swU_t@Ij2L`T&W*zK75<)L1AdM`ma*56SN*l{Ykp9Vk~ zAymOXmSS)XjauBHmbQac#R|r0lWN}mwdvURvO4S@O~wwuA?0J%g&98H6uw>aOX^^!^R<_jKy=uGkQ(|x^tIxC&erB?he{*=M zvhH~-SpzjXxO}w^Qi$=tcJN}aZAE$s(|UH!jdgHV=*)8b<6fq@X?zy#r$+Vjqs{P- z`t2LGvjj^3Fi6RquhM&-wMDOAM5f>|V-&jo9anBGHZv$xX+k*LZ}tugJgiYWk0tAO z_QQJy_qp3PZ55lu%BUjb`KMeCw-&oBVvbgS@)w`+PNn zSmSzpwV|W5$#mnXT%@R}PF8O0s8MI&1oA%49&Cl|5TXU{`NS!aZng#27XAl7T;!5m zgY8C<#GpkwzI&(x3AT(yozID|K_ZR|=DxkQ!QHSruwPf3dB+9pTqCShharJ4`%MHz zK9{+2zQ^(e_CacvpmT5HO&jy;op<2S{$FyW|*~Xj~Y&QENL!Wii-*p z>}zwh36bZJ|FZZ3`_@P2i#um*#fK)hWG)Si^=n)4%D_j9H@Rq)SaM^UpP}BQRoHB8 zWc;Q9H26(XC7!2f#$WLOn+!e?*4C;!L)t8tM`HU5~!yTG14l-rY++2%ME6Z5AirozsRO*Jj$3m>A zix|`C_X3Knky&e7z0*c5Wab%hudS=0vMy72xCU`np|lx zV_21I)Xf^Cx>fte9cGe2z5Y7?S+JM{mbKE^TA5tdlq?&=CkDeh(0NjNtZg^xXu8+L zrU8ISJY<7hE&c4CRkTk3-8*u;*B@^zb^pq`EAEcO?XPdQceHA`iT)&Ic&jPI)rKgow@ zOa-n_{Y{L-reT@-)wKM&jehqt0U<@&VbyToDI=7Qzejs!^#bfqz?Z6~GmlfYL5~k5 zX7(or^=e18qe^)A@6@Y-c)nRq+WyAP740QIigrXwR>tgjpU6)(PsvJ!PrcfYzq+={ zcv4na&kLj0lg~q9Zc&94DYNW(ywcNn;Zp&zUq32Qb;LeiO%495ou@EJYuKO1AEZF6+SsYT~IH@@{VYwWtw9eKfK6lUP>sAI0u050!N@xgUI9$tA zd~&`9soueRpZhPBL@ivvdW$yye(m##KRs|rVd{|kEI4B_FT#X3 znrAx^|7U{k&@Fp!f`zrmwB*V7D1J{DwOI8@jXUa&AXU((v#p6f!8tcnyr7QaklckX z?=MsNxw(GhITU%dY1D`l1qNXA;ZOMM#LTjw)4c*s)AV)Y+T`?Kh*fE;a`(YWN)+9b zurs*PUB`OQ&mXHoJz-&SP0XH@53pYz>Mh>-GTyM9B~LD&>RN;0xSU+40|h8=^k8v6 zMT_TMh8hObo$vc!!K|&5m>?ayA4Mv|YfctuA{#Pj-Lv8rf3U3Iy+@zDa*yS64e}*~ zGi5j~^Au-@cs3`VoG<`~i84zDTrUCsY%0hMw9xM-`;as5{?JNk3QRT-oj0;!HZ--(?FZjXr~^c>Yb5@ zct)KYu14^ge6?L~+7-@;U;A#^bWdVg1{Udltr9r-J1>ig7h}E8ZP}$@1XLGq!WY0qSxHM*Ch`YfI!~EV9b(zA+D!35WTOOt=h}fr}HV51M ztexNfsh(=9MQBJdUH?mhP5SW5qW&yf7WDfoLQwTFqrY*j>4@8p3Tga?5@wr=Lp`{?siU#~Smd%3i3dH=mX^v7qJcH0p24M15La~$v}(v(Dh4Mb1~8!2s0jl@99g$Z>!j3cVV%9 zxwY2M$wlsl3w7?bmqqvqj5>7KjT+;sCB;bD3kl?8lwSrsMP;jZzGnqE+h~Tv)Tp1> zm-Fe+DcWX2ol}93=4pLR=0AO%#)>Yf7^(I?9I6zN_ue(?9B>GWrF+j9+$UJc<8P2| zR>*RzeNMs|0|3MYeK}@knnK)XO@Al0FXf`|(l4}$@c`3JilS;N--5 zmNvJPm1k)Y69A@>e~(>3+jbqsF)$2B{zm1cx<=6eha#~L;^UMb#JoT1ib+-4^6#PU zXZ@Y4&4l8nK+0rjUo;%9ZlwkWZpPAq)1s1Edd<);%U$ZGDHcA`MqG%B1lwzNR&I`y z;+mf7`!v9SaO|(RIA!g#svdaqF zDhO!!D~c+pbhvC2cK}-qw?sYK6o`KeS6PQ zvEgj@`+0S3%~{qffQFf(s64pdu=+u2JC6Ie*IIp{?RYl0UzGZNa6hMStX+nFox{+& zUiGk6dx59&v#cV3hQhyAxu2bRzgVAa!ex<~;e4Iz{}~MTeod$J`1#>nnN(mtm9eCA zJow7rQSSvqPlG%(`aeRoWR9z-g8Hjl=>P-Jn_?KZ+kL>lMB z#Y&6uQ^V}*Jr{NFIjP@aA)tHT6qzS%t7}t72W{XrZ4L14!?5b%fbCz_+(q zfTiJ2Mv^2DQw7S;GcZmzFZ+$Rv+>6}mlC>82di5d0K$QysI*GQ{NiXd+SbqecNo$| zscd8X`{rPo3q4*oU=TBrFZdkwXE?o2+sttM))PkMl^Oj{Oe!TeNL-Fxrdz8hY`9=p z3OD8LpQO9t5o|R^g^X-uENv&Bk%%ul>Gl{LgkPU=M3`}r@y+`f*@nh7v3b%&;;veu zJ!9FXAd*45Mo#Hk3z1|2T^j9W#OwX{24dRo@`Z2UPb(AkH24ZfL+s7ye8 zp$uiDgg7VqF}d>gPQgdea_OrZbxK|SEig#2e#nq;scV!lAS539D}wR;sc+7(wcgFC zvLLHh+CHc03y@h>JwW3Q&t(>Vp(A&vHZ!RP5{~KBVy9Z<#F+YThuOAmaFGrBCt5 zd@BA;+l`T0TU=+s^zDfwBvh$quT3loh&xpjy`j=ErMJ&~#N&i3?^388BOc8Wb{uv| zzFw88r(@xcc8wAOaxTP1Mlo(5yx)l@pU)#6o|)3`oZ$BtIo6L&ew*8kXM=9jh1wPf zJ>4^R?-RQ#zK_S~sh*ugV9*l~?BU z|6)=h_@y;q_lE>O*<~}Nwnv-Ifx*97&s&pV1OTr}QHwN5?P_XwXbIuk>8-XuXppJNhwbibefS zQf0^<`Zb*SHxEqk*P(7PZX(syNH8r=)m&Lsx)^jg|J9|kH~5Y3FLTH7KVX3f`&U6) z3T?`;yBPNCe4CgWiIX<+3XLP?Jk2$LE_`|NO<&{`0rh4_vy&;0&g##A8+Ggtd;cY5%5(FtkUKYPb)~ z2C7@n0jFW=SHOo^Ai2v$!a{kcDdC{9rt_a65-!ObFsw``K|c++(LHU>S=P(IrZ~@K ze*V9b#@xq~{eS0(+b!XPKXuxSXOjbjR+OZWQ2zi)jRsrjdS4JYQ39tRrT}ks#ia1{ zeEyAew5TvYe&Dftx@#0IaLD^NsCp2Fu5IuJj}_=j5sTQ-;Xp|6b^#+T?T5LmsZDFq zm(LF`m&j_6bJcdIy)_GD2ePE{h@Om4Jz)!yVC!FO9|#b8#OYs z#i_Ns%Ogb6QHSEZYS$<#;7~U9S3F~|9{$_?>pjHfFfk09HOr*z1tJBFB2S_qKOr-w z!E)W7B!1}{{;F;z2Tqad^BUo>#p4s_zU?g>O4xpTiqSpezu;{Q10l5nR66O2-m=c| zf8*i1ARhKD(4L9xswy6H=NFLv7U3F22~bk~jfZt>M2chyq~s`~{bZJv7NEJnb6JR= z@G-52vtpDKvSGwaGXHg}PLogfhDfR4f-F74ObFn+KByN+=F!-^f zH2~$On+1HdTZ?eEW-{w{K`5ILB&Q4+Ib({VsD{d^(kP9FAfTm9HOqP)*bM&H3!92T zDn~r=8oxOV`~LI7O(NElFW{KpT2zmwlRImX!(E)Z@7kVc|QIe+`9em=y;zC#~bVfA3xK&_tMDRf0mU6 z*o5&Ul0wQO75_`(`LF6HY1Fs+&8`0vQjCKQLEC`J1ZU52N z`ckR}VP*t0H~AQahEhH!HRLX&hNCa-L7SK;Fm0tMs-i+JK|c!J@Q_}6x$BjHtG*A$ zKZw3bL2lNBz9UaUqM4))=HJ(|=KuEDPBc;vBh<{lVERw=lOFX0=No)C!J~cA%Cg>O zPj{W7AM;PBgEq{X`|EX0q~p=lg0+7ym>mkQIsWth2H-;*9=UwCvMn%`*+BKzBx#F8Dy#ULnKt2MqC#j zhC_%*9ZpgqR&|!Zw*$?v)2jDEKR4V+=NKwUp7u7~*x96o*S0Nnv`vipApP?Kz0~*J zTd9Z>dEB0G!weT0VE;xP>`a@k7|w(}&^>!baDWp0wMC@5FWjQFjW2YZl!)rN#=^{& z8u$B2cuaVzvnT|8cnDveZnTYg@+>kfhe*&f?71Mrt?lcc{H%sQgvcHH3v67w0*~A; z?e*-&%JMI~#~=fV2rZ$^q2fO@pN8<8VkcaWDK{0_x2Cp)g-53$BNagMlX#QkFm}u#|E2~l#gmCDreVo zAw}(|Y@wgLJ0yXRi*)Sdoyo4%)Ksh*Qt!~~d$dilfBoSwsR%zf_FbXrep3Fz`h|IS zE6YN4L}aXH{g{dLw|>VCYQZ7<4zos|#CJr${njT%ti^gSrxBLr0qw_ue*-r`CX%jb zQK{L$YH_@~2J>4uJc8lJzgpQb9?DaGw{18YfWOXGV_X8s$74m&2P&r$qnmcYOPz2l z6mh5XY|&(WBw(p-uM6FHv~|*g^{GRmPIm|j-(u4C_qeRyH0`X-JoKb#Oo7hSkc@2SA#+bZ;;&Dn9bPmI>wSBcDQJCiPVz0l@|A^HPT)A{CO}-_a{lLTe_huv@wVObjTmgHXC|G%!q-xa#biX#>uf3sKenBeGPTH>4|B#) dg}}Ch^VU>KL@A}NlMXv--q*cXqVn|h{{XGl+_wM# literal 11403 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

LP2V;aI)n~(kVq8*I$T{ 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