绘制与编辑
Leaflet 核心库不带绘制工具。搞清楚「点击加顶点、双击收尾、顶点可拖拽」这条主线, 自己实现一个够用的绘制器并不复杂,也省掉一个插件的样式依赖。
状态机先行
绘制器的本质是一个小状态机,先把状态列清楚,代码就不会乱:
| 状态 | 点击地图 | 双击 | 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)。