• 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

95.45
/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
import { getDefaultUrl } from '../utils/URLUtils';
20

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

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

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

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

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

174
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