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

geosolutions-it / MapStore2 / 15715217843

17 Jun 2025 06:25PM UTC coverage: 76.96% (-0.007%) from 76.967%
15715217843

Pull #11219

github

web-flow
Merge 91b01831f into 54f0948be
Pull Request #11219: Remove usage of object-assign

31112 of 48419 branches covered (64.26%)

39 of 53 new or added lines in 27 files covered. (73.58%)

5 existing lines in 1 file now uncovered.

38714 of 50304 relevant lines covered (76.96%)

36.23 hits per line

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

78.14
/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
} from '../../../utils/MapUtils';
23
import Rx from 'rxjs';
24

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

28
import './SingleClick';
29

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

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

92
    state = { };
40✔
93

94
    UNSAFE_componentWillMount() {
95
        this.zoomOffset = 0;
40✔
96
        if (this.props.mapOptions && this.props.mapOptions.view && this.props.mapOptions.view.resolutions && this.props.mapOptions.view.resolutions.length > 0) {
40!
97
            const scaleFun = L.CRS.EPSG3857.scale;
×
98
            const ratio = this.props.mapOptions.view.resolutions[0] / getGoogleMercatorResolutions(0, 23)[0];
×
NEW
99
            this.crs = Object.assign({}, L.CRS.EPSG3857, {
×
100
                scale: (zoom) => {
101
                    return scaleFun.call(L.CRS.EPSG3857, zoom) / Math.pow(2, Math.round(Math.log2(ratio)));
×
102
                }
103
            });
104
            this.zoomOffset = Math.round(Math.log2(ratio));
×
105
        }
106
    }
107
    componentDidMount() {
108
        const {limits = {}} = this.props;
40✔
109
        const maxBounds = limits.restrictedExtent && limits.crs && reprojectBbox(limits.restrictedExtent, limits.crs, "EPSG:4326");
40!
110
        let mapOptions = Object.assign({}, this.props.interactive ? {} : {
40!
111
            dragging: false,
112
            touchZoom: false,
113
            scrollWheelZoom: false,
114
            doubleClickZoom: false,
115
            boxZoom: false,
116
            tap: false,
117
            attributionControl: false,
118
            maxBounds: maxBounds && L.latLngBounds([
×
119
                [maxBounds[1], maxBounds[0]],
120
                [maxBounds[3], maxBounds[2]]
121
            ]),
122
            maxBoundsViscosity: maxBounds && 1.0,
×
123
            minZoom: limits && limits.minZoom,
×
124
            maxZoom: limits && limits.maxZoom || 23
×
125
        }, this.props.mapOptions, this.crs ? {crs: this.crs} : {});
40!
126
        // it is not possible to use #<id> in a query selector if the id starts with a number
127
        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],
40✔
128
            Math.round(this.props.zoom));
129

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

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

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

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

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

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

216
                this.addLayerObservable(event, true);
5✔
217

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

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

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

232
                });
233

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

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

253
        this.drawControl = null;
40✔
254
    }
255

256
    UNSAFE_componentWillReceiveProps(newProps) {
257

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

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

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

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

326
    getResolutions = () => {
40✔
327
        return this.props.resolutions;
304✔
328
    };
329

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

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

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

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

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

395
    _updateMapPositionFromNewProps = (newProps) => {
40✔
396
        // current implementation will update the map only if the movement
397
        // between 12 decimals in the reference system to avoid rounded value
398
        // changes due to float mathematic operations.
399
        const isNearlyEqual = function(a, b) {
6✔
400
            if (a === undefined || b === undefined) {
13!
401
                return false;
×
402
            }
403
            return a.toFixed(12) - b.toFixed(12) === 0;
13✔
404
        };
405

406
        // getting all centers we need to check
407
        const newCenter = newProps.center;
6✔
408
        const currentCenter = this.props.center;
6✔
409
        const mapCenter = this.map.getCenter();
6✔
410
        // checking if the current props are the same
411
        const propsCentersEqual = isNearlyEqual(newCenter.x, currentCenter.x) &&
6✔
412
                                  isNearlyEqual(newCenter.y, currentCenter.y);
413
        // if props are the same nothing to do, otherwise
414
        // we need to check if the new center is equal to map center
415
        const centerIsNotUpdated = propsCentersEqual ||
6✔
416
                                   isNearlyEqual(newCenter.x, mapCenter.lng) &&
417
                                    isNearlyEqual(newCenter.y, mapCenter.lat);
418

419
        // getting all zoom values we need to check
420
        const newZoom = newProps.zoom;
6✔
421
        const currentZoom = this.props.zoom;
6✔
422
        const mapZoom = this.map.getZoom();
6✔
423
        // checking if the current props are the same
424
        const propsZoomEqual = newZoom === currentZoom;
6✔
425
        // if props are the same nothing to do, otherwise
426
        // we need to check if the new zoom is equal to map zoom
427
        const zoomIsNotUpdated = propsZoomEqual || newZoom === mapZoom;
6✔
428

429
        // do the change at the same time, to avoid glitches
430
        if (!centerIsNotUpdated && !zoomIsNotUpdated) {
6✔
431
            this.map.setView([newProps.center.y, newProps.center.x], Math.round(newProps.zoom));
4✔
432
        } else if (!zoomIsNotUpdated) {
2!
433
            this.map.setZoom(newProps.zoom);
×
434
        } else if (!centerIsNotUpdated) {
2!
435
            this.map.setView([newProps.center.y, newProps.center.x]);
×
436
        }
437
    };
438

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

473
    setMousePointer = (pointer) => {
40✔
474
        if (this.map) {
40!
475
            const mapDiv = this.map.getContainer();
40✔
476
            mapDiv.style.cursor = pointer || 'auto';
40✔
477
        }
478
    };
479

480
    mouseMoveEvent = (event) => {
40✔
481
        let pos = event.latlng.wrap();
3✔
482
        const intersectedFeatures = this.getIntersectedFeatures(this.map, event.latlng);
3✔
483
        this.props.onMouseMove({
3✔
484
            x: pos.lng || 0.0,
3!
485
            y: pos.lat || 0.0,
3!
486
            z: this.getElevation(pos, event.containerPoint),
487
            crs: "EPSG:4326",
488
            pixel: {
489
                x: event.containerPoint.x,
490
                y: event.containerPoint.y
491
            },
492
            latlng: {
493
                lat: event.latlng.lat,
494
                lng: event.latlng.lng,
495
                z: this.getElevation(event.latlng, event.containerPoint)
496
            },
497
            rawPos: [event.latlng.lat, event.latlng.lng],
498
            intersectedFeatures
499
        });
500
    };
501

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

562
    addLayerObservable = (event, stopped) => {
40✔
563

564
        if (!event.layer.layerId
9!
565
        || event.layer && event.layer.options && event.layer.options.msLayer === 'vector') {
566
            return;
×
567
        }
568

569
        if (event && event.layer && event.layer.on && stopped) {
9✔
570

571
            event.layer._ms2LoadingTileCount = 0;
8✔
572

573
            event.layer.layerLoadingStream$ = new Rx.Subject();
8✔
574
            event.layer.layerLoadStream$ = new Rx.Subject();
8✔
575
            event.layer.layerErrorStream$ = new Rx.Subject();
8✔
576
            event.layer.layerErrorStream$
8✔
577
                .bufferToggle(
578
                    event.layer.layerLoadingStream$,
579
                    () => event.layer.layerLoadStream$)
6✔
580
                .subscribe({
581
                    next: errorEvent => {
582
                        const loadingTileCount = event.layer._ms2LoadingTileCount || errorEvent && errorEvent.length || 0;
3✔
583
                        if (errorEvent && errorEvent.length > 0) {
3✔
584
                            this.props.onLayerError(errorEvent[0].target.layerId, loadingTileCount, errorEvent.length);
2✔
585
                        }
586
                        event.layer._ms2LoadingTileCount = 0;
3✔
587
                    }
588
                });
589
        }
590
    };
591
}
592

593
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