使用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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容