经纬志
首页 / OpenLayers / 教程

交互与绘制

先看结论

地图上的画线、改点、框选都归 interaction 管。 它们彼此独立,按需添加和移除,同一时刻只保留一个绘制工具是常见做法。

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

interaction 与 control 的区别

interactioncontrol
作用位置地图画布内部画布之上的 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)。