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

geosolutions-it / MapStore2 / 29408043318

15 Jul 2026 10:24AM UTC coverage: 75.764% (+0.002%) from 75.762%
29408043318

push

github

web-flow
Fix gzip_types in Docker nginx configuration (#12654)

application/xm typo prevented XML compression; add image/svg+xml.

35966 of 56655 branches covered (63.48%)

44728 of 59036 relevant lines covered (75.76%)

122.73 hits per line

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

41.77
/web/client/utils/cesium/GeoJSONStyledFeatures.js
1
import * as Cesium from 'cesium';
2
import turfFlatten from '@turf/flatten';
3
import omit from 'lodash/omit';
4
import isArray from 'lodash/isArray';
5
import { v1 as uuid } from 'uuid';
6
import { getMapScaleForCesium } from '../MapUtils';
7
import { geoStylerScaleDenominatorFilter } from '../styleparser/StyleParserUtils';
8

9
/**
10
 * validate the coordinates and ensure:
11
 * - values inside are not nested arrays
12
 * - the current coordinate are not a duplication of the previous one
13
 * @param {number[]} coords array of coordinates [longitude, latitude, height]
14
 * @param {number[]} prevCoords array of coordinates [longitude, latitude, height]
15
 * @returns the coordinates if valid and null if invalid
16
 */
17
const validateCoordinatesValue = (coords, prevCoords) => {
3✔
18
    if (!isArray(coords[0]) && !isArray(coords[1]) && !isArray(coords[2])) {
429!
19
        // remove duplicated points
20
        // to avoid normalization errors
21
        return prevCoords && (prevCoords[0] === coords[0] && prevCoords[1] === coords[1] && prevCoords[2] === coords[2])
429✔
22
            ? null
23
            : coords;
24
    }
25
    return null;
×
26
};
27

28
const featureToCartesianPositions = (feature) => {
3✔
29
    if (feature.geometry.type === 'Point') {
144✔
30
        const validatedCoords = validateCoordinatesValue([
60✔
31
            feature.geometry.coordinates[0],
32
            feature.geometry.coordinates[1],
33
            feature.geometry.coordinates[2] || 0
108✔
34
        ]);
35
        return validatedCoords ? [[Cesium.Cartesian3.fromDegrees(
60!
36
            validatedCoords[0],
37
            validatedCoords[1],
38
            validatedCoords[2] || 0
108✔
39
        )]] : null;
40
    }
41
    if (feature.geometry.type === 'LineString') {
84✔
42
        const positions = feature.geometry.coordinates.map((coords, idx) =>
33✔
43
            validateCoordinatesValue(coords, feature.geometry.coordinates[idx - 1])
132!
44
                ? Cesium.Cartesian3.fromDegrees(coords[0], coords[1], coords[2])
45
                : null
46
        ).filter(coords => coords !== null);
132✔
47
        return positions.length > 1 ? [positions] : null;
33!
48
    }
49
    if (feature.geometry.type === 'Polygon') {
51!
50
        const positions = feature.geometry.coordinates.map(ring => {
51✔
51
            const ringPositions = ring.map((coords, idx) =>
51✔
52
                validateCoordinatesValue(coords, ring[idx - 1])
237✔
53
                    ? Cesium.Cartesian3.fromDegrees(coords[0], coords[1], coords[2])
54
                    : null
55
            ).filter(coords => coords !== null);
237✔
56
            return ringPositions.length > 2 ? ringPositions : null;
51✔
57
        }).filter(ring => ring !== null);
51✔
58
        return positions.length > 0 ? positions : null;
51✔
59
    }
60
    return null;
×
61
};
62
/**
63
 * Class to manage styles for layer with an array of features
64
 * @param {string} options.id layer identifier
65
 * @param {object} options.map a Cesium map instance
66
 * @param {number} options.opacity opacity of the layer
67
 * @param {boolean} options.queryable if false the features will not be queryable, default is true
68
 * @param {array} options.features array of valid geojson features
69
 * @param {boolean} options.mergePolygonFeatures if true will merge all polygons with similar styles in a single primitive. This could help to reduce the draw call to the render
70
 * @param {func} featureFilter a function to filter feature, it receives a GeoJSON feature as argument and it must return a boolean
71
 */
72
class GeoJSONStyledFeatures {
73
    constructor(options = {}) {
×
74
        this._msId = options.id;
39✔
75
        this._dataSource = new Cesium.CustomDataSource(options.id);
39✔
76
        this._primitives = new Cesium.PrimitiveCollection({ destroyPrimitives: true });
39✔
77
        this._map = options.map;
39✔
78
        this._onCameraMoveEndBound = this._updateScaleVisibility.bind(this);
39✔
79
        if (this._map) {
39✔
80
            this._map.scene.primitives.add(this._primitives);
30✔
81
            this._map.dataSources.add(this._dataSource);
30✔
82
            this._map.camera.moveEnd.addEventListener(this._onCameraMoveEndBound);
30✔
83
        }
84
        this._styledFeatures = [];
39✔
85
        // Map<id, { id, entity }>: keyed lookups during _updateEntities are
86
        // O(1) instead of O(N) scans. Wrapper shape preserved so the rest of
87
        // the class (and _updatePointEntity, _updateScaleVisibility, etc.)
88
        // can keep accessing { id, entity } unchanged.
89
        this._entities = new Map();
39✔
90
        this._opacity = options.opacity ?? 1;
39✔
91
        this._queryable = options.queryable === undefined ? true : !!options.queryable;
39✔
92
        this._mergePolygonFeatures = !!options?.mergePolygonFeatures;
39✔
93
        this._featureFilter = options.featureFilter;
39✔
94
        this._dataSource.entities.collectionChanged.addEventListener(() => {
39✔
95
            setTimeout(() => this._map.scene.requestRender(), 300);
9✔
96
        });
97
        // internal key to associate features with original features
98
        this._uuidKey = '__ms_uuid_key__' + uuid();
39✔
99
        // Concurrency control for _runUpdate. Only one style/render cycle
100
        // runs at a time. Requests received while a cycle is in flight
101
        // collapse into a single pending rerun, kicked off when the current
102
        // cycle completes. This serializes apply order (so an older, smaller
103
        // styledFeatures snapshot can't arrive after a newer one and erase
104
        // entities the newer one added) and still picks up later additions
105
        // (so a slow style function during streaming doesn't strand features).
106
        this._runUpdateInFlight = false;
39✔
107
        this._runUpdatePending = null;
39✔
108
        // needs to be run after this._uuidKey
109
        this.setFeatures(options.features);
39✔
110
        this._styleRules = options?.styleRules;
39✔
111
    }
112
    _addCustomProperties(obj) {
113
        obj._msIsQueryable = () => this._queryable;
9✔
114
        obj._msGetFeatureById = (id) => {
9✔
115
            const _id = id instanceof Cesium.Entity ? id.id : id;
×
116
            const [featureId] = _id.split(':');
×
117
            const feature = this._features.find((f) => f.id === featureId);
×
118
            const originalFeature = this._originalFeatures.find((f) => f.properties[this._uuidKey] === feature.properties[this._uuidKey]) || {};
×
119
            const { properties } = originalFeature;
×
120
            return {
×
121
                feature: {
122
                    ...originalFeature,
123
                    properties: omit(properties, [this._uuidKey])
124
                },
125
                msId: this._msId
126
            };
127
        };
128
    }
129
    _getEntityOptions(primitive) {
130
        const { entity, geometry, orientation, minimumHeights, maximumHeights } = primitive;
9✔
131
        if (entity.polygon) {
9!
132
            return {
×
133
                polygon: {
134
                    ...entity.polygon,
135
                    hierarchy: new Cesium.ConstantProperty(new Cesium.PolygonHierarchy(
136
                        geometry[0].map(cartesian => cartesian.clone()),
×
137
                        geometry
138
                            .filter((hole, idx) => idx > 0)
×
139
                            .map(hole => new Cesium.PolygonHierarchy(hole.map((cartesian) => cartesian.clone())))
×
140
                    ))
141
                }
142
            };
143
        }
144
        if (entity.wall) {
9!
145
            return {
×
146
                wall: {
147
                    ...entity.wall,
148
                    positions: geometry[0],
149
                    minimumHeights: minimumHeights?.[0],
150
                    maximumHeights: maximumHeights?.[0]
151
                }
152
            };
153
        }
154
        if (entity.polyline) {
9!
155
            return {
×
156
                polyline: {
157
                    ...entity.polyline,
158
                    positions: geometry[0]
159
                }
160
            };
161
        }
162
        if (entity.polylineVolume) {
9!
163
            return {
×
164
                polylineVolume: {
165
                    ...entity.polylineVolume,
166
                    positions: geometry[0]
167
                }
168
            };
169
        }
170
        return {
9✔
171
            ...entity,
172
            ...(orientation && { orientation }),
9!
173
            position: geometry
174
        };
175
    }
176
    _updatePointEntity(primitive, previous) {
177
        if (previous && (primitive.entity.billboard || primitive.entity.label || primitive.entity.model)) {
9!
178
            const entity = previous.entity;
×
179
            if (primitive.orientation) {
×
180
                entity.orientation = primitive.orientation;
×
181
            }
182
            if (primitive.geometry) {
×
183
                entity.position = primitive.geometry.clone();
×
184
            }
185
            const graphicKey = Object.keys(primitive.entity)[0];
×
186
            // it's better to recreate the point entity in case height reference change
187
            if (entity?.[graphicKey]?.heightReference?.getValue() !== primitive?.entity?.[graphicKey]?.heightReference) {
×
188
                return null;
×
189
            }
190
            const propertyKeys = Object.keys(primitive.entity[graphicKey]);
×
191
            propertyKeys.forEach((propertyKey) => {
×
192
                entity[graphicKey][propertyKey] = primitive.entity[graphicKey][propertyKey];
×
193
            });
194
            return previous;
×
195
        }
196
        return null;
9✔
197
    }
198
    _filterEntities({ primitive }) {
199
        return (this._mergePolygonFeatures && primitive?.entity?.polygon) ? false : !!primitive.entity;
9!
200
    }
201
    _updateEntities(newStyledFeatures, forceUpdate) {
202
        const previousEntities = this._styledFeatures.filter((feature) => this._filterEntities(feature));
15✔
203
        const entities = newStyledFeatures.filter((feature) => this._filterEntities(feature));
15✔
204
        const currentIdsSet = new Set();
15✔
205
        for (let i = 0; i < entities.length; i++) {
15✔
206
            currentIdsSet.add(entities[i].id);
9✔
207
        }
208
        // Do NOT wrap remove+add of same-id entities in suspendEvents: Cesium's
209
        // EntityCollection treats a queued remove followed by an add of the
210
        // same id as a net no-op (see add()/removeById() in EntityCollection.js
211
        // - both clear the opposite queue without populating their own).
212
        // The visualizer is then never notified, the new primitive is never
213
        // built, and the layer keeps rendering with the old style.
214
        for (let i = 0; i < previousEntities.length; i++) {
15✔
215
            const id = previousEntities[i].id;
×
216
            if (currentIdsSet.has(id)) {
×
217
                continue;
×
218
            }
219
            const entry = this._entities.get(id);
×
220
            if (entry) {
×
221
                this._dataSource.entities.remove(entry.entity);
×
222
            }
223
        }
224
        const newEntitiesMap = new Map();
15✔
225
        for (let i = 0; i < entities.length; i++) {
15✔
226
            const { id, action, primitive } = entities[i];
9✔
227
            const previous = this._entities.get(id);
9✔
228
            if (!forceUpdate && action === 'none') {
9!
229
                if (previous) {
×
230
                    newEntitiesMap.set(id, previous);
×
231
                }
232
                continue;
×
233
            }
234
            const updatedPoint = this._updatePointEntity(primitive, previous);
9✔
235
            if (updatedPoint) {
9!
236
                newEntitiesMap.set(id, updatedPoint);
×
237
                continue;
×
238
            }
239
            if (previous) {
9!
240
                this._dataSource.entities.remove(previous.entity);
×
241
            }
242
            const entity = this._dataSource.entities.add({
9✔
243
                id,
244
                ...this._getEntityOptions(primitive)
245
            });
246
            this._addCustomProperties(entity);
9✔
247
            newEntitiesMap.set(id, { id, entity });
9✔
248
        }
249
        this._entities = newEntitiesMap;
15✔
250
    }
251
    _updatePolygonPrimitive(newStyledFeatures, forceUpdate) {
252
        const previousPolygonPrimitives = this._styledFeatures.filter(({ primitive }) => primitive.type === 'polygon' && !primitive.clampToGround);
×
253
        const polygonPrimitives = newStyledFeatures.filter(({ primitive }) => primitive.type === 'polygon' && !primitive.clampToGround);
×
254
        const polygonPrimitivesIds = new Set(polygonPrimitives.map(({ id }) => id));
×
255
        const removedPrimitives = previousPolygonPrimitives.filter(({ id }) => !polygonPrimitivesIds.has(id));
×
256
        const noActions = !removedPrimitives.length && polygonPrimitives.length ? polygonPrimitives.every(({ action }) => !forceUpdate && action === 'none') : false;
×
257
        if (noActions) {
×
258
            return;
×
259
        }
260
        if (this?._polygonPrimitives?.length) {
×
261
            this._polygonPrimitives.forEach((primitive) => {
×
262
                this._primitives.remove(primitive);
×
263
            });
264
            this._polygonPrimitives = [];
×
265
        }
266
        if (polygonPrimitives.length <= 0) {
×
267
            return;
×
268
        }
269
        const groupByTranslucencyAndExtrusion = polygonPrimitives.reduce((acc, options) => {
×
270
            const { primitive } = options;
×
271
            const { material, extrudedHeight } = primitive?.entity?.polygon || {};
×
272
            const key = `t:${material.alpha === 1},e:${extrudedHeight !== undefined ? 'true' : 'false'}`;
×
273
            return {
×
274
                ...acc,
275
                [key]: [...(acc[key] || []), options]
×
276
            };
277
        }, {});
278
        this._polygonPrimitives = Object.keys(groupByTranslucencyAndExtrusion).map((key) => {
×
279
            const styledFeatures = groupByTranslucencyAndExtrusion[key];
×
280
            const cesiumPrimitive = new Cesium.Primitive({
×
281
                geometryInstances: styledFeatures.map(({ primitive, id }) => {
282
                    const polygon = primitive?.entity?.polygon || {};
×
283
                    return new Cesium.GeometryInstance({
×
284
                        geometry: new Cesium.PolygonGeometry({
285
                            polygonHierarchy: new Cesium.PolygonHierarchy(
286
                                primitive.geometry[0].map(cartesian => cartesian.clone()),
×
287
                                primitive.geometry
288
                                    .filter((hole, idx) => idx > 0)
×
289
                                    .map(hole => new Cesium.PolygonHierarchy(hole.map((cartesian) => cartesian.clone())))
×
290
                            ),
291
                            arcType: polygon.arcType,
292
                            perPositionHeight: polygon.height !== undefined ? false : polygon.perPositionHeight,
×
293
                            ...(polygon.height !== undefined && { height: polygon.height }),
×
294
                            ...(polygon.extrudedHeight !== undefined && { extrudedHeight: polygon.extrudedHeight }),
×
295
                            vertexFormat: Cesium.VertexFormat.POSITION_AND_NORMAL
296
                        }),
297
                        id,
298
                        attributes: {
299
                            color: new Cesium.ColorGeometryInstanceAttribute(
300
                                polygon.material.red,
301
                                polygon.material.green,
302
                                polygon.material.blue,
303
                                polygon.material.alpha
304
                            ),
305
                            // allow to click on multiple instances
306
                            show: new Cesium.ShowGeometryInstanceAttribute(true)
307
                        }
308
                    });
309
                }),
310
                appearance: new Cesium.PerInstanceColorAppearance({
311
                    flat: styledFeatures[0].primitive?.entity?.polygon?.extrudedHeight === undefined,
312
                    translucent: styledFeatures[0].primitive?.entity?.polygon?.material?.alpha !== 1
313
                }),
314
                asynchronous: true
315
            });
316
            this._addCustomProperties(cesiumPrimitive);
×
317
            return cesiumPrimitive;
×
318
        });
319
        this._polygonPrimitives.forEach((primitive) => {
×
320
            this._primitives.add(primitive);
×
321
        });
322
    }
323
    _updateGroundPolygonPrimitive(newStyledFeatures, forceUpdate) {
324
        const previousGroundPolygonPrimitives = this._styledFeatures.filter(({ primitive }) => primitive.type === 'polygon' && !!primitive.clampToGround);
×
325
        const groundPolygonPrimitives = newStyledFeatures.filter(({ primitive }) => primitive.type === 'polygon' && !!primitive.clampToGround);
×
326
        const groundPolygonPrimitivesIds = new Set(groundPolygonPrimitives.map(({ id }) => id));
×
327
        const removedPrimitives = previousGroundPolygonPrimitives.filter(({ id }) => !groundPolygonPrimitivesIds.has(id));
×
328
        const noActions = !removedPrimitives.length && groundPolygonPrimitives.length ? groundPolygonPrimitives.every(({ action }) => !forceUpdate && action === 'none') : false;
×
329
        if (noActions) {
×
330
            return;
×
331
        }
332
        if (this?._groundPolygonPrimitives?.length) {
×
333
            this._groundPolygonPrimitives.forEach((primitive) => {
×
334
                this._primitives.remove(primitive);
×
335
            });
336
            this._groundPolygonPrimitives = [];
×
337
        }
338
        if (groundPolygonPrimitives.length <= 0) {
×
339
            return;
×
340
        }
341
        const groupByColorAndClassification = groundPolygonPrimitives.reduce((acc, options) => {
×
342
            const { primitive } = options;
×
343
            const { material, classificationType } = primitive?.entity?.polygon || {};
×
344
            const key = `r:${material.red},g:${material.green},b:${material.blue},a:${material.alpha},c:${classificationType}`;
×
345
            return {
×
346
                ...acc,
347
                [key]: [...(acc[key] || []), options]
×
348
            };
349
        }, {});
350

351
        this._groundPolygonPrimitives = Object.keys(groupByColorAndClassification).map((key) => {
×
352
            const styledFeatures = groupByColorAndClassification[key];
×
353
            const cesiumPrimitive = new Cesium.GroundPrimitive({
×
354
                classificationType: styledFeatures[0].primitive?.entity?.polygon?.classificationType,
355
                geometryInstances: styledFeatures.map(({ primitive, id }) => {
356
                    const polygon = primitive?.entity?.polygon || {};
×
357
                    return new Cesium.GeometryInstance({
×
358
                        geometry: new Cesium.PolygonGeometry({
359
                            polygonHierarchy: new Cesium.PolygonHierarchy(
360
                                primitive.geometry[0].map(cartesian => cartesian.clone()),
×
361
                                primitive.geometry
362
                                    .filter((hole, idx) => idx > 0)
×
363
                                    .map(hole => new Cesium.PolygonHierarchy(hole.map((cartesian) => cartesian.clone())))
×
364
                            ),
365
                            arcType: polygon.arcType,
366
                            perPositionHeight: polygon.perPositionHeight
367
                        }),
368
                        id,
369
                        attributes: {
370
                            color: new Cesium.ColorGeometryInstanceAttribute(
371
                                polygon.material.red,
372
                                polygon.material.green,
373
                                polygon.material.blue,
374
                                polygon.material.alpha
375
                            ),
376
                            // allow to click on multiple instances
377
                            show: new Cesium.ShowGeometryInstanceAttribute(true)
378
                        }
379
                    });
380
                }),
381
                appearance: new Cesium.PerInstanceColorAppearance({
382
                    flat: true,
383
                    translucent: styledFeatures[0].primitive?.entity?.polygon?.material?.alpha !== 1
384
                }),
385
                asynchronous: true
386
            });
387

388
            this._addCustomProperties(cesiumPrimitive);
×
389
            return cesiumPrimitive;
×
390
        });
391

392
        this._groundPolygonPrimitives.forEach((primitive) => {
×
393
            this._primitives.add(primitive);
×
394
        });
395
    }
396
    _runUpdate(forceUpdate) {
397
        if (!this._styleFunction) {
15!
398
            return;
×
399
        }
400
        // If a cycle is already running, queue exactly one rerun. Multiple
401
        // requests during the same in-flight window collapse together; the
402
        // rerun, when it fires, will reflect whatever state (features, style,
403
        // filter, opacity) is current at that point.
404
        if (this._runUpdateInFlight) {
15!
405
            this._runUpdatePending = {
×
406
                forceUpdate: !!(this._runUpdatePending?.forceUpdate || forceUpdate)
×
407
            };
408
            return;
×
409
        }
410
        this._runUpdateInFlight = true;
15✔
411
        // Build an id-keyed lookup once per cycle so getPreviousStyledFeature
412
        // is O(1) per call instead of an Array.find over the entire previous
413
        // styled set. For large layers (where the style function calls this
414
        // for every feature) the prior O(N) per call multiplied to O(N²) per
415
        // cycle, the dominant cost during streaming.
416
        const previousStyledById = new Map();
15✔
417
        for (let i = 0; i < this._styledFeatures.length; i++) {
15✔
418
            const sf = this._styledFeatures[i];
×
419
            previousStyledById.set(sf.id, sf);
×
420
        }
421
        this._styleFunction({
15✔
422
            map: this._map,
423
            opacity: this._opacity,
424
            features: this._featureFilter ? this._features.filter(this._featureFilter) : this._features,
15!
425
            getPreviousStyledFeature: (styledFeature) => previousStyledById.get(styledFeature.id)
9✔
426
        })
427
            .then((styledFeatures) => {
428
                this._updateEntities(styledFeatures, forceUpdate);
15✔
429
                if (this._mergePolygonFeatures) {
15!
430
                    this._updatePolygonPrimitive(styledFeatures, forceUpdate);
×
431
                    this._updateGroundPolygonPrimitive(styledFeatures, forceUpdate);
×
432
                }
433
                this._styledFeatures = [...styledFeatures];
15✔
434
                this._updateScaleVisibility();
15✔
435
                setTimeout(() => this._map.scene.requestRender());
×
436
            })
437
            .finally(() => {
438
                this._runUpdateInFlight = false;
15✔
439
                if (this._runUpdatePending) {
15!
440
                    const pending = this._runUpdatePending;
×
441
                    this._runUpdatePending = null;
×
442
                    this._runUpdate(pending.forceUpdate);
×
443
                }
444
            });
445
    }
446
    _update(forceUpdate) {
447
        if (this._timeout) {
15!
448
            clearTimeout(this._timeout);
×
449
        }
450
        this._timeout = setTimeout(() => {
15✔
451
            this._timeout = undefined;
15✔
452
            this._runUpdate(forceUpdate);
15✔
453
        }, 300);
454
    }
455
    /**
456
     * Run any pending debounced _update synchronously. Used by the streaming
457
     * load path to force a render mid-load instead of waiting for the 300ms
458
     * debounce to fire after the last batch.
459
     */
460
    flushPendingUpdate(forceUpdate) {
461
        if (this._timeout) {
×
462
            clearTimeout(this._timeout);
×
463
            this._timeout = undefined;
×
464
        }
465
        this._runUpdate(forceUpdate);
×
466
    }
467
    setFeatures(newFeatures) {
468
        this._originalFeatures = (newFeatures ?? []).map((feature) => {
39!
469
            return {
33✔
470
                ...feature,
471
                properties: {
472
                    ...feature.properties,
473
                    [this._uuidKey]: feature?.properties?.[this._uuidKey] ?? uuid()
66✔
474
                }
475
            };
476
        });
477
        const { features } = turfFlatten({ type: 'FeatureCollection', features: this._originalFeatures });
39✔
478
        this._features = features.filter(feature => feature.geometry)
39✔
479
            .map((feature) => {
480
                return {
33✔
481
                    ...feature,
482
                    id: uuid(),
483
                    positions: featureToCartesianPositions(feature)
484
                };
485
            }).filter(feature => feature.positions !== null);
33✔
486

487
    }
488
    /**
489
     * Append features without rebuilding existing ones. Existing entities and
490
     * positions are preserved; only the new batch is transformed and added.
491
     * Triggers a debounced _update; callers that need a synchronous render can
492
     * follow up with flushPendingUpdate().
493
     */
494
    addFeatures(newFeatures) {
495
        if (!newFeatures || newFeatures.length === 0) {
×
496
            return;
×
497
        }
498
        const newOriginal = newFeatures.map((feature) => ({
×
499
            ...feature,
500
            properties: {
501
                ...feature.properties,
502
                [this._uuidKey]: feature?.properties?.[this._uuidKey] ?? uuid()
×
503
            }
504
        }));
505
        // In-place push avoids the O(N) re-allocation that array spread incurs
506
        // every batch; for 30k features streamed in 200-feature batches the
507
        // spread version is O(N²) cumulative.
508
        for (let i = 0; i < newOriginal.length; i++) {
×
509
            this._originalFeatures.push(newOriginal[i]);
×
510
        }
511
        const { features } = turfFlatten({ type: 'FeatureCollection', features: newOriginal });
×
512
        for (let i = 0; i < features.length; i++) {
×
513
            const feature = features[i];
×
514
            if (!feature.geometry) {
×
515
                continue;
×
516
            }
517
            const positions = featureToCartesianPositions(feature);
×
518
            if (positions === null) {
×
519
                continue;
×
520
            }
521
            this._features.push({
×
522
                ...feature,
523
                id: uuid(),
524
                positions
525
            });
526
        }
527
        this._update();
×
528
    }
529
    setOpacity(opacity) {
530
        const previousOpacity = this._opacity;
×
531
        this._opacity = opacity;
×
532
        this._update(previousOpacity !== opacity);
×
533
    }
534
    setStyleFunction(styleFunction) {
535
        this._styleFunction = styleFunction;
15✔
536
        this._update();
15✔
537
    }
538
    setFeatureFilter(featureFilter) {
539
        this._featureFilter = featureFilter;
×
540
        this._update();
×
541
    }
542
    _updateScaleVisibility() {
543
        if (!this._map) return;
15!
544
        const currentMapScale = getMapScaleForCesium(this._map);
15✔
545
        this._entities.forEach(wrapper => {
×
546
            const { entity} = wrapper;
×
547
            // If no custom rules → apply default visibility
548
            if (!this._styleRules || this._styleRules.length === 0) {
×
549
                entity.show = true; // Keep visible
×
550
                return;
×
551
            }
552
            const validRules = this._styleRules.filter(rule =>
×
553
                geoStylerScaleDenominatorFilter(rule, currentMapScale)
×
554
            );
555
            entity.show = validRules.length > 0;
×
556
        });
557
        this._map.scene.requestRender();
×
558
    }
559
    _setStyleRules(rules) {
560
        this._styleRules = rules;
×
561
    }
562
    destroy() {
563
        this._primitives.removeAll();
27✔
564
        this._map.scene.primitives.remove(this._primitives);
27✔
565
        this._dataSource.entities.removeAll();
27✔
566
        this._map.dataSources.remove(this._dataSource);
27✔
567
        this._map.camera.moveEnd.removeEventListener(this._onCameraMoveEndBound);
27✔
568
    }
569
}
570

571
GeoJSONStyledFeatures.featureToCartesianPositions = featureToCartesianPositions;
3✔
572

573
export default GeoJSONStyledFeatures;
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