交互与绘制
地图上的画线、改点、框选都归 interaction 管。 它们彼此独立,按需添加和移除,同一时刻只保留一个绘制工具是常见做法。
interaction 与 control 的区别
| interaction | control | |
|---|---|---|
| 作用位置 | 地图画布内部 | 画布之上的 DOM 元素 |
| 典型代表 | 拖动、缩放、绘制、选择 | 缩放按钮、比例尺、鹰眼 |
| 添加方式 | map.addInteraction() | map.addControl() |
| 是否可禁用 | setActive(false) | 一般没有这个概念 |
Map 创建时会自动装满默认交互:拖拽平移、滚轮缩放、双击缩放、
拖拽旋转、键盘导航。要删掉某一个,用 map.getInteractions() 遍历后移除,
或在构造时传 interactions 数组。
绘制:ol.interaction.Draw
最基本的用法只需要一个数据源和几何类型。画出来的要素会直接写入数据源:
var drawSource = new ol.source.Vector();
var draw = new ol.interaction.Draw({
source: drawSource,
type: 'Polygon',
style: new ol.style.Style({
fill: new ol.style.Fill({ color: 'rgba(31,95,208,0.15)' }),
stroke: new ol.style.Stroke({ color: '#1f5fd0', width: 2 })
})
});
map.addInteraction(draw);
draw.on('drawend', function (evt) {
var geom = evt.feature.getGeometry();
console.log(geom.getType(), geom.getCoordinates());
});
type 取值包括 Point、LineString、
Polygon、Circle。
画圆时得到的几何体是 ol.geom.Circle,它只有圆心和半径,
没有普通多边形的顶点序列,导出 GeoJSON 前要先转成多边形。
自由手绘与固定顶点
freehand: true 时鼠标按住即连续采点,适合勾勒边界;
默认的 freehandCondition 是按住 Shift 才进入自由手绘。
设置 maxPoints 可以限制折线或面最多几个顶点,
例如只允许画矩形时用 geometryFunction 传
ol.interaction.Draw.createBox()。
var drawBox = new ol.interaction.Draw({
source: drawSource,
type: 'Circle',
geometryFunction: ol.interaction.Draw.createBox()
});
修改与捕捉
ol.interaction.Modify 让已有要素的顶点可拖动,
与绘制工具同时存在时,拖动顶点不会触发新的绘制。
var modify = new ol.interaction.Modify({ source: drawSource });
map.addInteraction(modify);
ol.interaction.Snap 让绘制或修改的顶点吸附到其他要素的顶点、边或线上,
对拼接地块、接边道路很有用:
var snap = new ol.interaction.Snap({
source: drawSource,
pixelTolerance: 8 // 屏幕像素,不是地图单位
});
map.addInteraction(snap);
pixelTolerance 用像素而不是米,是因为它要表达的是“鼠标离多近算命中”,
这个距离与当前缩放级别无关。
选择:ol.interaction.Select
Select 默认监听单击和拖拽框选,把命中的要素放进自己的集合里。
它不会修改要素本身的样式,而是在渲染时用 style 覆盖:
var select = new ol.interaction.Select({
layers: [vecLayer],
style: new ol.style.Style({
fill: new ol.style.Fill({ color: 'rgba(208,69,58,0.25)' }),
stroke: new ol.style.Stroke({ color: '#d0453a', width: 3 })
})
});
map.addInteraction(select);
select.on('select', function (evt) {
var picked = evt.selected; // 本次新选中的要素
var dropped = evt.deselected; // 本次取消选中的要素
console.log(picked.length, dropped.length);
});
要读取全部选中项用 select.getFeatures()。
这个集合是 ol.Collection,可以直接
forEach,也可以监听 add 事件做联动。
同一时刻只启用一个工具
绘制点、线、面时通常按用户选择切换类型。 最稳妥的做法是每次新建 interaction,并移除上一个:
var currentDraw = null;
function startDraw(type) {
if (currentDraw) {
map.removeInteraction(currentDraw);
}
currentDraw = new ol.interaction.Draw({
source: drawSource,
type: type
});
map.addInteraction(currentDraw);
}
document.getElementById('btnLine').onclick = function () {
startDraw('LineString');
};
反复调用 setActive 切换也可以,但一个 interaction 实例绑定一种几何类型,
换类型必须重建,所以不如统一成“先移除再添加”。
不要复用同一个 Draw 实例修改 type:type 是构造参数,
没有公开的 setter。强行改内部字段会导致事件和几何冲突。
绘制结果的投影
用户画出来的坐标是视图投影下的米制值。要存库或调接口, 需要转回经纬度,这一步不能省:
draw.on('drawend', function (evt) {
var geom = evt.feature.getGeometry().clone();
geom.transform('EPSG:3857', 'EPSG:4326');
var lonlat = geom.getCoordinates();
console.log(JSON.stringify(lonlat));
});
用 clone() 是因为 transform 会原地修改几何体,
直接转会把地图上已画好的要素也一起改掉,视觉上表现为画完立刻偏移。
绘制后量算
线长度和面面积交给球面函数,避免墨卡托变形带来的误差:
draw.on('drawend', function (evt) {
var geom = evt.feature.getGeometry();
var info = document.getElementById('info');
if (geom.getType() === 'LineString') {
info.textContent = (ol.sphere.getLength(geom) / 1000).toFixed(2) + ' km';
} else if (geom.getType() === 'Polygon') {
info.textContent = (ol.sphere.getArea(geom) / 1e6).toFixed(2) + ' km²';
}
});
临时图层与业务图层分开
测量、框选这类一次性操作,建议使用独立的 ol.source.Vector 和图层,
清空时只清临时层,不影响已保存的业务数据。
用户按 Esc 取消绘制时监听 drawabort 事件即可。
| 事件 | 触发时机 | 常用处理 |
|---|---|---|
drawstart | 落第一个点 | 隐藏提示文字、记录起始坐标 |
drawend | 双击或闭合完成 | 取几何、量算、写库、移除工具 |
drawabort | 按 Esc 或右键取消 | 清理临时提示 |
modifystart | 拖动顶点开始 | 置脏标记 |
modifyend | 松开鼠标 | 重新量算、提交变更 |
经纬志(2026)。《交互与绘制》。https://www.jingweizhi.com/openlayers/tutorial/interaction-draw.html(访问日期 2026-11-03)。