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

geosolutions-it / MapStore2 / 17863671168

19 Sep 2025 04:09PM UTC coverage: 76.743% (-0.009%) from 76.752%
17863671168

Pull #11478

github

web-flow
Merge 3c4b169f2 into a3115d1ac
Pull Request #11478: #11437: Isochrone plugin implementation

31729 of 49420 branches covered (64.2%)

253 of 340 new or added lines in 14 files covered. (74.41%)

137 existing lines in 15 files now uncovered.

39512 of 51486 relevant lines covered (76.74%)

37.63 hits per line

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

97.36
/web/client/utils/ConfigUtils.js
1
/**
2
 * Copyright 2015, 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 Proj4js from 'proj4';
9
import PropTypes from 'prop-types';
10
import url from 'url';
11
import axios from 'axios';
12
import { castArray, isArray, isObject, endsWith, isNil, get, mergeWith } from 'lodash';
13
import isMobile from 'ismobilejs';
14
import {mergeConfigsPatch} from "@mapstore/patcher";
15

16
const epsg4326 = Proj4js ? new Proj4js.Proj('EPSG:4326') : null;
1!
17
const centerPropType = PropTypes.shape({
1✔
18
    x: PropTypes.number.isRequired,
19
    y: PropTypes.number.isRequired,
20
    crs: PropTypes.string
21
});
22

23
const urlQuery = url.parse(window.location.href, true).query;
1✔
24

25
let localConfigFile = 'configs/localConfig.json';
1✔
26

27
let defaultConfig = {
1✔
28
    // TODO: these should be changed tp relative paths, without /mapstore/ or / (to avoid the needing of overriding in default cases)
29
    proxyUrl: "/mapstore/proxy/?url=",
30
    geoStoreUrl: "/rest/geostore/",
31
    printUrl: "/mapstore/print/info.json",
32
    translationsPath: "translations",
33
    extensionsRegistry: "extensions/extensions.json",
34
    extensionsFolder: "extensions/",
35
    configurationFolder: "configs/",
36
    contextPluginsConfiguration: "configs/pluginsConfig.json",
37
    projectionDefs: [],
38
    themePrefix: "ms2",
39
    mapquestApiKey: null,
40
    mapboxAccessToken: '',
41
    defaultSourceType: "gxp_wmssource",
42
    backgroundGroup: "background",
43
    userSessions: {
44
        enabled: false
45
    }
46
};
47

48
export const getConfigurationOptions = function(query, defaultName, extension, geoStoreBase) {
1✔
49
    const mapId = query.mapId;
22✔
50
    let configUrl;
51
    if (mapId) {
22✔
52
        configUrl = ( geoStoreBase || defaultConfig.geoStoreUrl ) + "data/" + mapId;
12✔
53
    } else {
54
        configUrl = defaultConfig.configurationFolder + (query.config || defaultName || 'config') + '.' + (extension || 'json');
10✔
55
    }
56
    return {
22✔
57
        configUrl: configUrl,
58
        legacy: !!mapId
59
    };
60
};
61
/**
62
 * WORKAROUND: it removes the extra ? when the authkey param is present
63
 * the url was like         http......?authkey=....?service=....&otherparam
64
 * that should become like  http......?authkey=....&service=....&otherparam
65
 *
66
 * The problem happens when you have this in the Record.properties
67
 * file of the csw folder in GeoServer, with csw plugin installed:
68
 * references.scheme='OGC:WMS'
69
 * references.value=strConcat('${url.wms}?service=WMS&request=GetMap&layers=', prefixedName)
70
 * That is ok when you are not using an authkey, but If you have the authkey
71
 * module installed and you get record with a proper authkey parameter the
72
 * ${url.wms} URL will have also ?authkey=... and so the final URL is something like:
73
 * http://domain.org/geoserver/?autkey=abcdefghijklmnopqrstuvz1234567890?service=WMS&request=GetMap&layers=LAYER_NAME
74
 * ${url.wms} is replaced with the wms URL after the execution of strConcat,
75
 * so the url can not be parsed in any way to solve the problem via configuration,
76
 * because you can not know if ${url.wms} contains ? or not.
77
*/
78
export const cleanDuplicatedQuestionMarks = (urlToNormalize) => {
1✔
79
    const urlParts = urlToNormalize.split("?");
836✔
80
    if (urlParts.length > 2) {
836✔
81
        let newUrlParts = urlParts.slice(1);
3✔
82
        return urlParts[0] + "?" + newUrlParts.join("&");
3✔
83
    }
84
    return urlToNormalize;
833✔
85
};
86
/**
87
 * it removes some params from the query string
88
 * return the shrinked url
89
*/
90
export const getUrlWithoutParameters = (urlToFilter, skip) => {
1✔
91
    const urlparts = cleanDuplicatedQuestionMarks(urlToFilter).split('?');
419✔
92
    let paramsFiltered = "";
419✔
93
    if (urlparts.length >= 2 && urlparts[1]) {
419✔
94
        const pars = urlparts[1].split(/[&;]/g).filter( p => !!p);
373✔
95
        pars.forEach((par, i) => {
60✔
96
            const param = par.split('=');
372✔
97
            if (skip.indexOf(param[0].toLowerCase()) === -1) {
372✔
98
                let addAnd = i === (pars.length - 1) ? "" : "&";
317✔
99
                paramsFiltered += param.join("=") + addAnd;
317✔
100
            }
101
        });
102
    }
103
    return !!paramsFiltered ? urlparts[0] + "?" + paramsFiltered : urlparts[0];
419✔
104
};
105

106
export const filterUrlParams = (urlToFilter, params = []) => {
1!
107
    if (isNil(urlToFilter) || urlToFilter === "") {
365✔
108
        return null;
1✔
109
    }
110
    return getUrlWithoutParameters(cleanDuplicatedQuestionMarks(urlToFilter), params);
364✔
111
};
112

113
export const getParsedUrl = (urlToParse, options, params = []) => {
1✔
114
    if (urlToParse) {
17✔
115
        const parsed = url.parse(filterUrlParams(urlToParse, params), true);
2✔
116
        let newPathname = null;
2✔
117
        if (endsWith(parsed.pathname, "wfs") || endsWith(parsed.pathname, "wms") || endsWith(parsed.pathname, "ows")) {
2✔
118
            newPathname = parsed.pathname.replace(/(wms|ows|wfs|wps)$/, "wps");
1✔
119
            return url.format(Object.assign({}, parsed, {search: null, pathname: newPathname }, {
1✔
120
                query: Object.assign({
121
                    service: "WPS",
122
                    ...options
123
                }, parsed.query)
124
            }));
125
        }
126
    }
127
    return null;
16✔
128
};
129
export const getDefaults = function() {
1✔
130
    return {...defaultConfig};
185✔
131
};
132
export const setLocalConfigurationFile = function(file) {
1✔
133
    localConfigFile = file;
115✔
134
};
135

136
export const loadConfiguration = function() {
1✔
137
    if (localConfigFile) {
28✔
138
        const configFiles = castArray(localConfigFile);
27✔
139
        return axios.all(configFiles.map(config =>
27✔
140
            axios.get(config)
28✔
141
                .then(response => response.data)
26✔
142
                .catch(() => null)
2✔
143
        )).then(configs => {
144
            const [main, ...patches] = configs;
27✔
145
            if (!main) throw new Error("local configuration file is broken");
27✔
146
            const merged = mergeConfigsPatch(main, patches.filter(c => c && typeof c === "object"));
25✔
147
            defaultConfig = merged ? {...defaultConfig, ...merged} : defaultConfig;
25!
148
            return {...defaultConfig};
25✔
149
        });
150
    }
151
    return new Promise((resolve) => {
1✔
152
        resolve({...defaultConfig});
1✔
153
    });
154
};
155

156
export const getCenter = function(center, projection) {
1✔
157
    const point = isArray(center) ? {x: center[0], y: center[1]} : center;
49✔
158
    const crs = center.crs || projection || 'EPSG:4326';
49✔
159
    const transformed = crs !== 'EPSG:4326' ? Proj4js.transform(new Proj4js.Proj(crs), epsg4326, point) : point;
49✔
160
    return Object.assign({}, transformed, {crs: "EPSG:4326"});
49✔
161
};
162

163
export const setApiKeys = function(layer) {
1✔
164
    if (layer.type === 'mapquest') {
50✔
165
        layer.apiKey = defaultConfig.mapquestApiKey;
4✔
166
    }
167
    if (layer.type === 'tileprovider' && ['MapBoxStyle', 'MapBox'].includes(layer.provider)) {
50!
168
        // include an empty string if missing to avoid errors in the layer url template
UNCOV
169
        layer.accessToken = defaultConfig.mapboxAccessToken;
×
170
    }
171
    return layer;
50✔
172
};
173

174
export const setLayerId = function(layer, i) {
1✔
175
    if (!layer.id) {
50✔
176
        layer.id = layer.name + "__" + i;
46✔
177
    }
178
    return layer;
50✔
179
};
180

181
export const replacePlaceholders = function(inputUrl) {
1✔
182
    let currentUrl = inputUrl;
41✔
183
    (currentUrl.match(/\{.*?\}/g) || []).forEach((placeholder) => {
41✔
184
        const replacement = defaultConfig[placeholder.substring(1, placeholder.length - 1)];
7✔
185
        // replacement must exist, or the URL is intended as a real template for the URL (e.g REST URLs of WMTS)
186
        if (replacement !== undefined) {
7✔
187
            currentUrl = currentUrl.replace(placeholder, replacement || '');
6!
188
        }
189
    });
190
    return currentUrl;
41✔
191
};
192

193
export const setUrlPlaceholders = function(layer) {
1✔
194
    if (layer.url) {
53✔
195
        if (isArray(layer.url)) {
18✔
196
            layer.url = layer.url.map((currentUrl) => {
5✔
197
                return replacePlaceholders(currentUrl);
26✔
198
            });
199
        } else {
200
            layer.url = replacePlaceholders(layer.url);
13✔
201
        }
202
    }
203
    return layer;
53✔
204
};
205

206
export const normalizeConfig = function(config) {
1✔
207
    const {layers, groups, plugins, ...other} = config;
23✔
208
    other.center = getCenter(other.center);
23✔
209
    return {
23✔
210
        map: other,
211
        layers: layers.map(setApiKeys, config).map(setLayerId).map(setUrlPlaceholders),
212
        groups: groups,
213
        plugins: plugins
214
    };
215
};
216

217
export const getUserConfiguration = function(defaultName, extension, geoStoreBase) {
1✔
218
    return getConfigurationOptions(urlQuery, defaultName, extension, geoStoreBase);
1✔
219
};
220

221
export const getConfigUrl = ({mapId, config}) => {
1✔
222
    let id = mapId;
13✔
223
    let configUrl = config;
13✔
224
    // if mapId is a string, is the name of the config to load
225
    try {
13✔
226
        let mapIdNumber = parseInt(id, 10);
13✔
227
        if (isNaN(mapIdNumber)) {
13✔
228
            configUrl = mapId;
3✔
229
            id = null;
3✔
230
        }
231
    } catch (e) {
UNCOV
232
        configUrl = mapId;
×
233
        id = null;
×
234
    }
235
    return getConfigurationOptions({mapId: id, config: configUrl});
13✔
236
};
237

238
/**
239
 * set default wms source
240
 */
241
export const setupSources = function(sources, defaultSourceType) {
1✔
242
    var defType = defaultSourceType;
4✔
243
    var source;
244
    if (!defaultSourceType) {
4!
245
        defType = defaultConfig.defaultSourceType;
4✔
246
    }
247
    for (source in sources) {
4✔
248
        if (sources.hasOwnProperty(source)) {
24!
249
            if (!sources[source].ptype) {
24✔
250
                sources[source].ptype = defType;
8✔
251
            }
252
        }
253
    }
254
};
255

256
export const normalizeSourceUrl = function(sourceUrl) {
1✔
257
    if (sourceUrl && sourceUrl.indexOf('?') !== -1) {
24✔
258
        return sourceUrl.split('?')[0];
4✔
259
    }
260
    return sourceUrl;
20✔
261
};
262

263
/**
264
 * Copy important source options to layer options.
265
 */
266
export const copySourceOptions = function(layer, source) {
1✔
267
    layer.baseParams = source.baseParams;
24✔
268
    if (source.url) {
24✔
269
        let sourceParts = url.parse(source.url, true);
8✔
270
        for (let k in sourceParts.query) {
8✔
271
            if (k.toUpperCase() === "REQUEST" ) {
4!
UNCOV
272
                delete sourceParts.query[k];
×
273
            }
274
        }
275
        layer.baseParams = Object.assign({}, layer.baseParams, sourceParts.query);
8✔
276
    }
277
    layer.url = normalizeSourceUrl(source.url);
24✔
278
};
279

280
/**
281
 * Setup the layer visibility for the background group.
282
 * if background layers are not visible, sets the last one
283
 */
284
export const setupLayers = function(layers, sources, supportedSourceTypes) {
1✔
285
    // setup background visibility
286
    var candidateVisible;
287
    var i; var layer; var source;
288
    for (i = 0; i < layers.length; i++) {
4✔
289
        layer = layers[i];
24✔
290
        source = sources[layer.source];
24✔
291
        if (source) {
24!
292
            copySourceOptions(layer, source);
24✔
293
        }
294

295
        let type = source.ptype;
24✔
296
        if (type) {
24!
297
            layer.type = type.replace(/^gxp_(.*)source$/i, "$1");
24✔
298
        } else {
UNCOV
299
            layer.type = 'unknown';
×
300
        }
301
        if (layer) {
24!
302
            if (supportedSourceTypes.indexOf(source && source.ptype) >= 0) {
24!
303
                if (layer.group === defaultConfig.backgroundGroup) {
24✔
304
                    // force to false if undefined
305
                    layer.visibility = layer.visibility || false;
16✔
306
                    if (candidateVisible && candidateVisible.visibility) {
16✔
307
                        /* if more than one layer is visible in the background group
308
                            shows only the last one hiding the previous.
309
                        */
310
                        if (layer.visibility) {
4!
311
                            candidateVisible.visibility = false;
4✔
312
                            candidateVisible = layer;
4✔
313
                        }
314
                    } else {
315
                        candidateVisible = layer;
12✔
316
                    }
317
                }
318
            } else {
UNCOV
319
                layer.visibility = false;
×
320
            }
321
        }
322
    }
323
    // set the candidate visible
324
    if (candidateVisible) {
4!
325
        candidateVisible.visibility = true;
4✔
326
    }
327
};
328

329
export const convertFromLegacy = function(config) {
1✔
330
    var mapConfig = config.map;
4✔
331
    var sources = config.gsSources || config.sources;
4!
332
    var layers = mapConfig.layers.filter(layer => sources[layer.source]);
24✔
333
    var latLng = getCenter(mapConfig.center, mapConfig.projection);
4✔
334
    var zoom = mapConfig.zoom;
4✔
335
    var maxExtent = mapConfig.maxExtent || mapConfig.extent;
4!
336

337
    // setup layers and sources with defaults
338
    setupSources(sources, config.defaultSourceType);
4✔
339
    setupLayers(layers, sources, ["gxp_osmsource", "gxp_wmssource", "gxp_googlesource", "gxp_bingsource", "gxp_mapquestsource", "gxp_olsource"]);
4✔
340
    return normalizeConfig({
4✔
341
        center: latLng,
342
        zoom: zoom,
343
        maxExtent: maxExtent, // TODO convert maxExtent
344
        layers: layers,
345
        projection: mapConfig.projection || 'EPSG:3857'
4!
346
    });
347
};
348

349
/**
350
 * Utility to merge different configs
351
 */
352
export const mergeConfigs = function(baseConfig, mapConfig) {
1✔
353
    baseConfig.map = mapConfig.map;
1✔
354
    baseConfig.gsSources = mapConfig.gsSources || mapConfig.sources;
1✔
355
    return baseConfig;
1✔
356
};
357
export const getProxyUrl = function(config) {
1✔
358
    return config?.proxyUrl ? config.proxyUrl : defaultConfig.proxyUrl;
581✔
359
};
360

361
export const getProxiedUrl = function(uri, config = {}) {
1✔
362
    let sameOrigin = !(uri.indexOf("http") === 0);
4✔
363
    let urlParts = !sameOrigin && uri.match(/([^:]*:)\/\/([^:]*:?[^@]*@)?([^:\/\?]*):?([^\/\?]*)/);
4✔
364
    // ajax.addAuthenticationToAxios(config);
365
    if (urlParts) {
4!
366
        let location = window.location;
4✔
367
        sameOrigin =
4✔
368
            urlParts[1] === location.protocol &&
8✔
369
            urlParts[3] === location.hostname;
370
        let uPort = urlParts[4];
4✔
371
        let lPort = location.port;
4✔
372
        let defaultPort = location.protocol.indexOf("https") === 0 ? 443 : 80;
4!
373
        uPort = uPort === "" ? defaultPort + "" : uPort + "";
4!
374
        lPort = lPort === "" ? defaultPort + "" : lPort + "";
4!
375
        sameOrigin = sameOrigin && uPort === lPort;
4!
376
    }
377
    if (!sameOrigin) {
4!
378
        let proxyUrl = getProxyUrl(config);
4✔
379
        if (proxyUrl) {
4!
380
            let useCORS = [];
4✔
381
            if (isObject(proxyUrl)) {
4✔
382
                useCORS = proxyUrl.useCORS || [];
1!
383
                proxyUrl = proxyUrl.url;
1✔
384
            }
385
            const isCORS = useCORS.reduce((found, current) => found || uri.indexOf(current) === 0, false);
4✔
386
            if (!isCORS) {
4✔
387
                return proxyUrl + encodeURIComponent(uri);
3✔
388
            }
389
        }
390
    }
391
    return uri;
1✔
392
};
393
/**
394
* Utility to detect browser properties.
395
* Code from leaflet-src.js
396
*/
397
export const getBrowserProperties = function() {
1✔
398

399
    let ie = 'ActiveXObject' in window;
577✔
400
    let ielt9 = ie && !document.addEventListener;
577!
401
    let ie11 = ie && (window.location.hash === !!window.MSInputMethodContext && !!document.documentMode);
577!
402

403
    // terrible browser detection to work around Safari / iOS / Android browser bugs
404
    let ua = navigator.userAgent.toLowerCase();
577✔
405
    let webkit = ua.indexOf('webkit') !== -1;
577✔
406
    let chrome = ua.indexOf('chrome') !== -1;
577✔
407
    let safari = ua.indexOf('safari') !== -1 && ua.indexOf('chrome') === -1;
577✔
408
    let phantomjs = ua.indexOf('phantom') !== -1;
577✔
409
    let android = ua.indexOf('android') !== -1;
577✔
410
    let android23 = ua.search('android [23]') !== -1;
577✔
411
    let gecko = ua.indexOf('gecko') !== -1;
577✔
412

413
    let mobile = isMobile.any; // typeof window.orientation !== undefined + '';
577✔
414
    let msPointer = !window.PointerEvent && window.MSPointerEvent;
577!
415
    let pointer = window.PointerEvent && window.navigator.pointerEnabled && window.navigator.maxTouchPoints ||
577!
416
                msPointer;
417
    let retina = 'devicePixelRatio' in window && window.devicePixelRatio > 1 ||
577✔
418
                'matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') &&
419
                window.matchMedia('(min-resolution:144dpi)').matches;
420

421
    let doc = document.documentElement;
577✔
422
    let ie3d = ie && 'transition' in doc.style;
577!
423
    let webkit3d = 'WebKitCSSMatrix' in window && 'm11' in new window.WebKitCSSMatrix() && !android23;
577✔
424
    let gecko3d = 'MozPerspective' in doc.style;
577✔
425
    let opera3d = 'OTransition' in doc.style;
577✔
426
    let any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d) && !phantomjs;
577!
427

428
    let touch = !window.L_NO_TOUCH && !phantomjs && (pointer || 'ontouchstart' in window ||
577!
429
    window.DocumentTouch && document instanceof window.DocumentTouch);
430

431
    return {
577✔
432
        ie: ie,
433
        ie11: ie11,
434
        ielt9: ielt9,
435
        webkit: webkit,
436
        gecko: gecko && !webkit && !window.opera && !ie,
1,154!
437

438
        android: android,
439
        android23: android23,
440

441
        chrome: chrome,
442

443
        safari,
444

445
        ie3d: ie3d,
446
        webkit3d: webkit3d,
447
        gecko3d: gecko3d,
448
        opera3d: opera3d,
449
        any3d: any3d,
450

451
        mobile: mobile,
452
        mobileWebkit: mobile && webkit,
577!
453
        mobileWebkit3d: mobile && webkit3d,
577!
454
        mobileOpera: mobile && window.opera,
577!
455

456
        touch: touch,
457
        msPointer: msPointer,
458
        pointer: pointer,
459

460
        retina: retina
461
    };
462
};
463

464
export const getConfigProp = function(prop) {
1✔
465
    return defaultConfig[prop];
8,710✔
466
};
467
export const setConfigProp = function(prop, value) {
1✔
468
    defaultConfig[prop] = value;
826✔
469
};
470
export const removeConfigProp = function(prop) {
1✔
471
    delete defaultConfig[prop];
13✔
472
};
473

474
/**
475
 * Get misc settings from localConfig
476
 * @param {string} name of the prop to find
477
 * @param {any} defaultVal
478
 * @returns {any} value configured in misc settings
479
 */
480
export const getMiscSetting = (name, defaultVal) => {
1✔
481
    return get(getConfigProp('miscSettings') ?? {}, name, defaultVal);
5✔
482
};
483

484
// IDs for userSession
485
export const SESSION_IDS = {
1✔
486
    EVERYTHING: "everything",
487
    MAP: "map",
488
    MAP_POS: "map_pos",
489
    VISUALIZATION_MODE: "visualization_mode",
490
    LAYERS: "layers",
491
    ANNOTATIONS_LAYER: "annotations_layer",
492
    MEASUREMENTS_LAYER: "measurements_layer",
493
    BACKGROUND_LAYERS: "background_layers",
494
    OTHER_LAYERS: "other_layers",
495
    CATALOG_SERVICES: "catalog_services",
496
    WIDGETS: "widgets",
497
    SEARCH: "search",
498
    TEXT_SEARCH_SERVICES: "text_search_services",
499
    BOOKMARKS: "bookmarks",
500
    FEATURE_GRID: "feature_grid",
501
    OTHER: "other",
502
    USER_PLUGINS: "userPlugins"
503
};
504

505
/*
506
 * Function to update overrideConfig to clean specific settings
507
 * After the introduction of individual reset of the session, override Config is updated here. Previously it was clearing all session.
508
 * This function handles to update the overrideConfig(from session) to clean specific settings
509
 *
510
 * Layers are handled differently as they are partially updating properties(annotations, measurements, backgrounds, others) and merging function from loadash has problem in merging arrays of objects(it merges all properties of object from both arrays) which is used in `applyOverrides` function in this same file below
511
 * So for layers merge of original Config and overrideConfig  happens here. And applyOverrides use the value of overrideConfig
512
 * No Problem for arrays that gets entirely reset
513
 *
514
 * @param {object} override - current overrideConfig
515
 * @param {Array} thingsToClear - IDs of settings to be cleared
516
 * @param {object} originalConfig - original config
517
 * @param {object} customHandlers - session Saved By registerCustomSaveHandler
518
 * @returns {object} updated overrideConfig
519
*/
520
export const updateOverrideConfig = (override = {}, thingsToClear = [], originalConfig = {}, customHandlers = []) => {
1!
521
    let overrideConfig = JSON.parse(JSON.stringify(override));
19✔
522

523
    if (thingsToClear?.includes(SESSION_IDS.EVERYTHING)) {
19✔
524
        overrideConfig = {};
1✔
525
        return overrideConfig;
1✔
526
    }
527

528
    // zoom and center
529
    if (thingsToClear.includes(SESSION_IDS.MAP_POS)) {
18✔
530
        delete overrideConfig.map.zoom;
2✔
531
        delete overrideConfig.map.center;
2✔
532
    }
533
    // visualization mode
534
    if (thingsToClear.includes(SESSION_IDS.VISUALIZATION_MODE)) {
18✔
535
        delete overrideConfig.map.visualizationMode;
1✔
536
    }
537

538
    // layers is the only case that partially gets reset, and there is problem to merge arrays with different index(it merges properties from two array on same index)
539
    // so merging original layers here. And while merging override config with original config: Override config is given priority. Check applyOverrides function below in this file.
540

541
    // annotation layers
542
    if (thingsToClear?.includes(SESSION_IDS.ANNOTATIONS_LAYER)) {
18✔
543
        overrideConfig.map.layers = [...overrideConfig.map?.layers?.filter((l)=>!l.id?.includes('annotations')), ...originalConfig?.map?.layers.filter((l)=>l.id?.includes('annotations'))];
7✔
544
    }
545
    // measurements layers
546
    if (thingsToClear?.includes(SESSION_IDS.MEASUREMENTS_LAYER)) {
18✔
547
        overrideConfig.map.layers = [...overrideConfig.map?.layers?.filter((l)=>!l?.name?.includes('measurements')), ...originalConfig?.map?.layers.filter(l=> l?.name?.includes('measurements'))];
4✔
548
    }
549
    // background layers
550
    if (thingsToClear?.includes(SESSION_IDS.BACKGROUND_LAYERS)) {
18✔
551
        overrideConfig.map.layers = [...overrideConfig.map?.layers?.filter((l)=>!l?.group?.includes('background')), ...originalConfig?.map?.layers.filter(l=> l?.group?.includes('background'))];
10✔
552
    }
553
    // other layers
554
    if (thingsToClear?.includes(SESSION_IDS.OTHER_LAYERS)) {
18!
UNCOV
555
        overrideConfig.map.layers = [...overrideConfig.map?.layers?.filter(l=> l?.id?.includes('annotations') || l?.name?.includes('measurements') || l.group?.includes("background")), ...originalConfig?.map?.layers?.filter(l=> !l?.id?.includes('annotations') && !l?.name?.includes('measurements') && !l.group?.includes("background"))];
×
556
    }
557

558
    // reorder layers based on existing order of layers: since layers are modified in different orders, so sorting is important
559
    overrideConfig.map.layers = overrideConfig.map.layers.sort((a, b) =>
18✔
560
        override?.map?.layers.findIndex(item => item.id === a.id) - override?.map?.layers?.findIndex(item => item.id === b.id)
182✔
561
    );
562
    // catalog services
563
    if (thingsToClear?.includes(SESSION_IDS.CATALOG_SERVICES)) {
18✔
564
        delete overrideConfig.catalogServices;
1✔
565
    }
566
    // widgets
567
    if (thingsToClear?.includes(SESSION_IDS.WIDGETS)) {
18✔
568
        delete overrideConfig.widgetsConfig;
1✔
569
    }
570

571
    // search services
572
    if (thingsToClear?.includes(SESSION_IDS.TEXT_SEARCH_SERVICES)) {
18✔
573
        delete overrideConfig.map.text_search_config;
1✔
574
    }
575

576
    // bookmarks
577
    if (thingsToClear?.includes(SESSION_IDS.BOOKMARKS)) {
18✔
578
        delete overrideConfig.map.bookmark_search_config;
1✔
579
    }
580

581
    // feature grid
582
    if (thingsToClear?.includes(SESSION_IDS.FEATURE_GRID)) {
18✔
583
        // each properties are updated dynamically in featureGrid reducer(MAP_CONFIG_LOADED), so each attribute of featureGrid should be reset
584
        Object.keys(overrideConfig?.featureGrid ?? {}).forEach((fg)=>{
1!
585
            overrideConfig.featureGrid[fg] = {}; // all properties's value are in object
1✔
586
        });
587
    }
588

589
    if (thingsToClear?.includes(SESSION_IDS.USER_PLUGINS)) {
18✔
590
        delete overrideConfig.context.userPlugins;
1✔
591
    }
592

593
    // handle config from registerCustomSaveConfig
594
    customHandlers?.forEach((k) => {
18✔
595
        if (thingsToClear?.includes(k)) {
25✔
596
            delete overrideConfig[k];
1✔
597
        }
598
    });
599

600

601
    return overrideConfig;
18✔
602

603
};
604
/**
605
* Merge two configurations
606
* While overriding, overrideConfig properties and original config has arrays then overrideConfig gets more priority
607
* merge from loadash has problem while merging arrays of objects(it merges properties of objects from both), So merge logic has been changed
608
 * @param {object} config the configuration to override
609
 * @param {object} override the data to use for override
610
 * @returns {object}
611
 */
612
export const applyOverrides = (config, override) => {
1✔
613
    const merged = mergeWith({}, config, override, (objValue, srcValue) => {
27✔
614
        // Till now layers is the only case that get partially reset and has case where two array with objects tries to merge, so merging with original config happens in updateOverrideConfig(above in the this file) while reset
615
        if (Array.isArray(objValue) && Array.isArray(srcValue)) {
429✔
616
            // Give priority if there are some elements in override
617
            if (srcValue.length > 0) {
8✔
618
                return [...srcValue];
6✔
619
            }
620
            return [...objValue];
2✔
621

622
        }
623
        // default merge rules for other cases
624
        // eslint-disable-next-line consistent-return
625
        return undefined;
421✔
626
    });
627
    return merged;
27✔
628
};
629
const ConfigUtils = {
1✔
630
    PropTypes: {
631
        center: centerPropType,
632
        config: PropTypes.shape({
633
            center: centerPropType,
634
            zoom: PropTypes.number.isRequired
635
        }),
636
        mapStateSource: PropTypes.string
637
    },
638
    getParsedUrl,
639
    getDefaults,
640
    setLocalConfigurationFile,
641
    loadConfiguration,
642
    getCenter,
643
    normalizeConfig,
644
    getUserConfiguration,
645
    getConfigurationOptions,
646
    getConfigUrl,
647
    convertFromLegacy,
648
    setupSources,
649
    normalizeSourceUrl,
650
    copySourceOptions,
651
    setupLayers,
652
    mergeConfigs,
653
    getProxyUrl,
654
    cleanDuplicatedQuestionMarks,
655
    getUrlWithoutParameters,
656
    filterUrlParams,
657
    getProxiedUrl,
658
    getBrowserProperties,
659
    setApiKeys,
660
    setUrlPlaceholders,
661
    replacePlaceholders,
662
    setLayerId,
663
    getConfigProp,
664
    setConfigProp,
665
    removeConfigProp,
666
    getMiscSetting
667
};
668

669
export default ConfigUtils;
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