Reputation: 591
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="&copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors"
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<FeatureGroup>
{this.markers()}
</FeatureGroup>
</Map>
</div>
)
}
}
Thanks in advance.
Upvotes: 23
Views: 18091
Reputation: 448
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 >
)
}
Upvotes: 1
Reputation: 132
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='&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.
Upvotes: 9
Reputation: 59318
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='&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());
Upvotes: 13