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

geosolutions-it / MapStore2 / 12831531306

17 Jan 2025 03:01PM UTC coverage: 77.182% (+0.07%) from 77.115%
12831531306

Pull #10746

github

web-flow
Merge 501dbaeea into 4e4dabc03
Pull Request #10746: Fix #10739 Changing correctly resolutions limits when switching map CRS

30373 of 47156 branches covered (64.41%)

34 of 43 new or added lines in 2 files covered. (79.07%)

126 existing lines in 15 files now uncovered.

37769 of 48935 relevant lines covered (77.18%)

35.14 hits per line

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

55.26
/web/client/components/map/cesium/plugins/ThreeDTilesLayer.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

9
import Layers from '../../../../utils/cesium/Layers';
10
import * as Cesium from 'cesium';
11
import isEqual from 'lodash/isEqual';
12
import isNumber from 'lodash/isNumber';
13
import isNaN from 'lodash/isNaN';
14
import { getProxyUrl } from "../../../../utils/ProxyUtils";
15
import { getStyleParser } from '../../../../utils/VectorStyleUtils';
16
import { polygonToClippingPlanes } from '../../../../utils/cesium/PrimitivesUtils';
17
import tinycolor from 'tinycolor2';
18
import googleOnWhiteLogo from '../img/google_on_white_hdpi.png';
19
import googleOnNonWhiteLogo from '../img/google_on_non_white_hdpi.png';
20

21
const cleanStyle = (style, options) => {
1✔
22
    if (style && options?.pointCloudShading?.attenuation) {
×
23
        // remove pointSize if attenuation are applied
24
        const { pointSize, ...others } = style;
×
25
        return others;
×
26
    }
27
    return style;
×
28
};
29

30
function getStyle({ style, pointCloudShading = {} }) {
2✔
31
    const { format, body } = style || {};
2✔
32
    if (!format || !body) {
2!
33
        return Promise.resolve(null);
2✔
34
    }
35
    if (format === '3dtiles') {
×
36
        return Promise.resolve(cleanStyle(body, { pointCloudShading }));
×
37
    }
38
    if (format === 'geostyler') {
×
39
        return getStyleParser('3dtiles')
×
40
            .then((parser) => parser.writeStyle(body))
×
41
            .then((parsedStyle) => cleanStyle(parsedStyle, { pointCloudShading }));
×
42
    }
43
    return Promise.all([
×
44
        getStyleParser(format),
45
        getStyleParser('3dtiles')
46
    ])
47
        .then(([parser, threeDTilesParser]) =>
48
            parser
×
49
                .readStyle(body)
50
                .then(parsedStyle => threeDTilesParser.writeStyle(parsedStyle))
×
51
                .then((parsedStyle) => cleanStyle(parsedStyle, { pointCloudShading }))
×
52
        );
53
}
54

55
function updateModelMatrix(tileSet, { heightOffset }) {
56
    if (!isNaN(heightOffset) && isNumber(heightOffset)) {
2✔
57
        const boundingSphere = tileSet.boundingSphere;
1✔
58
        const cartographic = Cesium.Cartographic.fromCartesian(boundingSphere.center);          // undefined if the cartesian is at the center of the ellipsoid
1✔
59
        const surface = Cesium.Cartesian3.fromRadians(cartographic?.longitude || 0, cartographic?.latitude || 0, 0.0);
1!
60
        const offset = Cesium.Cartesian3.fromRadians(cartographic?.longitude || 0, cartographic?.latitude || 0, heightOffset);
1!
61
        const translation = Cesium.Cartesian3.subtract(offset, surface, new Cesium.Cartesian3());
1✔
62
        tileSet.modelMatrix =  Cesium.Matrix4.fromTranslation(translation);
1✔
63
    }
64
}
65

66
function clip3DTiles(tileSet, options, map) {
67

68
    const request = () => options.clippingPolygon
2!
69
        ? polygonToClippingPlanes(options.clippingPolygon, !!options.clippingPolygonUnion, options.clipOriginalGeometry)
70
        : Promise.resolve([]);
71

72
    request()
2✔
73
        .then((planes) => {
74
            if (planes?.length && !tileSet.clippingPlanes) {
2!
75
                tileSet.clippingPlanes = new Cesium.ClippingPlaneCollection({
×
76
                    modelMatrix: Cesium.Matrix4.inverse(
77
                        Cesium.Matrix4.multiply(
78
                            tileSet.root.computedTransform,
79
                            tileSet._initialClippingPlanesOriginMatrix,
80
                            new Cesium.Matrix4()
81
                        ),
82
                        new Cesium.Matrix4()),
83
                    planes,
84
                    unionClippingRegions: !!options.clippingPolygonUnion
85
                });
86
            }
87
            if (tileSet.clippingPlanes) {
2!
88
                tileSet.clippingPlanes.removeAll();
×
89
                tileSet.clippingPlanes.unionClippingRegions = !!options.clippingPolygonUnion;
×
90
                planes.forEach((plane) => {
×
91
                    tileSet.clippingPlanes.add(plane);
×
92
                });
93
                map.scene.requestRender();
×
94
            }
95
        });
96
}
97

98
let pendingCallbacks = {};
1✔
99

100
function ensureReady(layer, callback, eventKey) {
101
    const tileSet = layer.getTileSet();
2✔
102
    if (!tileSet && eventKey) {
2!
103
        pendingCallbacks[eventKey] = callback;
×
UNCOV
104
        return;
×
105
    }
106
    if (tileSet.ready) {
2!
107
        callback(tileSet);
2✔
108
    } else {
UNCOV
109
        tileSet.readyPromise.then(() => {
×
UNCOV
110
            callback(tileSet);
×
111
        });
112
    }
113
}
114

115
// Google Photorealistic 3D Tiles requires both attribution and brand logo (see https://cloud.google.com/blog/products/maps-platform/commonly-asked-questions-about-our-recently-launched-photorealistic-3d-tiles)
116
// The attribution are dynamic and updated directly with the `showCreditsOnScreen` property (see https://developers.google.com/maps/documentation/tile/policies#3d_tiles)
117
// The brand logo instead is not managed by the Cesium3DTileset class and must to be included in the credits
118
function updateGooglePhotorealistic3DTilesBrandLogo(map, options, tileSet) {
119
    if ((options?.url || '').includes('https://tile.googleapis.com')) {
4!
120
        if (!tileSet._googleCredit) {
×
UNCOV
121
            const bodyStyle = window?.getComputedStyle ? window.getComputedStyle(document.body, null) : null;
×
122
            const bodyBackgroundColor = bodyStyle?.getPropertyValue ? bodyStyle.getPropertyValue('background-color') : '#ffffff';
×
UNCOV
123
            const src = tinycolor(bodyBackgroundColor).isDark()
×
124
                ? googleOnNonWhiteLogo
125
                : googleOnWhiteLogo;
UNCOV
126
            tileSet._googleCredit = new Cesium.Credit(`<img src="${src}" title="Google" style="padding:0 0.5rem"/>`, true);
×
UNCOV
127
            return map.creditDisplay.addStaticCredit(tileSet._googleCredit);
×
128
        }
UNCOV
129
        return map.creditDisplay.removeStaticCredit(tileSet._googleCredit);
×
130
    }
131
    return null;
4✔
132
}
133

134
function updateShading(tileSet, options, map) {
135
    // point cloud
136
    tileSet.pointCloudShading.attenuation = !!options?.pointCloudShading?.attenuation;
2✔
137
    tileSet.pointCloudShading.maximumAttenuation = options?.pointCloudShading?.maximumAttenuation ?? 4;
2✔
138
    tileSet.pointCloudShading.eyeDomeLighting = !!options?.pointCloudShading?.eyeDomeLighting;
2✔
139
    tileSet.pointCloudShading.eyeDomeLightingStrength = options?.pointCloudShading?.eyeDomeLightingStrength ?? 1.0;
2✔
140
    tileSet.pointCloudShading.eyeDomeLightingRadius = options?.pointCloudShading?.eyeDomeLightingRadius ?? 1.0;
2✔
141
    setTimeout(() => map.scene.requestRender());
2✔
142
}
143

144
const createLayer = (options, map) => {
1✔
145
    if (!options.visibility) {
8✔
146
        return {
1✔
147
            detached: true,
UNCOV
148
            getResource: () => undefined,
×
149
            getTileSet: () => undefined,
1✔
150
            add: () => {},
151
            remove: () => {}
152
        };
153
    }
154
    let tileSet;
155
    let resource;
156
    let promise;
157
    const removeTileset = () => {
7✔
158
        updateGooglePhotorealistic3DTilesBrandLogo(map, options, tileSet);
2✔
159
        map.scene.primitives.remove(tileSet);
2✔
160
        tileSet = undefined;
2✔
161
    };
162
    const layer = {
7✔
163
        getTileSet: () => tileSet,
11✔
164
        getResource: () => resource
12✔
165
    };
166
    return {
7✔
167
        detached: true,
168
        ...layer,
169
        add: () => {
170
            resource = new Cesium.Resource({
4✔
171
                url: options.url,
172
                proxy: options.forceProxy ? new Cesium.DefaultProxy(getProxyUrl()) : undefined
4✔
173
                // TODO: axios supports also adding access tokens or credentials (e.g. authkey, Authentication header ...).
174
                // if we want to use internal cesium functionality to retrieve data
175
                // we need to create a utility to set a CesiumResource that applies also this part.
176
                // in addition to this proxy.
177
            });
178
            promise = Cesium.Cesium3DTileset.fromUrl(resource,
4✔
179
                {
180
                    showCreditsOnScreen: true
181
                }
182
            ).then((_tileSet) => {
183
                tileSet = _tileSet;
2✔
184
                updateGooglePhotorealistic3DTilesBrandLogo(map, options, tileSet);
2✔
185
                map.scene.primitives.add(tileSet);
2✔
186
                // assign the original mapstore id of the layer
187
                tileSet.msId = options.id;
2✔
188
                ensureReady(layer, () => {
2✔
189
                    updateModelMatrix(tileSet, options);
2✔
190
                    clip3DTiles(tileSet, options, map);
2✔
191
                    updateShading(tileSet, options, map);
2✔
192
                    getStyle(options)
2✔
193
                        .then((style) => {
194
                            if (style) {
2!
UNCOV
195
                                tileSet.style = new Cesium.Cesium3DTileStyle(style);
×
196
                            }
197
                            Object.keys(pendingCallbacks).forEach((eventKey) => {
2✔
198
                                pendingCallbacks[eventKey](tileSet);
×
199
                            });
200
                            pendingCallbacks = {};
2✔
201
                        });
202
                });
203
            });
204
        },
205
        remove: () => {
206
            if (tileSet) {
7✔
207
                removeTileset();
2✔
208
                return;
2✔
209
            }
210
            if (promise) {
5✔
211
                promise.then(() => {
2✔
212
                    removeTileset();
×
213
                });
214
            }
215
            return;
5✔
216
        }
217
    };
218
};
219

220
Layers.registerType('3dtiles', {
1✔
221
    create: createLayer,
222
    update: function(layer, newOptions, oldOptions, map) {
223
        if (newOptions.forceProxy !== oldOptions.forceProxy) {
3!
224
            return createLayer(newOptions, map);
3✔
225
        }
226
        if (
×
227
            (!isEqual(newOptions.clippingPolygon, oldOptions.clippingPolygon)
×
228
            || newOptions.clippingPolygonUnion !== oldOptions.clippingPolygonUnion
229
            || newOptions.clipOriginalGeometry !== oldOptions.clipOriginalGeometry)
230
        ) {
231
            ensureReady(layer, (tileSet) => {
×
UNCOV
232
                clip3DTiles(tileSet, newOptions, map);
×
233
            }, 'clip');
234
        }
UNCOV
235
        if ((
×
236
            !isEqual(newOptions.style, oldOptions.style)
×
237
            || newOptions?.pointCloudShading?.attenuation !== oldOptions?.pointCloudShading?.attenuation
238
        )) {
UNCOV
239
            ensureReady(layer, (tileSet) => {
×
UNCOV
240
                getStyle(newOptions)
×
241
                    .then((style) => {
242
                        if (style && tileSet) {
×
243
                            tileSet.makeStyleDirty();
×
UNCOV
244
                            tileSet.style = new Cesium.Cesium3DTileStyle(style);
×
245
                        }
246
                    });
247
            }, 'style');
248
        }
UNCOV
249
        if (!isEqual(newOptions.pointCloudShading, oldOptions.pointCloudShading)) {
×
UNCOV
250
            ensureReady(layer, (tileSet) => {
×
UNCOV
251
                updateShading(tileSet, newOptions, map);
×
252
            }, 'shading');
253
        }
UNCOV
254
        if (newOptions.heightOffset !== oldOptions.heightOffset) {
×
UNCOV
255
            ensureReady(layer, (tileSet) => {
×
UNCOV
256
                updateModelMatrix(tileSet, newOptions);
×
257
            }, 'matrix');
258
        }
UNCOV
259
        return null;
×
260
    }
261
});
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