Skip to content

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 km

Total 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.