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.