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

geosolutions-it / MapStore2 / 18713858357

22 Oct 2025 10:54AM UTC coverage: 76.929% (-0.003%) from 76.932%
18713858357

Pull #11620

github

web-flow
Merge 454fe1744 into 3d595db0f
Pull Request #11620: Fix #11619. Envorce caching headers

31944 of 49610 branches covered (64.39%)

39689 of 51592 relevant lines covered (76.93%)

37.77 hits per line

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

90.14
/web/client/components/map/openlayers/Layer.jsx
1
/**
2
 * Copyright 2015, 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 PropTypes from 'prop-types';
10
import React from 'react';
11
import Layers from '../../../utils/openlayers/Layers';
12
import {normalizeSRS, reprojectBbox, getExtentFromNormalized, isBboxCompatible, getPolygonFromExtent} from '../../../utils/CoordinatesUtils';
13
import Rx from 'rxjs';
14
import isNumber from 'lodash/isNumber';
15
import isArray from 'lodash/isArray';
16
import omit from 'lodash/omit';
17
import isEqual from 'lodash/isEqual';
18
import isNil from 'lodash/isNil';
19
import { getZoomFromResolution } from '../../../utils/MapUtils';
20

21
export default class OpenlayersLayer extends React.Component {
22
    static propTypes = {
1✔
23
        onWarning: PropTypes.func,
24
        maxExtent: PropTypes.array,
25
        map: PropTypes.object,
26
        mapId: PropTypes.string,
27
        srs: PropTypes.string,
28
        type: PropTypes.string,
29
        options: PropTypes.object,
30
        onLayerLoading: PropTypes.func,
31
        onLayerError: PropTypes.func,
32
        onCreationError: PropTypes.func,
33
        onLayerLoad: PropTypes.func,
34
        position: PropTypes.number,
35
        observables: PropTypes.array,
36
        securityToken: PropTypes.string,
37
        env: PropTypes.array,
38
        resolutions: PropTypes.array
39
    };
40

41
    static defaultProps = {
1✔
42
        observables: [],
43
        onLayerLoading: () => {},
44
        onLayerLoad: () => {},
45
        onLayerError: () => {},
46
        onCreationError: () => {},
47
        onWarning: () => {},
48
        srs: "EPSG:3857"
49
    };
50

51
    componentDidMount() {
52
        this.valid = true;
166✔
53
        this.tilestoload = 0;
166✔
54
        this.imagestoload = 0;
166✔
55
        this.createLayer(
166✔
56
            this.props.type,
57
            this.props.options,
58
            this.props.position,
59
            this.props.securityToken,
60
            this.props.env,
61
            this.props.resolutions
62
        );
63
    }
64

65
    UNSAFE_componentWillReceiveProps(newProps) {
66

67
        this.setLayerVisibility(newProps);
36✔
68

69
        const newOpacity = newProps.options && newProps.options.opacity !== undefined ? newProps.options.opacity : 1.0;
36✔
70
        this.setLayerOpacity(newOpacity);
36✔
71

72
        if (newProps.position !== this.props.position && this.layer && this.layer.setZIndex) {
36✔
73
            this.layer.setZIndex(newProps.position);
1✔
74
        }
75
        if (this.props.options) {
36!
76
            this.updateLayer(newProps, this.props);
36✔
77
        }
78
    }
79

80
    componentWillUnmount() {
81
        if (this.layer && this.props.map) {
73✔
82
            if (this.tileLoadEndStream$) {
72!
83
                this.tileLoadEndStream$.complete();
72✔
84
                this.tileStopStream$.complete();
72✔
85
                this.imageLoadEndStream$.complete();
72✔
86
                this.imageStopStream$.complete();
72✔
87
            }
88
            // detached layers are layers that do not attach directly to the map
89
            // they have their own lifecycle methods instead (e.g. remove)
90
            if (this.layer.detached) {
72!
91
                this.layer.remove();
×
92
            } else {
93
                this.props.map.removeLayer(this.layer);
72✔
94
            }
95
        }
96
        if (this.refreshTimer) {
73!
97
            clearInterval(this.refreshTimer);
×
98
        }
99
        Layers.removeLayer(this.props.type, this.props.options, this.props.map, this.props.mapId, this.layer);
73✔
100
    }
101

102
    render() {
103
        if (this.props.children) {
364✔
104
            const layer = this.layer;
78✔
105
            const children = layer ? React.Children.map(this.props.children, child => {
78✔
106
                return child ? React.cloneElement(child, {container: layer, styleName: this.props.options && this.props.options.styleName}) : null;
19!
107
            }) : null;
108
            return (
78✔
109
                <React.Fragment>
110
                    {children}
111
                </React.Fragment>
112
            );
113
        }
114

115
        return Layers.renderLayer(this.props.type, this.props.options, this.props.map, this.props.mapId, this.layer);
286✔
116
    }
117

118
    setLayerVisibility = (newProps) => {
166✔
119
        const oldVisibility = this.props.options && this.props.options.visibility !== false;
36✔
120
        const newVisibility = newProps.options && newProps.options.visibility !== false;
36✔
121
        if (newVisibility !== oldVisibility && this.layer && this.isValid()) {
36!
122
            this.layer.setVisible(newVisibility);
×
123
        }
124
    };
125

126
    setLayerOpacity = (opacity) => {
166✔
127
        var oldOpacity = this.props.options && this.props.options.opacity !== undefined ? this.props.options.opacity : 1.0;
36✔
128
        if (opacity !== oldOpacity && this.layer) {
36✔
129
            this.layer.setOpacity(opacity);
1✔
130
        }
131
    };
132

133
    generateOpts = (layerOptions, position, srs, securityToken, env, resolutions) => {
166✔
134
        const {
135
            minResolution,
136
            maxResolution,
137
            disableResolutionLimits,
138
            ...otherOptions
139
        } = layerOptions;
214✔
140
        const options = {
214✔
141
            ...otherOptions,
142
            ...(!disableResolutionLimits && {
427✔
143
                minResolution,
144
                maxResolution,
145
                // google layer
146
                minZoom: !isNil(maxResolution) ? getZoomFromResolution(maxResolution, resolutions) : undefined,
213✔
147
                maxZoom: !isNil(minResolution) ? getZoomFromResolution(minResolution, resolutions) : undefined
213✔
148
            })
149
        };
150
        return Object.assign({}, options, isNumber(position) ? {zIndex: position} : null, {
214✔
151
            srs,
152
            onError: () => {
153
                this.props.onCreationError(options);
1✔
154
            },
155
            securityToken,
156
            env
157
        });
158
    };
159

160
    createLayer = (type, options, position, securityToken, env, resolutions) => {
166✔
161
        if (type) {
166✔
162
            const layerOptions = this.generateOpts(options, position, normalizeSRS(this.props.srs), securityToken, env, resolutions);
162✔
163
            this.layer = Layers.createLayer(type, layerOptions, this.props.map, this.props.mapId);
162✔
164
            const compatible = Layers.isCompatible(type, layerOptions);
162✔
165
            // detached layers are layers that do not attach directly to the map
166
            // for this reason addLayer is not called on them
167
            if (this.layer && !this.layer.detached) {
162✔
168
                const parentMap = this.props.map;
153✔
169
                const mapExtent = parentMap && parentMap.getView().getProjection().getExtent();
153✔
170
                const layerExtent = options && options.bbox && options.bbox.bounds;
153✔
171
                const mapBboxPolygon = mapExtent && reprojectBbox(mapExtent, this.props.srs, 'EPSG:4326');
153✔
172
                let layerBboxPolygon = layerExtent && reprojectBbox(
153✔
173
                    getExtentFromNormalized(layerExtent, this.props.srs).extent,
174
                    'EPSG:4326'
175
                );
176
                if (layerBboxPolygon && layerBboxPolygon.length === 2 && isArray(layerBboxPolygon[1])) {
153!
177
                    layerBboxPolygon = layerBboxPolygon[1];
×
178
                }
179

180
                if (mapBboxPolygon && layerBboxPolygon &&
153!
181
                    !isBboxCompatible(getPolygonFromExtent(mapBboxPolygon), getPolygonFromExtent(layerBboxPolygon)) ||
182
                    !compatible) {
183
                    this.props.onWarning({
×
184
                        title: "warning",
185
                        message: "notification.incompatibleDataAndProjection",
186
                        action: {
187
                            label: "close"
188
                        },
189
                        position: "tc",
190
                        uid: "1"
191
                    });
192
                }
193
                this.addLayer(options);
153✔
194
            }
195

196
            this.forceUpdate();
162✔
197
        }
198
    };
199

200
    updateLayer = (newProps, oldProps) => {
166✔
201
        // optimization to avoid to update the layer if not necessary
202
        if (newProps.position === oldProps.position && newProps.srs === oldProps.srs && newProps.securityToken === oldProps.securityToken ) {
36✔
203
            // check if options are the same, except loading
204
            if (newProps.options === oldProps.options) return;
31✔
205
            if (isEqual( omit(newProps.options, ["loading"]), omit(oldProps.options, ["loading"]) ) ) {
21!
206
                return;
×
207
            }
208
        }
209
        const newLayer = Layers.updateLayer(
26✔
210
            this.props.type,
211
            this.layer,
212
            this.generateOpts(newProps.options, newProps.position, newProps.projection, newProps.securityToken, newProps.env, newProps.resolutions),
213
            this.generateOpts(oldProps.options, oldProps.position, oldProps.projection, oldProps.securityToken, oldProps.env, oldProps.resolutions),
214
            this.props.map,
215
            this.props.mapId);
216
        if (newLayer) {
26✔
217
            // detached layers are layers that do not attach directly to the map
218
            // for this reason addLayer /removeLayer should not be called on them
219
            if (!newLayer.detached) {
10!
220
                this.props.map.removeLayer(this.layer);
10✔
221
                this.layer = newLayer;
10✔
222
                this.addLayer(newProps.options);
10✔
223
            } else {
224
                this.layer = newLayer;
×
225
            }
226
        }
227
    };
228

229
    addLayer = (options) => {
166✔
230
        if (this.isValid()) {
163!
231
            this.props.map.addLayer(this.layer);
163✔
232

233
            const tileLoadEndStream$ = new Rx.Subject();
163✔
234
            const tileStopStream$ = new Rx.Subject();
163✔
235

236
            if (options.handleClickOnLayer) {
163✔
237
                this.layer.set("handleClickOnLayer", true);
27✔
238
            }
239
            this.layer.getSource().on('tileloadstart', () => {
163✔
240
                if (this.tilestoload === 0) {
54✔
241
                    this.props.onLayerLoading(options.id);
9✔
242
                    this.tilestoload++;
9✔
243
                } else {
244
                    this.tilestoload++;
45✔
245
                }
246
            });
247
            this.layer.getSource().on('tileloadend', () => {
163✔
248
                tileLoadEndStream$.next({type: 'tileloadend'});
5✔
249
                this.tilestoload--;
5✔
250
                if (this.tilestoload === 0) {
5✔
251
                    tileStopStream$.next();
2✔
252
                }
253
            });
254
            this.layer.getSource().on('tileloaderror', (event) => {
163✔
255
                tileLoadEndStream$.next({type: 'tileloaderror', event});
48✔
256
                this.tilestoload--;
48✔
257
                if (this.tilestoload === 0) {
48✔
258
                    tileStopStream$.next();
6✔
259
                }
260
            });
261

262
            tileLoadEndStream$
163✔
263
                .bufferWhen(() => tileStopStream$)
166✔
264
                .subscribe({
265
                    next: (tileEvents) => {
266
                        const errors = tileEvents.filter(e => e.type === 'tileloaderror');
75✔
267
                        if (errors.length > 0 && (options && !options.hideErrors || !options)) {
75!
268
                            this.props.onLayerLoad(options.id, {error: true});
4✔
269
                            this.props.onLayerError(options.id, tileEvents.length, errors.length);
4✔
270
                        } else {
271
                            this.props.onLayerLoad(options.id);
71✔
272
                        }
273
                    }
274
                });
275

276
            this.tileLoadEndStream$ = tileLoadEndStream$;
163✔
277
            this.tileStopStream$ = tileStopStream$;
163✔
278

279
            const imageLoadEndStream$ = new Rx.Subject();
163✔
280
            const imageStopStream$ = new Rx.Subject();
163✔
281

282
            this.layer.getSource().on('imageloadstart', () => {
163✔
283
                if (this.imagestoload === 0) {
5✔
284
                    this.props.onLayerLoading(options.id);
4✔
285
                    this.imagestoload++;
4✔
286
                } else {
287
                    this.imagestoload++;
1✔
288
                }
289
            });
290
            this.layer.getSource().on('imageloadend', () => {
163✔
291
                this.imagestoload--;
×
292
                imageLoadEndStream$.next({type: 'imageloadend'});
×
293
                if (this.imagestoload === 0) {
×
294
                    imageStopStream$.next();
×
295
                }
296
            });
297
            this.layer.getSource().on('imageloaderror', (event) => {
163✔
298
                this.imagestoload--;
2✔
299
                imageLoadEndStream$.next({type: 'imageloaderror', event});
2✔
300
                if (this.imagestoload === 0) {
2!
301
                    imageStopStream$.next();
2✔
302
                }
303
            });
304

305
            imageLoadEndStream$
163✔
306
                .bufferWhen(() => imageStopStream$)
165✔
307
                .subscribe({
308
                    next: (imageEvents) => {
309
                        const errors = imageEvents.filter(e => e.type === 'imageloaderror');
74✔
310
                        if (errors.length > 0) {
74✔
311
                            this.props.onLayerLoad(options.id, {error: true});
2✔
312
                            if (options && !options.hideErrors || !options) {
2!
313
                                this.props.onLayerError(options.id, imageEvents.length, errors.length);
2✔
314
                            }
315
                        } else {
316
                            this.props.onLayerLoad(options.id);
72✔
317
                        }
318
                    }
319
                });
320

321
            this.imageLoadEndStream$ = imageLoadEndStream$;
163✔
322
            this.imageStopStream$ = imageStopStream$;
163✔
323

324
            this.layer.getSource().on('vectorerror', () => {
163✔
325
                this.props.onLayerLoad(options.id, {error: true});
1✔
326
            });
327

328
            if (options.refresh) {
163!
329
                let counter = 0;
×
330
                this.refreshTimer = setInterval(() => {
×
331
                    this.layer.getSource().updateParams(Object.assign({}, options.params, {_refreshCounter: counter++}));
×
332
                }, options.refresh);
333
            }
334
        }
335
    };
336

337
    isValid = () => {
166✔
338
        const valid = Layers.isValid(this.props.type, this.layer);
163✔
339
        this.valid = valid;
163✔
340
        return valid;
163✔
341
    };
342
}
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

© 2025 Coveralls, Inc