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

geosolutions-it / MapStore2 / 29571694041

17 Jul 2026 09:55AM UTC coverage: 75.784% (-0.005%) from 75.789%
29571694041

push

github

web-flow
infra: update to use ssh keys instead of password for publishing arti… (#12497)

* infra: update to use ssh keys instead of password for publishing artifacts

* infra: update ssh key name as per the new secrets created

35964 of 56655 branches covered (63.48%)

44742 of 59039 relevant lines covered (75.78%)

123.5 hits per line

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

78.2
/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 {
14
    getGoogleMercatorResolutions,
15
    EXTENT_TO_ZOOM_HOOK,
16
    RESOLUTIONS_HOOK,
17
    COMPUTE_BBOX_HOOK,
18
    GET_PIXEL_FROM_COORDINATES_HOOK,
19
    GET_COORDINATES_FROM_PIXEL_HOOK,
20
    ZOOM_TO_EXTENT_HOOK,
21
    registerHook,
22
    isNearlyEqual
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 = {
3✔
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 = {
3✔
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 = { };
120✔
94

95
    UNSAFE_componentWillMount() {
96
        this.zoomOffset = 0;
120✔
97
        if (this.props.mapOptions && this.props.mapOptions.view && this.props.mapOptions.view.resolutions && this.props.mapOptions.view.resolutions.length > 0) {
120!
98
            const scaleFun = L.CRS.EPSG3857.scale;
×
99
            const ratio = this.props.mapOptions.view.resolutions[0] / getGoogleMercatorResolutions(0, 23)[0];
×
100
            this.crs = Object.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;
120✔
110
        const maxBounds = limits.restrictedExtent && limits.crs && reprojectBbox(limits.restrictedExtent, limits.crs, "EPSG:4326");
120!
111
        let mapOptions = Object.assign({}, this.props.interactive ? {} : {
120!
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([
×
120
                [maxBounds[1], maxBounds[0]],
121
                [maxBounds[3], maxBounds[2]]
122
            ]),
123
            maxBoundsViscosity: maxBounds && 1.0,
×
124
            minZoom: limits && limits.minZoom,
×
125
            maxZoom: limits && limits.maxZoom || 23
×
126
        }, this.props.mapOptions, this.crs ? {crs: this.crs} : {});
120!
127
        // it is not possible to use #<id> in a query selector if the id starts with a number
128
        const map = L.map(this.getDocument().querySelector(`[id='${this.props.id}'] > .map-viewport`), Object.assign({ zoomControl: false }, mapOptions) ).setView([this.props.center.y, this.props.center.x],
120✔
129
            Math.round(this.props.zoom));
130

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

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

151
        this.map.on('moveend', this.updateMapInfoState);
120✔
152
        // this uses the hook defined in ./SingleClick.js for leaflet 0.7.*
153
        this.map.on('singleclick', (event) => {
120✔
154
            if (this.props.onClick) {
9!
155
                const intersectedFeatures = this.getIntersectedFeatures(map, event.latlng);
9✔
156
                this.props.onClick({
9✔
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.getElevation(event.latlng, event.containerPoint)
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);
120✔
178
        this.map.on('dragstart', () => { this.map.off('mousemove', mouseMove); });
120✔
179
        this.map.on('dragend', () => { this.map.on('mousemove', mouseMove); });
120✔
180
        this.map.on('mousemove', mouseMove);
120✔
181
        this.map.on('contextmenu', () => {
120✔
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', () => {
120✔
188
            setTimeout(() => this.props.onMouseOut(), 150);
×
189
        });
190

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

205
            // avoid binding if not possible, e.g. for measurement vector layers
206
            if (!event.layer.layerId) {
63✔
207
                return;
48✔
208
            }
209
            if (event.layer && event.layer.options && event.layer.options.msLayer === 'vector') {
15!
210
                return;
×
211
            }
212

213
            if (event && event.layer && event.layer.on ) {
15!
214
                // TODO check event.layer.on is a function
215
                // Needed to fix GeoJSON Layer neverending loading
216

217
                this.addLayerObservable(event, true);
15✔
218

219
                if (!(event.layer.options && event.layer.options.hideLoading)) {
15✔
220
                    this.props.onLayerLoading(event.layer.layerId);
9✔
221
                    event.layer.layerLoadingStream$.next();
9✔
222
                }
223

224
                event.layer.on('loading', (loadingEvent) => {
15✔
225
                    this.props.onLayerLoading(loadingEvent.target.layerId);
×
226
                    event.layer.layerLoadingStream$.next();
×
227
                });
228

229
                event.layer.on('load', (loadEvent) => {
15✔
230
                    this.props.onLayerLoad(loadEvent.target.layerId);
×
231
                    event.layer.layerLoadStream$.next();
×
232

233
                });
234

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

246
        this.map.on('layerremove', (event) => {
120✔
247
            if (event.layer.layerLoadingStream$) {
×
248
                event.layer.layerLoadingStream$.complete();
×
249
                event.layer.layerLoadStream$.complete();
×
250
                event.layer.layerErrorStream$.complete();
×
251
            }
252
        });
253

254
        this.drawControl = null;
120✔
255
    }
256

257
    UNSAFE_componentWillReceiveProps(newProps) {
258

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

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

312
        if (this.mapZoomControl) {
120✔
313
            this.map.removeControl(this.mapZoomControl);
108✔
314
            this.mapZoomControl = undefined;
108✔
315
        }
316
        // remove all events
317
        this.map.off();
120✔
318
        // remove map and set to undefined for setTimeout for .invalidateSize action
319
        this.map.remove();
120✔
320
        this.map = undefined;
120✔
321
    }
322

323
    getDocument = () => {
120✔
324
        return this.props.document || document;
360✔
325
    };
326

327
    getResolutions = () => {
120✔
328
        return this.props.resolutions;
926✔
329
    };
330

331
    getIntersectedFeatures = (map, latlng) => {
120✔
332
        let groupIntersectedFeatures = {};
18✔
333
        const clickBounds = L.latLngBounds(latlng, latlng);
18✔
334
        map.eachLayer((layer) => {
18✔
335
            if (layer?.layerId && layer?.eachLayer) {
18✔
336
                layer.eachLayer(feature => {
3✔
337

338
                    const centerBounds = feature?.getLatLng
3!
339
                        ? L.latLngBounds(feature.getLatLng(), feature.getLatLng())
340
                        : null;
341
                    const bounds = feature?.getBounds
3!
342
                        ? feature.getBounds()
343
                        : centerBounds;
344

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

359
    getElevation(pos, containerPoint) {
360
        const elevationLayers = this.map.msElevationLayers || [];
27✔
361
        return elevationLayers?.[0]?.getElevation
27✔
362
            ? elevationLayers[0].getElevation(pos, containerPoint)
363
            : undefined;
364
    }
365

366
    render() {
367
        const map = this.map;
258✔
368
        const mapProj = this.props.projection;
258✔
369
        const children = map ? React.Children.map(this.props.children, child => {
258✔
370
            return child ? React.cloneElement(child, {
54✔
371
                map: map,
372
                projection: mapProj,
373
                zoomOffset: this.zoomOffset,
374
                onCreationError: this.props.onCreationError,
375
                onClick: this.props.onClick,
376
                resolutions: this.getResolutions(),
377
                zoom: this.props.zoom
378
            }) : null;
379
        }) : null;
380
        return (
258✔
381
            <div id={this.props.id} style={this.props.style}>
382
                <div
383
                    className="map-viewport"
384
                    style={{
385
                        position: 'relative',
386
                        overflow: 'hidden',
387
                        width: '100%',
388
                        height: '100%'
389
                    }}
390
                ></div>
391
                {children}
392
            </div>
393
        );
394
    }
395

396
    _updateMapPositionFromNewProps = (newProps) => {
120✔
397

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

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

421
        // do the change at the same time, to avoid glitches
422
        if (!centerIsNotUpdated && !zoomIsNotUpdated) {
18✔
423
            this.map.setView([newProps.center.y, newProps.center.x], Math.round(newProps.zoom));
12✔
424
        } else if (!zoomIsNotUpdated) {
6!
425
            this.map.setZoom(newProps.zoom);
×
426
        } else if (!centerIsNotUpdated) {
6!
427
            this.map.setView([newProps.center.y, newProps.center.x]);
×
428
        }
429
    };
430

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

465
    setMousePointer = (pointer) => {
120✔
466
        if (this.map) {
120!
467
            const mapDiv = this.map.getContainer();
120✔
468
            mapDiv.style.cursor = pointer || 'auto';
120✔
469
        }
470
    };
471

472
    mouseMoveEvent = (event) => {
120✔
473
        let pos = event.latlng.wrap();
9✔
474
        const intersectedFeatures = this.getIntersectedFeatures(this.map, event.latlng);
9✔
475
        this.props.onMouseMove({
9✔
476
            x: pos.lng || 0.0,
9!
477
            y: pos.lat || 0.0,
9!
478
            z: this.getElevation(pos, event.containerPoint),
479
            crs: "EPSG:4326",
480
            pixel: {
481
                x: event.containerPoint.x,
482
                y: event.containerPoint.y
483
            },
484
            latlng: {
485
                lat: event.latlng.lat,
486
                lng: event.latlng.lng,
487
                z: this.getElevation(event.latlng, event.containerPoint)
488
            },
489
            rawPos: [event.latlng.lat, event.latlng.lng],
490
            intersectedFeatures
491
        });
492
    };
493

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

554
    addLayerObservable = (event, stopped) => {
120✔
555

556
        if (!event.layer.layerId
27!
557
        || event.layer && event.layer.options && event.layer.options.msLayer === 'vector') {
558
            return;
×
559
        }
560

561
        if (event && event.layer && event.layer.on && stopped) {
27✔
562

563
            event.layer._ms2LoadingTileCount = 0;
24✔
564

565
            event.layer.layerLoadingStream$ = new Rx.Subject();
24✔
566
            event.layer.layerLoadStream$ = new Rx.Subject();
24✔
567
            event.layer.layerErrorStream$ = new Rx.Subject();
24✔
568
            event.layer.layerErrorStream$
24✔
569
                .bufferToggle(
570
                    event.layer.layerLoadingStream$,
571
                    () => event.layer.layerLoadStream$)
18✔
572
                .subscribe({
573
                    next: errorEvent => {
574
                        const loadingTileCount = event.layer._ms2LoadingTileCount || errorEvent && errorEvent.length || 0;
9✔
575
                        if (errorEvent && errorEvent.length > 0) {
9✔
576
                            this.props.onLayerError(errorEvent[0].target.layerId, loadingTileCount, errorEvent.length);
6✔
577
                        }
578
                        event.layer._ms2LoadingTileCount = 0;
9✔
579
                    }
580
                });
581
        }
582
    };
583
}
584

585
export default LeafletMap;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc