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

geosolutions-it / MapStore2 / 19735587487

27 Nov 2025 09:59AM UTC coverage: 76.667% (-0.3%) from 76.929%
19735587487

Pull #11119

github

web-flow
Fix: #11712 Support for template format on vector layers to visualize embedded conent (#11720)
Pull Request #11119: Layer Selection Plugin on ArcGIS, WFS & WMS layers

32268 of 50209 branches covered (64.27%)

7 of 13 new or added lines in 2 files covered. (53.85%)

3017 existing lines in 248 files now uncovered.

40158 of 52380 relevant lines covered (76.67%)

37.8 hits per line

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

61.48
/web/client/components/map/cesium/MapViewsSupport.jsx
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

9
import { useEffect, useRef } from 'react';
10
import * as Cesium from 'cesium';
11
import {
12
    getCesiumColor,
13
    createPolylinePrimitive,
14
    clearPrimitivesCollection,
15
    createCircleMarkerImage,
16
    createClippingPolygonsFromGeoJSON,
17
    applyClippingPolygons
18
} from '../../../utils/cesium/PrimitivesUtils';
19
import { computeAngle } from '../../../utils/cesium/MathUtils';
20
import {
21
    DefaultViewValues,
22
    formatClippingFeatures,
23
    getZoomFromHeight,
24
    ViewSettingsTypes
25
} from '../../../utils/MapViewsUtils';
26

27
const computeDestinationOrientation = (map, {
1✔
28
    center,
29
    cameraPosition,
30
    ...view
31
}) => {
32
    if (view.orientation && view.position) {
2!
UNCOV
33
        return {
×
34
            destination: view.position,
35
            orientation: view.orientation
36
        };
37
    }
38
    let tmp = {
2✔
39
        position: Cesium.Cartesian3.clone(map.camera.position),
40
        orientation: {
41
            heading: map.camera.heading,
42
            pitch: map.camera.pitch,
43
            roll: map.camera.roll
44
        }
45
    };
46

47
    const target = Cesium.Cartographic.toCartesian(
2✔
48
        Cesium.Cartographic.fromDegrees(center.longitude, center.latitude, center.height, new Cesium.Cartographic())
49
    );
50
    const targetUp = Cesium.Cartographic.toCartesian(
2✔
51
        Cesium.Cartographic.fromDegrees(center.longitude, center.latitude, center.height + 1, new Cesium.Cartographic())
52
    );
53
    const origin = Cesium.Cartographic.toCartesian(
2✔
54
        Cesium.Cartographic.fromDegrees(cameraPosition.longitude, cameraPosition.latitude, cameraPosition.height, new Cesium.Cartographic())
55
    );
56
    const originUp = Cesium.Cartographic.toCartesian(
2✔
57
        Cesium.Cartographic.fromDegrees(cameraPosition.longitude, cameraPosition.latitude, cameraPosition.height + 1, new Cesium.Cartographic())
58
    );
59

60
    const diff = Cesium.Cartesian3.subtract(origin, target, new Cesium.Cartesian3());
2✔
61
    const vertical = Cesium.Cartesian3.subtract(targetUp, target, new Cesium.Cartesian3());
2✔
62

63
    // if the direction of the camera is perpendicular to the globe
64
    // we are using a threshold of 5 degrees
65
    // we should reverse the up is the latitude is greater than 0
66
    const shouldReverseUp = center.latitude > 0 && Math.round(computeAngle(diff, vertical)) <= 5;
2✔
67
    const up = shouldReverseUp
2!
68
        ? Cesium.Cartesian3.subtract(origin, originUp, new Cesium.Cartesian3())
69
        : Cesium.Cartesian3.subtract(originUp, origin, new Cesium.Cartesian3());
70

71
    let direction;
72
    direction = Cesium.Cartesian3.subtract(target, origin, new Cesium.Cartesian3());
2✔
73
    Cesium.Cartesian3.normalize(direction, direction);
2✔
74
    map.camera.setView({
2✔
75
        destination: origin,
76
        orientation: {
77
            direction,
78
            up
79
        }
80
    });
81
    map.camera.setView({
2✔
82
        destination: origin,
83
        orientation: {
84
            direction,
85
            up
86
        }
87
    });
88
    const properties = {
2✔
89
        destination: {
90
            x: map.camera.position.x,
91
            y: map.camera.position.y,
92
            z: map.camera.position.z
93
        },
94
        orientation: {
95
            heading: map.camera.heading,
96
            pitch: map.camera.pitch,
97
            roll: map.camera.roll
98
        }
99
    };
100
    map.camera.setView({
2✔
101
        destination: tmp.position,
102
        orientation: tmp.orientation
103
    });
104
    return properties;
2✔
105
};
106

107
function MapViewSupport({
108
    map,
109
    selectedId,
110
    views,
111
    apiRef = () => { },
3✔
112
    showViewsGeometries,
113
    showClipGeometries,
114
    resources
115
}) {
116
    const selected = views?.find(view => view.id === selectedId);
11✔
117

118
    const staticPrimitivesCollection = useRef();
11✔
119
    const staticBillboardCollection = useRef();
11✔
120
    const staticLabelsCollection = useRef();
11✔
121
    const markerImage = useRef();
11✔
122
    const clipGeometriesDataSource = useRef();
11✔
123

124
    useEffect(() => {
11✔
125
        const api = {
7✔
126
            options: {
127
                settings: [
128
                    ViewSettingsTypes.DESCRIPTION,
129
                    ViewSettingsTypes.POSITION,
130
                    ViewSettingsTypes.ANIMATION,
131
                    ViewSettingsTypes.MASK,
132
                    ViewSettingsTypes.GLOBE_TRANSLUCENCY,
133
                    ViewSettingsTypes.LAYERS_OPTIONS
134
                ],
135
                unsupportedLayers: [],
136
                showClipGeometriesEnabled: true
137
            },
138
            getView: () => {
139
                const cartographicCameraPosition = map.camera.positionCartographic;
×
140
                const zoom = getZoomFromHeight(cartographicCameraPosition.height);
×
141
                const rectangle = map.camera.computeViewRectangle(map.scene.globe.ellipsoid, new Cesium.Rectangle());
×
142
                let target = map.scene.globe.pick(new Cesium.Ray(map.camera.position, map.camera.direction), map.scene);
×
143
                if (!target) {
×
UNCOV
144
                    target = Cesium.Cartesian3.add(
×
145
                        Cesium.Cartesian3.clone(map.camera.position),
146
                        Cesium.Cartesian3.multiplyByScalar(Cesium.Cartesian3.clone(map.camera.direction), 100000, new Cesium.Cartesian3() ),
147
                        new Cesium.Cartesian3()
148
                    );
149
                }
UNCOV
150
                const cartographicCenter = Cesium.Cartographic.fromCartesian(
×
151
                    new Cesium.Cartesian3(target.x, target.y, target.z)
152
                );
153

UNCOV
154
                return {
×
155
                    zoom,
156
                    center: {
157
                        longitude: Cesium.Math.toDegrees(cartographicCenter.longitude),
158
                        latitude: Cesium.Math.toDegrees(cartographicCenter.latitude),
159
                        height: cartographicCenter.height
160
                    },
161
                    cameraPosition: {
162
                        longitude: Cesium.Math.toDegrees(cartographicCameraPosition.longitude),
163
                        latitude: Cesium.Math.toDegrees(cartographicCameraPosition.latitude),
164
                        height: cartographicCameraPosition.height
165
                    },
166
                    bbox: [
167
                        Cesium.Math.toDegrees(rectangle.west),
168
                        Cesium.Math.toDegrees(rectangle.south),
169
                        Cesium.Math.toDegrees(rectangle.east),
170
                        Cesium.Math.toDegrees(rectangle.north)
171
                    ]
172
                };
173
            },
174
            setView: (view) => {
175
                const { destination, orientation } = computeDestinationOrientation(map, view);
2✔
176
                map.camera.cancelFlight();
2✔
177
                map.camera[view.flyTo ? 'flyTo' : 'setView']({
2!
178
                    destination,
179
                    orientation
180
                });
181
            },
182
            computeViewCoordinates: (view, newProperties) => {
183
                // update view with new center and cameraPosition properties
UNCOV
184
                api.setView({ ...newProperties, flyTo: false });
×
185
                // then update also zoom and bbox
186
                const cartographicCameraPosition = map.camera.positionCartographic;
×
187
                const zoom = getZoomFromHeight(cartographicCameraPosition.height);
×
188
                const rectangle = map.camera.computeViewRectangle(map.scene.globe.ellipsoid, new Cesium.Rectangle());
×
UNCOV
189
                return {
×
190
                    ...newProperties,
191
                    zoom,
192
                    bbox: [
193
                        Cesium.Math.toDegrees(rectangle.west),
194
                        Cesium.Math.toDegrees(rectangle.south),
195
                        Cesium.Math.toDegrees(rectangle.east),
196
                        Cesium.Math.toDegrees(rectangle.north)
197
                    ]
198
                };
199
            }
200
        };
201

202
        apiRef(api);
7✔
203

204
    }, [map]);
205

206
    useEffect(() => {
11✔
207
        if (map) {
7!
208
            staticPrimitivesCollection.current = new Cesium.PrimitiveCollection({ destroyPrimitives: true });
7✔
209
            map.scene.primitives.add(staticPrimitivesCollection.current);
7✔
210

211
            staticBillboardCollection.current = new Cesium.BillboardCollection();
7✔
212
            map.scene.primitives.add(staticBillboardCollection.current);
7✔
213

214
            staticLabelsCollection.current = new Cesium.LabelCollection();
7✔
215
            map.scene.primitives.add(staticLabelsCollection.current);
7✔
216

217
            markerImage.current = createCircleMarkerImage(16, { stroke: '#ffffff', strokeWidth: 2, fill: false});
7✔
218

219
            clipGeometriesDataSource.current = new Cesium.GeoJsonDataSource('clipGeometriesDataSource');
7✔
220
        }
221
        return () => {
7✔
222
            if (map?.isDestroyed && !map.isDestroyed()) {
7!
223
                clearPrimitivesCollection(map, staticPrimitivesCollection.current);
×
224
                staticPrimitivesCollection.current = null;
×
225
                clearPrimitivesCollection(map, staticBillboardCollection.current);
×
226
                staticBillboardCollection.current = null;
×
227
                clearPrimitivesCollection(map, staticLabelsCollection.current);
×
UNCOV
228
                staticLabelsCollection.current = null;
×
229
            }
230
        };
231
    }, [map]);
232

233
    useEffect(() => {
11✔
234
        if ((showViewsGeometries || showClipGeometries) && map?.isDestroyed && !map.isDestroyed() && views?.length > 0) {
9!
235
            if (showViewsGeometries) {
×
236
                views.forEach((view) => {
×
UNCOV
237
                    const position = Cesium.Cartographic.toCartesian(
×
238
                        Cesium.Cartographic.fromDegrees(view.cameraPosition.longitude, view.cameraPosition.latitude, view.cameraPosition.height)
239
                    );
UNCOV
240
                    const target = Cesium.Cartographic.toCartesian(
×
241
                        Cesium.Cartographic.fromDegrees(view.center.longitude, view.center.latitude, view.center.height)
242
                    );
243
                    const isSelected = view.id === selectedId;
×
UNCOV
244
                    staticPrimitivesCollection.current.add(createPolylinePrimitive({
×
245
                        color: isSelected ? '#ffcc33' : '#ffffff',
×
246
                        opacity: 1.0,
247
                        depthFailColor: '#000000',
248
                        depthFailOpacity: 0.0,
249
                        width: 1,
250
                        dashLength: 10,
251
                        coordinates: [
252
                            position,
253
                            target
254
                        ]
255
                    }));
UNCOV
256
                    staticBillboardCollection.current.add({
×
257
                        position,
258
                        image: markerImage.current,
259
                        color: getCesiumColor({
260
                            color: isSelected ? '#ffcc33' : '#ffffff'
×
261
                        }),
262
                        disableDepthTestDistance: Number.POSITIVE_INFINITY,
263
                        allowPicking: false
264
                    });
UNCOV
265
                    staticLabelsCollection.current.add({
×
266
                        position,
267
                        text: view.title,
268
                        font: '12px sans-serif',
269
                        disableDepthTestDistance: Number.POSITIVE_INFINITY,
270
                        fillColor: getCesiumColor({
271
                            color: '#ffffff'
272
                        }),
273
                        outlineColor: getCesiumColor({
274
                            color: '#000000'
275
                        }),
276
                        outlineWidth: 4,
277
                        style: Cesium.LabelStyle.FILL_AND_OUTLINE,
278
                        showBackground: false,
279
                        backgroundPadding: new Cesium.Cartesian2(4, 4),
280
                        pixelOffset: new Cesium.Cartesian2(0, -16),
281
                        horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
282
                        verticalOrigin: Cesium.VerticalOrigin.BASELINE
283
                    });
284
                });
285
            }
286
            if (showClipGeometries) {
×
UNCOV
287
                resources
×
288
                    .filter((resource) => {
289
                        const isUsedByView = !!views?.find((view) => view?.terrain?.clippingLayerResourceId === resource.id || view?.layers?.find(layer => layer?.clippingLayerResourceId === resource.id));
×
UNCOV
290
                        return !!(resource?.data?.collection?.features && isUsedByView);
×
291
                    })
292
                    .forEach(resource => {
293
                        formatClippingFeatures(resource.data.collection.features).forEach((feature) => {
×
UNCOV
294
                            staticPrimitivesCollection.current.add(createPolylinePrimitive({
×
295
                                color: '#ff0000',
296
                                opacity: 1.0,
297
                                width: 3,
298
                                dashLength: 10,
299
                                clampToGround: true,
UNCOV
300
                                coordinates: Cesium.Cartesian3.fromDegreesArray(feature.geometry.coordinates[0].reduce((acc, coords) => [...acc, ...coords], []))
×
301
                            }));
UNCOV
302
                            staticLabelsCollection.current.add({
×
303
                                position: Cesium.Cartesian3.fromDegreesArray(feature.geometry.coordinates[0][0])[0],
304
                                text: feature.id,
305
                                font: '12px sans-serif',
306
                                disableDepthTestDistance: Number.POSITIVE_INFINITY,
307
                                fillColor: getCesiumColor({
308
                                    color: '#ffffff'
309
                                }),
310
                                outlineColor: getCesiumColor({
311
                                    color: '#000000'
312
                                }),
313
                                outlineWidth: 4,
314
                                style: Cesium.LabelStyle.FILL_AND_OUTLINE,
315
                                showBackground: false,
316
                                backgroundPadding: new Cesium.Cartesian2(4, 4),
317
                                pixelOffset: new Cesium.Cartesian2(0, -16),
318
                                horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
319
                                verticalOrigin: Cesium.VerticalOrigin.BASELINE
320
                            });
321
                        });
322
                    });
323
            }
UNCOV
324
            map.scene.requestRender();
×
325
        }
326
        return () => {
9✔
327
            if (map?.isDestroyed && !map.isDestroyed()) {
9!
328
                staticPrimitivesCollection.current?.removeAll();
×
329
                staticBillboardCollection.current?.removeAll();
×
330
                staticLabelsCollection.current?.removeAll();
×
UNCOV
331
                map.scene.requestRender();
×
332
            }
333
        };
334
    }, [views, selectedId, map, showViewsGeometries, showClipGeometries, resources]);
335

336
    useEffect(() => {
11✔
337
        const scene = map.scene;
7✔
338
        const globe = scene.globe;
7✔
339
        const { globeTranslucency = {} } = selected || {};
7✔
340
        globe.translucency.frontFaceAlphaByDistance = new Cesium.NearFarScalar(
7✔
341
            selected?.globeTranslucency?.nearDistance ?? DefaultViewValues.TRANSLUCENCY_NEAR_DISTANCE,
14✔
342
            0.0,
343
            selected?.globeTranslucency?.farDistance ?? DefaultViewValues.TRANSLUCENCY_FAR_DISTANCE,
14✔
344
            1.0
345
        );
346
        const opacity = globeTranslucency?.opacity ?? DefaultViewValues.TRANSLUCENCY_OPACITY;
7✔
347
        globe.translucency.enabled = globeTranslucency?.enabled ?? false;
7✔
348
        globe.translucency.frontFaceAlphaByDistance.nearValue = opacity;
7✔
349
        globe.translucency.frontFaceAlphaByDistance.farValue = globeTranslucency?.fadeByDistance
7!
350
            ? 1.0
351
            : opacity;
352
        map.scene.requestRender();
7✔
353
        return () => {
7✔
354
            if (map?.isDestroyed && !map.isDestroyed()) {
7!
UNCOV
355
                globe.translucency.enabled = false;
×
356
            }
357
        };
358
    }, [
359
        selected?.globeTranslucency?.enabled,
360
        selected?.globeTranslucency?.fadeByDistance,
361
        selected?.globeTranslucency?.nearDistance,
362
        selected?.globeTranslucency?.farDistance,
363
        selected?.globeTranslucency?.opacity
364
    ]);
365

366
    useEffect(() => {
11✔
367
        const scene = map.scene;
7✔
368
        scene.invertClassification = !!selected?.mask?.enabled;
7✔
369
        scene.invertClassificationColor = new Cesium.Color(0, 0, 0, 0.0);
7✔
370
        return () => {
7✔
371
            if (map?.isDestroyed && !map.isDestroyed()) {
7!
UNCOV
372
                scene.invertClassification = false;
×
373
            }
374
        };
375
    }, [
376
        selected?.mask?.enabled
377
    ]);
378

379
    useEffect(() => {
11✔
380
        const scene = map.scene;
9✔
381
        const globe = scene.globe;
9✔
382
        const terrainClippingLayerResource = resources?.find(resource => resource.id === selected?.terrain?.clippingLayerResourceId)?.data;
9✔
383
        const clippingPolygon = formatClippingFeatures(terrainClippingLayerResource?.collection?.features)?.find((feature) => feature.id === selected?.terrain?.clippingPolygonFeatureId);
9✔
384
        if (clippingPolygon) {
9!
UNCOV
385
            const polygons = createClippingPolygonsFromGeoJSON(clippingPolygon);
×
386
            applyClippingPolygons({
×
387
                target: globe,
388
                polygons: polygons,
389
                inverse: !!selected?.terrain?.clippingPolygonUnion,
390
                scene: map.scene,
391
                additionalProperties: {
392
                    backFaceCulling: true,
393
                    showSkirts: true
394
                }
395
            });
396
        } else {
397
            globe?.clippingPolygons?.removeAll();
9✔
398
            map.scene.requestRender();
9✔
399
        }
400
        return () => {
9✔
401
            if (map?.isDestroyed && !map.isDestroyed()) {
9!
402
                globe?.clippingPolygons?.removeAll();
×
403
                map.scene.requestRender();
×
404
            }
405
        };
406
    }, [
407
        selected?.terrain?.clippingPolygonFeatureId,
408
        selected?.terrain?.clippingPolygonUnion,
409
        selected?.terrain?.clipOriginalGeometry,
410
        selected?.terrain?.clippingLayerResourceId,
411
        resources
412
    ]);
413

414
    return null;
11✔
415
}
416

417
export default MapViewSupport;
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