经纬志
首页 / OpenLayers / 教程

地图事件与坐标换算

先看结论

地图事件同时给出三种坐标:屏幕像素、视图坐标、浏览器事件。 用错其中任何一个,点击位置就会偏,而且偏得很稳定,容易误判成数据问题。

最后验证 2026-11-03 · OpenLayers 10.3.1 · 难度:入门 · 核验 2026-10-29 · 引用本文

事件对象里的三个坐标系

字段单位含义
event.pixel像素相对地图容器左上角
event.coordinate视图投影单位(默认米)点击位置对应的地理坐标
event.originalEvent浏览器事件原始事件,可取 clientX 等
event.map—事件所属地图实例

常用事件有 singleclick、dblclick、 pointermove、movestart、moveend。 点击请使用 singleclick,它已经过滤掉双击与拖动产生的伪点击。

点到哪个要素

map.forEachFeatureAtPixel 按像素位置查找要素, 可以限定图层与容差。容差让细线的点击更容易命中。

map.on('singleclick', function (event) {
  var hit = map.forEachFeatureAtPixel(
    event.pixel,
    function (feature, layer) {
      return { feature: feature, layer: layer };
    },
    {
      hitTolerance: 6,
      layerFilter: function (layer) {
        return layer.get('queryable') !== false;
      }
    }
  );

  if (!hit) {
    console.log('空白处', event.coordinate);
    return;
  }

  console.log('命中', hit.feature.get('name'), '图层', hit.layer.get('title'));
});

回调返回非空值即停止继续查找,因此返回对象比返回 true 更有用。需要列出所有叠加要素时,在回调里收集后返回 undefined, 或者改用 forEachFeatureAtPixel 的遍历语义。

容差是像素,不是米。hitTolerance: 6 表示指针周围 6 像素内都算命中, 与缩放级别无关。放大后同样的数值覆盖的地面范围会变小。

像素与地理坐标互转

拖拽、定位标注、判断落点是否在范围内,都用到这两个方法。 它们都基于当前视图状态,视图改变后结果随之变化。

// 像素 -> 视图坐标
var coord = map.getCoordinateFromPixel([120, 80]);

// 视图坐标 -> 像素
var pixel = map.getPixelFromCoordinate(coord);

// 视图坐标 -> 经纬度(默认投影为 3857 时)
var lngLat = ol.proj.toLonLat(coord);

// 经纬度 -> 视图坐标
var viewCoord = ol.proj.fromLonLat([116.397, 39.908]);

console.log(coord, pixel, lngLat, viewCoord);

这些换算不依赖事件,任何时刻都能调用。但不要在 movestart 时读取,此时视图尚未完成更新,拿到的是上一帧的位置。

指针移动要节流

pointermove 每秒触发几十次,每次做要素查找会明显拖慢地图。 只处理变化幅度足够大的移动,并在没有命中时立刻恢复默认光标。

var lastPixel = null;
var ticking = false;

map.on('pointermove', function (event) {
  if (lastPixel &&
      Math.abs(event.pixel[0] - lastPixel[0]) + Math.abs(event.pixel[1] - lastPixel[1]) < 5) {
    return;
  }
  lastPixel = event.pixel;

  if (ticking) return;
  ticking = true;

  requestAnimationFrame(function () {
    var hit = map.hasFeatureAtPixel(event.pixel, { hitTolerance: 4 });
    map.getTargetElement().style.cursor = hit ? 'pointer' : '';
    ticking = false;
  });
});

hasFeatureAtPixel 只判断是否存在,找到第一个就返回, 比 forEachFeatureAtPixel 更适合悬停判断。

注意事项

  • click 是底层浏览器事件,会与拖动冲突;地图级点击统一用 singleclick。
  • event.coordinate 是视图投影坐标,不是经纬度,展示前要经过 ol.proj.toLonLat。
  • 隐藏图层默认仍参与 forEachFeatureAtPixel,用 layerFilter 排除。
  • 要素数量大时拾取会逐层遍历,配合 layerFilter 只查必要图层能显著提速。
  • 地图销毁前用 ol.Observable.unByKey 解绑监听,否则回调仍持有地图引用。
引用本文

经纬志(2026)。《地图事件与坐标换算》。https://www.jingweizhi.com/openlayers/tutorial/map-events.html(访问日期 2026-11-03)。