KunYu
epsgcoordinate-conversionproj4crs

EPSG 坐标系之间的坐标转换

使用 proj4js、pyproj 和 ogr2ogr 在 EPSG 坐标系之间转换坐标,含 WGS 84、Web Mercator、UTM 和中国坐标系(GCJ-02/BD-09)实战示例。

KunYu TeamMarch 25, 202617 分钟阅读

你拿到一组 WGS 84 的 GPS 测量点,需要叠加到 UTM Zone 50N 投影的设计图上。粘贴坐标后,点全跑到了大洋中间,偏了几千公里。数字看着差不多,但两个坐标系不做转换就是不兼容。在 EPSG 坐标系之间转换坐标是 GIS 里最常见的操作之一,搞错了轻则偏几米(肉眼看不出来),重则飞到另一个大洲。

EPSG 坐标转换通过数学定义的基准变换和地图投影,将点坐标从一个坐标参考系统转换到另一个。下面是 JavaScript、Python 和命令行的实现代码,以及那些会悄悄搞坏你数据的常见错误。

什么是坐标转换?什么时候需要它?

坐标转换把点数据从一个 CRS 重投影到另一个。当你需要合并不同坐标系的数据集、在 Web 地图上显示 GPS 数据、在地理坐标(度)和投影坐标(米)之间转换、或者处理中国的偏移坐标系(GCJ-02、BD-09)时,都要用到它。

两个概念经常被混淆。坐标转换(conversion) 是在同一基准面上换投影,比如把 WGS 84 地理坐标(EPSG:4326)转成 WGS 84 / UTM Zone 50N(EPSG:32650)。坐标变换(transformation) 是换基准面本身,比如 NAD27 转 WGS 84,涉及物理模型参数,因为两个基准面对地球形状的定义不同。大多数 GIS 库把两者透明处理了,但在调试精度问题时,理解这个区别很重要。

五个你会用到坐标转换的场景:

  1. 合并不同 CRS 的数据集 — 施工方交付的测量数据用的是国家坐标系(比如英国的 OSGB 1936,EPSG:27700),你需要和你的 WGS 84 基线数据合并。
  2. GPS 到 Web 地图 — GPS 原始输出是 WGS 84(度),但 Web 地图用 Web Mercator(米)渲染。每个瓦片地图底层都在做这个转换。
  3. 按客户要求的 CRS 交付测量数据 — 客户的 CAD 系统要 Lambert-93(EPSG:2154),你的测量设备记录的是 WGS 84。
  4. 对接中国地图 — 高德地图、腾讯地图和百度地图分别使用偏移坐标系(GCJ-02 和 BD-09),和 WGS 84 不能直接互换。
  5. UTM 分区转换 — 项目跨两个 UTM 分区,需要统一到一个投影坐标系来保证距离和面积计算一致。

如何将 EPSG:4326 转换为 EPSG:3857

从 EPSG:4326(WGS 84,度)转到 EPSG:3857(Web Mercator,米),本质上是应用墨卡托投影公式。JavaScript 里用 proj4("EPSG:4326", "EPSG:3857", [lng, lat]),Python 里用 Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True)。转换将经纬度映射为以米为单位的东移/北移值。

以北京坐标为例:

// JavaScript — proj4js
import proj4 from "proj4";

const [easting, northing] = proj4(
  "EPSG:4326",
  "EPSG:3857",
  [116.4074, 39.9042] // 北京 (lng, lat)
);
console.log(easting, northing);
// → 12958175.0, 4852834.1
# Python — pyproj
from pyproj import Transformer

transformer = Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True)
easting, northing = transformer.transform(116.4074, 39.9042)
print(easting, northing)
# → 12958175.0, 4852834.1

输入是两个小数字(度),输出是两个大数字(米,从 0°N, 0°E 原点算起)。这是正常的,Web Mercator 以赤道和本初子午线的交点为坐标原点。

注意:EPSG:3857 在大约 ±85.06° 纬度处截断。墨卡托投影会把两极映射到无穷大,所以 85°N 以北或 85°S 以南的坐标无法表示。北极和南极数据需要用极地立体投影,比如 EPSG:3413(北极)或 EPSG:3031(南极)。

你可以在坤舆的坐标转换工具中即时验证任何转换,粘贴坐标、选择源和目标 CRS,无需安装任何东西。

任意两个 EPSG 代码之间的转换

同样的模式适用于任何 EPSG 代码对。对于非内置代码,你需要 CRS 定义(proj4 字符串或 WKT)。proj4js 需要你手动注册自定义 CRS 定义;pyproj 自带完整的 EPSG 数据库。

JavaScript (proj4js)

Proj4js 开箱只认识 EPSG:4326 和 EPSG:3857,其他都需要定义字符串,可以在 epsg.io 查。

import proj4 from "proj4";

// 注册 Lambert-93(法国)— 定义来自 epsg.io/2154
proj4.defs(
  "EPSG:2154",
  "+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 " +
    "+x_0=700000 +y_0=6600000 +ellps=GRS80 +units=m +no_defs"
);

// WGS 84 → Lambert-93
const [x, y] = proj4("EPSG:4326", "EPSG:2154", [2.3522, 48.8566]);
console.log(x, y);
// → 652469.5, 6862035.9(巴黎,Lambert-93 米制坐标)

有个容易踩的坑:proj4 定义字符串必须完全匹配。我有一次花了一小时排查一个 CGCS2000 投影数据集里 3 米的偏移,最后发现 Shapefile 附带的 .prj 文件用的椭球参数和 epsg.io 上列出的略有不同。解决办法是用 gdalsrsinfo input.shp 直接从 .prj 内容中提取 proj4 字符串,而不是查 EPSG 代码然后假设定义一定匹配。精度要求高的时候,永远从源数据本身提取定义。

Python (pyproj)

Pyproj 自带完整的 PROJ 数据库,不需要手动注册定义。关键参数是 always_xy=True,不加的话 pyproj 会按 EPSG 标准轴序(地理 CRS 纬度在前),静默交换你的坐标。

from pyproj import Transformer

# pyproj 认识所有 EPSG 代码,无需手动注册
transformer = Transformer.from_crs("EPSG:4326", "EPSG:2154", always_xy=True)
x, y = transformer.transform(2.3522, 48.8566)
print(x, y)
# → 652469.5, 6862035.9

命令行 (ogr2ogr)

Shapefile、GeoJSON、GeoPackage 等文件级重投影,用 GDAL 的 ogr2ogr,一条命令批量搞定。

# 把 GeoJSON 从 WGS 84 重投影到 UTM Zone 50N
ogr2ogr -t_srs EPSG:32650 output.geojson input.geojson

# 把 Shapefile 从 OSGB 1936 重投影到 WGS 84
ogr2ogr -s_srs EPSG:27700 -t_srs EPSG:4326 output.shp input.shp

当输入文件缺少 CRS 元数据(没有 .prj 文件或内嵌投影信息)时,用 -s_srs 显式指定源 CRS。如果源 CRS 已经在文件中定义好了,只用 -t_srs 就够了。

转换中国坐标(WGS 84、GCJ-02、BD-09)

中国对所有公开地图服务强制要求坐标偏移。GCJ-02("火星坐标")基于 Krasovsky 1940 椭球,用非线性算法将 WGS 84 坐标偏移 100–700 米。BD-09 在 GCJ-02 基础上再加一层偏移。它们不是 EPSG 注册的坐标系,proj4 和 pyproj 等标准工具无法转换,需要专门的偏移算法。

偏移的存在是因为中国测绘法规要求所有公开数字地图使用加密坐标系。正向变换(WGS 84 → GCJ-02)是确定性公式,反向变换(GCJ-02 → WGS 84)没有解析解,需要迭代逼近,通常 10 次迭代可以达到亚米级精度(~0.1m)。

各地图服务使用的坐标系:

地图服务 坐标系 相对 WGS 84 的偏移
Google Maps(中国区) GCJ-02 100–700 m
高德地图 GCJ-02 100–700 m
腾讯地图 GCJ-02 100–700 m
Apple Maps(中国区) GCJ-02 100–700 m
百度地图 BD-09 100–700 m + 额外偏移
OpenStreetMap WGS 84

转换链是:WGS 84 ↔ GCJ-02 ↔ BD-09。直接在 WGS 84 和 BD-09 之间转换而跳过 GCJ-02 中间步骤,结果是错的。坤舆的坐标转换工具实现了完整的转换链和迭代反算算法,这是大多数国际 GIS 工具不支持的。

以我的经验,偏移在中国西部最严重。在乌鲁木齐附近我见过超过 600 米的偏移,足以把一个兴趣点标到河对岸或公路的另一边。东部沿海城市如上海偏移较小(大约 200–300 米),但仍然大到不能忽略。微妙的陷阱在于偏移在全国范围内是平滑变化的,不能靠抽查一个地点来验证。在北京"看着对"的转换,到了成都可能就明显偏了。

坐标转换

在 EPSG 坐标系之间转换坐标,支持批量处理和智能识别。

Try it now

五个常见的坐标转换错误

最常见的坐标转换错误是:选错源 CRS、轴序搞混、用 Web Mercator 做量测、忽略基准变换、以及假设所有度制坐标都是 WGS 84。每一个都会产生看似正常、实则错误的结果,直到下游出了问题才暴露。

选错源 CRS

如果你的源数据是 NAD83(EPSG:4269),但你告诉转换器它是 WGS 84(EPSG:4326),在北美大部分地区差异只有 ~1–2 米,测试时容易忽略,但对测量级精度来说很要命。把 NAD27 和 WGS 84 搞混更糟糕:根据位置不同偏移 10–200 米,足以把一栋建筑放到地块的另一边。

轴序混淆(lat/lon vs lon/lat)

EPSG:4326 的正式定义是纬度在前(lat, lng)。但 GeoJSON、proj4js、Leaflet 的 L.latlng() 构造函数(尽管名字叫 latlng)以及大多数 Web 地图库都期望经度在前(lng, lat)。如果转换后的点在地图上随机散布但大致比例正确,你大概率是把 lat 和 lon 搞反了。Pyproj 的 always_xy=True 参数就是专门解决这个问题的。

用 Web Mercator 做量测

EPSG:3857 是用来显示的,不是用来计算的。在 Web Mercator 下计算面积,高纬度地区会严重膨胀——格陵兰岛看起来比实际大 14 倍。做距离和面积计算时,先转到本地投影坐标系(UTM 分区或国家坐标网)再量测。

遗漏基准变换

在使用不同基准面的 CRS 之间转换,比如欧洲的 ED50 到 ETRS89,需要带特定参数的基准变换。用通用或默认变换可能引入好几米的误差。GDAL 和 pyproj 在有网格偏移文件时会自动处理,但你应该验证使用的变换方法。

以为所有度制坐标都是 WGS 84

很多地理 CRS 都用度:EPSG:4326(WGS 84)、EPSG:4269(NAD83)、EPSG:4490(CGCS2000)、EPSG:4612(JGD2000)。坐标 139.6917, 35.6895 可能是其中任何一个,格式完全一样但基准面不同。转换前一定要检查源数据的元数据。

选择合适的坐标转换工具

单点或少量坐标转换,浏览器工具最快,不用安装不用写代码。文件重投影用 ogr2ogr 或 QGIS。程序化管线用 proj4js(JavaScript)或 pyproj(Python)直接嵌入代码。中国坐标系方面,大多数国际工具都不行。

场景 推荐工具 理由
快速单点验证 坤舆坐标转换 浏览器运行,6000+ EPSG,含中国坐标系
文件重投影 (SHP/GeoJSON) ogr2ogr (GDAL) 支持任意格式,批处理,可脚本化
QGIS 项目 重投影图层工具 GUI 操作,保留属性和样式
JavaScript Web 应用 proj4js 客户端运行,~100KB,不需要服务器
Python 数据管线 pyproj 完整 PROJ 数据库,兼容 NumPy
中国坐标系 (GCJ-02/BD-09) 坤舆坐标转换 内置偏移算法,大多数工具没有

说实话,凡是需要重复执行的任务我都用 ogr2ogr。有一次我要把一个市政数据集的 200 多个 GeoJSON 文件从本地 Lambert 投影转到 WGS 84,给 Web 应用用。在 QGIS 里就是 200 次手动"导出 → 另存为 → 设置 CRS"。用 ogr2ogr 一行 shell 循环搞定:for f in *.geojson; do ogr2ogr -t_srs EPSG:4326 out/"$f" "$f"; done,一分钟不到。QGIS 更适合需要可视化验证重投影结果、或者源 CRS 没嵌在文件里需要交互式尝试几种选项的场景。

EPSG 查询

搜索和浏览 EPSG 坐标参考系数据库。

Try it now

FAQ

如何将 EPSG:4326 转换为 UTM?

先算出你的坐标落在哪个 UTM 分区:zone = floor((longitude + 180) / 6) + 1。然后用对应的 EPSG 代码转换——北半球用 EPSG:326xx,南半球用 EPSG:327xx,xx 是分区号。比如北京在 116.4°E,落在 UTM Zone 50N,对应 EPSG:32650。

坐标转换(conversion)和坐标变换(transformation)有什么区别?

坐标转换在同一基准面上换投影(比如 WGS 84 地理坐标转 WGS 84 / UTM)。坐标变换换的是基准面本身(比如 NAD27 转 WGS 84),涉及物理模型参数。pyproj 和 GDAL 这类 GIS 工具把两者透明处理了,这个区别主要在排查精度问题时有用。

为什么转换后的坐标偏了好几百米?

最常见的原因是源 CRS 选错了。在中国,WGS 84 和 GCJ-02 混用会产生 100–700 米的偏移,这是设计如此,不是 bug。在中国以外,NAD27 和 WGS 84 搞混,或者用了错误的基准变换参数,也会产生类似的偏移。转换前一定要先确认源 CRS。

不写代码能批量转换坐标吗?

可以。坤舆的坐标转换工具支持多坐标批量转换(一行一个),直接在浏览器里操作,不用注册,数据不离开你的设备。如果是 Shapefile 或 GeoJSON 文件级的批量转换,可以用格式转换工具或命令行的 ogr2ogr。

proj4js 和 PROJ 是同一个东西吗?

不是。PROJ 是 OSGeo 社区维护的 C/C++ 坐标变换库,有 10,000+ 个 CRS 定义,支持基于网格的基准变换。Proj4js 是它的 JavaScript 移植版,实现了相同的核心算法,但默认只包含 EPSG:4326 和 EPSG:3857,其他 CRS 定义都需要手动注册。

记住这个模式

套路永远一样:确定源 CRS → 确定目标 CRS → 执行转换。快速验证用在线转换工具,文件重投影用 ogr2ogr,应用代码里嵌 proj4js 或 pyproj。如果你不确定该用哪个 EPSG 代码,先看 EPSG 入门指南,或者直接用 EPSG 搜索工具查。 /