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

geosolutions-it / MapStore2 / 4690030586

pending completion
4690030586

push

github

GitHub
backport C044-2022.02.xx -  #9085 changes to the buildConfig calls (#9086) (#9090)

26119 of 40137 branches covered (65.07%)

21 of 21 new or added lines in 2 files covered. (100.0%)

32407 of 41699 relevant lines covered (77.72%)

43.1 hits per line

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

75.66
/web/client/components/map/leaflet/Map.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
import L from 'leaflet';
9
import PropTypes from 'prop-types';
10
import React from 'react';
11
import ConfigUtils from '../../../utils/ConfigUtils';
12
import {reprojectBbox, reproject} from '../../../utils/CoordinatesUtils';
13
import assign from 'object-assign';
14
import {
15
    getGoogleMercatorResolutions,
16
    EXTENT_TO_ZOOM_HOOK,
17
    RESOLUTIONS_HOOK,
18
    COMPUTE_BBOX_HOOK,
19
    GET_PIXEL_FROM_COORDINATES_HOOK,
20
    GET_COORDINATES_FROM_PIXEL_HOOK,
21
    ZOOM_TO_EXTENT_HOOK,
22
    registerHook
23
} from '../../../utils/MapUtils';
24
import Rx from 'rxjs';
25

26
import {throttle} from 'lodash';
27
import 'leaflet/dist/leaflet.css';
28

29
import './SingleClick';
30

31
class LeafletMap extends React.Component {
32
    static propTypes = {
1✔
33
        id: PropTypes.string,
34
        document: PropTypes.object,
35
        center: ConfigUtils.PropTypes.center,
36
        zoom: PropTypes.number.isRequired,
37
        viewerOptions: PropTypes.object,
38
        mapStateSource: ConfigUtils.PropTypes.mapStateSource,
39
        style: PropTypes.object,
40
        projection: PropTypes.string,
41
        onMapViewChanges: PropTypes.func,
42
        onClick: PropTypes.func,
43
        onRightClick: PropTypes.func,
44
        mapOptions: PropTypes.object,
45
        limits: PropTypes.object,
46
        zoomControl: PropTypes.bool,
47
        mousePointer: PropTypes.string,
48
        onMouseMove: PropTypes.func,
49
        onLayerLoading: PropTypes.func,
50
        onLayerLoad: PropTypes.func,
51
        onLayerError: PropTypes.func,
52
        resize: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
53
        measurement: PropTypes.object,
54
        changeMeasurementState: PropTypes.func,
55
        registerHooks: PropTypes.bool,
56
        interactive: PropTypes.bool,
57
        resolutions: PropTypes.array,
58
        hookRegister: PropTypes.object,
59
        onCreationError: PropTypes.func,
60
        onMouseOut: PropTypes.func,
61
        onResolutionsChange: PropTypes.func
62
    };
63

64
    static defaultProps = {
1✔
65
        id: 'map',
66
        onMapViewChanges: () => {},
67
        onCreationError: () => {},
68
        onClick: null,
69
        onMouseMove: () => {},
70
        zoomControl: true,
71
        mapOptions: {
72
            zoomAnimation: true,
73
            attributionControl: false
74
        },
75
        projection: "EPSG:3857",
76
        center: {x: 13, y: 45, crs: "EPSG:4326"},
77
        zoom: 5,
78
        onLayerLoading: () => {},
79
        onLayerLoad: () => {},
80
        onLayerError: () => {},
81
        resize: 0,
82
        registerHooks: true,
83
        hookRegister: {
84
            registerHook
85
        },
86
        style: {},
87
        interactive: true,
88
        resolutions: getGoogleMercatorResolutions(0, 23),
89
        onMouseOut: () => {},
90
        onResolutionsChange: () => {}
91
    };
92

93
    state = { };
64✔
94

95
    UNSAFE_componentWillMount() {
96
        this.zoomOffset = 0;
64✔
97
        if (this.props.mapOptions && this.props.mapOptions.view && this.props.mapOptions.view.resolutions && this.props.mapOptions.view.resolutions.length > 0) {
64!
98
            const scaleFun = L.CRS.EPSG3857.scale;
×
99
            const ratio = this.props.mapOptions.view.resolutions[0] / getGoogleMercatorResolutions(0, 23)[0];
×
100
            this.crs = assign({}, L.CRS.EPSG3857, {
×
101
                scale: (zoom) => {
102
                    return scaleFun.call(L.CRS.EPSG3857, zoom) / Math.pow(2, Math.round(Math.log2(ratio)));
×
103
                }
104
            });
105
            this.zoomOffset = Math.round(Math.log2(ratio));
×
106
        }
107
    }
108
    componentDidMount() {
109
        const {limits = {}} = this.props;
64✔
110
        const maxBounds = limits.restrictedExtent && limits.crs && reprojectBbox(limits.restrictedExtent, limits.crs, "EPSG:4326");
64!
111
        let mapOptions = assign({}, this.props.interactive ? {} : {
64✔
112
            dragging: false,
113
            touchZoom: false,
114
            scrollWheelZoom: false,
115
            doubleClickZoom: false,
116
            boxZoom: false,
117
            tap: false,
118
            attributionControl: false,
119
            maxBounds: maxBounds && L.latLngBounds([
12!
120
                [maxBounds[1], maxBounds[0]],
121
                [maxBounds[3], maxBounds[2]]
122
            ]),
123
            maxBoundsViscosity: maxBounds && 1.0,
12!
124
            minZoom: limits && limits.minZoom,
24✔
125
            maxZoom: limits && limits.maxZoom || 23
36✔
126
        }, this.props.mapOptions, this.crs ? {crs: this.crs} : {});
64!
127

128
        const map = L.map(this.getDocument().getElementById(this.props.id), assign({ zoomControl: false }, mapOptions) ).setView([this.props.center.y, this.props.center.x],
64✔
129
            Math.round(this.props.zoom));
130

131
        this.map = map;
64✔
132
        if (this.props.registerHooks) {
64✔
133
            this.registerHooks();
59✔
134
        }
135
        // store zoomControl in the class to target the right control while add/remove
136
        if (this.props.zoomControl) {
64✔
137
            this.mapZoomControl = L.control.zoom();
48✔
138
            this.map.addControl(this.mapZoomControl);
48✔
139
        }
140

141
        this.attribution = L.control.attribution();
64✔
142
        this.attribution.addTo(this.map);
64✔
143
        const mapDocument = this.getDocument();
64✔
144
        if (this.props.mapOptions.attribution && this.props.mapOptions.attribution.container) {
64✔
145
            mapDocument.querySelector(this.props.mapOptions.attribution.container).appendChild(this.attribution.getContainer());
2✔
146
            if (mapDocument.querySelector('.leaflet-control-container .leaflet-control-attribution')) {
2!
147
                mapDocument.querySelector('.leaflet-control-container .leaflet-control-attribution').parentNode.removeChild(mapDocument.querySelector('.leaflet-control-container .leaflet-control-attribution'));
2✔
148
            }
149
        }
150

151
        this.map.on('moveend', this.updateMapInfoState);
64✔
152
        // this uses the hook defined in ./SingleClick.js for leaflet 0.7.*
153
        this.map.on('singleclick', (event) => {
64✔
154
            if (this.props.onClick) {
3!
155
                const intersectedFeatures = this.getIntersectedFeatures(map, event.latlng);
3✔
156
                this.props.onClick({
3✔
157
                    pixel: {
158
                        x: event.containerPoint.x,
159
                        y: event.containerPoint.y
160
                    },
161
                    latlng: {
162
                        lat: event.latlng.lat,
163
                        lng: event.latlng.lng,
164
                        z: this.elevationLayer && this.elevationLayer.getElevation(event.latlng, event.containerPoint) || undefined
6✔
165
                    },
166
                    rawPos: [event.latlng.lat, event.latlng.lng],
167
                    modifiers: {
168
                        alt: event.originalEvent.altKey,
169
                        ctrl: event.originalEvent.ctrlKey,
170
                        metaKey: event.originalEvent.metaKey, // MAC OS
171
                        shift: event.originalEvent.shiftKey
172
                    },
173
                    intersectedFeatures
174
                });
175
            }
176
        });
177
        const mouseMove = throttle(this.mouseMoveEvent, 100);
64✔
178
        this.map.on('dragstart', () => { this.map.off('mousemove', mouseMove); });
64✔
179
        this.map.on('dragend', () => { this.map.on('mousemove', mouseMove); });
64✔
180
        this.map.on('mousemove', mouseMove);
64✔
181
        this.map.on('contextmenu', () => {
64✔
182
            if (this.props.onRightClick) {
×
183
                this.props.onRightClick(event.containerPoint);
×
184
            }
185
        });
186
        // The timeout is needed to cover the delay we have for the throttled mouseMove event.
187
        this.map.on('mouseout', () => {
64✔
188
            setTimeout(() => this.props.onMouseOut(), 150);
×
189
        });
190

191
        this.updateMapInfoState();
64✔
192
        this.setMousePointer(this.props.mousePointer);
64✔
193
        // NOTE: this re-call render function after div creation to have the map initialized.
194
        this.forceUpdate();
64✔
195
        this.props.onResolutionsChange(this.getResolutions());
64✔
196
        this.map.on('layeradd', (event) => {
64✔
197
            // we want to run init code only the first time a layer is added to the map
198
            if (event.layer._ms2Added) {
73!
199
                const isStopped = event.layer.layerLoadingStream$ && event.layer.layerLoadingStream$.isStopped;
×
200
                this.addLayerObservable(event, isStopped);
×
201
                return;
×
202
            }
203
            event.layer._ms2Added = true;
73✔
204
            if (event.layer.getElevation) {
73✔
205
                this.elevationLayer = event.layer;
3✔
206
            }
207

208
            // avoid binding if not possible, e.g. for measurement vector layers
209
            if (!event.layer.layerId) {
73✔
210
                return;
19✔
211
            }
212
            if (event.layer && event.layer.options && event.layer.options.msLayer === 'vector') {
54!
213
                return;
×
214
            }
215

216
            if (event && event.layer && event.layer.on ) {
54!
217
                // TODO check event.layer.on is a function
218
                // Needed to fix GeoJSON Layer neverending loading
219

220
                this.addLayerObservable(event, true);
54✔
221

222
                if (!(event.layer.options && event.layer.options.hideLoading)) {
54✔
223
                    this.props.onLayerLoading(event.layer.layerId);
52✔
224
                    event.layer.layerLoadingStream$.next();
52✔
225
                }
226

227
                event.layer.on('loading', (loadingEvent) => {
54✔
228
                    this.props.onLayerLoading(loadingEvent.target.layerId);
×
229
                    event.layer.layerLoadingStream$.next();
×
230
                });
231

232
                event.layer.on('load', (loadEvent) => {
54✔
233
                    this.props.onLayerLoad(loadEvent.target.layerId);
×
234
                    event.layer.layerLoadStream$.next();
×
235

236
                });
237

238
                event.layer.on('tileloadstart ', () => { event.layer._ms2LoadingTileCount++; });
54✔
239
                if (event.layer.options && !event.layer.options.hideErrors || !event.layer.options) {
54!
240
                    event.layer.on('tileerror', (errorEvent) => { event.layer.layerErrorStream$.next(errorEvent); });
54✔
241
                }
242
                // WFS data
243
                event.layer.on('loaderror', (error) => {
54✔
244
                    this.props.onLayerError(error.target.layerId);
×
245
                });
246
            }
247
        });
248

249
        this.map.on('layerremove', (event) => {
64✔
250
            if (event.layer.layerLoadingStream$) {
×
251
                event.layer.layerLoadingStream$.complete();
×
252
                event.layer.layerLoadStream$.complete();
×
253
                event.layer.layerErrorStream$.complete();
×
254
            }
255
        });
256

257
        this.drawControl = null;
64✔
258
    }
259

260
    UNSAFE_componentWillReceiveProps(newProps) {
261

262
        if (newProps.mousePointer !== this.props.mousePointer) {
23!
263
            this.setMousePointer(newProps.mousePointer);
×
264
        }
265
        // update the position if the map is not the source of the state change
266
        if (this.map && newProps.mapStateSource !== this.props.id) {
23✔
267
            this._updateMapPositionFromNewProps(newProps);
21✔
268
        }
269
        if (newProps.zoomControl !== this.props.zoomControl) {
23!
270
            if (newProps.zoomControl) {
×
271
                this.mapZoomControl = L.control.zoom();
×
272
                this.map.addControl(this.mapZoomControl);
×
273
            } else if (this.mapZoomControl && !newProps.zoomControl) {
×
274
                this.map.removeControl(this.mapZoomControl);
×
275
                this.mapZoomControl = undefined;
×
276
            }
277
        }
278
        if (newProps.resize !== this.props.resize) {
23✔
279
            setTimeout(() => {
3✔
280
                if (this.map) {
3!
281
                    this.map.invalidateSize(false);
×
282
                }
283
            }, 0);
284
        }
285
        // update map limits
286
        if (this.props.limits !== newProps.limits) {
23✔
287
            const {limits = {}} = newProps;
1!
288
            const {limits: oldLimits} = this.props;
1✔
289
            if (limits.restrictedExtent !== (oldLimits && oldLimits.restrictedExtent)) {
1!
290
                const maxBounds = limits.restrictedExtent && limits.crs && reprojectBbox(limits.restrictedExtent, limits.crs, "EPSG:4326");
×
291
                this.map.setMaxBounds(limits.restrictedExtent && L.latLngBounds([
×
292
                    [maxBounds[1], maxBounds[0]],
293
                    [maxBounds[3], maxBounds[2]]
294
                ]));
295
            }
296
            if (limits.minZoom !== (oldLimits && oldLimits.minZoom)) {
1!
297
                this.map.setMinZoom(limits.minZoom);
×
298
            }
299
            this.props.onResolutionsChange(this.getResolutions());
1✔
300
        }
301
        return false;
23✔
302
    }
303

304
    componentWillUnmount() {
305
        const mapDocument = this.getDocument();
64✔
306
        const attributionContainer = this.props.mapOptions.attribution && this.props.mapOptions.attribution.container && mapDocument.querySelector(this.props.mapOptions.attribution.container);
64✔
307
        if (attributionContainer && this.attribution.getContainer() && attributionContainer.querySelector('.leaflet-control-attribution')) {
64✔
308
            try {
1✔
309
                attributionContainer.removeChild(this.attribution.getContainer());
1✔
310
            } catch (e) {
311
                // do nothing... probably an old configuration
312
            }
313
        }
314

315
        if (this.mapZoomControl) {
64✔
316
            this.map.removeControl(this.mapZoomControl);
48✔
317
            this.mapZoomControl = undefined;
48✔
318
        }
319
        // remove all events
320
        this.map.off();
64✔
321
        // remove map and set to undefined for setTimeout for .invalidateSize action
322
        this.map.remove();
64✔
323
        this.map = undefined;
64✔
324
    }
325

326
    getDocument = () => {
64✔
327
        return this.props.document || document;
192✔
328
    };
329

330
    getResolutions = () => {
64✔
331
        return this.props.resolutions;
431✔
332
    };
333

334
    getIntersectedFeatures = (map, latlng) => {
64✔
335
        let groupIntersectedFeatures = {};
3✔
336
        const clickBounds = L.latLngBounds(latlng, latlng);
3✔
337
        map.eachLayer((layer) => {
3✔
338
            if (layer?.layerId && layer?.eachLayer) {
4✔
339
                layer.eachLayer(feature => {
1✔
340

341
                    const centerBounds = feature?.getLatLng
1!
342
                        ? L.latLngBounds(feature.getLatLng(), feature.getLatLng())
343
                        : null;
344
                    const bounds = feature?.getBounds
1!
345
                        ? feature.getBounds()
346
                        : centerBounds;
347

348
                    if (bounds && feature?.toGeoJSON) {
1!
349
                        if (bounds && clickBounds.intersects(bounds)) {
1!
350
                            const geoJSONFeature = feature.toGeoJSON();
1✔
351
                            groupIntersectedFeatures[layer.layerId] = groupIntersectedFeatures[layer.layerId]
1!
352
                                ? [ ...groupIntersectedFeatures[layer.layerId], geoJSONFeature ]
353
                                : [ geoJSONFeature ];
354
                        }
355
                    }
356
                });
357
            }
358
        });
359
        return Object.keys(groupIntersectedFeatures).map(id => ({ id, features: groupIntersectedFeatures[id] }));
3✔
360
    }
361

362
    render() {
363
        const map = this.map;
149✔
364
        const mapProj = this.props.projection;
149✔
365
        const children = map ? React.Children.map(this.props.children, child => {
149✔
366
            return child ? React.cloneElement(child, {
181✔
367
                map: map,
368
                projection: mapProj,
369
                zoomOffset: this.zoomOffset,
370
                onCreationError: this.props.onCreationError,
371
                onClick: this.props.onClick,
372
                resolutions: this.getResolutions(),
373
                zoom: this.props.zoom
374
            }) : null;
375
        }) : null;
376
        return (
149✔
377
            <div id={this.props.id} style={this.props.style}>
378
                {children}
379
            </div>
380
        );
381
    }
382

383
    _updateMapPositionFromNewProps = (newProps) => {
64✔
384
        // current implementation will update the map only if the movement
385
        // between 12 decimals in the reference system to avoid rounded value
386
        // changes due to float mathematic operations.
387
        const isNearlyEqual = function(a, b) {
21✔
388
            if (a === undefined || b === undefined) {
43!
389
                return false;
×
390
            }
391
            return a.toFixed(12) - b.toFixed(12) === 0;
43✔
392
        };
393

394
        // getting all centers we need to check
395
        const newCenter = newProps.center;
21✔
396
        const currentCenter = this.props.center;
21✔
397
        const mapCenter = this.map.getCenter();
21✔
398
        // checking if the current props are the same
399
        const propsCentersEqual = isNearlyEqual(newCenter.x, currentCenter.x) &&
21✔
400
                                  isNearlyEqual(newCenter.y, currentCenter.y);
401
        // if props are the same nothing to do, otherwise
402
        // we need to check if the new center is equal to map center
403
        const centerIsNotUpdated = propsCentersEqual ||
21✔
404
                                   isNearlyEqual(newCenter.x, mapCenter.lng) &&
405
                                    isNearlyEqual(newCenter.y, mapCenter.lat);
406

407
        // getting all zoom values we need to check
408
        const newZoom = newProps.zoom;
21✔
409
        const currentZoom = this.props.zoom;
21✔
410
        const mapZoom = this.map.getZoom();
21✔
411
        // checking if the current props are the same
412
        const propsZoomEqual = newZoom === currentZoom;
21✔
413
        // if props are the same nothing to do, otherwise
414
        // we need to check if the new zoom is equal to map zoom
415
        const zoomIsNotUpdated = propsZoomEqual || newZoom === mapZoom;
21✔
416

417
        // do the change at the same time, to avoid glitches
418
        if (!centerIsNotUpdated && !zoomIsNotUpdated) {
21✔
419
            this.map.setView([newProps.center.y, newProps.center.x], Math.round(newProps.zoom));
4✔
420
        } else if (!zoomIsNotUpdated) {
17!
421
            this.map.setZoom(newProps.zoom);
×
422
        } else if (!centerIsNotUpdated) {
17!
423
            this.map.setView([newProps.center.y, newProps.center.x]);
×
424
        }
425
    };
426

427
    updateMapInfoState = () => {
64✔
428
        const bbox = this.map.getBounds().toBBoxString().split(',');
74✔
429
        const size = {
74✔
430
            height: this.map.getSize().y,
431
            width: this.map.getSize().x
432
        };
433
        const center = this.map.getCenter();
74✔
434
        const zoom = this.map.getZoom();
74✔
435
        const viewerOptions = this.props.viewerOptions;
74✔
436
        this.props.onMapViewChanges(
74✔
437
            {
438
                x: center.lng,
439
                y: center.lat,
440
                crs: "EPSG:4326"
441
            },
442
            zoom,
443
            {
444
                bounds: {
445
                    minx: parseFloat(bbox[0]),
446
                    miny: parseFloat(bbox[1]),
447
                    maxx: parseFloat(bbox[2]),
448
                    maxy: parseFloat(bbox[3])
449
                },
450
                crs: 'EPSG:4326',
451
                rotation: 0
452
            },
453
            size,
454
            this.props.id,
455
            this.props.projection,
456
            viewerOptions, // viewerOptions
457
            this.getResolutions()[zoom] // resolution
458
        );
459
    };
460

461
    setMousePointer = (pointer) => {
64✔
462
        if (this.map) {
64!
463
            const mapDiv = this.map.getContainer();
64✔
464
            mapDiv.style.cursor = pointer || 'auto';
64✔
465
        }
466
    };
467

468
    mouseMoveEvent = (event) => {
64✔
469
        let pos = event.latlng.wrap();
2✔
470
        this.props.onMouseMove({
2✔
471
            x: pos.lng || 0.0,
2!
472
            y: pos.lat || 0.0,
2!
473
            z: this.elevationLayer && this.elevationLayer.getElevation(pos, event.containerPoint) || undefined,
4✔
474
            crs: "EPSG:4326",
475
            pixel: {
476
                x: event.containerPoint.x,
477
                y: event.containerPoint.x
478
            },
479
            latlng: {
480
                lat: event.latlng.lat,
481
                lng: event.latlng.lng,
482
                z: this.elevationLayer && this.elevationLayer.getElevation(event.latlng, event.containerPoint) || undefined
4✔
483
            },
484
            rawPos: [event.latlng.lat, event.latlng.lng]
485
        });
486
    };
487

488
    registerHooks = () => {
64✔
489
        this.props.hookRegister.registerHook(EXTENT_TO_ZOOM_HOOK, (extent) => {
59✔
490
            var repojectedPointA = reproject([extent[0], extent[1]], this.props.projection, 'EPSG:4326');
×
491
            var repojectedPointB = reproject([extent[2], extent[3]], this.props.projection, 'EPSG:4326');
×
492
            return this.map.getBoundsZoom([[repojectedPointA.y, repojectedPointA.x], [repojectedPointB.y, repojectedPointB.x]]) - 1;
×
493
        });
494
        this.props.hookRegister.registerHook(RESOLUTIONS_HOOK, () => {
59✔
495
            return this.getResolutions();
126✔
496
        });
497
        this.props.hookRegister.registerHook(COMPUTE_BBOX_HOOK, (center, zoom) => {
59✔
498
            let latLngCenter = L.latLng([center.y, center.x]);
1✔
499
            // this call will use map internal size
500
            let topLeftPoint = this.map._getNewPixelOrigin(latLngCenter, zoom);
1✔
501
            let pixelBounds = new L.Bounds(topLeftPoint, topLeftPoint.add(this.map.getSize()));
1✔
502
            let southWest = this.map.unproject(pixelBounds.getBottomLeft(), zoom);
1✔
503
            let northEast = this.map.unproject(pixelBounds.getTopRight(), zoom);
1✔
504
            let bbox = new L.LatLngBounds(southWest, northEast).toBBoxString().split(',');
1✔
505
            return {
1✔
506
                bounds: {
507
                    minx: parseFloat(bbox[0]),
508
                    miny: parseFloat(bbox[1]),
509
                    maxx: parseFloat(bbox[2]),
510
                    maxy: parseFloat(bbox[3])
511
                },
512
                crs: 'EPSG:4326',
513
                rotation: 0
514
            };
515
        });
516
        this.props.hookRegister.registerHook(GET_PIXEL_FROM_COORDINATES_HOOK, (pos) => {
59✔
517
            let latLng = reproject(pos, this.props.projection, 'EPSG:4326');
×
518
            let pixel = this.map.latLngToContainerPoint([latLng.y, latLng.x]);
×
519
            return [pixel.x, pixel.y];
×
520
        });
521
        this.props.hookRegister.registerHook(GET_COORDINATES_FROM_PIXEL_HOOK, (pixel) => {
59✔
522
            const point = this.map.containerPointToLatLng(pixel);
×
523
            let pos = reproject([point.lng, point.lat], 'EPSG:4326', this.props.projection);
×
524
            return [pos.x, pos.y];
×
525
        });
526
        this.props.hookRegister.registerHook(ZOOM_TO_EXTENT_HOOK, (extent, { padding, crs, maxZoom, duration } = {}) => {
59!
527
            const paddingTopLeft = padding && L.point(padding.left || 0, padding.top || 0);
2!
528
            const paddingBottomRight = padding && L.point(padding.right || 0, padding.bottom || 0);
2!
529
            const bounds = reprojectBbox(extent, crs, 'EPSG:4326');
2✔
530
            // bbox is minx, miny, maxx, maxy'southwest_lng,southwest_lat,northeast_lng,northeast_lat'
531
            this.map.fitBounds([
2✔
532
                // sw
533
                [bounds[1], bounds[0]],
534
                // ne
535
                [bounds[3], bounds[2]]
536
            ],
537
            {
538
                paddingTopLeft,
539
                paddingBottomRight,
540
                maxZoom,
541
                duration,
542
                animate: duration === 0 ? false : undefined
2✔
543
            }
544
            );
545
        });
546
    };
547

548
    addLayerObservable = (event, stopped) => {
64✔
549

550
        if (!event.layer.layerId
58!
551
        || event.layer && event.layer.options && event.layer.options.msLayer === 'vector') {
552
            return;
×
553
        }
554

555
        if (event && event.layer && event.layer.on && stopped) {
58✔
556

557
            event.layer._ms2LoadingTileCount = 0;
57✔
558

559
            event.layer.layerLoadingStream$ = new Rx.Subject();
57✔
560
            event.layer.layerLoadStream$ = new Rx.Subject();
57✔
561
            event.layer.layerErrorStream$ = new Rx.Subject();
57✔
562
            event.layer.layerErrorStream$
57✔
563
                .bufferToggle(
564
                    event.layer.layerLoadingStream$,
565
                    () => event.layer.layerLoadStream$)
55✔
566
                .subscribe({
567
                    next: errorEvent => {
568
                        const loadingTileCount = event.layer._ms2LoadingTileCount || errorEvent && errorEvent.length || 0;
3✔
569
                        if (errorEvent && errorEvent.length > 0) {
3✔
570
                            this.props.onLayerError(errorEvent[0].target.layerId, loadingTileCount, errorEvent.length);
2✔
571
                        }
572
                        event.layer._ms2LoadingTileCount = 0;
3✔
573
                    }
574
                });
575
        }
576
    };
577
}
578

579
export default LeafletMap;
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