• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geosolutions-it / MapStore2 / 15000047957

13 May 2025 03:02PM UTC coverage: 76.901% (-0.09%) from 76.993%
15000047957

Pull #10515

github

web-flow
Merge f2dcb1e61 into d8d2cc134
Pull Request #10515: #10514 - FeatureEditor filter by geometric area

30975 of 48268 branches covered (64.17%)

27 of 42 new or added lines in 6 files covered. (64.29%)

532 existing lines in 55 files now uncovered.

38583 of 50172 relevant lines covered (76.9%)

35.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.29
/web/client/components/map/openlayers/plugins/WMTSLayer.js
1
/**
2
 * Copyright 2017, GeoSolutions Sas.
3
 * All rights reserved.
4
 *
5
 * This source code is licensed under the BSD-style license found in the
6
 * LICENSE file in the root directory of this source tree.
7
 */
8

9
import Layers from '../../../../utils/openlayers/Layers';
10
import isEqual from 'lodash/isEqual';
11
import castArray from 'lodash/castArray';
12
import head from 'lodash/head';
13
import last from 'lodash/last';
14
import axios from '../../../../libs/ajax';
15
import { proxySource } from '../../../../utils/openlayers/WMSUtils';
16
import {getCredentials, addAuthenticationParameter} from '../../../../utils/SecurityUtils';
17
import * as WMTSUtils from '../../../../utils/WMTSUtils';
18
import CoordinatesUtils from '../../../../utils/CoordinatesUtils';
19
import MapUtils from '../../../../utils/MapUtils';
20
import { isVectorFormat} from '../../../../utils/VectorTileUtils';
21
import urlParser from 'url';
22
import { isValidResponse } from '../../../../utils/WMSUtils';
23

24
import { get, getTransform } from 'ol/proj';
25
import { applyTransform, getIntersection } from 'ol/extent';
26
import TileLayer from 'ol/layer/Tile';
27
import VectorTileLayer from 'ol/layer/VectorTile';
28
import WMTS from 'ol/source/WMTS';
29
import VectorTile from 'ol/source/VectorTile';
30
import WMTSTileGrid from 'ol/tilegrid/WMTS';
31
import MVT from 'ol/format/MVT';
32
import GeoJSON from 'ol/format/GeoJSON';
33
import TopoJSON from 'ol/format/TopoJSON';
34

35
import { getStyle } from '../VectorStyle';
36
import { creditsToAttribution } from '../../../../utils/LayersUtils';
37

38
const OL_VECTOR_FORMATS = {
1✔
39
    'application/vnd.mapbox-vector-tile': MVT,
40
    'application/json;type=geojson': GeoJSON,
41
    'application/json;type=topojson': TopoJSON
42
};
43

44
function getWMSURLs(urls, requestEncoding) {
45
    return urls.map((url) => requestEncoding === 'REST' ? url : url.split("\?")[0]);
24✔
46
}
47

48
const tileLoadFunction = (options) => (image, src) => {
1✔
UNCOV
49
    const storedProtectedService = options.security ? getCredentials(options.security?.sourceId) : {};
×
UNCOV
50
    axios.get(src, {
×
51
        headers: {
52
            "Authorization": `Basic ${btoa(storedProtectedService.username + ":" + storedProtectedService.password)}`
53
        },
54
        responseType: 'blob'
55
    }).then(response => {
UNCOV
56
        if (isValidResponse(response)) {
×
UNCOV
57
            image.getImage().src = URL.createObjectURL(response.data);
×
58
        } else {
UNCOV
59
            image.getImage().src = null;
×
UNCOV
60
            console.error("error: " + response.data);
×
61
        }
62
    }).catch(e => {
UNCOV
63
        console.error(e);
×
64
    });
65
};
66

67
const createLayer = options => {
1✔
68
    // options.urls is an alternative name of URL.
69
    // WMTS Capabilities has "RESTful"/"KVP", OpenLayers uses "REST"/"KVP";
70
    let requestEncoding = options.requestEncoding === "RESTful" ? "REST" : options.requestEncoding;
23✔
71
    const urls = getWMSURLs(castArray(options.url), requestEncoding);
23✔
72
    const srs = CoordinatesUtils.normalizeSRS(options.srs || 'EPSG:3857', options.allowedSRS);
23✔
73
    const projection = get(srs);
23✔
74
    const metersPerUnit = projection.getMetersPerUnit();
23✔
75
    const { tileMatrixSetName, tileMatrixSet, matrixIds } = WMTSUtils.getTileMatrix(options, srs);
23✔
76
    const scales = tileMatrixSet && tileMatrixSet?.TileMatrix.map(t => Number(t.ScaleDenominator));
24✔
77
    const mapResolutions = MapUtils.getResolutions();
23✔
78
    /*
79
     * WMTS assumes a DPI 90.7 instead of 96 as documented in the WMTSCapabilities document:
80
     * "The tile matrix set that has scale values calculated based on the dpi defined by OGC specification
81
     * (dpi assumes 0.28mm as the physical distance of a pixel)."
82
     */
83
    const scaleToResolution = s => s * 0.28E-3 / metersPerUnit;
24✔
84
    const matrixResolutions = options.resolutions || scales && scales.map(scaleToResolution);
23✔
85
    const resolutions = matrixResolutions || mapResolutions;
23✔
86

87
    /* - enu - the default easting, north-ing, elevation
88
    * - neu - north-ing, easting, up - useful for "lat/long" geographic coordinates, or south orientated transverse mercator
89
    * - wnu - westing, north-ing, up - some planetary coordinate systems have "west positive" coordinate systems
90
    */
91
    const switchOriginXY = projection.getAxisOrientation().substr(0, 2) === 'ne';
23✔
92
    const origins = tileMatrixSet
23✔
93
        && tileMatrixSet.TileMatrix
94
        && tileMatrixSet.TileMatrix
95
            .map(({ TopLeftCorner } = {}) => TopLeftCorner && CoordinatesUtils.parseString(TopLeftCorner))
24!
96
            .map(({ x, y } = {}) => switchOriginXY ? [y, x] : [x, y]);
24!
97

98
    const sizes = tileMatrixSet
23✔
99
        && tileMatrixSet.TileMatrix
100
        && tileMatrixSet.TileMatrix
101
            .map(({ MatrixWidth, MatrixHeight } = {}) => ([parseInt(MatrixWidth, 10), parseInt(MatrixHeight, 10)]));
24!
102

103
    const tileSizes = tileMatrixSet
23✔
104
        && tileMatrixSet.TileMatrix
105
        && tileMatrixSet.TileMatrix
106
            .map(({ TileWidth, TileHeight } = {}) => ([parseInt(TileWidth, 10), parseInt(TileHeight, 10)]));
24!
107

108
    // if the layer comes with bbox, it can be used as extent to define the tile source's extent (and avoid to load tiles out of this area). Otherwise the default extent of the projection will be used.
109
    const bbox = options.bbox;
23✔
110
    const layerExtent = bbox
23✔
111
        ? applyTransform([
112
            parseFloat(bbox.bounds.minx),
113
            parseFloat(bbox.bounds.miny),
114
            parseFloat(bbox.bounds.maxx),
115
            parseFloat(bbox.bounds.maxy)
116
        ], getTransform(bbox.crs, options.srs))
117
        : undefined;
118
    // based on this PR https://github.com/openlayers/openlayers/pull/11532 on ol
119
    // the extent has effect to the tile ranges
120
    // we should skip the extent if the layer does not provide bounding box
121
    let extent = layerExtent && getIntersection(layerExtent, projection.getExtent());
23✔
122
    const queryParameters = {};
23✔
123
    urls.forEach(url => addAuthenticationParameter(url, queryParameters, options.securityToken));
24✔
124
    const queryParametersString = urlParser.format({ query: { ...queryParameters } });
23✔
125

126
    // TODO: support tileSizes from  matrix
127
    const TILE_SIZE = 256;
23✔
128

129
    // Temporary fix for https://github.com/openlayers/openlayers/issues/8700 . It should be solved in OL 5.3.0
130
    // it's exclusive so the map lower resolution that draws the image in less then 0.5 pixels have to be the maxResolution
131
    const maxResolution = options.maxResolution || last(mapResolutions.filter((r = []) => resolutions[0] / r * TILE_SIZE < 0.5));
682!
132
    const format = (options.availableFormats || []).indexOf(options.format) !== -1 && options.format
23!
133
        || !options.availableFormats && options.format || 'image/png';
134
    const isVector = isVectorFormat(format);
23✔
135

136
    const wmtsOptions = {
23✔
137
        requestEncoding,
138
        urls: urls.map(u => u + queryParametersString),
24✔
139
        layer: options.name,
140
        version: options.version || "1.0.0",
46✔
141
        matrixSet: tileMatrixSetName,
142
        ...(options.credits && {attributions: creditsToAttribution(options.credits)}),
24✔
143
        format,
144
        style: options.style || "",
45✔
145
        tileGrid: new WMTSTileGrid({
146
            origins,
147
            origin: !origins ? [20037508.3428, -20037508.3428] : undefined, // Either origin or origins must be configured, never both.
23✔
148
            resolutions,
149
            matrixIds: WMTSUtils.limitMatrix((matrixIds || WMTSUtils.getDefaultMatrixId(options) || []).map((el) => el.identifier), resolutions.length),
496!
150
            sizes,
151
            extent,
152
            tileSizes,
153
            tileSize: !tileSizes && (options.tileSize || [TILE_SIZE, TILE_SIZE])
53✔
154
        }),
155
        wrapX: true
156
    };
157
    if (options.security?.sourceId) {
23!
UNCOV
158
        wmtsOptions.urls = urls.map(url => proxySource(options.forceProxy, url));
×
UNCOV
159
        wmtsOptions.tileLoadFunction = tileLoadFunction(options);
×
160
    }
161

162
    const wmtsSource = new WMTS(wmtsOptions);
23✔
163
    const Layer = isVector ? VectorTileLayer : TileLayer;
23✔
164
    const wmtsLayer = new Layer({
23✔
165
        msId: options.id,
166
        opacity: options.opacity !== undefined ? options.opacity : 1,
23!
167
        zIndex: options.zIndex,
168
        minResolution: options.minResolution,
169
        maxResolution: options.maxResolution < maxResolution ? options.maxResolution : maxResolution,
23!
170
        visible: options.visibility !== false,
171
        source: isVector
23✔
172
            ? new VectorTile({
173
                ...wmtsOptions,
174
                format: new OL_VECTOR_FORMATS[options.format]({
175
                    dataProjection: srs
176
                }),
UNCOV
177
                tileUrlFunction: (...args) => wmtsSource.tileUrlFunction(...args)
×
178
            })
179
            : wmtsSource
180
    });
181

182
    if (isVector) wmtsLayer.setStyle(getStyle(options));
23✔
183

184
    return wmtsLayer;
23✔
185
};
186

187
const updateLayer = (layer, newOptions, oldOptions) => {
1✔
188
    if (oldOptions.securityToken !== newOptions.securityToken
5✔
189
    || oldOptions.srs !== newOptions.srs
190
    || oldOptions.format !== newOptions.format
191
    || oldOptions.style !== newOptions.style
192
    || oldOptions.credits !== newOptions.credits) {
193
        return createLayer(newOptions);
4✔
194
    }
195
    if (oldOptions.minResolution !== newOptions.minResolution) {
1!
196
        layer.setMinResolution(newOptions.minResolution === undefined ? 0 : newOptions.minResolution);
1!
197
    }
198
    if (oldOptions.maxResolution !== newOptions.maxResolution) {
1!
199
        layer.setMaxResolution(newOptions.maxResolution === undefined ? Infinity : newOptions.maxResolution);
1!
200
    }
201
    if (!isEqual(oldOptions.security, newOptions.security)) {
1!
UNCOV
202
        layer.getSource().setTileLoadFunction(tileLoadFunction(newOptions));
×
203
    }
204
    return null;
1✔
205
};
206

207
const hasSRS = (srs, layer) => {
1✔
208
    const { tileMatrixSetName, tileMatrixSet } = WMTSUtils.getTileMatrix(layer, srs);
39✔
209
    if (tileMatrixSet) {
39✔
210
        return CoordinatesUtils.getEPSGCode(tileMatrixSet["ows:SupportedCRS"]) === srs;
25✔
211
    }
212
    return tileMatrixSetName === srs;
14✔
213
};
214

215
const compatibleLayer = layer =>
1✔
216
    head(CoordinatesUtils.getEquivalentSRS(layer.srs || 'EPSG:3857').filter(srs => hasSRS(srs, layer))) ? true : false;
39!
217

218

219
Layers.registerType('wmts', { create: createLayer, update: updateLayer, isCompatible: compatibleLayer });
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc