Skip to content

Both copy text to your clipboard — Build with AI copies a setup prompt to paste into Claude Code, Cursor, Codex or Copilot; Copy page as Markdown copies this page to paste into a chat. How it works

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.