Atlaslog
Atlaslog / Examples / Leaflet / Measure
Leaflet · Runnable Example

Measure

Runnable Leaflet example: Measure. Open the live demo, read the complete source, and copy the file into your project. No build step or API key.

Open standalone → Chinese notes All Leaflet examples
/demos/leaflet-measure.htmlLIVE
single HTML file · CDN dependencies onlyinterface may contain Chinese labels

Source

demos/leaflet-measure.html
<!--
  示例:测距
  要点:逐点折线、分段距离工具提示、双击去重收尾、总长与撤销清理
  依赖:Leaflet 1.9.4 · jsDelivr CDN + Esri 瓦片
-->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta name="robots" content="noindex,follow">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>测距</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.css">
<style>
  html,body{height:100%;margin:0}
  body{display:flex;flex-direction:column;overflow:hidden;
       font:13px/1.6 -apple-system,"Segoe UI","PingFang SC",sans-serif}
  .bar{flex:0 0 auto;padding:8px 12px;background:#f6f7f9;border-bottom:1px solid #e3e6ea;
       display:flex;gap:8px;align-items:center;font-size:12px;color:#58606e}
  .bar button{font:inherit;font-size:12px;padding:4px 10px;border-radius:4px;cursor:pointer;
       border:1px solid #ccd1d9;background:#fff;color:#16181d}
  .bar button:hover{background:#eef0f3}
  .bar button:disabled{opacity:.5;cursor:default}
  .bar .spacer{flex:1 1 auto}
  .bar #total{color:#16181d;font-weight:600;font-variant-numeric:tabular-nums}
  #map{flex:1 1 auto;min-height:0;cursor:crosshair}
  .vtx{width:8px;height:8px;border-radius:50%;background:#fff;border:2px solid #0e7490;
       box-sizing:border-box}
  .dist-tip{background:rgba(15,23,42,.86);border:0;border-radius:3px;color:#fff;
       font-size:11px;padding:1px 5px;box-shadow:none}
  .dist-tip::before{border-top-color:rgba(15,23,42,.86)}
</style>
</head>
<body>
<div class="bar">
  <button id="reset" type="button">重新测量</button>
  <button id="undo" type="button" disabled>撤销</button>
  <button id="finish" type="button" disabled>结束</button>
  <span class="spacer"></span>
  <span>总长 <span id="total">0 m</span></span>
</div>
<div id="map"></div>
<script src="https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
(function () {
  'use strict';

  var map = L.map('map', {
    center: [39.907, 116.391],
    zoom: 13,
    doubleClickZoom: false
  });

  L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {
    maxZoom: 19,
    attribution: 'Tiles © Esri'
  }).addTo(map);

  var vertexIcon = L.divIcon({
    className: '',
    html: '<div class="vtx"></div>',
    iconSize: [8, 8],
    iconAnchor: [4, 4]
  });

  var points = [];
  var markers = [];
  var labels = [];
  var line = L.polyline([], {
    color: '#0e7490',
    weight: 3,
    dashArray: '7 5',
    interactive: false
  }).addTo(map);

  var finished = false;
  var totalEl = document.getElementById('total');
  var undoBtn = document.getElementById('undo');
  var finishBtn = document.getElementById('finish');

  function fmt(m) {
    return m >= 1000 ? (m / 1000).toFixed(2) + ' km' : Math.round(m) + ' m';
  }

  function segments() {
    var sum = 0;
    for (var i = 1; i < points.length; i++) {
      sum += points[i - 1].distanceTo(points[i]);
    }
    return sum;
  }

  function clearLabels() {
    labels.forEach(function (t) { map.removeLayer(t); });
    labels = [];
  }

  function redraw() {
    line.setLatLngs(points);
    clearLabels();

    for (var i = 1; i < points.length; i++) {
      var a = points[i - 1];
      var b = points[i];
      var mid = L.latLng((a.lat + b.lat) / 2, (a.lng + b.lng) / 2);
      var tip = L.tooltip({
        permanent: true,
        direction: 'center',
        className: 'dist-tip',
        opacity: 1
      }).setLatLng(mid).setContent(fmt(a.distanceTo(b))).addTo(map);
      labels.push(tip);
    }

    totalEl.textContent = fmt(segments());
    undoBtn.disabled = points.length === 0 || finished;
    finishBtn.disabled = points.length < 2 || finished;
  }

  function addPoint(latlng) {
    if (finished) return;
    points.push(latlng);
    markers.push(L.marker(latlng, { icon: vertexIcon, interactive: false }).addTo(map));
    redraw();
  }

  function popPoint() {
    if (finished || !points.length) return;
    points.pop();
    var m = markers.pop();
    if (m) map.removeLayer(m);
    redraw();
  }

  function finish() {
    if (points.length < 2) return;
    finished = true;
    markers.forEach(function (m) { map.removeLayer(m); });
    markers = [];
    redraw();
  }

  function reset() {
    points = [];
    markers.forEach(function (m) { map.removeLayer(m); });
    markers = [];
    clearLabels();
    line.setLatLngs([]);
    finished = false;
    totalEl.textContent = '0 m';
    undoBtn.disabled = true;
    finishBtn.disabled = true;
  }

  map.on('click', function (e) {
    addPoint(e.latlng);
  });

  map.on('dblclick', function () {
    if (finished || !points.length) return;
    /* 双击会先产生两次 click,去掉重复的末端顶点 */
    if (points.length >= 2) {
      var last = points[points.length - 1];
      var prev = points[points.length - 2];
      if (last.distanceTo(prev) < 1) popPoint();
    }
    if (points.length >= 2) finish();
  });

  document.getElementById('reset').addEventListener('click', reset);
  document.getElementById('undo').addEventListener('click', popPoint);
  document.getElementById('finish').addEventListener('click', finish);

  redraw();
})();
</script>
</body>
</html>
Language: the code and this page are in English; the full tutorial and API notes for this example are in Chinese. The demo itself is the authoritative version — it runs exactly as shown.

More Leaflet examples

Leaflet · EXAMPLEMinimap Leaflet · EXAMPLEOffline Tile Leaflet · EXAMPLEPane Order