你拿到一組 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 程式庫把兩者透明處理了,但在除錯精度問題時,理解這個區別很重要。
五個你會用到座標轉換的場景:
- 合併不同 CRS 的資料集 — 施工方交付的測量資料用的是國家座標系(例如英國的 OSGB 1936,EPSG:27700),你需要和你的 WGS 84 基線資料合併。
- GPS 到 Web 地圖 — GPS 原始輸出是 WGS 84(度),但 Web 地圖用 Web Mercator(公尺)算繪。每個瓦片地圖底層都在做這個轉換。
- 按客戶要求的 CRS 交付測量資料 — 客戶的 CAD 系統要 Lambert-93(EPSG:2154),你的測量設備記錄的是 WGS 84。
- 對接中國地圖 — 高德地圖、騰訊地圖和百度地圖分別使用偏移座標系(GCJ-02 和 BD-09),和 WGS 84 不能直接互換。
- 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 公尺),但仍然大到不能忽略。微妙的陷阱在於偏移在全國範圍內是平滑變化的,不能靠抽查一個地點來驗證。在北京「看起來對」的轉換,到了成都可能就明顯偏了。
五個常見的座標轉換錯誤
最常見的座標轉換錯誤是:選錯來源 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 沒嵌在檔案裡需要互動式嘗試幾種選項的場景。
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 搜尋工具查。