Measure Distances
Click on the map to place points and calculate the distance between them.
Click on the map to start measuring. Click again to add more points.
Haversine Distance Formula
Calculate the great-circle distance between two coordinates:
javascript
function haversineKm(pointA, pointB) {
const R = 6371; // Earth radius in km
const dLat = (pointB[1] - pointA[1]) * Math.PI / 180;
const dLng = (pointB[0] - pointA[0]) * Math.PI / 180;
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(pointA[1] * Math.PI / 180) *
Math.cos(pointB[1] * Math.PI / 180) *
Math.sin(dLng / 2) * Math.sin(dLng / 2);
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}
const distKm = haversineKm([2.35, 48.85], [-0.12, 51.50]);
console.log(distKm.toFixed(2) + ' km'); // ~341 kmTotal Route Distance
javascript
let totalKm = 0;
for (let i = 1; i < points.length; i++) {
totalKm += haversineKm(points[i - 1], points[i]);
}
console.log('Total:', totalKm.toFixed(2), 'km');Complete Example
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link href="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.css" rel="stylesheet" />
<script src="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.js"></script>
<style>#map { height: 500px; width: 100%; cursor: crosshair; }</style>
</head>
<body>
<div id="map"></div>
<div id="info">Click to measure</div>
<button onclick="clear()">Clear</button>
<script>
const map = new mapmetricsgl.Map({ container: 'map', style: '<StyleFile_URL_with_Token>', center: [2.35, 48.85], zoom: 4 });
let points = [];
function haversineKm(a, b) {
const R = 6371, dLat = (b[1]-a[1])*Math.PI/180, dLng = (b[0]-a[0])*Math.PI/180;
const x = Math.sin(dLat/2)**2 + Math.cos(a[1]*Math.PI/180)*Math.cos(b[1]*Math.PI/180)*Math.sin(dLng/2)**2;
return R * 2 * Math.atan2(Math.sqrt(x), Math.sqrt(1-x));
}
map.on('load', () => {
map.addSource('pts', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } });
map.addSource('ln', { type: 'geojson', data: { type: 'Feature', geometry: { type: 'LineString', coordinates: [] } } });
map.addLayer({ id: 'ln', type: 'line', source: 'ln', paint: { 'line-color': '#ef4444', 'line-width': 2 } });
map.addLayer({ id: 'pts', type: 'circle', source: 'pts', paint: { 'circle-radius': 6, 'circle-color': '#ef4444' } });
});
map.on('click', (e) => {
points.push([e.lngLat.lng, e.lngLat.lat]);
map.getSource('pts').setData({ type: 'FeatureCollection', features: points.map(p => ({ type: 'Feature', geometry: { type: 'Point', coordinates: p } })) });
map.getSource('ln').setData({ type: 'Feature', geometry: { type: 'LineString', coordinates: points } });
if (points.length > 1) {
let d = 0;
for (let i = 1; i < points.length; i++) d += haversineKm(points[i-1], points[i]);
document.getElementById('info').textContent = `Distance: ${d.toFixed(2)} km`;
}
});
function clear() { points = []; map.getSource('pts')?.setData({ type: 'FeatureCollection', features: [] }); map.getSource('ln')?.setData({ type: 'Feature', geometry: { type: 'LineString', coordinates: [] } }); }
</script>
</body>
</html>jsx
import React, { useEffect, useRef, useState } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
function haversineKm(a, b) {
const R = 6371, dLat = (b[1]-a[1])*Math.PI/180, dLng = (b[0]-a[0])*Math.PI/180;
const x = Math.sin(dLat/2)**2 + Math.cos(a[1]*Math.PI/180)*Math.cos(b[1]*Math.PI/180)*Math.sin(dLng/2)**2;
return R * 2 * Math.atan2(Math.sqrt(x), Math.sqrt(1-x));
}
const MeasureDistances = () => {
const mapContainer = useRef(null);
const map = useRef(null);
const points = useRef([]);
const [info, setInfo] = useState('Click to measure');
useEffect(() => {
if (map.current) return;
map.current = new mapmetricsgl.Map({ container: mapContainer.current, style: '<StyleFile_URL_with_Token>', center: [2.35, 48.85], zoom: 4 });
map.current.getCanvas().style.cursor = 'crosshair';
map.current.on('load', () => {
map.current.addSource('pts', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } });
map.current.addSource('ln', { type: 'geojson', data: { type: 'Feature', geometry: { type: 'LineString', coordinates: [] } } });
map.current.addLayer({ id: 'ln', type: 'line', source: 'ln', paint: { 'line-color': '#ef4444', 'line-width': 2 } });
map.current.addLayer({ id: 'pts', type: 'circle', source: 'pts', paint: { 'circle-radius': 6, 'circle-color': '#ef4444' } });
});
map.current.on('click', (e) => {
points.current.push([e.lngLat.lng, e.lngLat.lat]);
map.current.getSource('pts')?.setData({ type: 'FeatureCollection', features: points.current.map(p => ({ type: 'Feature', geometry: { type: 'Point', coordinates: p } })) });
map.current.getSource('ln')?.setData({ type: 'Feature', geometry: { type: 'LineString', coordinates: points.current } });
if (points.current.length > 1) {
let d = 0;
for (let i = 1; i < points.current.length; i++) d += haversineKm(points.current[i-1], points.current[i]);
setInfo(`Distance: ${d.toFixed(2)} km`);
}
});
return () => { map.current?.remove(); map.current = null; };
}, []);
const clear = () => {
points.current = [];
map.current?.getSource('pts')?.setData({ type: 'FeatureCollection', features: [] });
map.current?.getSource('ln')?.setData({ type: 'Feature', geometry: { type: 'LineString', coordinates: [] } });
setInfo('Click to measure');
};
return (
<div>
<div ref={mapContainer} style={{ height: '500px', width: '100%' }} />
<p>{info}</p>
<button onClick={clear}>Clear</button>
</div>
);
};
export default MeasureDistances;For more information, visit the MapMetrics GitHub repository.