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

geosolutions-it / MapStore2 / 16473467170

23 Jul 2025 02:26PM UTC coverage: 76.92% (-0.003%) from 76.923%
16473467170

Pull #11352

github

web-flow
Merge c109054ee into fe89aa09b
Pull Request #11352: Fix #11258 :For clipping/masking 3D tiles removed ClippingPlaneCollection and introduced ClippingPolygonCollection

31359 of 48780 branches covered (64.29%)

6 of 17 new or added lines in 1 file covered. (35.29%)

21 existing lines in 1 file now uncovered.

38874 of 50538 relevant lines covered (76.92%)

36.45 hits per line

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

60.0
/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
    polygonToClippingPlanes
17
} from '../../../utils/cesium/PrimitivesUtils';
18
import { computeAngle } from '../../../utils/cesium/MathUtils';
19
import {
20
    DefaultViewValues,
21
    formatClippingFeatures,
22
    getZoomFromHeight,
23
    ViewSettingsTypes
24
} from '../../../utils/MapViewsUtils';
25

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

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

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

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

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

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

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

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

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

201
        apiRef(api);
7✔
202

203
    }, [map]);
204

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

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

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

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

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

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

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

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

378
    useEffect(() => {
11✔
379
        const scene = map.scene;
9✔
380
        const globe = scene.globe;
9✔
381
        const terrainClippingLayerResource = resources?.find(resource => resource.id === selected?.terrain?.clippingLayerResourceId)?.data;
9✔
382
        const clippingPolygon = formatClippingFeatures(terrainClippingLayerResource?.collection?.features)?.find((feature) => feature.id === selected?.terrain?.clippingPolygonFeatureId);
9✔
383
        if (clippingPolygon) {
9!
384
            polygonToClippingPlanes(clippingPolygon, !!selected?.terrain?.clippingPolygonUnion, selected?.terrain?.clipOriginalGeometry)
×
385
                .then((planes) => {
386
                    globe.clippingPlanes = new Cesium.ClippingPlaneCollection({
×
387
                        planes,
388
                        edgeWidth: 1.0,
389
                        edgeColor: Cesium.Color.WHITE,
390
                        unionClippingRegions: !!selected?.terrain?.clippingPolygonUnion
391
                    });
392
                    globe.backFaceCulling = true;
×
393
                    globe.showSkirts = true;
×
394
                    map.scene.requestRender();
×
395
                });
396
        } else {
397
            globe.clippingPlanes = new Cesium.ClippingPlaneCollection({ planes: [] });
9✔
398
            map.scene.requestRender();
9✔
399
        }
400
        return () => {
9✔
401
            if (map?.isDestroyed && !map.isDestroyed()) {
9!
402
                globe.clippingPlanes = new Cesium.ClippingPlaneCollection({ planes: [] });
×
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