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

geosolutions-it / MapStore2 / 19933858499

04 Dec 2025 01:54PM UTC coverage: 76.636% (-0.03%) from 76.661%
19933858499

Pull #11648

github

web-flow
Update User Guide - Itinerary plugin (#11768)

* add_11664

* Update docs/user-guide/itinerary.md

Co-authored-by: Suren <dsuren1@gmail.com>

* Update docs/user-guide/itinerary.md

Co-authored-by: Suren <dsuren1@gmail.com>

* Update docs/user-guide/itinerary.md

Co-authored-by: Suren <dsuren1@gmail.com>

* Update docs/user-guide/itinerary.md

Co-authored-by: Suren <dsuren1@gmail.com>

---------

Co-authored-by: Suren <dsuren1@gmail.com>
Pull Request #11648: #11644: Implement dynamic request configurations

32316 of 50296 branches covered (64.25%)

132 of 155 new or added lines in 40 files covered. (85.16%)

283 existing lines in 32 files now uncovered.

40207 of 52465 relevant lines covered (76.64%)

37.89 hits per line

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

95.79
/web/client/api/CSW.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

9
import urlUtil from 'url';
10

11
import { get, head, last, template, isNil, castArray, isEmpty } from 'lodash';
12
import xml2js from 'xml2js';
13
import axios from '../libs/ajax';
14
import { cleanDuplicatedQuestionMarks } from '../utils/ConfigUtils';
15
import { extractCrsFromURN, makeBboxFromOWS, makeNumericEPSG, getExtentFromNormalized } from '../utils/CoordinatesUtils';
16
import WMS from "../api/WMS";
17
import { THREE_D_TILES, getCapabilities } from './ThreeDTiles';
18
import { getDefaultUrl } from '../utils/URLUtils';
19
import { getAuthorizationBasic } from '../utils/SecurityUtils';
20

21
export const parseUrl = (url) => {
1✔
22
    const parsed = urlUtil.parse(getDefaultUrl(url), true);
25✔
23
    return urlUtil.format(Object.assign({}, parsed, { search: null }, {
25✔
24
        query: Object.assign({
25
            service: "CSW",
26
            version: "2.0.2"
27
        }, parsed.query, { request: undefined })
28
    }));
29
};
30

31
const defaultStaticFilter =
32
    `<ogc:Or>
1✔
33
            <ogc:PropertyIsEqualTo>
34
                <ogc:PropertyName>dc:type</ogc:PropertyName>
35
                <ogc:Literal>dataset</ogc:Literal>
36
            </ogc:PropertyIsEqualTo>
37
            <ogc:PropertyIsEqualTo>
38
                <ogc:PropertyName>dc:type</ogc:PropertyName>
39
                <ogc:Literal>http://purl.org/dc/dcmitype/Dataset</ogc:Literal>
40
            </ogc:PropertyIsEqualTo>
41
       </ogc:Or>`;
42

43
const defaultDynamicFilter = "<ogc:PropertyIsLike wildCard='%' singleChar='_' escapeChar='\\'>" +
1✔
44
    "<ogc:PropertyName>csw:AnyText</ogc:PropertyName> " +
45
    "<ogc:Literal>%${searchText}%</ogc:Literal> " +
46
    "</ogc:PropertyIsLike> ";
47
/**
48
 * Create the SortProperty xml definition
49
 * @param {options}
50
 * @param {string} options.name property name to order
51
 * @param {string} options.order order type
52
 * @returns {string} sort by definition in xml format
53
 */
54
const sortByXml = ({ name, order }) => "<ogc:SortBy>" +
1✔
55
"<ogc:SortProperty>" +
56
  `<ogc:PropertyName>${name}</ogc:PropertyName>` +
57
  `<ogc:SortOrder>${order}</ogc:SortOrder>` +
58
"</ogc:SortProperty>" +
59
"</ogc:SortBy>";
60
/**
61
 * Create the GetRecords xml body
62
 * @param {options}
63
 * @param {number} options.startPosition staring index of record in catalog
64
 * @param {number} options.maxRecords maximum number of records returned
65
 * @param {number} options.filterXml a filter definition in xml format
66
 * @param {number} options.sortBy sort by definition in xml format
67
 * @returns {string} get record xml body
68
 */
69
export const cswGetRecordsXml = (options) => '<csw:GetRecords xmlns:csw="http://www.opengis.net/cat/csw/2.0.2" ' +
55✔
70
    'xmlns:ogc="http://www.opengis.net/ogc" ' +
71
    'xmlns:gml="http://www.opengis.net/gml" ' +
72
    'xmlns:dc="http://purl.org/dc/elements/1.1/" ' +
73
    'xmlns:dct="http://purl.org/dc/terms/" ' +
74
    'xmlns:gmd="http://www.isotc211.org/2005/gmd" ' +
75
    'xmlns:gco="http://www.isotc211.org/2005/gco" ' +
76
    'xmlns:gmi="http://www.isotc211.org/2005/gmi" ' +
77
    `xmlns:ows="http://www.opengis.net/ows" service="CSW" version="2.0.2" resultType="results" startPosition="${options.startPosition}" maxRecords="${options.maxRecords}">` +
78
    '<csw:Query typeNames="csw:Record">' +
79
    '<csw:ElementSetName>full</csw:ElementSetName>' +
80
    '<csw:Constraint version="1.1.0">' +
81
    '<ogc:Filter>' +
82
    (options.filterXml || '') +
75✔
83
    '</ogc:Filter>' +
84
    '</csw:Constraint>' +
85
    (options.sortBy || '') +
109✔
86
    '</csw:Query>' +
87
    '</csw:GetRecords>';
88
/**
89
 * Get crs information given a CSW record bounding box
90
 * @param {object} boundingBox CSW bounding box in json format
91
 * @returns {object} { crs, extractedCrs }
92
 */
93
const getCRSFromCSWBoundingBox = (boundingBox) => {
1✔
94
    const crsValue = boundingBox?.$?.crs ?? '';
55!
95
    const urn = crsValue.match(/[\w-]*:[\w-]*:[\w-]*:[\w-]*:[\w-]*:[^:]*:(([\w-]+\s[\w-]+)|[\w-]*)/)?.[0];
55✔
96
    const epsg = makeNumericEPSG(crsValue.match(/EPSG:[0-9]+/)?.[0]);
55✔
97
    const extractedCrs = epsg || (extractCrsFromURN(urn) || last(crsValue.split(':')));
55✔
98
    if (!extractedCrs) {
55!
UNCOV
99
        return { crs: 'EPSG:4326', extractedCrs };
×
100
    }
101
    if (extractedCrs.slice(0, 5) === 'EPSG:') {
55✔
102
        return { crs: makeNumericEPSG(extractedCrs), extractedCrs };
29✔
103
    }
104
    return { crs: makeNumericEPSG(`EPSG:${extractedCrs}`), extractedCrs };
26✔
105
};
106
/**
107
 * Get bounding box information given a CSW record
108
 * @param {object} cswRecord CSW record in json format
109
 * @returns {object} { crs, extent }
110
 */
111
const getBoundingBoxFromCSWRecord = (cswRecord) => {
1✔
112
    if (cswRecord?.['ows:BoundingBox']) {
57✔
113
        const boundingBox = castArray(cswRecord['ows:BoundingBox'])[0];
55✔
114
        const { crs, extractedCrs } = getCRSFromCSWBoundingBox(boundingBox);
55✔
115
        let lc = (boundingBox?.['ows:LowerCorner'] || '-180 -90').split(' ').map(parseFloat);
55!
116
        let uc = (boundingBox?.['ows:UpperCorner'] || '180 90').split(' ').map(parseFloat);
55!
117
        // Usually switched, GeoServer sometimes doesn't.
118
        // See https://docs.geoserver.org/latest/en/user/services/wfs/axis_order.html#axis-ordering
119
        if (crs === 'EPSG:4326' && extractedCrs !== 'CRS84' && extractedCrs !== 'OGC:CRS84') {
55✔
120
            lc = [lc[1], lc[0]];
20✔
121
            uc = [uc[1], uc[0]];
20✔
122
        }
123
        return {
55✔
124
            extent: makeBboxFromOWS(lc, uc),
125
            crs: 'EPSG:4326'
126
        };
127
    }
128
    return null;
2✔
129
};
130
/**
131
 * Get dc properties given a CSW record
132
 * @param {object} cswRecord CSW record in json format
133
 * @returns {object} dc
134
 */
135
const getDCFromCSWRecord = (cswRecord) => {
1✔
136
    // extract each dc or dct tag item in the XML
137
    const dc = Object.keys(cswRecord || {}).reduce((acc, key) => {
60✔
138
        const isDCElement = key.indexOf('dc:') === 0;
592✔
139
        const isDCTElement = key.indexOf('dct:') === 0;
592✔
140
        if (isDCElement || isDCTElement) {
592✔
141
            const name = isDCElement ? key.replace('dc:', '') :  key.replace('dct:', '');
472✔
142
            let value = cswRecord[key];
472✔
143
            if (name === 'references') {
472✔
144
                value = castArray(cswRecord[key]).map((reference) => {
14✔
145
                    return {
14✔
146
                        value: cleanDuplicatedQuestionMarks(reference?._),
147
                        scheme: reference?.$?.scheme
148
                    };
149
                });
150
            }
151
            if (name === 'URI') {
472✔
152
                value = castArray(cswRecord[key]).map((uri) => {
42✔
153
                    return {
81✔
154
                        description: uri?.$?.description,
155
                        name: uri?.$?.name,
156
                        protocol: uri?.$?.protocol,
157
                        value: uri?._
158
                    };
159
                });
160
            }
161
            return {
472✔
162
                ...acc,
163
                [name]: value
164
            };
165
        }
166
        return acc;
120✔
167
    }, {});
168
    return isEmpty(dc) ? null : dc;
60✔
169
};
170
/**
171
 * Get error message given the parsed response from a CSW request
172
 * @param {object} cswResponse CSW response in json format
173
 * @returns {string} error message
174
 */
175
const getCSWError = (cswResponse) => {
1✔
176
    const exceptionReport = cswResponse?.['ows:ExceptionReport'];
4✔
177
    if (exceptionReport) {
4!
178
        const exceptionText = exceptionReport?.['ows:Exception']?.['ows:ExceptionText'];
4✔
179
        return exceptionText || 'GenericError';
4!
180
    }
UNCOV
181
    return '';
×
182
};
183
/**
184
 * Parse a CSW axios response
185
 * @param {object} response axios response
186
 * @returns {object} it could return the parsed result, an error or null
187
 */
188
const parseCSWResponse = (response) => {
1✔
189
    if (!response) {
22!
UNCOV
190
        return null;
×
191
    }
192
    let json;
193
    xml2js.parseString(response.data, { explicitArray: false }, (ignore, result) => {
22✔
194
        json = result;
22✔
195
    });
196
    const searchResults = json?.['csw:GetRecordsResponse']?.['csw:SearchResults'];
22✔
197
    if (searchResults) {
22✔
198
        const cswRecords = searchResults?.['csw:Record'] || searchResults?.['gmd:MD_Metadata'];
19✔
199
        const records = !cswRecords ? null : castArray(cswRecords).map((cswRecord) => {
19!
200
            const boundingBox = getBoundingBoxFromCSWRecord(cswRecord);
57✔
201
            const dc = getDCFromCSWRecord(cswRecord);
57✔
202
            return {
57✔
203
                dateStamp: cswRecord?.['gmd:dateStamp']?.['gco:Date'],
204
                fileIdentifier: cswRecord?.['gmd:fileIdentifier']?.['gco:CharacterString'],
205
                identificationInfo: cswRecord?.['gmd:identificationInfo']?.['gmd:AbstractMD_Identification'],
206
                ...(boundingBox && { boundingBox }),
112✔
207
                ...(dc && { dc })
112✔
208
            };
209
        });
210
        const _dcRef = records ? records.map(({ dc = {} }) => {
19!
211
            const URIs = castArray(dc?.references?.length > 0 ? dc.references : dc.URI);
57✔
212
            return URIs;
57✔
213
        }).flat() : undefined;
214
        return {
19✔
215
            result: {
216
                numberOfRecordsMatched: parseFloat(searchResults?.$?.numberOfRecordsMatched),
217
                numberOfRecordsReturned: parseFloat(searchResults?.$?.numberOfRecordsReturned),
218
                nextRecord: parseFloat(searchResults?.$?.nextRecord),
219
                ...(records && { records })
38✔
220
            },
221
            _dcRef
222
        };
223
    }
224
    const error = getCSWError(json);
3✔
225
    if (error) {
3!
226
        return { error };
3✔
227
    }
UNCOV
228
    return null;
×
229
};
230
/**
231
 * Construct XML body to get records from the CSW service
232
 * @param {object} [options] the options to pass to withIntersectionObserver enhancer.
233
 * @param {number} startPosition
234
 * @param {number} maxRecords
235
 * @param {string} searchText
236
 * @param {object} filter object holds static and dynamic filter configured for the CSW service
237
 * @param {object} filter.staticFilter filter to fetch all record applied always i.e even when no search text is present
238
 * @param {object} filter.dynamicFilter filter when search text is present and is applied in conjunction with static filter
239
 * @return {string} constructed xml string
240
 */
241
export const constructXMLBody = (startPosition, maxRecords, searchText, { options: { service } = {} } = {}) => {
1✔
242
    const { filter, sortBy: sortObj } = service ?? {};
31✔
243
    const staticFilter = filter?.staticFilter || defaultStaticFilter;
31✔
244
    const dynamicFilter = `<ogc:And>
31✔
245
        ${template(filter?.dynamicFilter || defaultDynamicFilter)({ searchText })}
61✔
246
        ${staticFilter}
247
    </ogc:And>`;
248
    const sortExp = sortObj?.name ? sortByXml({ name: sortObj?.name, order: sortObj?.order ?? "ASC"}) : '';
31!
249
    return cswGetRecordsXml({ filterXml: !searchText ? staticFilter : dynamicFilter, startPosition, maxRecords, sortBy: sortExp});
31✔
250
};
251

252
// Extract the relevant information from the wms URL for (RNDT / INSPIRE)
253
const extractWMSParamsFromURL = wms => {
1✔
254
    const lowerCaseParams = new Map(Array.from(new URLSearchParams(wms.value)).map(([key, value]) => [key.toLowerCase(), value]));
9✔
255
    const layerName = lowerCaseParams.get('layers');
3✔
256
    const wmsVersion = lowerCaseParams.get('version');
3✔
257
    if (layerName) {
3✔
258
        return {
2✔
259
            ...wms,
260
            protocol: 'OGC:WMS',
261
            name: layerName,
262
            value: `${wms.value.match(/[^\?]+[\?]+/g)}SERVICE=WMS${wmsVersion && `&VERSION=${wmsVersion}`}`
4✔
263
        };
264
    }
265
    return false;
1✔
266
};
267

268
// Extract the relevant information from the wfs URL for (RNDT / INSPIRE)
269
const extractWFSParamsFromURL = wfs => {
1✔
270
    const lowerCaseParams = new Map(Array.from(new URLSearchParams(wfs.value)).map(([key, value]) => [key.toLowerCase(), value]));
3✔
271
    const layerName = lowerCaseParams.get('typename');
1✔
272
    if (layerName) {
1!
273
        return {
1✔
274
            ...wfs,
275
            protocol: 'OGC:WFS',
276
            name: layerName,
277
            value: `${wfs.value.match(/[^\?]+[\?]+/g)}service=WFS`
278
        };
279
    }
UNCOV
280
    return false;
×
281
};
282

283
const toReference = (layerType, data, options) => {
1✔
284
    if (!data.name) {
45✔
285
        return null;
8✔
286
    }
287
    switch (layerType) {
37!
288
    case 'wms':
289
    case 'wfs':
290
        const urlValue = !(data.value.indexOf("http") === 0)
34✔
291
            ? (options && options.catalogURL || "") + "/" + data.value
21✔
292
            : data.value;
293
        return {
34✔
294
            type: data.protocol || data.scheme,
46✔
295
            url: urlValue,
296
            SRS: [],
297
            params: {
298
                name: data.name
299
            }
300
        };
301
    case 'arcgis':
302
        return {
3✔
303
            type: 'arcgis',
304
            url: data.value,
305
            SRS: [],
306
            params: {
307
                name: data.name
308
            }
309
        };
310
    default:
UNCOV
311
        return null;
×
312
    }
313
};
314

315
const REGEX_WMS_EXPLICIT = [/^OGC:WMS-(.*)-http-get-map/g, /^OGC:WMS/g];
1✔
316
const REGEX_WFS_EXPLICIT = [/^OGC:WFS-(.*)-http-get-(capabilities|feature)/g, /^OGC:WFS/g];
1✔
317
const REGEX_WMS_EXTRACT = /serviceType\/ogc\/wms/g;
1✔
318
const REGEX_WFS_EXTRACT = /serviceType\/ogc\/wfs/g;
1✔
319
const REGEX_WMS_ALL = REGEX_WMS_EXPLICIT.concat(REGEX_WMS_EXTRACT);
1✔
320

321
export const getLayerReferenceFromDc = (dc, options, checkEsri = true) => {
1✔
322
    const URI = dc?.URI && castArray(dc.URI);
92✔
323
    const isWMS = isNil(options?.type) || options?.type === "wms";
92✔
324
    const isWFS = options?.type === "wfs";
92✔
325
    // look in URI objects for wms and thumbnail
326
    if (URI) {
92✔
327
        if (isWMS) {
51✔
328
            const wms = head(URI.map(uri => {
32✔
329
                if (uri.protocol) {
53✔
330
                    if (REGEX_WMS_EXPLICIT.some(regex => uri.protocol.match(regex))) {
79✔
331
                    /** wms protocol params are explicitly defined as attributes (INSPIRE)*/
332
                        return uri;
18✔
333
                    }
334
                    if (uri.protocol.match(REGEX_WMS_EXTRACT)) {
26✔
335
                    /** wms protocol params must be extracted from the element text (RNDT / INSPIRE) */
336
                        return extractWMSParamsFromURL(uri);
3✔
337
                    }
338
                }
339
                return false;
32✔
340
            }).filter(item => item));
53✔
341
            if (wms) {
32✔
342
                return toReference('wms', wms, options);
20✔
343
            }
344
        }
345
        if (isWFS) {
31✔
346
            const wfs = head(URI.map(uri => {
19✔
347
                if (uri.protocol) {
28✔
348
                    if (REGEX_WFS_EXPLICIT.some(regex => uri.protocol.match(regex))) {
38✔
349
                    /** wfs protocol params are explicitly defined as attributes (INSPIRE)*/
350
                        return uri;
1✔
351
                    }
352
                    if (uri.protocol.match(REGEX_WFS_EXTRACT)) {
18✔
353
                    /** wfs protocol params must be extracted from the element text (RNDT / INSPIRE) */
354
                        return extractWFSParamsFromURL(uri);
1✔
355
                    }
356
                }
357
                return false;
26✔
358
            }).filter(item => item));
28✔
359
            if (wfs) {
19✔
360
                return toReference('wfs', wfs, options);
2✔
361
            }
362
        }
363
    }
364
    // look in references objects
365
    if (dc?.references?.length) {
70✔
366
        const refs = castArray(dc.references);
37✔
367
        const wms = head(refs.filter((ref) => { return ref.scheme && REGEX_WMS_EXPLICIT.some(regex => ref.scheme.match(regex)); }));
108✔
368
        const wfs = head(refs.filter((ref) => { return ref.scheme && REGEX_WFS_EXPLICIT.some(regex => ref.scheme.match(regex)); }));
124✔
369
        if (isWMS && wms) {
37✔
370
            let urlObj = urlUtil.parse(getDefaultUrl(wms.value), true);
16✔
371
            let layerName = urlObj.query && urlObj.query.layers || dc.alternative;
16✔
372
            return toReference('wms', { ...wms, value: urlUtil.format(urlObj), name: layerName }, options);
16✔
373
        }
374
        if (isWFS && wfs) {
21✔
375
            let urlObj = urlUtil.parse(getDefaultUrl(wfs.value), true);
4✔
376
            let layerName = urlObj.query && urlObj.query.layers || dc.alternative;
4✔
377
            return toReference('wfs', { ...wfs, value: urlUtil.format(urlObj), name: layerName }, options);
4✔
378
        }
379
        if (checkEsri) {
17!
380
            // checks for esri arcgis in geonode csw
381
            const esri = head(refs.filter((ref) => {
17✔
382
                return ref.scheme && ref.scheme === "WWW:DOWNLOAD-REST_MAP";
27✔
383
            }));
384
            if (esri) {
17✔
385
                return toReference('arcgis', { ...esri, name: dc.alternative }, options);
3✔
386
            }
387
        }
388
    }
389
    return null;
47✔
390
};
391

392
let capabilitiesCache = {};
1✔
393

394
/**
395
 * Add capabilities data to CSW records
396
 * if corresponding capability flag is enabled and WMS url is found
397
 * Currently limited to only scale denominators (visibility limits)
398
 * @param {object[]} _dcRef dc.reference or dc.URI
399
 * @param {object} result csw results object
400
 * @param {object} options csw service options
401
 * @return {object} csw records
402
 */
403
const addCapabilitiesToRecords = (_dcRef, result, options) => {
1✔
404
    // Currently, visibility limits is the only capability info added to the records
405
    // hence `autoSetVisibilityLimits` flag is used to determine if `getCapabilities` is required
406
    // This should be modified when additional capability info is required
407
    const invokeCapabilities = get(options, "options.service.autoSetVisibilityLimits", false);
19✔
408

409
    if (!invokeCapabilities) {
19✔
410
        return result;
15✔
411
    }
412
    const { value: _url } = _dcRef?.find(t =>
4✔
413
        REGEX_WMS_ALL.some(regex => t?.scheme?.match(regex) || t?.protocol?.match(regex))) || {}; // Get WMS URL from references
28✔
414
    const [parsedUrl] = _url && _url.split('?') || [];
4✔
415
    if (!parsedUrl) return { ...result }; // Return record when no url found
4✔
416

417
    const cached = capabilitiesCache[parsedUrl];
3✔
418
    const isCached = !isEmpty(cached);
3✔
419
    return Promise.resolve(
3✔
420
        isCached
3✔
421
            ? cached
422
            : WMS.getCapabilities(parsedUrl + '?version=')
423
                .then((caps) => WMS.flatLayers(caps.Capability))
1✔
UNCOV
424
                .catch(() => []))
×
425
        .then((layers) => {
426
            if (!isCached) {
3✔
427
                capabilitiesCache[parsedUrl] = layers;
1✔
428
            }
429
            // Add visibility limits scale data of the layer to the record
430
            return {
3✔
431
                ...result,
432
                records: result?.records?.map(record => {
433
                    const name = get(getLayerReferenceFromDc(record?.dc, null, false), 'params.name', '');
12✔
434
                    const {
435
                        MinScaleDenominator,
436
                        MaxScaleDenominator
437
                    } = layers.find(l => l.Name === name) || {};
17✔
438
                    return {
12✔
439
                        ...record,
440
                        ...((!isNil(MinScaleDenominator) || !isNil(MaxScaleDenominator))
24✔
441
                            && { capabilities: { MaxScaleDenominator, MinScaleDenominator } })
442
                    };
443
                })
444
            };
445
        });
446
};
447
/**
448
 * handle getting bbox from capabilities in case of 3D tile layer for CSW records
449
 * @param {object} result csw results object
450
 * @return {object} csw records
451
 */
452
const getBboxFor3DLayersToRecords = async(result)=> {
1✔
453
    if (!result) return result;
22!
454
    let { records } = result;
22✔
455
    if (records?.length) {
22✔
456
        let records3DPromisesForCapabilities = records.map((rec)=>{
19✔
457
            if (rec?.dc?.format === THREE_D_TILES) {
57✔
458
                let tilesetJsonURL = rec.dc?.URI?.value;
3✔
459
                return getCapabilities(tilesetJsonURL);
3✔
460
            }
461
            return Promise.resolve(null);
54✔
462
        });
463
        let allPromises = await Promise.all(records3DPromisesForCapabilities);
19✔
464

465
        const newRecords = records.map(
19✔
466
            (record, idx) => {
467
                const capabilityResult = allPromises[idx];
57✔
468
                if (!capabilityResult || !capabilityResult?.bbox?.bounds || !capabilityResult?.bbox?.crs) {
57✔
469
                    return record;
54✔
470
                }
471
                let bbox = getExtentFromNormalized(capabilityResult.bbox.bounds, capabilityResult.bbox.crs);
3✔
472
                return {
3✔
473
                    ...record,
474
                    boundingBox: {
475
                        extent: bbox.extent,
476
                        crs: capabilityResult.bbox.crs
477
                    }
478
                };
479
            });
480
        return {
19✔
481
            ...result,
482
            records: newRecords
483
        };
484
    }
485
    return result;
3✔
486
};
487
/**
488
 * API for local config
489
 */
490
const Api = {
1✔
491
    parseUrl,
492
    getRecordById: function(catalogURL) {
493
        return axios.get(catalogURL)
3✔
494
            .then((response) => {
495
                if (response) {
3!
496
                    let json;
497
                    xml2js.parseString(response.data, { explicitArray: false }, (ignore, result) => {
3✔
498
                        json = result;
3✔
499
                    });
500
                    const record = json?.['csw:GetRecordByIdResponse']?.['csw:Record'];
3✔
501
                    const dc = getDCFromCSWRecord(record);
3✔
502
                    if (dc) {
3✔
503
                        return { dc };
2✔
504
                    }
505
                    const error = getCSWError(json);
1✔
506
                    if (error) {
1!
507
                        return { error };
1✔
508
                    }
509
                }
UNCOV
510
                return null;
×
511
            });
512
    },
513
    getRecords: function(url, startPosition, maxRecords, text, options) {
514
        const body = constructXMLBody(startPosition, maxRecords, text, options);
24✔
515
        const protectedId = options?.options?.service?.protectedId;
24✔
516
        let headers = getAuthorizationBasic(protectedId);
24✔
517
        return axios.post(parseUrl(url), body, {
24✔
518
            headers: {
519
                'Content-Type': 'application/xml',
520
                ...headers
521
            }
522
        }).then((response) => {
523
            const { error, _dcRef, result } = parseCSWResponse(response) || {};
22!
524
            if (result) {
22✔
525
                return addCapabilitiesToRecords(_dcRef, result, options);
19✔
526
            }
527
            if (error) {
3!
528
                return { error };
3✔
529
            }
UNCOV
530
            return null;
×
531
        }).then(results => getBboxFor3DLayersToRecords(results)); // handle getting bbox from capabilities in case of 3D tile layer
22✔
532
    },
533
    textSearch: function(url, startPosition, maxRecords, text, options) {
534
        return new Promise((resolve) => {
8✔
535
            resolve(Api.getRecords(url, startPosition, maxRecords, text, options));
8✔
536
        });
537
    },
538
    workspaceSearch: function(url, startPosition, maxRecords, text, workspace) {
539
        const workspaceTerm = workspace || "%";
2✔
540
        const layerNameTerm = text && "%" + text + "%" || "%";
2✔
541
        return Api.getRecords(url, startPosition, maxRecords, '', {
2✔
542
            options: {
543
                service: {
544
                    filter: {
545
                        staticFilter: "<ogc:PropertyIsLike wildCard=\"%\" singleChar=\"_\" escapeChar=\"\\\\\">" +
546
                            "<ogc:PropertyName>dc:identifier</ogc:PropertyName>" +
547
                            `<ogc:Literal>${workspaceTerm + ":" + layerNameTerm}</ogc:Literal>` +
548
                        "</ogc:PropertyIsLike>"
549
                    }
550
                }
551
            }
552
        });
553
    },
554
    reset: () => { }
555
};
556

557
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

© 2025 Coveralls, Inc