使用PostgreSQL+PostGIS實現地圖引擎(經歷ArcGIS Server/GeoServer/MapServer后)

地圖服務器方案探索

研究GIS服務器已經很長一段時間,最開始使用ArcGIS Server,后來由于費用問題公司基于nginx和lua自己做了一套矢量瓦片的地圖服務器,再后來開始研究使用Geoserver,再后來由于性能問題改研究Mapserver,最近由于開發的靈活性和業務需求開始研究PostgreSQL+PostGIS的方案。

2021.2.1更新

最近實際使用中發現了些問題,在重慶范圍的數據,疊加在3857的天地圖上時,如果放大到14級以上看不出來偏移,便是縮小到能看全中國,能看全球的時候地圖就偏移到了西伯利亞去了。

所以有條件還是應該選用“方案三”在地圖生成過程中使用st_transform函數進行坐標轉換。

PostGIS官方方案

WITH mvtgeom AS
(
  SELECT ST_AsMVTGeom(geom, ST_TileEnvelope(12,513,412)) AS geom, name, description
  FROM points_of_interest
  WHERE ST_Intersects(geom, ST_TileEnvelope(12,513,412)
)
SELECT ST_AsMVT(mvtgeom.*)
FROM mvtgeom;

存在問題

ST_TileEnvelope只能生成3857坐標的邊界,類似于: POLYGON((11858134.820049 3561354.02186289,11858134.820049 3580921.90110389,11877702.69929 3580921.90110389,11877702.69929 3561354.02186289,11858134.820049 3561354.02186289))

我們數據庫里的數據都是4490坐標的邊界,即類似于: POLYGON((-180 -90,-180 90,180 90,180 -90,-180 -90))

如果直接使用ST_TileEnvelope生成的邊界來計算就必須要求數據庫里的數據轉換為3857坐標系的樣式。

解決辦法有五個思路,一是修改源數據為3857的數據后再導入,二是4490坐標系的數據設置坐標系為3857,三是在計算過程中用st_transform強制投影轉換,四是計算過程中采用::geography強制轉換,五是放棄ST_TileEnvelope函數采用ST_MakeEnvelope并從代碼中計算好4490下的瓦片邊界后使用。

方案一: 不可取。由于國家要求和實際計算的需要,我們只能在庫里存4490坐標系的數據。

方案二:

不可取。 update "DS_YJJBNT_5" set geom=st_setsrid(geom,4490)這一行語句只是修改了表里面的坐標系描述,即只改了元數據,并不會去動表中的任何一條數據,所以設置坐標系的方式是無法使用的。

SELECT st_srid(geom) FROM "DS_YJJBNT_5" limit 1;查詢數據表的坐標系。

方案三: 不可取。 官方文檔中提示說使用st_transform需要有proj4這個庫編譯進PostgreSQL庫,但是重新編譯又是一項大工程。 UPDATE "DS_YJJBNT_5" SET geom=st_transform(st_geomfromtext((st_astext(geom)),4490),3857);

方案四: 不可取。 還沒試成功過,具體原理未知。

方案五: 可以成功。

方案五的實現

通過xyz生成任意瓦片矩形邊界

python版

func tile2lon( x int,  z int)(a float64) {
    return float64(x) /math.Pow(2, float64(z)) * 360.0 - 180;
 }

 func tile2lat( y int,  z int)(a float64) {
   n := math.Pi - (2.0 * math.Pi * float64(y)) / math.Pow(2, float64(z));
   return math.Atan(math.Sinh(n))*180/math.Pi;
 }

ymax :=FloatToString(tile2lat(int(xyz.y), int(xyz.z)));
ymin := FloatToString(tile2lat(int(xyz.y+1), int(xyz.z)));
xmin := FloatToString(tile2lon(int(xyz.x), int(xyz.z)));
xmax := FloatToString(tile2lon(int(xyz.x+1), int(xyz.z)));

node.js版

let xmin = x/Math.pow(2,z)*360.0-180;

let n = Math.PI - (2.0 * Math.PI * y) / Math.pow(2,z);
let ymax = Math.atan((Math.exp(n)-Math.exp(-n))/2)*180/Math.PI;

Sinh(n)與(Math.exp(n)-Math.exp(-n))/2等價

原版SQL

WITH mvtgeom AS (SELECT ST_AsMVTGeom(geom, ST_MakeEnvelope(106.5234375, 30.44867367928757, 106.69921875, 30.600093873550065, 4490)) AS geom, bsm, objectid 
FROM "DS_YJJBNT_5" WHERE ST_Intersects(geom, ST_MakeEnvelope(106.5234375, 30.44867367928757, 106.69921875, 30.600093873550065, 4490))) 
SELECT ST_AsMVT(mvtgeom.*,'points') FROM mvtgeom;

解決了地圖縮放到任何層級切出來圖片大小都會蓋全圖的問題

(設置extent=4096,buffer為0,裁剪為true)

WITH mvtgeom AS (SELECT ST_AsMVTGeom(geom, ST_MakeEnvelope(106.5234375, 30.44867367928757, 106.69921875, 30.600093873550065, 4490),4096, 0, true) AS geom, bsm, objectid 
FROM "DS_YJJBNT_5" WHERE ST_Intersects(geom, ST_MakeEnvelope(106.5234375, 30.44867367928757, 106.69921875, 30.600093873550065, 4490))) 
SELECT ST_AsMVT(mvtgeom.*,'points') FROM mvtgeom;

解決了放大到最大層級時相對geoserver部分數據丟失且偏移的問題

(去除where相交的條件)


數據偏移且丟失的情況
WITH mvtgeom AS (SELECT ST_AsMVTGeom(geom, ST_MakeEnvelope(106.5234375, 30.44867367928757, 106.69921875, 30.600093873550065, 4490),4096, 0, true) AS geom, bsm, objectid 
FROM "DS_YJJBNT_5" ) 
SELECT ST_AsMVT(mvtgeom.*,'points') FROM mvtgeom;

重組美化SQL后

select ST_AsMVT ( mvtgeom.*, '${tableName}' )as data from (
   SELECT
      ST_AsMVTGeom ( geom, ST_MakeEnvelope ( #{xmin},#{ymin},#{xmax},#{ymax}, 4490 ),4096,0,true ) AS geom,
      bsm,
      objectid
   FROM
      "${tableName}"
    )mvtgeom

GIS引擎開發

最近自己用Mapserver包裝了一個GIS引擎作為Geoserver的替代方案,適合小微企業和個人用戶,下載地址:

Mapserver-server.zip

解壓密碼:2234,后續我將按計劃進行完善,力爭做到輕量、易用、穩定、高性能、開源。

同時我將按低價提供服務,并低價出售源代碼,以保證日常開銷。

參考資料:

https://www.cnblogs.com/polong/p/9831106.html

http://postgis.net/docs/manual-3.0/ST_TileEnvelope.html

http://postgis.net/docs/manual-3.0/ST_AsMVT.html

http://postgis.net/docs/manual-3.0/ST_AsMVTGeom.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容