前言
最近因為工作需求碰到地理空間搜尋與儲存的技術,其中包含 PostGIS 這個 PostgreSQL 實作的地理物件函式庫,裏頭涵蓋各地區座標系統的 SRID(Spatial Reference Identifier),令人欣慰的是台灣地區常用的 TWD97 與 TWD67 兩種座標系統也涵蓋如此,但在實作過程發現 TWD67 的座標點位有高達數百公尺的誤差,追溯源頭後意外發現,原來是 PostGIS 內的參數設定有誤,解決之後就順手紀錄一下踩坑的過程。
座標參考系格式 - WKT vs proj.4 vs SRID
開始以前要先提一下地理知識,因為地球是圓的不是平的,所以要將一個橢圓形的物件投影到紙面上勢必會有投影扭曲的問題,而為了降低投影扭曲的影響,學術上有很多轉換方式,包含等面積投影、等角投影(麥卡托投影)…,但不管選用哪一種投影都只能降低扭曲的幅度,並不存在絕對好的投影方式。
為了描述座標參考系(CRS, Coordinate Reference System),訂了幾種常用的描述格式,包含:
是一種描述點、線、多邊形、幾何形狀的文本格式,例如:
POINT(121.5 22.5)
,相較於其他格式更 human-readable。舉例來說,Google map 使用的坐標系 WGS84 的 WKT 格式:
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]
在 PostGIS 中被應用在
spatial_ref_sys
表內的srtext
欄位中
proj.4:
- 是另一種描述投影的文本格式,例如:
+proj=tmerc +lat_0=0 +lon_0=121 +k=0.9999 +x_0=250000 +y_0=0 +ellps=aust_SA +units=m +no_defs
,用幾個參數如橢圓角度、基準、長度單位…來定義投影的方式。 - Proj.4 是開源社群常用的地圖投影資料庫,據我所知 PostGIS 和 GDAL/OGR 這些軟體都直接或間接的使用到 Proj.4 資料庫
- 在 PostGIS 中被應用在
spatial_ref_sys
表內的proj4text
欄位中
- 是另一種描述投影的文本格式,例如:
SRID:
- SRID 是 Spatial Reference Identifier 的縮寫,代表某地區的座標參考系,是一串數字組成。例如: WGS84 的 SRID 是
4326
- 而每個 SRID 都會有一個授權機構(authority),以 PostGIS 為例,內建的 SRID 都是在於 EPSG 這個機構,類似的機構還有 ESRI
- 可以在 spatialreference 網站上查到 EPSG 上有哪些 SRID
- 在 PostGIS 中被應用在
spatial_ref_sys
表內的srid
欄位中
- SRID 是 Spatial Reference Identifier 的縮寫,代表某地區的座標參考系,是一串數字組成。例如: WGS84 的 SRID 是
想了解更多比較可以看這篇 文章
重現 TWD67 座標偏差問題
了解這些描述格式後,為了重現遇到的 TWD67 問題,我找了台北 101 景觀台的座標點來 demo。
打開 Google Map,把台北 101 的經緯度座標右鍵複製下來,經緯度是 25.03375, 121.56487
,這邊的格式是 緯度,經度
。
接著,打開 成大水工試驗所座標轉換程式 網站,將剛剛複製的經緯度貼上來,並選擇 TWD97(WGS84)經緯度
,按下轉換後可以得到 TWD67 二度分帶坐標值: 306171.309, 2769839.581
有了台北 101 景觀大樓在 TWD67 的座標後,我們來用 PostGIS 將他轉回 WGS84 看看 (TWD67 的 SRID 是 3828, WGS84 的 SRID 是 4326)
SELECT ST_Transform(ST_GeomFromText('POINT(306171.309 2769839.581)', 3828), 4326)
結果如下,左邊紅色圈圈是用 PostGIS 內建的 TWD67 轉換的座標,右邊圈圈是台北 101 景觀大樓真實位置。可以看到兩者差了不知道多遠…
解決方法: 重新調整 TWD67 投影參數
為了解決這個問題,我直覺的先去看一下 PostGIS 內建的 TWD67 座標參數,用 SQL 查一下,TWD67 的 SRID 是 3828
SELECT * FROM spatial_ref_sys WHERE srid=3828;
查詢結果中 proj4text
欄位的值是 +proj=tmerc +lat_0=0 +lon_0=121 +k=0.9999 +x_0=250000 +y_0=0 +ellps=aust_SA +units=m +no_defs
這個 proj.4 文字描述的是 PostGIS 內 TWD67 坐標系如何做參數的轉換,上網查了一下發現其他座標轉換軟體如 QGIS 也有類似的問題,詳細可見 這篇。
研究了一陣子後,發現錯誤的原因是 PostGIS 的 proj4 投影參數少了towgs84
,應該要修正成以下內容:
原本的 proj4 參數:
+proj=tmerc +lat_0=0 +lon_0=121 +k=0.9999 +x_0=250000 +y_0=0 +ellps=aust_SA +units=m +no_defs
應修改為:
+proj=tmerc +lat_0=0 +lon_0=121 +k=0.9999 +x_0=250000 +y_0=0 +ellps=aust_SA +towgs84=-752,-358,-179,-0.0000011698,0.0000018398,0.0000009822,0.00002329 +units=m +no_defs
主要差異是:
+towgs84=-752,-358,-179,-0.0000011698,0.0000018398,0.0000009822,0.00002329
這其實就是七參數轉換法,包含 x, y, z 三向的平移與旋轉,再加上尺度縮放,所以共有七個參數,來決定如何將座標點位的參考系轉換成 WGS84。
OK,找到解法後,接下來就是想辦法修改 PostGIS 內的 proj4 內容即可,但我還是想保留原本 TWD67(3828)的座標參考系,所以選擇額外新增一個客製化的座標參考系,就選用最近沒被用到的 SRID 3830。
其中 srtext 的內容可以用另一個座標轉換軟體 GDAL 來生成
gdalsrsinfo.exe mycrs.prj > mycrs.txt
執行完後會產出 WKT 並寫進 mycrs.txt 內,複製出來貼到 PostGIS 內的 rtext 即可
'PROJCS["TWD67 / TM2 zone 121",GEOGCS["TWD67",DATUM["Taiwan_Datum_1967 based on Australian Natl & S. Amer. 1969 ellipsoid",ELLIPSOID["Australian Natl & S. Amer. 1969",6378160,298.25,LENGTHUNIT["metre",1,ID["EPSG",9001]]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8901]]],CONVERSION["TWD67",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",121,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9999,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",250000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["metre",1,ID["EPSG",9001]]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["metre",1,ID["EPSG",9001]]]]'
整體 SQL 如下:
/* Add custom TWD67' with 7 params transformation */
INSERT INTO spatial_ref_sys
(srid, auth_name, auth_srid, proj4text, srtext)
VALUES (
3830,
'custom-twd-67',
3830,
'+proj=tmerc +lat_0=0 +lon_0=121 +k=0.9999 +x_0=250000 +y_0=0 +ellps=aust_SA +towgs84=-752,-358,-179,-0.0000011698,0.0000018398,0.0000009822,0.00002329 +units=m +no_defs'
'PROJCS["TWD67 / TM2 zone 121",GEOGCS["TWD67",DATUM["Taiwan_Datum_1967 based on Australian Natl & S. Amer. 1969 ellipsoid",ELLIPSOID["Australian Natl & S. Amer. 1969",6378160,298.25,LENGTHUNIT["metre",1,ID["EPSG",9001]]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8901]]],CONVERSION["TWD67",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",121,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9999,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",250000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["metre",1,ID["EPSG",9001]]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["metre",1,ID["EPSG",9001]]]]'
);
接著,實測客製化 TWD67(3830)的座標轉換
SELECT ST_Transform(ST_GeomFromText('POINT(306171.309 2769839.581)', 3830), 4326)
結果如下,可以看到座標轉換的結果比預設的 TWD67 好太多了!
以上就是這次踩坑 PostGIS 的紀錄,沒想到依照國際地理標準的這些軟體工具,背後的投影參數也是有可能需要被調整的,幸好這些標準都依循既定的格式來描述,所以修改起來負擔不大,擴充上仍保有彈性。