How do you call fitBounds() when using leaflet-react?
Asked Answered
N

3

23

I cannot figure out how to call fitBounds() on the Leaflet map.

If I was just using vanilla leaflet, this solution would work perfectly: Zoom to fit all markers in Mapbox or Leaflet

Unfortunately, I am using react-leaflet.

Here is the solution if I was just using leaflet by itself.

var leafletMap = new L.featureGroup([marker1, marker2, marker3]);
map.fitBounds(leafletMap.getBounds());

I think this code (my code) this.mapRef.current.leafletElement is equivalent to var leafletMap = new L.featureGroup([marker1, marker2, marker3]); leafletMap.getBounds();, but what is map.fitBounds(); equivalent to in react-leaflet?

Basically, I am trying to display multiple markers on the map and have the view adjust accordingly (zoom in, zoom out, fly to, etc.).

Here is my code.

import React, { createRef, Component } from 'react'
import { Map, TileLayer, Marker, Popup, FeatureGroup } from 'react-leaflet'

export default class MasterLeafletMap extends Component {
  constructor(props) {
    super(props);
    this.markers = this.markers.bind(this);
    this.handleClick = this.handleClick.bind(this);
    this.mapRef = createRef()
  }

  handleClick() {
    const leafletMap = this.mapRef.current.leafletElement;
    this.mapRef.current.fitBounds(leafletMap.getBounds()); // Doesn't work
    leafletMap.fitBounds(leafletMap.getBounds()); // Doesn't work (just trying to get the bounds of the markers that are there and adjust the view)
    this.mapRef.current.leafletElement.flyToBounds(leafletMap.getBounds()); // Doesn't work
  }
  markers() {
    if (this.props.search.items instanceof Array) {
      return this.props.search.items.map(function(object, i) {
        const position = [object._geoloc.lat, object._geoloc.lng];
        return <Marker position={position}>
          <Popup>
            <span>
              <h4>{object.title}</h4>
              {object.address}, <br /> {object.city}, {object.state}, {object.zip} <br /> {object._geoloc.lat}, {object._geoloc.lng}
            </span>
          </Popup>
        </Marker>
      })
    }

  }
  render() {
    const hasLoaded = this.props.search.items instanceof Array;
    if (!hasLoaded) {
      return null;
    }

    const position = [this.props.search.items[0]._geoloc.lat, this.props.search.items[0]._geoloc.lng];

    return (
      <div className="leaflet-map-container">
        <div onClick={this.handleClick}>Hello</div>
        <Map center={position} zoom={13} ref={this.mapRef}>
          <TileLayer
            attribution="&amp;copy <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors"
            url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
          />
          <FeatureGroup>
            {this.markers()}
          </FeatureGroup>
        </Map>
      </div>
    )
  }
}

Thanks in advance.

Nebulosity answered 14/6, 2018 at 16:23 Comment(1)
I am trying to understand your example. When you call map.fitBounds() you should provide the bounds you want to fit the map into. Since you try to fitBounds on the current bounds nothing will happen I guess? Can you try to fitBounds on al the LatLng values of your markers array? (I think the markers array === this.props.search.items?Tungstate
V
13

Here is an example how to accomplish it via react-leaflet

handleClick() {
    const map = this.mapRef.current.leafletElement;  //get native Map instance
    const group = this.groupRef.current.leafletElement; //get native featureGroup instance
    map.fitBounds(group.getBounds());
}

where

<div>
    <button onClick={this.handleClick}>Zoom</button>
    <Map
      center={this.props.center}
      zoom={this.props.zoom}
      ref={this.mapRef}
    >
      <TileLayer
        attribution='&amp;copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <FeatureGroup ref={this.groupRef}>
        {this.props.locations.map(location => (
          <Marker
            key={location.name}
            position={{ lat: location.lat, lng: location.lng }}
          >
            <Popup>
              <span>
                <h4>{location.name}</h4>
              </span>
            </Popup>
          </Marker>
        ))}
      </FeatureGroup>
    </Map>
 </div>

which corresponds to

var leafletMap = new L.featureGroup([marker1, marker2, marker3]);
map.fitBounds(leafletMap.getBounds());

Here is a demo

Vashtee answered 25/9, 2019 at 9:12 Comment(3)
is there any way to do it without using refPresent
You can replace this.mapRef.current.leafletElement with useMap() to replace one of the refs with a hook. This is a bit cleaner: react-leaflet.js.org/docs/api-map/#usemap. Also I am using React hooks and found getBounds defined on current rather than leafletElement such that map.fitBounds(groupRef.current.getBounds()) works.Naxos
I am using const map = useMap(); this seems to work and zoom to the correct lat and lng value.Knock
O
9

If you want this behavior on map load, you can use the onlayeradd event.

Example:

const fitToCustomLayer = () => {
    if (mapRef.current && customAreaRef.current) { //we will get inside just once when loading
        const map = mapRef.current.leafletElement
        const layer = customAreaRef.current.leafletElement
        map.fitBounds(layer.getBounds().pad(0.5))
    }
}

return (
<Map ref={mapRef} onlayeradd={fitToCustomLayer}>
    <LayersControl position="topright">
        <Overlay checked key="customArea" name="Área de interesse">
            <GeoJSON ref={customAreaRef} data={collection} style={geoJSONStyle} />
        </Overlay>
        <BaseLayer name="Open Street Map">
            <TileLayer attribution='&amp;copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors' 
 url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"/>
        </BaseLayer>            
    </LayersControl>
</Map>
)

*Edit: If the ref layer is the last one, this doens't seems to work. Putting it before the base layers doesn't affect the rendering and it works.

If you need to use the onlayeradd for it's rightful purpose, make sure to add another flag to avoid getting inside the if and firing fitBounds again and lose map's current position.

Ps: I tried the onload method of react-leaflet, but it doesn't seems to work.

Obliquity answered 8/4, 2020 at 23:22 Comment(1)
This way fitBounds can be called multiple times. onlayeradd is called for all layers so for each layer after GeoJSON those refs exist. One possible fix can be replacing GeoJSON ref by useCallback hook.Hylan
S
1

The bounds property of the MapContainer component automatically sets the boundaries of the markers.

To create these markers, for each element of the array, returns an marker with its position set.

The correct thing would be to pass the bounds by props and thus reuse the component. Remember the structure as in the example const bounds: number[][].

The example code is with Typecript but it also works with Javascript just removing the types.

import L from 'leaflet'
import { MapContainer, Marker, Popup, TileLayer } from 'react-leaflet'
import { useEffect } from 'react'
import 'leaflet/dist/leaflet.css'

import iconRetinaUrl from 'leaflet/dist/images/marker-icon-2x.png';
import iconUrl from 'leaflet/dist/images/marker-icon.png';
import shadowUrl from 'leaflet/dist/images/marker-shadow.png';

export default function Map(
    {
        children,
        className = '',
    }: {
        children: React.ReactNode,
        className?: string,
    }
) {
    useEffect(() => {
        delete L.Icon.Default.prototype._getIconUrl
        L.Icon.Default.mergeOptions({
            iconRetinaUrl: iconRetinaUrl.src,
            iconUrl: iconUrl.src,
            shadowUrl: shadowUrl.src,
        })
    }, [])

    const bounds = [
        [18.857724075033246, -97.07290331560448],
        [18.851899271104973, -97.09363134073438],
        [18.91465394724728, -97.02766818860734],
    ]
    return (
        <MapContainer
            bounds={bounds as L.LatLngBoundsExpression}
            className={`w-full ${className}`}
            attributionControl={false}
            zoom={15}
        >
            <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
            {bounds.map((point) => (
                <Marker position={point as L.LatLngExpression}>
                    <Popup>
                        {children}
                    </Popup>
                </Marker>
            ))}
        </MapContainer >
    )
}
Spillway answered 11/12, 2022 at 18:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.