Skip to content

[Proposal] WMS Tile Grid Settings

Lorenzo Natali edited this page Mar 30, 2023 · 12 revisions

Overview

The goal of this improvement is to simplify the current MapStore project creation and update system.

Proposed by

  • Lorenzo Natali (author and writer of the proposal)

Assigned to Release

The proposal is for 2023.02.00 or next

State

  • Under Discussion
  • In Progress
  • Completed
  • Rejected
  • Deferred

Motivation

MapStore, and in particular the Openlayers implementation of Tiled WMS, generates the a tile grid based to the map resolutions. This limits the possibility to use the tile caches from GeoWebCache when the map have custom resolutions. Also creating custom gridsets causes problems because the current implementation allows to define a fixed origin (top-left corner) for the layer, while GeoWebCache generates the tilegrid by starting from the bottom-left corner. In case the resolutions are not exponential (that generate tiles of power of 2, that are integer), this means that the top-left corner vary on every zoom level.

Moreover, in order to pass to a more modern web mapping (e.g. with fractional scales or ready for the 3D modes), we have to bind the tile grid system to detouch the tile grid generation strategy from the map resolutions and allow to configure them accordingly with the source.

Original issue proposed here:

https://github.com/geosolutions-it/MapStore2/issues/9025

Proposal

We should implement some improvement to MapStore in order to:

  • Use by default the exponential generation of tilegrid, that guarantees a better alignment to default gridsets generated by GeoWebCache
  • Allow to define from UI and store in the Map JSON:
    • default tilegrid settings for WMS source (advanced layer settings)
    • layer tilegrid settings for WMS Layer (layer settings)

Here a sample about Tile grid settings button in WMS Source

image

The same will be present in layer settings.

About 3D

Actually the 3D map relies on EPSG:4326. We can configure a proper custom TilingScheme but using the default for now is enough and it is not part of the estimation.

Things to check

  • Printing

Tasks Daft

  • Properly define new defaults (while deveolping): 1pt
  • Change WMSLayer in OpenLayers to use a tileGrid object: 2pt
  • Extract/export proper functionalities from MapUtils (e.g. get resolutions for map): 1pt
  • UI to insert configurations in WMS source and WMS Layer tile grid configuration (near tile-sizes, a new button, should allow to completely customize tile grid in a dialog):
    • allow to define origin(s), tileSize(s),resolutions (or scales) manually: 2-3pt
    • Reuse the dialog in both layer properties and WMS source: 1pt
    • Optional: retrieve them from WMTS (GeoServer only, helps to auto-load) 2-3pt
  • Optional: In map settings, allow to configure the scales/resolutions of the map manually: 2-3pt
  • Testing with various CRS/Tile grids: (also developer need to test) 3pt

Temptative Implementation

This (missing export getResolutionsForProjection from MapUtils) seems to work, but generates some MISS for first testing, to verify

diff --git a/web/client/components/map/openlayers/plugins/WMSLayer.js b/web/client/components/map/openlayers/plugins/WMSLayer.js
index 87ad6931c..516b376dc 100644
--- a/web/client/components/map/openlayers/plugins/WMSLayer.js
+++ b/web/client/components/map/openlayers/plugins/WMSLayer.js
@@ -20,7 +20,7 @@ import { getConfigProp } from '../../../../utils/ConfigUtils';
 
 import {optionsToVendorParams} from '../../../../utils/VendorParamsUtils';
 import {addAuthenticationToSLD, addAuthenticationParameter, getAuthenticationHeaders} from '../../../../utils/SecurityUtils';
-import { creditsToAttribution, getWMSVendorParams } from '../../../../utils/LayersUtils';
+import { creditsToAttribution } from '../../../../utils/LayersUtils';
 
 import MapUtils from '../../../../utils/MapUtils';
 import  {loadTile, getElevation as getElevationFunc} from '../../../../utils/ElevationUtils';
@@ -123,7 +123,7 @@ function wmsToOpenlayersOptions(options) {
         TRANSPARENT: options.transparent !== undefined ? options.transparent : true,
         SRS: CoordinatesUtils.normalizeSRS(options.srs || 'EPSG:3857', options.allowedSRS),
         CRS: CoordinatesUtils.normalizeSRS(options.srs || 'EPSG:3857', options.allowedSRS),
-        ...getWMSVendorParams(options),
+        TILED: options.singleTile ? false : (!isNil(options.tiled) ? options.tiled : true),
         VERSION: options.version || "1.3.0"
     }, assign(
         {},
@@ -189,6 +189,64 @@ function getElevation(pos) {
 }
 const toOLAttributions = credits => credits && creditsToAttribution(credits) || undefined;
 
+/**
+ * Generates the tile grid for the layer based on the options.
+ * If the options contains a tileGridStrategy, it will be used to generate the tile grid.
+ * Otherwise, the default tile grid will be used.
+ * @param {object} options layer options. If it contains a `tileGridStrategy`, it will be used to generate the tile grid.
+ * @param {string} options.tileGridStrategy the tile grid strategy to use. Valid values are:
+ * - `default`: the tile grid will be generated by calculating the resolutions based on the current projection extent, exponentially increasing the resolution.
+ * - `map`: the tile grid will be generated using the current map resolutions.
+ * - `custom`: the tile grid will be generated using the resolutions provided in the `resolutions` option. Also `origin` or `origins` can be provided.
+ * @param {map} map the map object
+ * @returns {TileGrid} the tile grid for the layer
+ */
+const generateTileGrid = (options, map) => {
+
+    const strategy = options.tileGridStrategy // use a tileGrid object with strategy object, containing information about the tile grid (by crs)
+        || (options.resolutions && (options.origin || options.origins)
+            ? 'custom'
+            : 'default');
+    const mapSrs = map && map.getView() && map.getView().getProjection() && map.getView().getProjection().getCode() || 'EPSG:3857';
+    const normalizedSrs = CoordinatesUtils.normalizeSRS(options.srs || mapSrs, options.allowedSRS);
+    const tileSize = options.tileSize ? options.tileSize : 256; // TODO check tileSizes
+    // if tileSizes is defined, it overrides tileSize.
+    // tileSizes is an array of tile sizes for each resolution (array of same length of resolutions)
+    // other openlayers TileGrid arguments are
+    const extent = options.extent
+        || get(normalizedSrs).getExtent()
+        || CoordinatesUtils.getExtentForProjection(normalizedSrs).extent;
+    switch (strategy) {
+    case 'map':
+        return new TileGrid({
+            extent: extent,
+            tileSize,
+            resolutions: options.resolutions || MapUtils.getResolutions(),
+            origin: options.origin ? options.origin : [extent[0], extent[1]]
+        });
+    case 'custom':
+        return new TileGrid({
+            extent: extent,
+            // minZoom: options.minZoom, // TODO: check
+            origin: options.origin ? options.origin : [extent[0], extent[1]],
+            origins: options.origins,
+            resolutions: options.resolutions,
+            // sizes: options.sizes, // Number of tile rows and columns for each zoom level. // TODO: check
+            tileSize,
+            tileSizes: options.tileSizes
+
+        });
+    case 'default':
     }
-    const mapSrs = map && map.getView() && map.getView().getProjection() && map.getView().getProjection().getCode() || 'EPSG:3857';
-    const normalizedSrs = CoordinatesUtils.normalizeSRS(options.srs || mapSrs, options.allowedSRS);
-    const extent = get(normalizedSrs).getExtent() || CoordinatesUtils.getExtentForProjection(normalizedSrs).extent;
+
+    const tileGrid = generateTileGrid(options, map);
     const sourceOptions = addTileLoadFunction({
         attributions: toOLAttributions(options.credits),
         urls: urls,
         crossOrigin: options.crossOrigin,
         params: queryParameters,
-        tileGrid: new TileGrid({
-            extent: extent,
-            resolutions: options.resolutions || MapUtils.getResolutions(),
-            tileSize: options.tileSize ? options.tileSize : 256,
-            origin: options.origin ? options.origin : [extent[0], extent[1]]
-        }),
+        tileGrid,
         tileLoadFunction: loadFunction(options, headers)
     }, options);
     const wmsSource = new TileWMS({ ...sourceOptions });