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

geosolutions-it / MapStore2 / 14797665772

02 May 2025 02:55PM UTC coverage: 76.933% (-0.06%) from 76.991%
14797665772

Pull #11067

github

web-flow
Merge 99dcd7f92 into d28bf7035
Pull Request #11067: Fix #10966 basic auth for services

30858 of 48053 branches covered (64.22%)

104 of 172 new or added lines in 24 files covered. (60.47%)

3 existing lines in 3 files now uncovered.

38384 of 49893 relevant lines covered (76.93%)

35.93 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✔
NEW
49
    const storedProtectedService = options.security ? getCredentials(options.security?.sourceId) : {};
×
NEW
50
    axios.get(src, {
×
51
        headers: {
52
            "Authorization": `Basic ${btoa(storedProtectedService.username + ":" + storedProtectedService.password)}`
53
        },
54
        responseType: 'blob'
55
    }).then(response => {
NEW
56
        if (isValidResponse(response)) {
×
NEW
57
            image.getImage().src = URL.createObjectURL(response.data);
×
58
        } else {
NEW
59
            image.getImage().src = null;
×
NEW
60
            console.error("error: " + response.data);
×
61
        }
62
    }).catch(e => {
NEW
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!
NEW
158
        wmtsOptions.urls = urls.map(url => proxySource(options.forceProxy, url));
×
NEW
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
                }),
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!
NEW
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

© 2025 Coveralls, Inc