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

geosolutions-it / MapStore2 / 14534587011

18 Apr 2025 11:41AM UTC coverage: 76.977% (-0.02%) from 76.993%
14534587011

Pull #11037

github

web-flow
Merge f22d700f6 into 48d6a1a15
Pull Request #11037: Remove object assign pollyfills

30792 of 47937 branches covered (64.23%)

446 of 556 new or added lines in 94 files covered. (80.22%)

8 existing lines in 4 files now uncovered.

38277 of 49725 relevant lines covered (76.98%)

36.07 hits per line

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

86.14
/web/client/api/SLDService.js
1
/*
2
 * Copyright 2018, 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 { urlParts } from '../utils/URLUtils';
10

11
import url from 'url';
12
import { sortBy, head, castArray, isNumber, isString } from 'lodash';
13
import chroma from 'chroma-js';
14
import { getLayerUrl } from '../utils/LayersUtils';
15
import { standardClassificationScales as standardColors } from '../utils/ClassificationUtils';
16

17
const isAttributeAllowed = (type) => ['Integer', 'Long', 'Double', 'Float', 'BigDecimal'].indexOf(type) !== -1;
3✔
18
const getSimpleType = () => {
1✔
19
    return 'number';
2✔
20
};
21

22
const addViewParam = (actual, key, value) => {
1✔
23
    return {
4✔
24
        viewparams: (actual ? (actual + ';') : '') + key + ':' + value
4!
25
    };
26
};
27

28
const isParam = (key, params = []) => {
1!
29
    return params.filter(f => f.field === key).length > 0;
12✔
30
};
31

32
const isViewParam = (key, params) => {
1✔
33
    return isParam(key, params);
12✔
34
};
35

36
const getCustomClassification = (classification) => {
1✔
37
    if (classification) {
×
38
        return {
×
39
            customClasses: classification.reduce((previous, classItem) => {
40
                return [...previous, classItem.min + ',' + classItem.max + ',' + classItem.color];
×
41
            }, []).join(';')
42
        };
43
    }
44
    return {};
×
45
};
46

47
const getColor = (layer, name, intervals, customRamp) => {
1✔
48
    const chosenColors = layer
13✔
49
        ? head((layer.thematic.colors || layer.thematic.additionalColors || [])
15✔
50
            .filter(c => c.name === name)
2✔
51
        ) || head(standardColors.filter(c => c.name === name))
84✔
52
        : customRamp
9✔
53
            ? head([ customRamp, ...standardColors].filter(c => c.name === name))
43✔
54
            : head(standardColors.filter(c => c.name === name));
336✔
55
    if (chosenColors && (isString(chosenColors.colors) || chosenColors.colors.length >= 2)) {
13✔
56
        return {
12✔
57
            ramp: "custom",
58
            colors: chroma.scale(chosenColors.colors).colors(intervals).join(',')
59
        };
60
    }
61
    return { ramp: name };
1✔
62
};
63

64
const mapParams = (layer, params) => {
1✔
65
    const configParams = layer.thematic && layer.thematic.params || [];
4!
66
    const otherParams = layer.thematic && layer.thematic.fieldAsParam && ['field'] || [];
4!
67
    return Object.keys(params).reduce((previous, key) => {
4✔
68
        if (isViewParam(key, [...configParams, ...otherParams])) {
12✔
69
            return Object.assign(previous, addViewParam(previous.viewparams, key, params[key]));
4✔
70
        }
71
        if (key === 'ramp') {
8!
NEW
72
            return Object.assign(previous, getColor(layer, params[key], params.intervals || 5));
×
73
        }
74
        if (key === 'classification') {
8!
NEW
75
            return Object.assign(previous, getCustomClassification(params[key]));
×
76
        }
77
        if (key === 'attribute') {
8!
NEW
78
            return Object.assign(previous, {
×
79
                attribute: layer.thematic && layer.thematic.fieldAsParam ? params[key] : params.field
×
80
            });
81
        }
82
        if (key === 'field' && layer.thematic && !layer.thematic.fieldAsParam) {
8!
83
            return previous;
×
84
        }
85
        if (key === 'strokeWeight' && !params.strokeOn) {
8!
NEW
86
            return Object.assign(previous, {
×
87
                [key]: -1
88
            });
89
        }
90
        if (key === 'strokeOn') {
8!
91
            return previous;
×
92
        }
93
        return Object.assign(previous, {
8✔
94
            [key]: params[key]
95
        });
96
    }, {});
97
};
98

99
const getUrl = (parts) => {
1✔
100
    return Object.assign({
24✔
101
        protocol: parts.protocol,
102
        hostname: parts.domain
103
    }, parts.port ? {
24✔
104
        port: parts.port
105
    } : {});
106
};
107

108
const getNumber = (candidates) => {
1✔
109
    return candidates.reduce((previous, current) => {
44✔
110
        return isNumber(current) ? current : previous;
132✔
111
    }, null);
112
};
113

114
const getGeometryType = (rule) => {
1✔
115
    if (rule.PolygonSymbolizer) {
27✔
116
        return 'Polygon';
25✔
117
    }
118
    if (rule.LineSymbolizer) {
2✔
119
        return 'LineString';
1✔
120
    }
121
    if (rule.PointSymbolizer) {
1!
122
        return 'Point';
1✔
123
    }
124
    return null;
×
125
};
126

127
const getRuleColor = (rule) => {
1✔
128
    if (rule.PolygonSymbolizer) {
27✔
129
        return rule.PolygonSymbolizer.Fill && rule.PolygonSymbolizer.Fill.CssParameter
25!
130
            && rule.PolygonSymbolizer.Fill.CssParameter.$ || '#808080'; // OGC default color
131
    }
132
    if (rule.LineSymbolizer) {
2✔
133
        return rule.LineSymbolizer.Stroke && rule.LineSymbolizer.Stroke.CssParameter
1!
134
            && rule.LineSymbolizer.Stroke.CssParameter.$ || '#808080'; // OGC default color
135
    }
136
    if (rule.PointSymbolizer) {
1!
137
        return rule.PointSymbolizer.Graphic && rule.PointSymbolizer.Graphic.Mark && rule.PointSymbolizer.Graphic.Mark.Fill
1!
138
            && rule.PointSymbolizer.Graphic.Mark.Fill.CssParameter
139
            && rule.PointSymbolizer.Graphic.Mark.Fill.CssParameter.$ || '#808080'; // OGC default color
140
    }
141
    return '#808080';
×
142
};
143

144
const validateClassification = (classificationObj) => {
1✔
145
    if (classificationObj && classificationObj.Rule) {
10!
146
        throw new Error("toc.thematic.invalid_attribute");
×
147
    }
148
    if (!classificationObj || !classificationObj.Rules || !classificationObj.Rules.Rule) {
10!
149
        throw new Error("toc.thematic.invalid_object");
×
150
    }
151
    const rules = castArray(classificationObj.Rules.Rule);
10✔
152
    rules.forEach(rule => {
10✔
153
        if (!rule.PolygonSymbolizer && !rule.LineSymbolizer && !rule.PointSymbolizer) {
28✔
154
            throw new Error("toc.thematic.invalid_geometry");
1✔
155
        }
156
    });
157
};
158

159
/**
160
 * API for GeoServer SLDService {@link http://docs.geoserver.org/stable/en/user/community/sldservice/index.html}.
161
 *
162
 * Can be used to implement @see {@link api/plugins#plugins.ThematicLayer}
163
 *
164
 * @memberof API
165
 * @name SLDService
166
 */
167
const API = {
1✔
168
    /**
169
     * Returns a url to get a full SLD for classification, given a layer cfg object and a list
170
     * of classification parameters.
171
     *
172
     * @memberof API.SLDService
173
     * @method getStyleService
174
     * @param {object} layer layer configuration object
175
     * @param {object} params map of classification parameters: they will be used to build parameters for the SLDService classify service
176
     * @returns {string} url to get a classification SLD
177
     */
178
    getStyleService: (layer, params) => {
179
        const parts = urlParts(getLayerUrl(layer));
2✔
180
        return url.format(Object.assign(getUrl(parts), {
2✔
181
            pathname: parts.applicationRootPath + "/rest/sldservice/" + layer.name + "/classify.xml",
182
            query: Object.assign({}, mapParams(layer, params), { fullSLD: true })
183
        }));
184
    },
185
    getCapabilitiesUrl: (layer) => {
186
        const parts = urlParts(getLayerUrl(layer));
6✔
187
        return url.format(Object.assign(getUrl(parts), {
6✔
188
            pathname: parts.applicationRootPath + "/rest/sldservice/capabilities.json"
189
        }));
190
    },
191
    /**
192
     * Returns a url to get a classification JSON, given a layer cfg object and a list
193
     * of classification parameters.
194
     *
195
     * @memberof API.SLDService
196
     * @method getStyleMetadataService
197
     * @param {object} layer layer configuration object
198
     * @param {object} params map of classification parameters: they will be used to build parameters for the SLDService classify service
199
     * @param {object} styleService style service configuration object
200
     * @returns {string} url to get a classification metadata JSON
201
     */
202
    getStyleMetadataService: (layer, params, styleService) => {
203
        const { baseUrl = '', isStatic = false } = styleService || {};
14✔
204
        const parts = urlParts(isStatic ? baseUrl : getLayerUrl(layer));
14✔
205
        return url.format(Object.assign(getUrl(parts), {
14✔
206
            pathname: parts.applicationRootPath + "/rest/sldservice/" + layer.name + "/classify.json",
207
            query: params
208
        }));
209
    },
210
    /**
211
     * Returns an object with additional parameters to apply a classification style to a WMS layer.
212
     *  - SLD: dynamic link to the classification style service
213
     *  - viewparams: SQL views parameters actual values
214
     *
215
     * @memberof API.SLDService
216
     * @method getStyleParameters
217
     * @param {object} layer layer configuration object
218
     * @param {object} params map of classification parameters: they will be used to build parameters for the SLDService classify service
219
     * @returns {object} classification style object (SLD, viewparams)
220
     */
221
    getStyleParameters: (layer, params) => {
222
        return {
1✔
223
            SLD: API.getStyleService(layer, params),
224
            viewparams: mapParams(layer, params).viewparams
225
        };
226
    },
227
    /**
228
     * Returns an object with parameters mapped from UI ones, to WMS ones.
229
     *
230
     * @memberof API.SLDService
231
     * @method getMetadataParameters
232
     * @param {object} layer layer configuration object
233
     * @param {object} params map of classification parameters: they will be used to build parameters for the SLDService classify service
234
     * @returns {object} classification parameters object
235
     */
236
    getMetadataParameters: (layer, params) => {
237
        return mapParams(layer, params);
1✔
238
    },
239
    /**
240
     * Returns the url to get a list of fields available for the given layer.
241
     * If the layer has a datatable configuration property in the thematic object, this is used to extract fields list,
242
     * otherwise the original layer is used (datatable is used for SQLViews doing aggregation, where the original list of data attributes
243
     * is not directly available on the aggregated layer).
244
     *
245
     * @memberof API.SLDService
246
     * @method getFieldsService
247
     * @param {object} layer layer configuration object
248
     * @returns {string} url of the attributes service
249
     */
250
    getFieldsService: (layer) => {
251
        const parts = urlParts(getLayerUrl(layer));
2✔
252
        const table = layer.thematic && layer.thematic.datatable || layer.name;
2✔
253
        return url.format(Object.assign(getUrl(parts), {
2✔
254
            pathname: parts.applicationRootPath + "/rest/sldservice/" + table + "/attributes.json"
255
        }));
256
    },
257
    /**
258
     * Reads attributes returned by the attributes service and builds a simple list of:
259
     *  - name
260
     *  - type (only number is currently supported)
261
     * If the layer has a datatable configuration property in the thematic object, this is used to extract fields list,
262
     * otherwise the original layer is used (datatable is used for SQLViews doing aggregation, where the original list of data attributes
263
     * is not directly available on the aggregated layer).
264
     *
265
     * @memberof API.SLDService
266
     * @method readFields
267
     * @param {object} fieldsObj object returned by SLDService attributes service
268
     * @returns {array} simplified fields list
269
     */
270
    readFields: (fieldsObj) => {
271
        return sortBy(castArray(fieldsObj.Attributes.Attribute || [])
1!
272
            .filter(a => isAttributeAllowed(a.type))
3✔
273
            .map(a => ({
2✔
274
                name: a.name,
275
                type: getSimpleType(a.type)
276
            })), a => a.name);
2✔
277
    },
278
    /**
279
     * Reads classification classes returned by the classification service and builds a simple list of:
280
     *  - color
281
     *  - min
282
     *  - max
283
     * If the layer has a datatable configuration property in the thematic object, this is used to extract fields list,
284
     * otherwise the original layer is used (datatable is used for SQLViews doing aggregation, where the original list of data attributes
285
     * is not directly available on the aggregated layer).
286
     *
287
     * @memberof API.SLDService
288
     * @method readClassification
289
     * @param {object} classificationObj object returned by SLDService classifier service
290
     * @param {string} method name of classification
291
     * @returns {array} simplified classification classes list
292
     */
293
    readClassification: (classificationObj, method) => {
294
        validateClassification(classificationObj);
10✔
295
        const rules = castArray(classificationObj.Rules.Rule || []);
9!
296
        return rules.map((rule, idx) => ({
27!
297
            title: rule.Title,
298
            color: getRuleColor(rule),
299
            type: getGeometryType(rule),
300
            ...(method === 'uniqueInterval'
27✔
301
                ? { unique: rule.Filter.PropertyIsEqualTo && rule.Filter.PropertyIsEqualTo.Literal }
10✔
302
                : {
303
                    min: getNumber([
304
                        rule.Filter.And && (rule.Filter.And.PropertyIsGreaterThanOrEqualTo || rule.Filter.And.PropertyIsGreaterThan).Literal,
66✔
305
                        rule.Filter.PropertyIsEqualTo && rule.Filter.PropertyIsEqualTo.Literal,
22!
306
                        // standard deviation
307
                        idx === rules.length - 1 && rule?.Filter?.PropertyIsGreaterThanOrEqualTo?.Literal
30✔
308
                    ]),
309
                    max: getNumber([
310
                        rule.Filter.And && (rule.Filter.And.PropertyIsLessThanOrEqualTo || rule.Filter.And.PropertyIsLessThan).Literal,
79✔
311
                        rule.Filter.PropertyIsEqualTo && rule.Filter.PropertyIsEqualTo.Literal,
22!
312
                        // standard deviation
313
                        idx === 0 && rule?.Filter?.PropertyIsLessThan?.Literal
30✔
314
                    ])
315
                })
316
        })) || [];
317
    },
318
    /**
319
     * Reads classification entries returned by the raster classification service and builds a simple list of:
320
     *  - color
321
     *  - opacity
322
     *  - label
323
     *  - quantity
324
     * @memberof API.SLDService
325
     * @method readRasterClassification
326
     * @param {object} rasterClassificationObj object returned by SLDService classifier service
327
     * @returns {array} simplified classification classes list
328
     */
329
    readRasterClassification: (rasterClassificationObj) => {
330
        const rules = castArray(rasterClassificationObj?.Rules?.Rule);
4✔
331
        const entries = rules[0]?.RasterSymbolizer?.ColorMap?.ColorMapEntry || [];
4!
332
        return entries.map((entry) => ({
16✔
333
            color: entry['@color'],
334
            opacity: entry['@opacity'] === undefined
16!
335
                ? 1
336
                : entry['@opacity'],
337
            label: entry['@label'],
338
            quantity: parseFloat(entry['@quantity'])
339
        }));
340
    },
341
    /**
342
     * supported classification methods
343
     *
344
     * @memberof API.SLDService
345
     * @property {array} methods supported classification methods
346
     */
347
    methods: [
348
        'equalInterval', 'quantile', 'jenks'
349
    ],
350
    /**
351
     * Inlines configuration parameters, replacing known types (e.g. aggregate) with actual configuration.
352
     *
353
     * @memberof API.SLDService
354
     * @method getThematicParameters
355
     * @param {object} params simplified parameters configuration
356
     * @returns {array} list of actual parameters configuration (standard params are inlined)
357
     */
358
    getThematicParameters: (params) => {
359
        return params.map((param) => param.type && API.standardParams[param.type] && Object.assign({}, API.standardParams[param.type], param) || param);
2✔
360
    },
361
    /**
362
     * Supported known parameters: some parameters have a special behaviour (preconfigured and localized labels and values):
363
     *  - type=aggregate (sum, count, min, ...)
364
     *
365
     * @memberof API.SLDService
366
     * @property {object} standardParams supported known parameters
367
     */
368
    standardParams: {
369
        aggregate: {
370
            "title": "toc.thematic.classification_aggregate",
371
            "defaultValue": "sum",
372
            "values": [{
373
                "name": "toc.thematic.values.sum",
374
                "value": "sum"
375
            }, {
376
                "name": "toc.thematic.values.avg",
377
                "value": "avg"
378
            }, {
379
                "name": "toc.thematic.values.count",
380
                "value": "count"
381
            },
382
            {
383
                "name": "toc.thematic.values.min",
384
                "value": "min"
385
            },
386
            {
387
                "name": "toc.thematic.values.max",
388
                "value": "max"
389
            }]
390
        }
391
    },
392
    getColor,
393
    /**
394
     * Gets a list of color samples for all the given palettes.
395
     *
396
     * @memberof API.SLDService
397
     * @method getColors
398
     * @param {object} baseColors base color palettes
399
     * @param {object} layer layer configuration (can contain thematic.colors for color palette overrides or thematic.additionalColors for appending)
400
     * @param {number} samples number of samples for each palette
401
     * @returns {array} list of palettes with sample colors
402
     */
403
    getColors: (baseColors = standardColors, layer, samples, customRamp) => {
3✔
404
        const colors = layer
4!
405
            ? layer.thematic.colors || [...baseColors, ...(layer.thematic.additionalColors || [])]
12✔
406
            : customRamp ? [ customRamp, ...baseColors ] : [...baseColors];
×
407

408
        return colors.map((color) => !isString(color.colors) && color.colors.length >= samples
87!
409
            ? color
410
            : {
411
                ...color,
412
                colors: chroma.scale(color.colors.length === 1 ? [color.colors[0], color.colors[0]] : color.colors).colors(samples)
87!
413
            });
414
    },
415
    /**
416
     * Checks if the given layer has a thematic style applied on it (SLD param not empty)
417
     *
418
     * @memberof API.SLDService
419
     * @method hasThematicStyle
420
     * @param {object} layer layer configuration
421
     * @returns {boolean} true if thematic style is applied, false otherwise
422
     */
423
    hasThematicStyle: (layer) => {
424
        return layer && layer.params && layer.params.SLD && true || false;
2✔
425
    },
426
    /**
427
     * Removes a thematic style from the given layer (SLD and viewparams params are removed)
428
     *
429
     * @memberof API.SLDService
430
     * @method removeThematicStyle
431
     * @param {object} params layer params configuration
432
     * @returns {object} layer params configuration without the thematic style parameters
433
     */
434
    removeThematicStyle: (params) => {
435
        const {SLD, viewparams, ...other} = params;
1✔
436
        return Object.assign({}, other, {
1✔
437
            SLD: null,
438
            viewparams: null
439
        });
440
    },
441

442
    defaultParams: {
443
        attribute: "",
444
        intervals: 5,
445
        method: "equalInterval",
446
        ramp: 'red',
447
        field: "",
448
        open: false,
449
        strokeWeight: 0.2,
450
        strokeColor: '#ff0000',
451
        strokeOn: false
452
    }
453
};
454

455
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