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

geosolutions-it / MapStore2 / 5278726914

pending completion
5278726914

push

github

web-flow
[Backport c047-2023.01.xx] Fix #9025 & #9193 WMS caching with custom scales (#9222)

* #9025 WMS caching with custom scales (projection resolutions strategy) (#9168)

* Fix #9025 WMS caching with custom scales (custom resolutions strategy from WMTS) (#9184)

---------

Co-authored-by: Lorenzo Natali <lorenzo.natali@geosolutionsgroup.com>

* Fix #9025 Available tile grids popup always reports mismatch in geostories and dashboards (#9196)

* Fix #9193 Add a cache options checks/info also for default WMS tile grid (#9195)

* Fix #9193 failing test (#9207)

* #9025 add caching options to wms background settings (#9213)

* fix failing tests

---------

Co-authored-by: Lorenzo Natali <lorenzo.natali@geosolutionsgroup.com>

27722 of 42099 branches covered (65.85%)

133 of 133 new or added lines in 12 files covered. (100.0%)

34374 of 43748 relevant lines covered (78.57%)

42.93 hits per line

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

84.26
/web/client/api/WMTS.js
1
/**
2
 * Copyright 2016, 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
import axios from '../libs/ajax';
9

10
import { getConfigProp } from '../utils/ConfigUtils';
11
import urlUtil from 'url';
12
import assign from 'object-assign';
13
import xml2js from 'xml2js';
14

15
const capabilitiesCache = {};
1✔
16

17
import { castArray } from 'lodash';
18
import { getEPSGCode } from '../utils/CoordinatesUtils';
19

20
import {
21
    getOperations,
22
    getOperation,
23
    getRequestEncoding,
24
    getDefaultStyleIdentifier,
25
    getDefaultFormat
26
} from '../utils/WMTSUtils';
27

28
const parseUrl = (url) => {
1✔
29
    const parsed = urlUtil.parse(url, true);
12✔
30
    return urlUtil.format(assign({}, parsed, {search: null}, {
12✔
31
        query: assign({
32
            SERVICE: "WMTS",
33
            VERSION: "1.0.0",
34
            REQUEST: "GetCapabilities"
35
        }, parsed.query)
36
    }));
37
};
38

39
const searchAndPaginate = (json, startPosition, maxRecords, text, url) => {
1✔
40
    const root = json.Capabilities.Contents;
5✔
41
    const operations = getOperations(json);
5✔
42
    const requestEncoding = getRequestEncoding(json);
5✔
43
    const TileMatrixSet = root.TileMatrixSet && castArray(root.TileMatrixSet) || [];
5!
44
    let SRSList = [];
5✔
45
    let len = TileMatrixSet.length;
5✔
46
    for (let i = 0; i < len; i++) {
5✔
47
        SRSList.push(getEPSGCode(TileMatrixSet[i]["ows:SupportedCRS"]));
5✔
48
    }
49
    const layersObj = root.Layer;
5✔
50
    const layers = castArray(layersObj);
5✔
51
    const filteredLayers = layers
5✔
52
        .filter((layer) => !text || layer["ows:Identifier"].toLowerCase().indexOf(text.toLowerCase()) !== -1 || layer["ows:Title"] && layer["ows:Title"].toLowerCase().indexOf(text.toLowerCase()) !== -1);
19✔
53
    return {
5✔
54
        numberOfRecordsMatched: filteredLayers.length,
55
        numberOfRecordsReturned: Math.min(maxRecords, filteredLayers.length),
56
        nextRecord: startPosition + Math.min(maxRecords, filteredLayers.length) + 1,
57
        records: filteredLayers
58
            .filter((layer, index) => index >= startPosition - 1 && index < startPosition - 1 + maxRecords)
15✔
59
            .map((layer) => assign({}, layer, {
12✔
60
                SRS: SRSList,
61
                TileMatrixSet,
62
                // Only KVP is supported by MapInfo, for the moment. TODO: Support single layer's InfoFormat
63
                queryable: !!getOperation(operations, "GetFeatureInfo", "KVP"),
64
                requestEncoding: requestEncoding,
65
                style: getDefaultStyleIdentifier(layer), // it must be collected because it can be used in RESTful version to create the path
66
                format: getDefaultFormat(layer),
67
                GetTileURL: getOperation(operations, "GetTile", requestEncoding),
68
                capabilitiesURL: url
69
            }))
70
    };
71
};
72

73
const Api = {
1✔
74
    parseUrl,
75
    getRecords: function(url, startPosition, maxRecords, text) {
76
        const cached = capabilitiesCache[url];
5✔
77
        if (cached && new Date().getTime() < cached.timestamp + (getConfigProp('cacheExpire') || 60) * 1000) {
5✔
78
            return new Promise((resolve) => {
1✔
79
                resolve(searchAndPaginate(cached.data, startPosition, maxRecords, text, url));
1✔
80
            });
81
        }
82
        return axios.get(parseUrl(url)).then((response) => {
4✔
83
            let json;
84
            xml2js.parseString(response.data, {explicitArray: false}, (ignore, result) => {
4✔
85
                json = result;
4✔
86
            });
87
            capabilitiesCache[url] = {
4✔
88
                timestamp: new Date().getTime(),
89
                data: json
90
            };
91
            return searchAndPaginate(json, startPosition, maxRecords, text, url);
4✔
92
        });
93
    },
94
    getCapabilities: (url, options) => {
95
        if (options?.force && capabilitiesCache[url]) {
8!
96
            delete capabilitiesCache[url];
×
97
        }
98
        const cached = capabilitiesCache[url];
8✔
99
        if (cached && new Date().getTime() < cached.timestamp + (getConfigProp('cacheExpire') || 60) * 1000) {
8!
100
            return new Promise((resolve) => {
×
101
                resolve(cached.data);
×
102
            });
103
        }
104
        return axios.get(parseUrl(url)).then((response) => {
8✔
105
            let json;
106
            xml2js.parseString(response.data, {explicitArray: false}, (ignore, result) => {
7✔
107
                json = result;
7✔
108
            });
109
            capabilitiesCache[url] = {
7✔
110
                timestamp: new Date().getTime(),
111
                data: json
112
            };
113
            return json;
7✔
114
        });
115
    },
116
    textSearch: function(url, startPosition, maxRecords, text) {
117
        return Api.getRecords(url, startPosition, maxRecords, text);
2✔
118
    },
119
    reset: () => {
120
        Object.keys(capabilitiesCache).forEach(key => {
16✔
121
            delete capabilitiesCache[key];
11✔
122
        });
123
    }
124
};
125

126
/**
127
 * Return tileMatrixSets, tileMatrixSetLinks, tileGrids, styles and formats of from WMTS capabilities for a specific layer
128
 * @param {string} url wmts endpoint url
129
 * @param {string} layerName layer name
130
 * @param {object} options optional configuration
131
 * @param {boolean} options.force if true delete the cache for this url and force the request
132
 * @return {promise}
133
 */
134
export const getLayerTileMatrixSetsInfo = (url, layerName = '', options) => {
1!
135
    return Api.getCapabilities(url, { force: options?.force })
7✔
136
        .then((response) => {
137
            const layerParts = layerName.split(':');
6✔
138
            const layers = castArray(response?.Capabilities?.Contents?.Layer || []);
6!
139
            const wmtsLayer = layers.find((layer) => layer['ows:Identifier'] === layerParts[1] || layer['ows:Identifier'] === layerName);
6!
140
            const tileMatrixSetLinks = castArray(wmtsLayer?.TileMatrixSetLink || []).map(({ TileMatrixSet }) => TileMatrixSet);
11!
141
            const tileMatrixSets = castArray(response?.Capabilities?.Contents?.TileMatrixSet || []).filter((tileMatrixSet) => tileMatrixSetLinks.includes(tileMatrixSet['ows:Identifier']));
12!
142
            const tileGrids = tileMatrixSets.map((tileMatrixSet) => {
6✔
143
                const origins = tileMatrixSet.TileMatrix.map((tileMatrixLevel) => tileMatrixLevel.TopLeftCorner.split(' ').map(parseFloat));
33✔
144
                const tileSizes = tileMatrixSet.TileMatrix.map((tileMatrixLevel) => [parseFloat(tileMatrixLevel.TileWidth), parseFloat(tileMatrixLevel.TileHeight)]);
33✔
145
                const firstOrigin = origins[0];
11✔
146
                const firsTileSize = tileSizes[0];
11✔
147
                const isSingleOrigin = origins.every(entry => firstOrigin[0] === entry[0] && firstOrigin[1] === entry[1]);
33✔
148
                const isSingleTileSize = tileSizes.every(entry => firsTileSize[0] === entry[0] && firsTileSize[1] === entry[1]);
33✔
149
                return {
11✔
150
                    id: tileMatrixSet['ows:Identifier'],
151
                    crs: getEPSGCode(tileMatrixSet['ows:SupportedCRS']),
152
                    scales: tileMatrixSet.TileMatrix.map((tileMatrixLevel) => parseFloat(tileMatrixLevel.ScaleDenominator)),
33✔
153
                    ...(isSingleOrigin ? { origin: firstOrigin } : { origins }),
11✔
154
                    ...(isSingleTileSize ? { tileSize: firsTileSize } : { tileSizes })
11!
155
                };
156
            });
157
            return {
6✔
158
                tileMatrixSets,
159
                tileMatrixSetLinks,
160
                tileGrids,
161
                styles: castArray(wmtsLayer?.Style || []).map((style) => style['ows:Identifier']),
6!
162
                formats: castArray(wmtsLayer?.Format || [])
6!
163
            };
164
        });
165
};
166

167
export default Api;
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