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

geosolutions-it / MapStore2 / 23803084840

31 Mar 2026 02:37PM UTC coverage: 76.77% (+0.02%) from 76.749%
23803084840

push

github

MV88
Fix #12044: fix wfs layer hidden in 3D + scale arrow not rendering (#12155)

* fix #12044: fix wfs layer hidden in 3D + scale arrow not rendering

- fix not hidding wfs layers with scale limits
- handle showing arrow for current scale in scales DD for scaleDenominator

* - Fix: Arrow UI disappears when zooming out in 3D styler view

* - fix the scale limit filter to exclude max value and include min scale value

* - for 3D, ehnace getMapScaleForCesium to take camera prependicular poosition into account
- enhance calc. scale value in geoStylerStyleToOlParserStyleFct to match the predefined scale values list
- fix FE unit test

* - fix FE unit test

* - add unit tests for getMapScaleForCesium and isCameraPerpendicularToSurface

32344 of 50279 branches covered (64.33%)

14 of 18 new or added lines in 4 files covered. (77.78%)

313 existing lines in 14 files now uncovered.

40240 of 52416 relevant lines covered (76.77%)

38.14 hits per line

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

85.98
/web/client/utils/styleparser/CesiumStyleParser.js
1
/*
2
 * Copyright 2022, 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 * as Cesium from 'cesium';
9
import chroma from 'chroma-js';
10
import { castArray, isNumber, isEqual, range, isNaN } from 'lodash';
11
import { needProxy, getProxyUrl } from '../ProxyUtils';
12
import {
13
    resolveAttributeTemplate,
14
    geoStylerStyleFilter,
15
    getImageIdFromSymbolizer,
16
    parseSymbolizerExpressions
17
} from './StyleParserUtils';
18
import { drawIcons } from './IconUtils';
19
import { geometryFunctionsLibrary } from './GeometryFunctionsUtils';
20
import EllipseGeometryLibrary from '@cesium/engine/Source/Core/EllipseGeometryLibrary';
21
import CylinderGeometryLibrary from '@cesium/engine/Source/Core/CylinderGeometryLibrary';
22

23
const getGeometryFunction = geometryFunctionsLibrary.cesium({ Cesium });
1✔
24

25
function getCesiumColor({ color, opacity }) {
26
    if (!color) {
63✔
27
        return new Cesium.Color(0, 0, 0, 0);
6✔
28
    }
29
    const [r, g, b, a] = chroma(color).gl();
57✔
30
    if (opacity !== undefined) {
57!
31
        return new Cesium.Color(r, g, b, opacity);
57✔
32
    }
33
    return new Cesium.Color(r, g, b, a);
×
34
}
35

36
function getCesiumDashArray({ color, opacity, dasharray }) {
37
    if (dasharray?.length <= 0) {
4!
38
        return getCesiumColor({ color, opacity });
×
39
    }
40
    const dashLength = dasharray.reduce((acc, value) => acc + value, 0);
8✔
41
    return new Cesium.PolylineDashMaterialProperty({
4✔
42
        color: getCesiumColor({ color, opacity }),
43
        dashLength,
44
        dashPattern: parseInt((dasharray
45
            .map((value) => Math.floor(value / dashLength * 16))
8✔
46
            .map((value, idx) => range(value).map(() => idx % 2 === 0 ? '1' : '0').join(''))
64✔
47
            .join('')), 2)
48
    });
49
}
50

51
const getNumberAttributeValue = (value) => {
1✔
52
    const constantHeight = parseFloat(value);
156✔
53
    if (!isNaN(constantHeight) && isNumber(constantHeight)) {
156✔
54
        return constantHeight;
52✔
55
    }
56
    return null;
104✔
57
};
58

59
const getPositionsRelativeToTerrain = ({
1✔
60
    map,
61
    positions,
62
    heightReference: _heightReference,
63
    sampleTerrain,
64
    initialHeight
65
}) => {
66

67
    const heightReference = _heightReference  ?? 'none';
28✔
68

69
    const heightReferenceMap = {
28✔
70
        none: (originalHeight) => originalHeight,
29✔
71
        relative: (originalHeight, sampledHeight) => originalHeight + sampledHeight,
21✔
72
        clamp: (originalHeight, sampledHeight) => sampledHeight
18✔
73
    };
74

75
    let originalHeights = [];
28✔
76

77
    const computeHeight = (cartographicPositions) => {
28✔
78
        const computeHeightReference = heightReferenceMap[heightReference];
28✔
79
        let minHeight = Infinity;
28✔
80
        let maxHeight = -Infinity;
28✔
81
        let minSampledHeight = Infinity;
28✔
82
        let maxSampledHeight = -Infinity;
28✔
83
        const newPositions = cartographicPositions.map((cartographic, idx) => {
28✔
84
            const originalHeight = originalHeights[idx] || 0;
68✔
85
            const sampledHeight = heightReference === 'none' ? 0 : cartographic.height || 0;
68✔
86
            const height = computeHeightReference(originalHeight, sampledHeight);
68✔
87
            minHeight = height < minHeight ? height : minHeight;
68✔
88
            maxHeight = height > maxHeight ? height : maxHeight;
68✔
89
            minSampledHeight = sampledHeight < minSampledHeight ? sampledHeight : minSampledHeight;
68✔
90
            maxSampledHeight = sampledHeight > maxSampledHeight ? sampledHeight : maxSampledHeight;
68✔
91
            return Cesium.Cartesian3.fromRadians(
68✔
92
                cartographic.longitude,
93
                cartographic.latitude,
94
                height
95
            );
96
        });
97
        return {
28✔
98
            height: {
99
                min: minHeight,
100
                max: maxHeight
101
            },
102
            sampledHeight: {
103
                min: minSampledHeight,
104
                max: maxSampledHeight
105
            },
106
            positions: newPositions
107
        };
108
    };
109

110
    const cartographicPositions = positions.map(cartesian => {
28✔
111
        const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
68✔
112
        originalHeights.push(initialHeight ?? cartographic.height ?? 0);
68!
113
        return new Cesium.Cartographic(cartographic.longitude, cartographic.latitude, initialHeight ?? 0);
68✔
114
    });
115

116
    const terrainProvider = map?.terrainProvider;
28✔
117

118
    if (heightReference === 'none' || !terrainProvider) {
28✔
119
        return Promise.resolve(computeHeight(cartographicPositions));
17✔
120
    }
121

122
    const promise = terrainProvider?.availability
11!
123
        ? Cesium.sampleTerrainMostDetailed(
124
            terrainProvider,
125
            cartographicPositions
126
        )
127
        : sampleTerrain(
128
            terrainProvider,
129
            terrainProvider?.sampleTerrainZoomLevel ?? 18,
22✔
130
            cartographicPositions
131
        );
132
    if (Cesium.defined(promise)) {
11!
133
        return promise
11✔
134
            .then((updatedCartographicPositions) => {
135
                return computeHeight(updatedCartographicPositions);
11✔
136
            })
137
            // the sampleTerrainMostDetailed from the Cesium Terrain is still using .otherwise
138
            // and it resolve everything in the .then
139
            // while the sampleTerrain uses .catch
140
            // the optional chain help us to avoid error if catch is not exposed by the promise
141
            ?.catch?.(() => {
142
                return computeHeight(cartographicPositions);
×
143
            });
144
    }
145
    return computeHeight(cartographicPositions);
×
146

147
};
148

149
const cachedLeaderLineCanvas = {};
1✔
150

151
function createLeaderLineCanvas({
152
    offset = [],
×
153
    msLeaderLineWidth
154
}) {
155
    const lineWidth = msLeaderLineWidth ?? 1;
1!
156
    const width = Math.abs(offset[0] || 1);
1!
157
    const height = Math.abs(offset[1] || 1);
1!
158
    const isLeftTopDiagonal = Math.sign(offset[0]) === Math.sign(offset[1]);
1✔
159
    const key = [width, height, lineWidth, isLeftTopDiagonal].join(';');
1✔
160
    if (cachedLeaderLineCanvas[key]) {
1!
161
        return cachedLeaderLineCanvas[key];
×
162
    }
163
    const canvas = document.createElement('canvas');
1✔
164
    canvas.setAttribute('width', width);
1✔
165
    canvas.setAttribute('height', height);
1✔
166
    const ctx = canvas.getContext('2d');
1✔
167
    ctx.strokeStyle = '#ffffff';
1✔
168
    ctx.lineWidth = lineWidth;
1✔
169
    ctx.beginPath();
1✔
170
    ctx.moveTo(...(isLeftTopDiagonal ? [0, 0] : [width, 0]));
1!
171
    ctx.lineTo(...(isLeftTopDiagonal ? [width, height] : [0, height]));
1!
172
    ctx.stroke();
1✔
173
    cachedLeaderLineCanvas[key] = canvas;
1✔
174
    return canvas;
1✔
175
}
176

177
const translatePoint = (cartesian, symbolizer) => {
1✔
178
    const { msTranslateX, msTranslateY } = symbolizer || {};
32!
179
    const x = getNumberAttributeValue(msTranslateX);
32✔
180
    const y = getNumberAttributeValue(msTranslateY);
32✔
181
    return (x || y)
32✔
182
        ? Cesium.Matrix4.multiplyByPoint(
183
            Cesium.Transforms.eastNorthUpToFixedFrame(cartesian),
184
            new Cesium.Cartesian3(x || 0, y || 0, 0),
4!
185
            new Cesium.Cartesian3()
186
        )
187
        : cartesian;
188
};
189

190
const HEIGHT_REFERENCE_CONSTANTS_MAP = {
1✔
191
    none: 'NONE',
192
    relative: 'RELATIVE_TO_GROUND',
193
    clamp: 'CLAMP_TO_GROUND'
194
};
195

196
const anchorToOrigin = (anchor) => {
1✔
197
    switch (anchor) {
4!
198
    case 'top-left':
199
        return {
×
200
            horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
201
            verticalOrigin: Cesium.VerticalOrigin.TOP
202
        };
203
    case 'top':
204
        return {
×
205
            horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
206
            verticalOrigin: Cesium.VerticalOrigin.TOP
207
        };
208
    case 'top-right':
209
        return {
1✔
210
            horizontalOrigin: Cesium.HorizontalOrigin.RIGHT,
211
            verticalOrigin: Cesium.VerticalOrigin.TOP
212
        };
213
    case 'left':
214
        return {
×
215
            horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
216
            verticalOrigin: Cesium.VerticalOrigin.CENTER
217
        };
218
    case 'center':
219
        return {
×
220
            horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
221
            verticalOrigin: Cesium.VerticalOrigin.CENTER
222
        };
223
    case 'right':
224
        return {
×
225
            horizontalOrigin: Cesium.HorizontalOrigin.RIGHT,
226
            verticalOrigin: Cesium.VerticalOrigin.CENTER
227
        };
228
    case 'bottom-left':
229
        return {
1✔
230
            horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
231
            verticalOrigin: Cesium.VerticalOrigin.BOTTOM
232
        };
233
    case 'bottom':
234
        return {
×
235
            horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
236
            verticalOrigin: Cesium.VerticalOrigin.BOTTOM
237
        };
238
    case 'bottom-right':
239
        return {
×
240
            horizontalOrigin: Cesium.HorizontalOrigin.RIGHT,
241
            verticalOrigin: Cesium.VerticalOrigin.BOTTOM
242
        };
243
    default:
244
        return {
2✔
245
            horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
246
            verticalOrigin: Cesium.VerticalOrigin.CENTER
247
        };
248
    }
249
};
250

251
const getVolumeShape = (shape = 'Square', radius = 1) => {
1!
252
    if (shape === 'Circle') {
2!
253
        const positions = [];
2✔
254
        for (let i = 0; i < 360; i++) {
2✔
255
            const radians = Cesium.Math.toRadians(i);
720✔
256
            positions.push(
720✔
257
                new Cesium.Cartesian2(
258
                    radius * Math.cos(radians),
259
                    radius * Math.sin(radians)
260
                )
261
            );
262
        }
263
        return positions;
2✔
264
    }
265
    if (shape === 'Square') {
×
266
        return [
×
267
            new Cesium.Cartesian2(-radius, -radius),
268
            new Cesium.Cartesian2(radius, -radius),
269
            new Cesium.Cartesian2(radius, radius),
270
            new Cesium.Cartesian2(-radius, radius)
271
        ];
272
    }
273
    return [];
×
274
};
275

276
const isCompatibleGeometry = ({ geometry, symbolizer }) => {
1✔
277
    if (geometry.type === 'Point' && ['Fill', 'Line'].includes(symbolizer.kind)) {
41!
278
        return false;
×
279
    }
280
    if (geometry.type === 'LineString' && ['Fill'].includes(symbolizer.kind)) {
41!
281
        return false;
×
282
    }
283
    if (geometry.type === 'Polygon' && ['Line'].includes(symbolizer.kind)) {
41!
284
        return false;
×
285
    }
286
    return true;
41✔
287
};
288

289
const getOrientation = (position, symbolizer) => {
1✔
290
    const { heading, pitch, roll } = symbolizer || {};
24!
291
    if (heading || pitch || roll) {
24!
292
        const hpr = new Cesium.HeadingPitchRoll(
×
293
            Cesium.Math.toRadians(heading ?? 0),
×
294
            Cesium.Math.toRadians(pitch ?? 0),
×
295
            Cesium.Math.toRadians(roll ?? 0));
×
296
        const orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);
×
297
        return orientation;
×
298
    }
299
    return null;
24✔
300
};
301

302
const getCirclePositions = (position, symbolizer) => {
1✔
303
    const radius = symbolizer.radius;
2✔
304
    const geodesic = symbolizer.geodesic;
2✔
305
    const slices = 128;
2✔
306
    const center = position;
2✔
307
    let positions;
308
    if (geodesic) {
2!
309
        const { outerPositions } = EllipseGeometryLibrary.computeEllipsePositions({
2✔
310
            granularity: 0.02,
311
            semiMajorAxis: radius,
312
            semiMinorAxis: radius,
313
            rotation: 0,
314
            center
315
        }, false, true);
316
        positions = Cesium.Cartesian3.unpackArray(outerPositions);
2✔
317
        positions = [...positions, positions[0]];
2✔
318
    } else {
319
        const modelMatrix = Cesium.Matrix4.multiplyByTranslation(
×
320
            Cesium.Transforms.eastNorthUpToFixedFrame(
321
                center
322
            ),
323
            new Cesium.Cartesian3(0, 0, 0),
324
            new Cesium.Matrix4()
325
        );
326
        positions = CylinderGeometryLibrary.computePositions(0.0, radius, radius, slices, false);
×
327
        positions = Cesium.Cartesian3.unpackArray(positions);
×
328
        positions = [...positions.splice(0, Math.ceil(positions.length / 2))];
×
329
        positions = positions.map((cartesian) =>
×
330
            Cesium.Matrix4.multiplyByPoint(modelMatrix, cartesian, new Cesium.Cartesian3())
×
331
        );
332
        positions = [...positions, positions[0]];
×
333
    }
334
    return positions;
2✔
335
};
336

337
const changePositionHeight = (cartesian, symbolizer) =>{
1✔
338
    const { msHeight } = symbolizer || {};
32!
339
    const height = getNumberAttributeValue(msHeight);
32✔
340
    if (height !== null) {
32✔
341
        const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
6✔
342
        return Cesium.Cartesian3.fromRadians(
6✔
343
            cartographic.longitude,
344
            cartographic.latitude,
345
            height
346
        );
347
    }
348
    return cartesian;
26✔
349
};
350

351
const primitiveGeometryTypes = {
1✔
352
    point: (options) => {
353
        const { feature, primitive, parsedSymbolizer } = options;
24✔
354
        if (feature.geometry.type === 'Point') {
24!
355
            const position = changePositionHeight(feature.positions[0][0], parsedSymbolizer);
24✔
356
            const orientation = getOrientation(position, parsedSymbolizer);
24✔
357
            return {
24✔
358
                ...options,
359
                primitive: {
360
                    ...primitive,
361
                    orientation,
362
                    geometry: translatePoint(position, parsedSymbolizer)
363
                }
364
            };
365
        }
366
        const geometryFunction = getGeometryFunction({ msGeometry: { name: 'centerPoint' }, ...parsedSymbolizer });
×
367
        const { position } = geometryFunction(feature);
×
368
        const orientation = getOrientation(position, parsedSymbolizer);
×
369
        return {
×
370
            ...options,
371
            primitive: {
372
                ...primitive,
373
                orientation,
374
                geometry: translatePoint(
375
                    changePositionHeight(position, parsedSymbolizer),
376
                    parsedSymbolizer
377
                )
378
            }
379
        };
380
    },
381
    leaderLine: (options, { map, sampleTerrain }) => {
382
        const { parsedSymbolizer } = options;
8✔
383
        // remove the translations and height options
384
        // to get the original position
385
        const { msTranslateX, msTranslateY, msHeight, ...pointParsedSymbolizer } = parsedSymbolizer;
8✔
386
        // use point function to compute geometry transformations
387
        const { primitive } = primitiveGeometryTypes.point({ ...options, parsedSymbolizer: pointParsedSymbolizer });
8✔
388
        return Promise.all([
8✔
389
            getPositionsRelativeToTerrain({
390
                map,
391
                positions: [primitive.geometry],
392
                heightReference: 'clamp',
393
                sampleTerrain
394
            }).then((computed) => computed.positions[0]),
8✔
395
            getPositionsRelativeToTerrain({
396
                map,
397
                positions: [
398
                    translatePoint(
399
                        changePositionHeight(primitive.geometry, parsedSymbolizer),
400
                        parsedSymbolizer
401
                    )
402
                ],
403
                heightReference: parsedSymbolizer.msHeightReference,
404
                sampleTerrain
405
            }).then((computed) => computed.positions[0])
8✔
406
        ]).then((positions) => {
407
            return {
8✔
408
                ...options,
409
                primitive: {
410
                    ...primitive,
411
                    geometry: [positions]
412
                }
413
            };
414
        });
415
    },
416
    polyline: (options, { map, sampleTerrain }) => {
417
        const { feature, primitive, parsedSymbolizer } = options;
15✔
418
        const extrudedHeight = getNumberAttributeValue(parsedSymbolizer.msExtrudedHeight);
15✔
419
        const height = getNumberAttributeValue(parsedSymbolizer.msHeight);
15✔
420
        if (height !== null || !!parsedSymbolizer.msExtrusionRelativeToGeometry) {
15✔
421
            let minHeight = Infinity;
8✔
422
            let maxHeight = -Infinity;
8✔
423
            return Promise.all(feature?.positions.map((positions) => {
8✔
424
                return getPositionsRelativeToTerrain({
8✔
425
                    map,
426
                    positions,
427
                    heightReference: parsedSymbolizer.msHeightReference,
428
                    sampleTerrain,
429
                    initialHeight: height
430
                }).then((computed) => {
431
                    const computedHeight = computed[parsedSymbolizer.msExtrusionRelativeToGeometry ? 'height' : 'sampledHeight'];
8✔
432
                    minHeight = computedHeight.min < minHeight ? computedHeight.min : minHeight;
8!
433
                    maxHeight = computedHeight.max > maxHeight ? computedHeight.max : maxHeight;
8!
434
                    return computed.positions;
8✔
435
                });
436
            })).then((geometry) => {
437
                return {
8✔
438
                    ...options,
439
                    primitive: {
440
                        ...primitive,
441
                        geometry,
442
                        // in case of relative or clamp
443
                        // extrusion will be relative to the height
444
                        // so 0 value should be considered undefined
445
                        extrudedHeight: extrudedHeight
8!
446
                            ? extrudedHeight + (
447
                                extrudedHeight > 0
8!
448
                                    ? maxHeight
449
                                    : minHeight
450
                            )
451
                            : undefined
452
                    }
453
                };
454
            });
455
        }
456
        return {
7✔
457
            ...options,
458
            primitive: {
459
                ...primitive,
460
                geometry: feature?.positions,
461
                ...(extrudedHeight !== null && { extrudedHeight })
9✔
462
            }
463
        };
464
    },
465
    wall: (options, configs) => {
466
        return Promise.resolve(primitiveGeometryTypes.polyline(options, configs))
8✔
467
            .then(({ primitive }) => {
468
                const wallResult = {
8✔
469
                    ...options,
470
                    primitive: {
471
                        ...primitive,
472
                        geometry: primitive?.geometry,
473
                        minimumHeights: primitive?.geometry?.map((positions) => {
474
                            return positions.map((cartesian) => {
8✔
475
                                return Cesium.Cartographic.fromCartesian(cartesian).height;
32✔
476
                            });
477
                        }),
478
                        maximumHeights: primitive?.geometry?.map((positions) => {
479
                            return positions.map(() => {
8✔
480
                                return primitive.extrudedHeight;
32✔
481
                            });
482
                        })
483
                    }
484
                };
485

486
                if (options.primitive.type === 'wallOutline') {
8✔
487
                    const outlineMaterial = options.primitive.outlineMaterial;
1✔
488
                    const outlineWidth = options.primitive.outlineWidth;
1✔
489
                    const outlineEntities = [];
1✔
490

491
                    wallResult.primitive.geometry?.forEach((positions, gIdx) => {
1✔
492
                        const minHeights = wallResult.primitive.minimumHeights?.[gIdx] || [];
1!
493
                        const maxHeights = wallResult.primitive.maximumHeights?.[gIdx] || [];
1!
494

495
                        const bottomPositions = positions.map((cartesian, i) => {
1✔
496
                            const carto = Cesium.Cartographic.fromCartesian(cartesian);
4✔
497
                            return Cesium.Cartesian3.fromRadians(carto.longitude, carto.latitude, minHeights[i] ?? carto.height);
4!
498
                        });
499
                        const topPositions = positions.map((cartesian, i) => {
1✔
500
                            const carto = Cesium.Cartographic.fromCartesian(cartesian);
4✔
501
                            return Cesium.Cartesian3.fromRadians(carto.longitude, carto.latitude, maxHeights[i] ?? carto.height);
4!
502
                        });
503

504
                        outlineEntities.push(
1✔
505
                            {
506
                                type: 'wallOutline',
507
                                geometryType: 'polyline',
508
                                entity: {
509
                                    polyline: {
510
                                        material: outlineMaterial,
511
                                        width: outlineWidth,
512
                                        arcType: Cesium.ArcType.GEODESIC
513
                                    }
514
                                },
515
                                geometry: [bottomPositions]
516
                            },
517
                            {
518
                                type: 'wallOutline',
519
                                geometryType: 'polyline',
520
                                entity: {
521
                                    polyline: {
522
                                        material: outlineMaterial,
523
                                        width: outlineWidth,
524
                                        arcType: Cesium.ArcType.GEODESIC
525
                                    }
526
                                },
527
                                geometry: [topPositions]
528
                            }
529
                        );
530

531
                        for (let i = 0; i < positions.length; i++) {
1✔
532
                            outlineEntities.push({
4✔
533
                                type: 'wallOutline',
534
                                geometryType: 'polyline',
535
                                entity: {
536
                                    polyline: {
537
                                        material: outlineMaterial,
538
                                        width: outlineWidth,
539
                                        arcType: Cesium.ArcType.NONE
540
                                    }
541
                                },
542
                                geometry: [[bottomPositions[i], topPositions[i]]]
543
                            });
544
                        }
545
                    });
546

547
                    return {
1✔
548
                        ...wallResult,
549
                        primitive: {
550
                            ...wallResult.primitive,
551
                            geometry: outlineEntities[0]?.geometry,
552
                            _extrusionOutlineEntities: outlineEntities
553
                        }
554
                    };
555
                }
556

557
                return wallResult;
7✔
558
            });
559
    },
560
    polygon: (options, { map, sampleTerrain }) => {
561
        const { feature, primitive, parsedSymbolizer } = options;
13✔
562
        const extrudedHeight = getNumberAttributeValue(parsedSymbolizer.msExtrudedHeight);
13✔
563
        const height = getNumberAttributeValue(parsedSymbolizer.msHeight);
13✔
564
        if ((parsedSymbolizer.msHeightReference || 'none') !== 'none' || !!parsedSymbolizer.msExtrusionRelativeToGeometry) {
13✔
565
            let minHeight = Infinity;
4✔
566
            let maxHeight = -Infinity;
4✔
567
            return Promise.all(feature?.positions.map((positions) => {
4✔
568
                return getPositionsRelativeToTerrain({
4✔
569
                    map,
570
                    positions,
571
                    heightReference: parsedSymbolizer.msHeightReference,
572
                    sampleTerrain,
573
                    initialHeight: height
574
                }).then((computed) => {
575
                    const computedHeight = computed[parsedSymbolizer.msExtrusionRelativeToGeometry ? 'height' : 'sampledHeight'];
4✔
576
                    minHeight = computedHeight.min < minHeight ? computedHeight.min : minHeight;
4!
577
                    maxHeight = computedHeight.max > maxHeight ? computedHeight.max : maxHeight;
4!
578
                    return computed.positions;
4✔
579
                });
580
            })).then((geometry) => {
581
                const extrusionParams = {
4✔
582
                    // height will be computed on the geometry
583
                    height: undefined,
584
                    // in case of relative or clamp
585
                    // extrusion will be relative to the height
586
                    // so 0 value should be considered undefined
587
                    extrudedHeight: extrudedHeight
4!
588
                        ? extrudedHeight + (
589
                            extrudedHeight > 0
4!
590
                                ? maxHeight
591
                                : minHeight
592
                        )
593
                        : undefined
594
                };
595
                return {
4✔
596
                    ...options,
597
                    primitive: {
598
                        ...primitive,
599
                        geometry,
600
                        ...(primitive?.entity?.polygon
4!
601
                            ? {
602
                                entity: {
603
                                    polygon: {
604
                                        ...primitive?.entity?.polygon,
605
                                        ...extrusionParams
606
                                    }
607
                                }
608
                            }
609
                            : extrusionParams)
610
                    }
611
                };
612
            });
613
        }
614
        const extrusionParams = {
9✔
615
            ...(extrudedHeight !== null && { extrudedHeight }),
15✔
616
            ...(height !== null && {
15✔
617
                height,
618
                perPositionHeight: false
619
            })
620
        };
621
        return {
9✔
622
            ...options,
623
            primitive: {
624
                ...primitive,
625
                geometry: feature?.positions,
626
                ...(primitive?.entity?.polygon
9✔
627
                    ? {
628
                        entity: {
629
                            polygon: {
630
                                ...primitive?.entity?.polygon,
631
                                ...extrusionParams
632
                            }
633
                        }
634
                    }
635
                    : extrusionParams)
636
            }
637
        };
638
    },
639
    extrusionOutline: (options, configs) => {
640
        return Promise.resolve(primitiveGeometryTypes.polygon(options, configs))
2✔
641
            .then((resolved) => {
642
                const { primitive } = resolved;
2✔
643
                const geometry = primitive.geometry;
2✔
644
                const extrudedHeight = primitive?.entity?.polygon?.extrudedHeight ?? primitive?.extrudedHeight;
2✔
645
                const baseHeight = primitive?.entity?.polygon?.height ?? primitive?.height;
2✔
646

647
                if (!geometry || extrudedHeight === undefined) {
2!
UNCOV
648
                    return resolved;
×
649
                }
650

651
                const outlineMaterial = primitive.outlineMaterial;
2✔
652
                const outlineWidth = primitive.outlineWidth;
2✔
653
                const outlineEntities = [];
2✔
654

655
                geometry.forEach((ring) => {
2✔
656
                    const bottomPositions = ring.map((cartesian) => {
2✔
657
                        if (baseHeight !== undefined) {
10!
658
                            const carto = Cesium.Cartographic.fromCartesian(cartesian);
10✔
659
                            return Cesium.Cartesian3.fromRadians(carto.longitude, carto.latitude, baseHeight);
10✔
660
                        }
UNCOV
661
                        return cartesian;
×
662
                    });
663
                    const topPositions = ring.map((cartesian) => {
2✔
664
                        const carto = Cesium.Cartographic.fromCartesian(cartesian);
10✔
665
                        return Cesium.Cartesian3.fromRadians(carto.longitude, carto.latitude, extrudedHeight);
10✔
666
                    });
667

668
                    outlineEntities.push(
2✔
669
                        {
670
                            type: 'extrusionOutline',
671
                            geometryType: 'polyline',
672
                            entity: {
673
                                polyline: {
674
                                    material: outlineMaterial,
675
                                    width: outlineWidth,
676
                                    arcType: Cesium.ArcType.GEODESIC
677
                                }
678
                            },
679
                            geometry: [bottomPositions]
680
                        },
681
                        {
682
                            type: 'extrusionOutline',
683
                            geometryType: 'polyline',
684
                            entity: {
685
                                polyline: {
686
                                    material: outlineMaterial,
687
                                    width: outlineWidth,
688
                                    arcType: Cesium.ArcType.GEODESIC
689
                                }
690
                            },
691
                            geometry: [topPositions]
692
                        }
693
                    );
694

695
                    for (let i = 0; i < ring.length; i++) {
2✔
696
                        outlineEntities.push({
10✔
697
                            type: 'extrusionOutline',
698
                            geometryType: 'polyline',
699
                            entity: {
700
                                polyline: {
701
                                    material: outlineMaterial,
702
                                    width: outlineWidth,
703
                                    arcType: Cesium.ArcType.NONE
704
                                }
705
                            },
706
                            geometry: [[bottomPositions[i], topPositions[i]]]
707
                        });
708
                    }
709
                });
710

711
                return {
2✔
712
                    ...resolved,
713
                    primitive: {
714
                        ...primitive,
715
                        geometry: outlineEntities[0]?.geometry,
716
                        _extrusionOutlineEntities: outlineEntities
717
                    }
718
                };
719
            });
720
    },
721
    circlePolyline: (options) => {
722
        const { feature, primitive, parsedSymbolizer } = options;
1✔
723
        if (feature.geometry.type === 'Point') {
1!
724
            const position = Cesium.Cartesian3.fromDegrees(
1✔
725
                feature.geometry.coordinates[0],
726
                feature.geometry.coordinates[1],
727
                feature.geometry.coordinates[2] || 0
2✔
728
            );
729
            const positions = getCirclePositions(position, parsedSymbolizer);
1✔
730
            return {
1✔
731
                ...options,
732
                primitive: {
733
                    ...primitive,
734
                    geometry: [positions]
735
                }
736
            };
737
        }
UNCOV
738
        return options;
×
739
    },
740
    circlePolygon: (options) => {
741
        const { feature, primitive, parsedSymbolizer } = options;
1✔
742
        if (feature.geometry.type === 'Point') {
1!
743
            const position = Cesium.Cartesian3.fromDegrees(
1✔
744
                feature.geometry.coordinates[0],
745
                feature.geometry.coordinates[1],
746
                feature.geometry.coordinates[2] || 0
2✔
747
            );
748
            const positions = getCirclePositions(position, parsedSymbolizer);
1✔
749
            return {
1✔
750
                ...options,
751
                primitive: {
752
                    ...primitive,
753
                    geometry: [positions]
754
                }
755
            };
756
        }
UNCOV
757
        return options;
×
758
    }
759
};
760

761
const symbolizerToPrimitives = {
1✔
762
    Mark: ({ parsedSymbolizer, globalOpacity, images, symbolizer }) => {
763
        const { src, width, height } = images.find(({ id }) => id === getImageIdFromSymbolizer(parsedSymbolizer, symbolizer)) || {};
9!
764
        const side = width > height ? width : height;
9!
765
        const scale = (parsedSymbolizer.radius * 2) / side;
9✔
766
        return src && !isNaN(scale) ? [
9✔
767
            {
768
                type: 'point',
769
                geometryType: 'point',
770
                entity: {
771
                    billboard: {
772
                        image: src,
773
                        scale,
774
                        rotation: Cesium.Math.toRadians(-1 * parsedSymbolizer.rotate || 0),
14✔
775
                        disableDepthTestDistance: parsedSymbolizer.msBringToFront ? Number.POSITIVE_INFINITY : 0,
8✔
776
                        heightReference: Cesium.HeightReference[HEIGHT_REFERENCE_CONSTANTS_MAP[parsedSymbolizer.msHeightReference] || 'NONE'],
13✔
777
                        color: getCesiumColor({
778
                            color: '#ffffff',
779
                            opacity: 1 * globalOpacity
780
                        })
781
                    }
782
                }
783
            },
784
            ...(parsedSymbolizer.msLeaderLineWidth ? [
8✔
785
                {
786
                    type: 'leaderLine',
787
                    geometryType: 'leaderLine',
788
                    entity: {
789
                        polyline: {
790
                            material: getCesiumColor({
791
                                color: parsedSymbolizer.msLeaderLineColor || '#000000',
4!
792
                                opacity: (parsedSymbolizer.msLeaderLineOpacity ?? 1) * globalOpacity
4!
793
                            }),
794
                            width: parsedSymbolizer.msLeaderLineWidth
795
                        }
796
                    }
797
                }
798
            ] : [])
799
        ] : [];
800
    },
801
    Icon: ({ parsedSymbolizer, globalOpacity, images, symbolizer }) => {
802
        const { src, width, height } = images.find(({ id }) => id === getImageIdFromSymbolizer(parsedSymbolizer, symbolizer)) || {};
4!
803
        const side = width > height ? width : height;
3!
804
        const scale = parsedSymbolizer.size / side;
3✔
805
        return src && !isNaN(scale) ? [{
3✔
806
            type: 'point',
807
            geometryType: 'point',
808
            entity: {
809
                billboard: {
810
                    image: src,
811
                    scale,
812
                    ...anchorToOrigin(parsedSymbolizer.anchor),
813
                    pixelOffset: parsedSymbolizer.offset ? new Cesium.Cartesian2(parsedSymbolizer.offset[0], parsedSymbolizer.offset[1]) : null,
2!
814
                    rotation: Cesium.Math.toRadians(-1 * parsedSymbolizer.rotate || 0),
2!
815
                    disableDepthTestDistance: parsedSymbolizer.msBringToFront ? Number.POSITIVE_INFINITY : 0,
2!
816
                    heightReference: Cesium.HeightReference[HEIGHT_REFERENCE_CONSTANTS_MAP[parsedSymbolizer.msHeightReference] || 'NONE'],
4✔
817
                    color: getCesiumColor({
818
                        color: '#ffffff',
819
                        opacity: parsedSymbolizer.opacity * globalOpacity
820
                    })
821
                }
822
            }
823
        },
824
        ...(parsedSymbolizer.msLeaderLineWidth ? [
2✔
825
            {
826
                type: 'leaderLine',
827
                geometryType: 'leaderLine',
828
                entity: {
829
                    polyline: {
830
                        material: getCesiumColor({
831
                            color: parsedSymbolizer.msLeaderLineColor || '#000000',
1!
832
                            opacity: (parsedSymbolizer.msLeaderLineOpacity ?? 1) * globalOpacity
1!
833
                        }),
834
                        width: parsedSymbolizer.msLeaderLineWidth
835
                    }
836
                }
837
            }
838
        ] : [])] : [];
839
    },
840
    Text: ({ parsedSymbolizer, feature, globalOpacity }) => {
841
        const offsetX = getNumberAttributeValue(parsedSymbolizer?.offset?.[0]);
2✔
842
        const offsetY = getNumberAttributeValue(parsedSymbolizer?.offset?.[1]);
2✔
843
        return [
2✔
844
            {
845
                type: 'point',
846
                geometryType: 'point',
847
                entity: {
848
                    label: {
849
                        text: resolveAttributeTemplate({ properties: feature.properties }, parsedSymbolizer.label, ''),
850
                        font: [parsedSymbolizer.fontStyle, parsedSymbolizer.fontWeight,  `${parsedSymbolizer.size}px`, castArray(parsedSymbolizer.font || ['serif']).join(', ')]
2!
851
                            .filter(val => val)
8✔
852
                            .join(' '),
853
                        fillColor: getCesiumColor({
854
                            color: parsedSymbolizer.color,
855
                            opacity: 1 * globalOpacity
856
                        }),
857
                        ...anchorToOrigin(parsedSymbolizer.anchor),
858
                        disableDepthTestDistance: parsedSymbolizer.msBringToFront ? Number.POSITIVE_INFINITY : 0,
2!
859
                        heightReference: Cesium.HeightReference[HEIGHT_REFERENCE_CONSTANTS_MAP[parsedSymbolizer.msHeightReference] || 'NONE'],
4✔
860
                        pixelOffset: new Cesium.Cartesian2(offsetX ?? 0, offsetY ?? 0),
4!
861
                        // rotation is not available as property
862
                        ...(parsedSymbolizer.haloWidth > 0 && {
4✔
863
                            style: Cesium.LabelStyle.FILL_AND_OUTLINE,
864
                            outlineColor: getCesiumColor({
865
                                color: parsedSymbolizer.haloColor,
866
                                opacity: 1 * globalOpacity
867
                            }),
868
                            outlineWidth: parsedSymbolizer.haloWidth
869
                        })
870
                    }
871
                }
872
            },
873
            ...(parsedSymbolizer.msLeaderLineWidth ? [
2✔
874
                {
875
                    type: 'leaderLine',
876
                    geometryType: 'leaderLine',
877
                    entity: {
878
                        polyline: {
879
                            material: getCesiumColor({
880
                                color: parsedSymbolizer.msLeaderLineColor || '#000000',
1!
881
                                opacity: (parsedSymbolizer.msLeaderLineOpacity ?? 1) * globalOpacity
1!
882
                            }),
883
                            width: parsedSymbolizer.msLeaderLineWidth
884
                        }
885
                    }
886
                }
887
            ] : []),
888
            ...(parsedSymbolizer.msLeaderLineWidth && (offsetX || offsetY) ? [
5!
889
                {
890
                    type: 'offset',
891
                    geometryType: 'point',
892
                    entity: {
893
                        billboard: {
894
                            image: createLeaderLineCanvas(parsedSymbolizer),
895
                            scale: 1,
896
                            pixelOffset: new Cesium.Cartesian2(
897
                                (offsetX || 0) / 2,
1!
898
                                (offsetY || 0) / 2
1!
899
                            ),
900
                            color: getCesiumColor({
901
                                color: parsedSymbolizer.msLeaderLineColor || '#000000',
1!
902
                                opacity: (parsedSymbolizer.msLeaderLineOpacity ?? 1) * globalOpacity
1!
903
                            })
904
                        }
905
                    }
906
                }
907
            ] : [])
908
        ];
909
    },
910
    Model: ({ parsedSymbolizer, globalOpacity }) => {
911
        return parsedSymbolizer?.model ? [
3!
912
            {
913
                type: 'point',
914
                geometryType: 'point',
915
                entity: {
916
                    model: {
917
                        uri: new Cesium.Resource({
918
                            proxy: needProxy(parsedSymbolizer?.model) ? new Cesium.DefaultProxy(getProxyUrl()) : undefined,
3!
919
                            url: parsedSymbolizer?.model
920
                        }),
921
                        color: getCesiumColor({
922
                            color: parsedSymbolizer.color ?? '#ffffff',
3!
923
                            opacity: (parsedSymbolizer.opacity ?? 1) * globalOpacity
3!
924
                        }),
925
                        scale: parsedSymbolizer?.scale ?? 1,
3!
926
                        heightReference: Cesium.HeightReference[HEIGHT_REFERENCE_CONSTANTS_MAP[parsedSymbolizer.msHeightReference] || 'NONE']
3!
927
                    }
928
                }
929
            },
930
            ...(parsedSymbolizer.msLeaderLineWidth ? [
3✔
931
                {
932
                    type: 'leaderLine',
933
                    geometryType: 'leaderLine',
934
                    entity: {
935
                        polyline: {
936
                            material: getCesiumColor({
937
                                color: parsedSymbolizer.msLeaderLineColor || '#000000',
2!
938
                                opacity: (parsedSymbolizer.msLeaderLineOpacity ?? 1) * globalOpacity
2!
939
                            }),
940
                            width: parsedSymbolizer.msLeaderLineWidth
941
                        }
942
                    }
943
                }
944
            ] : [])
945
        ] : [];
946
    },
947
    Line: ({ parsedSymbolizer, feature, globalOpacity }) => {
948
        const geometryFunction = getGeometryFunction(parsedSymbolizer);
11✔
949
        const additionalOptions = geometryFunction ? geometryFunction(feature) : {};
11!
950
        const isWallExtrusion = !parsedSymbolizer.msClampToGround && parsedSymbolizer.msExtrudedHeight && !parsedSymbolizer.msExtrusionType;
11✔
951
        const isVolumeExtrusion = !parsedSymbolizer.msClampToGround && parsedSymbolizer.msExtrudedHeight && parsedSymbolizer.msExtrusionType;
11✔
952
        const extrusionOutlineColor = parsedSymbolizer.msExtrusionOutlineColor;
11✔
953
        const extrusionOutlineWidth = parsedSymbolizer.msExtrusionOutlineWidth;
11✔
954
        const hasExtrusionOutline = extrusionOutlineColor && extrusionOutlineWidth;
11✔
955
        const extrusionOutlineMaterial = hasExtrusionOutline
11✔
956
            ? getCesiumColor({
957
                color: extrusionOutlineColor,
958
                opacity: (parsedSymbolizer.msExtrusionOutlineOpacity ?? 1) * globalOpacity
2!
959
            })
960
            : undefined;
961
        return [
11✔
962
            ...(parsedSymbolizer.color && parsedSymbolizer.width !== 0 ? [{
24✔
963
                type: 'polyline',
964
                geometryType: 'polyline',
965
                entity: {
966
                    polyline: {
967
                        material: parsedSymbolizer?.dasharray
2✔
968
                            ? getCesiumDashArray({
969
                                color: parsedSymbolizer.color,
970
                                opacity: parsedSymbolizer.opacity * globalOpacity,
971
                                dasharray: parsedSymbolizer.dasharray
972
                            })
973
                            : getCesiumColor({
974
                                color: parsedSymbolizer.color,
975
                                opacity: parsedSymbolizer.opacity * globalOpacity
976
                            }),
977
                        width: parsedSymbolizer.width,
978
                        clampToGround: parsedSymbolizer.msClampToGround,
979
                        arcType: parsedSymbolizer.msClampToGround
2✔
980
                            ? Cesium.ArcType.GEODESIC
981
                            : Cesium.ArcType.NONE,
982
                        ...additionalOptions
983
                    }
984
                }
985
            }] : []),
986
            ...(isWallExtrusion ? [{
11✔
987
                type: 'polylineVolume',
988
                geometryType: 'wall',
989
                entity: {
990
                    wall: {
991
                        material: getCesiumColor({
992
                            color: parsedSymbolizer.msExtrusionColor || '#000000',
7!
993
                            opacity: (parsedSymbolizer.msExtrusionOpacity ?? 1) * globalOpacity
7!
994
                        })
995
                    }
996
                }
997
            }] : []),
998
            ...(isWallExtrusion && hasExtrusionOutline ? [{
29✔
999
                type: 'wallOutline',
1000
                geometryType: 'wall',
1001
                outlineMaterial: extrusionOutlineMaterial,
1002
                outlineWidth: extrusionOutlineWidth,
1003
                entity: {}
1004
            }] : []),
1005
            ...(isVolumeExtrusion ? [{
11✔
1006
                type: 'polylineVolume',
1007
                geometryType: 'polyline',
1008
                entity: {
1009
                    polylineVolume: {
1010
                        material: getCesiumColor({
1011
                            color: parsedSymbolizer.msExtrusionColor || '#000000',
2!
1012
                            opacity: (parsedSymbolizer.msExtrusionOpacity ?? 1) * globalOpacity
2!
1013
                        }),
1014
                        shape: getVolumeShape(parsedSymbolizer.msExtrusionType, parsedSymbolizer.msExtrudedHeight / 2),
1015
                        ...(hasExtrusionOutline && {
3✔
1016
                            outline: true,
1017
                            outlineColor: extrusionOutlineMaterial,
1018
                            outlineWidth: extrusionOutlineWidth
1019
                        })
1020
                    }
1021
                }
1022
            }] : [])
1023
        ];
1024
    },
1025
    Fill: ({ parsedSymbolizer, feature, globalOpacity }) => {
1026
        const isExtruded = !parsedSymbolizer.msClampToGround && !!parsedSymbolizer.msExtrudedHeight;
11✔
1027
        const geometryFunction = getGeometryFunction(parsedSymbolizer);
11✔
1028
        const additionalOptions = geometryFunction ? geometryFunction(feature) : {};
11!
1029
        const outlineMaterial = parsedSymbolizer?.outlineDasharray
11✔
1030
            ? getCesiumDashArray({
1031
                color: parsedSymbolizer.outlineColor,
1032
                opacity: parsedSymbolizer.outlineOpacity * globalOpacity,
1033
                dasharray: parsedSymbolizer.outlineDasharray
1034
            })
1035
            : getCesiumColor({
1036
                color: parsedSymbolizer.outlineColor,
1037
                opacity: parsedSymbolizer.outlineOpacity * globalOpacity
1038
            });
1039
        const hasOutline = parsedSymbolizer.outlineColor && parsedSymbolizer.outlineWidth !== 0;
11✔
1040
        return [
11✔
1041
            {
1042
                type: 'polygon',
1043
                geometryType: 'polygon',
1044
                clampToGround: parsedSymbolizer.msClampToGround,
1045
                entity: {
1046
                    polygon: {
1047
                        material: getCesiumColor({
1048
                            color: parsedSymbolizer.color,
1049
                            opacity: parsedSymbolizer.fillOpacity * globalOpacity
1050
                        }),
1051
                        perPositionHeight: !parsedSymbolizer.msClampToGround,
1052
                        ...(!parsedSymbolizer.msClampToGround ? undefined : {classificationType: parsedSymbolizer.msClassificationType === 'terrain' ?
13!
1053
                            Cesium.ClassificationType.TERRAIN :
1054
                            parsedSymbolizer.msClassificationType === '3d' ?
×
1055
                                Cesium.ClassificationType.CESIUM_3D_TILE :
1056
                                Cesium.ClassificationType.BOTH} ),
1057
                        arcType: parsedSymbolizer.msClampToGround
11✔
1058
                            ? Cesium.ArcType.GEODESIC
1059
                            : undefined,
1060
                        ...additionalOptions
1061
                    }
1062
                }
1063
            },
1064
            // outline properties is not working in some browser see https://github.com/CesiumGS/cesium/issues/40
1065
            // this is a workaround to visualize the outline with the correct width
1066
            // for non-extruded: draw footprint polyline
1067
            // for extruded: draw bottom ring, top ring, and vertical edge polylines
1068
            ...(hasOutline && !isExtruded ? [
27✔
1069
                {
1070
                    type: 'polyline',
1071
                    geometryType: 'polyline',
1072
                    entity: {
1073
                        polyline: {
1074
                            material: outlineMaterial,
1075
                            width: parsedSymbolizer.outlineWidth,
1076
                            clampToGround: parsedSymbolizer.msClampToGround,
1077
                            ...(!parsedSymbolizer.msClampToGround ? undefined : {classificationType: parsedSymbolizer.msClassificationType === 'terrain' ?
5!
1078
                                Cesium.ClassificationType.TERRAIN :
1079
                                parsedSymbolizer.msClassificationType === '3d' ?
×
1080
                                    Cesium.ClassificationType.CESIUM_3D_TILE :
1081
                                    Cesium.ClassificationType.BOTH} ),
1082
                            arcType: parsedSymbolizer.msClampToGround
3✔
1083
                                ? Cesium.ArcType.GEODESIC
1084
                                : Cesium.ArcType.NONE,
1085
                            ...additionalOptions
1086
                        }
1087
                    }
1088
                }
1089
            ] : []),
1090
            ...(hasOutline && isExtruded ? [
27✔
1091
                {
1092
                    type: 'extrusionOutline',
1093
                    geometryType: 'extrusionOutline',
1094
                    clampToGround: false,
1095
                    outlineMaterial,
1096
                    outlineWidth: parsedSymbolizer.outlineWidth,
1097
                    entity: {}
1098
                }
1099
            ] : [])
1100
        ];
1101
    },
1102
    Circle: ({ parsedSymbolizer, globalOpacity }) => {
1103
        return [{
1✔
1104
            type: 'polygon',
1105
            geometryType: 'circlePolygon',
1106
            clampToGround: parsedSymbolizer.msClampToGround,
1107
            entity: {
1108
                polygon: {
1109
                    material: getCesiumColor({
1110
                        color: parsedSymbolizer.color,
1111
                        opacity: parsedSymbolizer.opacity * globalOpacity
1112
                    }),
1113
                    ...(parsedSymbolizer.geodesic
1!
1114
                        ? {
1115
                            perPositionHeight: !parsedSymbolizer.msClampToGround,
1116
                            ...(!parsedSymbolizer.msClampToGround ? undefined : {classificationType: parsedSymbolizer.msClassificationType === 'terrain' ?
1!
1117
                                Cesium.ClassificationType.TERRAIN :
1118
                                parsedSymbolizer.msClassificationType === '3d' ?
×
1119
                                    Cesium.ClassificationType.CESIUM_3D_TILE :
1120
                                    Cesium.ClassificationType.BOTH} ),
1121
                            arcType: Cesium.ArcType.GEODESIC
1122
                        }
1123
                        : {
1124
                            perPositionHeight: true,
1125
                            arcType: undefined
1126
                        })
1127
                }
1128
            }
1129
        },
1130
        // outline properties is not working in some browser see https://github.com/CesiumGS/cesium/issues/40
1131
        // this is a workaround to visualize the outline with the correct side
1132
        // this only for the footprint
1133
        ...(parsedSymbolizer.outlineColor && parsedSymbolizer.outlineWidth !== 0) ? [{
3!
1134
            type: 'polyline',
1135
            geometryType: 'circlePolyline',
1136
            entity: {
1137
                polyline: {
1138
                    material: parsedSymbolizer?.outlineDasharray
1!
1139
                        ? getCesiumDashArray({
1140
                            color: parsedSymbolizer.outlineColor,
1141
                            opacity: parsedSymbolizer.outlineOpacity * globalOpacity,
1142
                            dasharray: parsedSymbolizer.outlineDasharray
1143
                        })
1144
                        : getCesiumColor({
1145
                            color: parsedSymbolizer.outlineColor,
1146
                            opacity: parsedSymbolizer.outlineOpacity * globalOpacity
1147
                        }),
1148
                    width: parsedSymbolizer.outlineWidth,
1149
                    ...(parsedSymbolizer.geodesic
1!
1150
                        ? {
1151
                            clampToGround: parsedSymbolizer.msClampToGround,
1152
                            ...(!parsedSymbolizer.msClampToGround ? undefined : {classificationType: parsedSymbolizer.msClassificationType === 'terrain' ?
1!
1153
                                Cesium.ClassificationType.TERRAIN :
1154
                                parsedSymbolizer.msClassificationType === '3d' ?
×
1155
                                    Cesium.ClassificationType.CESIUM_3D_TILE :
1156
                                    Cesium.ClassificationType.BOTH} ),
1157
                            arcType: Cesium.ArcType.GEODESIC
1158
                        }
1159
                        : {
1160
                            clampToGround: false,
1161
                            arcType: Cesium.ArcType.NONE
1162
                        })
1163
                }
1164
            }
1165
        }] : []];
1166
    }
1167
};
1168

1169
const isGeometryChanged = (previousSymbolizer, currentSymbolizer) => {
1✔
UNCOV
1170
    return previousSymbolizer?.msGeometry?.name !== currentSymbolizer?.msGeometry?.name
×
1171
        || previousSymbolizer?.msHeight !== currentSymbolizer?.msHeight
1172
        || previousSymbolizer?.msHeightReference !== currentSymbolizer?.msHeightReference
1173
        || previousSymbolizer?.msExtrudedHeight !== currentSymbolizer?.msExtrudedHeight
1174
        || previousSymbolizer?.msExtrusionRelativeToGeometry !== currentSymbolizer?.msExtrusionRelativeToGeometry
1175
        || previousSymbolizer?.msExtrusionType !== currentSymbolizer?.msExtrusionType
1176
        || previousSymbolizer?.msTranslateX !== currentSymbolizer?.msTranslateX
1177
        || previousSymbolizer?.msTranslateY !== currentSymbolizer?.msTranslateY
1178
        || previousSymbolizer?.heading !== currentSymbolizer?.heading
1179
        || previousSymbolizer?.pitch !== currentSymbolizer?.pitch
1180
        || previousSymbolizer?.roll !== currentSymbolizer?.roll
1181
        || previousSymbolizer?.outlineColor !== currentSymbolizer?.outlineColor
1182
        || previousSymbolizer?.outlineWidth !== currentSymbolizer?.outlineWidth
1183
        || previousSymbolizer?.outlineOpacity !== currentSymbolizer?.outlineOpacity
1184
        || previousSymbolizer?.msExtrusionOutlineColor !== currentSymbolizer?.msExtrusionOutlineColor
1185
        || previousSymbolizer?.msExtrusionOutlineWidth !== currentSymbolizer?.msExtrusionOutlineWidth
1186
        || previousSymbolizer?.msExtrusionOutlineOpacity !== currentSymbolizer?.msExtrusionOutlineOpacity;
1187
};
1188

1189
const isSymbolizerChanged = (previousSymbolizer, currentSymbolizer) => {
1✔
UNCOV
1190
    const { msGeometry: previousMsGeometry, ...previous } = previousSymbolizer;
×
UNCOV
1191
    const { msGeometry, ...current } = currentSymbolizer;
×
UNCOV
1192
    return !isEqual(previous, current);
×
1193
};
1194

1195
const getStyledFeatures = ({ rules, features, globalOpacity, images }) => {
1✔
1196
    return rules?.map((rule) => {
38✔
1197
        const filteredFeatures = features
41✔
1198
            .filter(({ properties }) => !rule.filter || geoStylerStyleFilter({ properties: properties || {}}, rule.filter));
53!
1199
        return rule.symbolizers.map((symbolizer) => {
41✔
1200
            return filteredFeatures.filter(({ geometry }) => isCompatibleGeometry({ geometry, symbolizer })).map((feature) => {
41✔
1201
                const parsedSymbolizer = parseSymbolizerExpressions(symbolizer, feature);
41✔
1202
                const primitivesFunction = symbolizerToPrimitives[parsedSymbolizer.kind]
41✔
1203
                    ? symbolizerToPrimitives[parsedSymbolizer.kind]
1204
                    : () => [];
1✔
1205
                return primitivesFunction({
41✔
1206
                    feature,
1207
                    symbolizer,
1208
                    images,
1209
                    parsedSymbolizer,
1210
                    globalOpacity
1211
                }).map((primitive) => ({
54✔
1212
                    id: `${feature.id}:${parsedSymbolizer.symbolizerId}:${primitive.type}`,
1213
                    feature,
1214
                    primitive,
1215
                    symbolizer,
1216
                    parsedSymbolizer
1217
                }));
1218
            }).flat();
1219
        }).flat();
1220
    }).flat();
1221
};
1222

1223
function getStyleFuncFromRules({
×
1224
    rules = []
×
1225
} = {}) {
1226
    return ({
38✔
1227
        opacity: globalOpacity = 1,
34✔
1228
        features,
1229
        getPreviousStyledFeature = () => {},
34✔
1230
        map,
1231
        sampleTerrain = Cesium.sampleTerrain
29✔
1232
    }) => {
1233
        return drawIcons({ rules }, { features })
38✔
1234
            .then((images) => {
1235
                const styledFeatures = getStyledFeatures({ rules, features, globalOpacity, images });
38✔
1236
                return Promise.all(styledFeatures.map((currentFeature) => {
38✔
1237
                    const previousFeature = getPreviousStyledFeature(currentFeature);
54✔
1238
                    if (!previousFeature || isGeometryChanged(previousFeature.parsedSymbolizer, currentFeature.parsedSymbolizer)) {
54!
1239
                        const computeGeometry = primitiveGeometryTypes[currentFeature?.primitive?.geometryType]
54!
1240
                            ? primitiveGeometryTypes[currentFeature?.primitive?.geometryType]
UNCOV
1241
                            : () => currentFeature;
×
1242
                        return Promise.resolve(computeGeometry(currentFeature, { map, sampleTerrain }))
54✔
1243
                            .then((payload) => {
1244
                                return {
54✔
1245
                                    ...payload,
1246
                                    action: 'replace'
1247
                                };
1248
                            });
1249
                    }
UNCOV
1250
                    return Promise.resolve({
×
1251
                        ...currentFeature,
1252
                        primitive: {
1253
                            ...previousFeature?.primitive,
1254
                            ...currentFeature?.primitive,
1255
                            geometry: previousFeature?.primitive?.geometry,
1256
                            entity: {
1257
                                ...(Object.keys(currentFeature?.primitive?.entity || {}).reduce((acc, key) => {
×
UNCOV
1258
                                    return {
×
1259
                                        ...acc,
1260
                                        [key]: {
1261
                                            ...previousFeature?.primitive?.entity?.[key],
1262
                                            ...currentFeature?.primitive?.entity?.[key]
1263
                                        }
1264
                                    };
1265
                                }, {}))
1266
                            }
1267
                        },
1268
                        action: isSymbolizerChanged(previousFeature.parsedSymbolizer, currentFeature.parsedSymbolizer)
×
1269
                            ? 'update'
1270
                            : 'none'
1271
                    });
1272
                }));
1273
            })
1274
            .then((updatedStyledFeatures) => {
1275
                // expand extrusion outline entities into individual styled features
1276
                const expanded = updatedStyledFeatures.flatMap((styledFeature) => {
38✔
1277
                    const outlineEntities = styledFeature.primitive?._extrusionOutlineEntities;
54✔
1278
                    if (outlineEntities && outlineEntities.length > 0) {
54✔
1279
                        return outlineEntities.map((outlineEntity, idx) => ({
20✔
1280
                            ...styledFeature,
1281
                            id: `${styledFeature.id}:${idx}`,
1282
                            primitive: {
1283
                                ...outlineEntity
1284
                            },
1285
                            action: styledFeature.action
1286
                        }));
1287
                    }
1288
                    return [styledFeature];
51✔
1289
                });
1290
                // remove all styled features without geometry
1291
                return expanded.filter(({ primitive }) => !!primitive.geometry);
71✔
1292
            });
1293
    };
1294
}
1295

1296
class CesiumStyleParser {
1297

1298
    readStyle() {
1299
        return new Promise((resolve, reject) => {
1✔
1300
            try {
1✔
1301
                resolve(null);
1✔
1302
            } catch (error) {
UNCOV
1303
                reject(error);
×
1304
            }
1305
        });
1306
    }
1307

1308
    writeStyle(geoStylerStyle) {
1309
        return new Promise((resolve, reject) => {
38✔
1310
            try {
38✔
1311
                const styleFunc = getStyleFuncFromRules(geoStylerStyle);
38✔
1312
                resolve(styleFunc);
38✔
1313
            } catch (error) {
UNCOV
1314
                reject(error);
×
1315
            }
1316
        });
1317
    }
1318
}
1319

1320
export default CesiumStyleParser;
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