经纬志
首页 / OpenLayers / 教程

投影与坐标系

先看结论

坐标问题是 OpenLayers 上手阶段唯一真正的门槛。 地图、要素、服务、鼠标事件四处的坐标必须统一在同一个投影下,否则就会出现偏移、飞到海里、只显示半个世界。

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

默认坐标系不是经纬度

OpenLayers 的 View 默认使用 EPSG:3857,也就是 Web 墨卡托, 坐标单位是米。它的世界范围大致是 [-20037508, -20037508, 20037508, 20037508], 原点在本初子午线与赤道的交点,向东、向北为正。

GIS 业务数据绝大多数是 EPSG:4326,单位是度,范围是经度 ±180、纬度 ±90。 把 [116.39, 39.90] 直接交给 EPSG:3857 的视图, 等于告诉地图“往东走 116 米、往北走 39 米”,于是视角落在几内亚湾附近, 屏幕上只剩一片海洋。

判断方法:如果地图打开后中心在非洲西海岸外,或者缩到 0 级看到整个世界, 基本可以确定是经纬度没转换。

两个转换函数

一对函数解决 90% 的问题,方向不要记反:

// 经纬度 -> 米(给 View 的 center、Feature 的 geometry 用)
var center = ol.proj.fromLonLat([116.391263, 39.907359]);

// 米 -> 经纬度(把事件里的坐标回显给用户时用)
var lonlat = ol.proj.toLonLat(evt.coordinate);

函数内部判断当前视图投影,默认就是 EPSG:3857。 如果视图改成了别的投影,要显式传第二个参数:

var center = ol.proj.fromLonLat([116.39, 39.90], 'EPSG:4490');

三处坐标,别混用

位置坐标含义来源
view.getCenter()视图投影下的米创建时用 fromLonLat 转换
feature.getGeometry().getCoordinates()要素所在数据源的投影读取 GeoJSON 时指定 featureProjection
evt.coordinate视图投影下的米回显前用 toLonLat 转换

map.getCoordinateFromPixel() 返回的同样是视图投影下的米制坐标, 常有人拿它当经纬度存库,之后所有空间查询都偏出几百公里。

读取数据时的投影声明

矢量数据本身带有自己的坐标系。用格式解析器读取时, 必须同时告诉它“数据是什么投影”和“我要什么投影”, OpenLayers 会自动逐点重投影:

var format = new ol.format.GeoJSON();

var features = format.readFeatures(geojson, {
  dataProjection: 'EPSG:4326',       // 文件里是经纬度
  featureProjection: 'EPSG:3857'     // 地图视图用墨卡托
});

漏掉 featureProjection 的典型后果是:地图上什么也看不到, 因为要素坐标落在 [116, 39] 这一小片区域里, 而当前视野在几千万米之外。用 view.fit(source.getExtent()) 倒是能看到, 但整张图会挤成一个点。

数据源级别的声明:如果是手工 addFeature, 可以在 ol.source.Vector 上写 projection: 'EPSG:4326', 但更稳妥的做法是在构造几何体时就转好,避免后续量算单位混乱。

视图切到别的投影

View 的 projection 参数决定地图的坐标参考。 改成 EPSG:4326 后,地图容器里的坐标直接用经纬度, 代价是墨卡托的等角特性消失,高纬度地区变形明显,瓦片也要能接受 4326 请求。

var view = new ol.View({
  projection: 'EPSG:4326',
  center: [116.391263, 39.907359],   // 此时不需要 fromLonLat
  zoom: 8
});

更换投影后,fromLonLat 的目标投影也跟着变, 所以这段代码里不能再套一层转换,否则会得到双重投影的坐标。

注册国内常用投影

政务数据常见 EPSG:4490(CGCS2000 经纬度)、 EPSG:4547(CGCS2000 三度带高斯投影)。 OpenLayers 只内置了少数几个,其余要借助 proj4 注册:

<script src="https://cdn.jsdelivr.net/npm/proj4@2.12.1/dist/proj4.js"></script>
<script>
  proj4.defs('EPSG:4547',
    '+proj=tmerc +lat_0=0 +lon_0=114 +k=1 +x_0=500000 +y_0=0 ' +
    '+ellps=GRS80 +units=m +no_defs');

  ol.proj.proj4.register(proj4);

  var view = new ol.View({
    projection: 'EPSG:4547',
    center: [500000, 4420000],
    zoom: 10
  });
</script>

注册之后再调用 ol.proj.fromLonLat([116.39, 39.90], 'EPSG:4547') 就能拿到平面坐标,图层与视图的投影不一致时也会自动重投影。

分辨率和缩放级别

在 EPSG:3857 下,0 级一张 256 像素的瓦片覆盖整个赤道周长 40075016.686 米,所以某一级的分辨率是:

resolution = 40075016.686 / (256 * Math.pow(2, zoom));

这个值表示“一个屏幕像素代表多少米”,常用于按比例设置线宽、 判断当前是否该显示某个图层。代码里可以直接读: map.getView().getResolution()。

距离和面积用球面公式

墨卡托投影会放大高纬度的距离,用平面勾股算出来的长度并不可信。 OpenLayers 提供了基于 WGS84 椭球的计算函数:

var length = ol.sphere.getLength(lineGeometry);   // 米
var area = ol.sphere.getArea(polygonGeometry);    // 平方米

这两个函数接受任意投影的几何体,内部会先转成经纬度再计算, 所以不必手动 toLonLat。

坐标问题排查表

症状原因修正
打开后停在海面上经纬度直接当米用ol.proj.fromLonLat()
要素全挤在一个点未指定 featureProjection读取时补上目标投影
点位整体偏移几百米数据是 GCJ-02 或地方坐标系先用转换库纠偏再交给地图
WMS 图与原底图错位服务端 CRS 与视图投影不一致统一 projection 或改用 4326 视图
量算距离偏大用平面坐标算欧氏距离改用 ol.sphere.getLength

排查时最有效的一步是打印三个值:视图投影、要素第一个坐标、 以及 ol.proj.toLonLat 之后的经纬度。 三者对不上,问题一定在投影环节。

引用本文

经纬志(2026)。《投影与坐标系》。https://www.jingweizhi.com/openlayers/tutorial/projection.html(访问日期 2026-11-03)。