GoToCoordinates component for React Leaflet
React Leaflet is a React library that exposes Leaflet classes as React components, making it very easy to add interactive maps to React web apps.
This is the fourth in a series of posts on how I use the library. My intention is to share back some of the things I’ve learned and implemented in the hope of them being useful to others.
In this post, I present the GoToCoordinates
component which accepts a pair of
latitude and longitude coordinates and centers the map, drawing a marker on the
exact point. You can see it in action on vouna.gr.
First, the component:
import React from 'react';
import PropTypes from 'prop-types';
import { useMap } from 'react-leaflet';
const MAP_BOUNDS = [[31.08, 14.26], [44.91, 34.69]]; // Greece
const MIN_ZOOM = 13;
const PLACEHOLDER = '37.984167, 23.728056'; // Athens, center
const COORDINATES_REGEX =
/^\s*-?(90|[1-8]?[0-9])(\.\d+)?\s*°?(\s*,\s*|\s+)-?(180|1[0-7][0-9]|[1-9]?[0-9])(\.\d+)?\s*°?\s*$/;
function coordinatesWithinBounds(latitude, longitude) {
const [[minLat, minLon], [maxLat, maxLon]] = MAP_BOUNDS;
return (
latitude >= minLat &&
latitude <= maxLat &&
longitude >= minLon &&
longitude <= maxLon
);
}
function GoToCoordinates(props) {
const { setPointCoordinates, clearPointCoordinates } = props;
const [coordinates, setCoordinates] = React.useState('');
const map = useMap();
function handleCoordinatesChange(event) {
const {
target: { value },
} = event;
setCoordinates(value);
if (value.length === 0 || !COORDINATES_REGEX.test(value)) { clearPointCoordinates(); return; }
const [latitudeStr, longitudeStr] = value.split(/\s*,\s*/);
const latitude = parseFloat(latitudeStr, 10);
const longitude = parseFloat(longitudeStr, 10);
if (!coordinatesWithinBounds(latitude, longitude)) { clearPointCoordinates(); return; }
setPointCoordinates([latitude, longitude]);
const zoom = Math.max(MIN_ZOOM, map.getZoom());
map.setView([latitude, longitude], zoom);
}
function handleClearChange() {
setCoordinates('');
clearPointCoordinates();
}
return (
<div className="leaflet-control">
<input
type="text"
size="15"
title="Go to coordinates"
placeholder={PLACEHOLDER}
value={coordinates}
onChange={handleCoordinatesChange}
/>
{coordinates.length > 0 && ( <button onClick={handleClearChange} title="Καθαρισμός"> × </button> )} </div>
);
}
GoToCoordinates.propTypes = {
setPointCoordinates: PropTypes.func.isRequired,
clearPointCoordinates: PropTypes.func.isRequired,
};
export default GoToCoordinates;
Things to note:
- Lines 36-39: If text is empty or not a valid coordinate pair, the marker is removed
- Lines 45-48: If text is a valid coordinate pair but not within the desired bounds (in this example, Greece), the marker is removed
- Lines 71-75: If text is present, a clear “x” button is shown that empties the text field and removes the marker when clicked
Then, the necessary styles for the clear “x” button:
.leaflet-control.go-to-coordinates button {
border: none;
background: transparent;
display: inline-block;
margin-left: -30px;
width: 30px;
text-align: center;
font-size: 1.5em;
cursor: pointer;
opacity: 50%;
}
And finally, here’s how you’d use it in a map:
import React from 'react';
import { Marker } from 'react-leaflet';
import GoToCoordinates from './GoToCoordinates';
const MAP_BOUNDS = [[31.08, 14.26], [44.91, 34.69]]; // Greece
function round(number, precision = 0) {
return (
Math.round(number * Math.pow(10, precision) + Number.EPSILON) /
Math.pow(10, precision)
);
}
function formatLatitude(latitude) {
const direction = latitude > 0 ? 'N' : 'S';
return `${round(Math.abs(latitude), 6)}° ${direction}`;
}
function formatLongitude(longitude) {
const direction = longitude > 0 ? 'E' : 'W';
return `${round(Math.abs(longitude), 6)}° ${direction}`;
}
function MyMap() {
const [goToPoint, setGoToPoint] = React.useState(null);
function clearPointCoordinates() {
setGoToPoint(null);
}
return (
<MapContainer
bounds={MAP_BOUNDS}
style={{ width: '100%', height: '100vh' }}
>
<TileLayer
url="https://{s}.tile.osm.org/{z}/{x}/{y}.png"
attribution='© <a href="https://osm.org/copyright">OpenStreetMap</a> contributors'
/>
{goToPoint !== null && ( <Marker position={goToPoint}> <Tooltip> {formatLatitude(goToPoint[0])}, {formatLongitude(goToPoint[1])} </Tooltip> </Marker> )} <div className="leaflet-bottom leaflet-left">
<GoToCoordinates setPointCoordinates={setGoToPoint} clearPointCoordinates={clearPointCoordinates} /> </div>
</MapContainer>
);
}
export default MyMap;
Things to note:
- Lines 41-47: The marker is shown only if its coordinates are present. When shown, its tooltip displays its formatted coordinates.
- Lines 49-52: The component in action