Atlaslog
Atlaslog / Examples / Leaflet / Polygon Edit
Leaflet · Runnable Example

Polygon Edit

Runnable Leaflet example: Polygon Edit. 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-polygon-edit.htmlLIVE
single HTML file · CDN dependencies onlyinterface may contain Chinese labels

Source

demos/leaflet-polygon-edit.html
<!--
  示例:多边形编辑
  要点:顶点拖拽改形状、点击地图追加顶点、球面面积计算、顶点图层重建
  依赖:Leaflet 1.9.4 · jsDelivr CDN
-->
<!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;flex-wrap:wrap;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.on{background:#1f5fd0;border-color:#1f5fd0;color:#fff}
  .bar b{color:#16181d;font-weight:600;font-variant-numeric:tabular-nums}
  #map{flex:1 1 auto;min-height:0}
  .vertex{width:11px;height:11px;background:#fff;border:2px solid #1f5fd0;border-radius:2px;
    box-shadow:0 0 0 1px rgba(0,0,0,.18)}
</style>
</head>
<body>
<div class="bar">
  <button id="add">加点:关</button>
  <button id="undo">撤销顶点</button>
  <button id="reset">重置</button>
  <span>顶点 <b id="n">4</b> · 面积 <b id="area">—</b></span>
  <span id="hint">拖动白色方块改形状,点击方块删除该顶点</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.910, 116.392], zoom: 13 });

  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 INITIAL = [
    [39.900, 116.370],
    [39.900, 116.408],
    [39.918, 116.412],
    [39.921, 116.380]
  ];

  var pts = INITIAL.map(function (p) { return L.latLng(p[0], p[1]); });

  var polygon = L.polygon(pts, {
    color: '#1f5fd0',
    weight: 2,
    fillColor: '#1f5fd0',
    fillOpacity: 0.12
  }).addTo(map);

  var vertexLayer = L.layerGroup().addTo(map);
  var adding = false;

  var nEl = document.getElementById('n');
  var areaEl = document.getElementById('area');

  /* 球面多边形面积:对每条边做一次带状积分 */
  function areaOf(latlngs) {
    var R = 6378137;
    var rad = Math.PI / 180;
    var sum = 0;
    for (var i = 0, len = latlngs.length; i < len; i++) {
      var p1 = latlngs[i];
      var p2 = latlngs[(i + 1) % len];
      sum += (p2.lng - p1.lng) * rad * (2 + Math.sin(p1.lat * rad) + Math.sin(p2.lat * rad));
    }
    return Math.abs(sum * R * R / 2);
  }

  function fmtArea(m2) {
    return m2 > 1e6 ? (m2 / 1e6).toFixed(2) + ' km²' : Math.round(m2) + ' m²';
  }

  function refresh() {
    polygon.setLatLngs(pts);
    nEl.textContent = pts.length;
    areaEl.textContent = fmtArea(areaOf(pts));
  }

  /* 顶点图层每次重建:数量少,重建比增量维护简单可靠 */
  function buildVertices() {
    vertexLayer.clearLayers();

    pts.forEach(function (latlng, idx) {
      var icon = L.divIcon({
        className: '',
        html: '<div class="vertex"></div>',
        iconSize: [11, 11],
        iconAnchor: [5.5, 5.5]
      });

      var vm = L.marker(latlng, { icon: icon, draggable: true }).addTo(vertexLayer);

      vm.on('drag', function (e) {
        pts[idx] = e.target.getLatLng();
        polygon.setLatLngs(pts);
        areaEl.textContent = fmtArea(areaOf(pts));
      });

      vm.on('dragend', function () {
        pts[idx] = vm.getLatLng();
        refresh();
      });

      vm.on('click', function () {
        if (pts.length <= 3) {
          document.getElementById('hint').textContent = '三角形是下限,再删就没有面了';
          return;
        }
        pts.splice(idx, 1);
        refresh();
        buildVertices();
        document.getElementById('hint').textContent = '已删除顶点,剩余 ' + pts.length + ' 个';
      });
    });
  }

  document.getElementById('add').addEventListener('click', function () {
    adding = !adding;
    this.textContent = '加点:' + (adding ? '开' : '关');
    this.classList.toggle('on', adding);
    map.getContainer().style.cursor = adding ? 'crosshair' : '';
  });

  map.on('click', function (e) {
    if (!adding) return;
    pts.push(e.latlng);
    refresh();
    buildVertices();
    document.getElementById('hint').textContent = '已追加顶点,共 ' + pts.length + ' 个';
  });

  document.getElementById('undo').addEventListener('click', function () {
    if (pts.length <= 3) return;
    pts.pop();
    refresh();
    buildVertices();
  });

  document.getElementById('reset').addEventListener('click', function () {
    pts = INITIAL.map(function (p) { return L.latLng(p[0], p[1]); });
    refresh();
    buildVertices();
    map.fitBounds(polygon.getBounds(), { padding: [40, 40] });
    document.getElementById('hint').textContent = '已恢复初始四边形';
  });

  refresh();
  buildVertices();
})();
</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 · EXAMPLEPopup Position Leaflet · EXAMPLEScale Control Leaflet · EXAMPLETile Cache