经纬志
首页 / Leaflet / 教程

绘制与编辑

先看结论

Leaflet 核心库不带绘制工具。搞清楚「点击加顶点、双击收尾、顶点可拖拽」这条主线, 自己实现一个够用的绘制器并不复杂,也省掉一个插件的样式依赖。

最后验证 2026-11-03 · Leaflet 1.9.4 · 更新 2026-10-27 · 引用本文

状态机先行

绘制器的本质是一个小状态机,先把状态列清楚,代码就不会乱:

状态点击地图双击Esc
空闲进入绘制,记录第一个顶点无动作无动作
绘制中(顶点 ≥ 3)追加顶点并更新形状结束绘制放弃本次绘制
绘制中(顶点 < 3)继续追加提示顶点不足放弃
已结束不响应不响应退出编辑态

点击加点

监听 map.on('click'),把 e.latlng 推进数组, 再用 L.polygon(points, options) 或 polyline.setLatLngs(points) 刷新几何。每次点击重建整个图层最简单,顶点数量在几十个以内时性能无差别。

顶点本身也要可见可拖拽,用 L.marker 配一个 L.divIcon 画小圆点, 不需要图片:

var dot = L.divIcon({
  className: '',
  html: '<div class="vertex"></div>',
  iconSize: [10, 10],
  iconAnchor: [5, 5]
});

var vertex = L.marker(latlng, { icon: dot, draggable: true })
  .on('drag', function (e) {
    points[index] = e.target.getLatLng();
    polygon.setLatLngs(points);
    refreshArea();
  })
  .addTo(map);

拖拽事件用 drag,不要用 dragend。 drag 在拖动过程中持续触发,形状和面积能跟着手指实时变化; dragend 只在松手时触发一次,中间过程会看到线与点分离。

双击收尾的冲突

双击会先触发两次 click,再触发一次 dblclick。 如果两个事件都直接加顶点,最后一个位置会被加两次。处理方式有两种:

  • 点击时延迟 250 ms 再加点,双击到来时取消待执行的点击。延迟会让操作手感变钝。
  • 点击立即加点,双击时把最后一个重复顶点删掉再结束。响应快,代码少,是更常用的一种。

另外必须关掉双击缩放,否则每次收尾地图都会放大一级:

var map = L.map('map', {
  center: [39.907359, 116.391263],
  zoom: 12,
  doubleClickZoom: false
});

map.on('dblclick', function () {
  if (points.length > 1) {
    var last = points[points.length - 1];
    var prev = points[points.length - 2];
    if (last.distanceTo(prev) < 1) {
      points.pop();
      vertexGroup.removeLayer(vertexGroup.getLayers().pop());
    }
  }
  if (points.length >= 3) finish();
});

球面面积

L.polygon 没有面积方法。用经纬度直接按平面算会随纬度产生明显误差, 应使用球面多边形面积公式:

function ringArea(latlngs) {
  var R = 6378137;
  var rad = Math.PI / 180;
  var sum = 0;
  for (var i = 0, n = latlngs.length; i < n; i++) {
    var p1 = latlngs[i];
    var p2 = latlngs[(i + 1) % n];
    sum += (p2.lng - p1.lng) * rad *
           (2 + Math.sin(p1.lat * rad) + Math.sin(p2.lat * rad));
  }
  return Math.abs(sum * R * R / 2);
}

返回值单位是平方米。展示时除以 106 得到平方公里。 带洞的多边形需要外环面积减去各内环面积,polygon.getLatLngs() 返回的就是按环分组的嵌套数组。

撤销与清理

撤销一次 = 弹出最后一个顶点 + 移除对应的小圆点 + 刷新几何。 清理则是把多边形、顶点标记、临时线全部移除,并复位状态。 这两件事要放在同一个函数里,避免某条路径漏删 DOM 节点。

function cancel() {
  map.removeLayer(polygon);
  vertexGroup.clearLayers();
  points = [];
  drawing = false;
  status.textContent = '点击地图开始绘制';
}

什么时候该用插件

需要矩形、圆、切割、吸附、撤销重做栈、导入导出 Shapefile 这些能力时, 直接实现会迅速膨胀。此时选择插件更合理,但要评估它的图标资源、样式覆盖成本, 以及与自有数据模型的契合度。只画一两种几何、只需要简单顶点编辑时, 核心 API 的实现往往不足百行。

引用本文

经纬志(2026)。《绘制与编辑》。https://www.jingweizhi.com/leaflet/tutorial/draw-edit.html(访问日期 2026-11-03)。