基于springboot+postgis的矢量切片


概述:
本文将介绍如何基于java+postgis进行矢量切片的制作;
矢量切片介绍:
1.矢量切片是一种利用协议缓冲技术的紧凑的二进制格式用来传递信息。当渲染地图时矢量切片使用一系列存储的内部数据进行制图。被组织到矢量切片的图层(比如道路、水、区域),每一层都有包含集合图形和可变属性的独立要素(例如姓名、类型等等)。通俗的说,就是将矢量数据以建立金子塔的方式,然后在前段根据显示需要按需请求不同的矢量瓦片数据进行Web绘图。
2.常见格式
GeoJson、TopoJson,pbf,mvt等
Postgis矢量切片
postgis是基于PostgreSql的空间扩展。提供了很多的空间分析和操作函数,9.6以上postgresql+2.4postgis,提供了两个矢量切片的空间函数:ST_AsMVT,ST_AsMVTGeom(mapbox格式);
ST_AsMVT:返回一个MapBox矢量瓦片的一组行(返回Byte数组);
bytea ST_AsMVT(anyelement set row);(任意元素集合行,数据来源表)
bytea ST_AsMVT(anyelement row, text name);
bytea ST_AsMVT(anyelement row, text name, integer extent);
bytea ST_AsMVT(anyelement row, text name, integer extent, text geom_name);

row:一个几何列的行数据
name:图层名称
extent:按照规范定义的平铺坐标空间中的平铺范围。默认4096
geom_name:指定行数据列里面的空间字段名称,默认是找到的第一个空间字段
ST_AsMVTGeom:将几何图形转换成MAPBOX矢量瓦片的坐标空间。
geometry ST_AsMVTGeom(geometry geom, box2d bounds, integer extent=4096, integer buffer=256, boolean clip_geom=true);

geom:指定需要变换的几何字段
bounds:不带缓冲区的几何边界
extent:按照规范定义的平铺坐标空间中的平铺范围。默认4096
buffer:平铺坐标空间中任意修剪几何图形的缓冲区距离,默认256
clip_geom:用于控制几何图形是否被裁剪活编码,默认true

测试sql:
SELECT ST_AsMVT(q, ‘test’, 4096, ‘geom’) FROM (SELECT 1 AS c1,
ST_AsMVTGeom(ST_GeomFromText(‘POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))’),
ST_MakeBox2D(ST_Point(0, 0), ST_Point(4096, 4096)), 4096, 0, false) AS geom) AS q;
——————————————————————–
\x1a320a0474657374121d12020000180322150946ec3f1a14453b0a09280f091413121e09091e0f1a026331220228012880207802

实现过程:
1.自行创建空间数据表,并模拟一百万数据

CREATE TABLE “public”.”vector_tile” (
“id” int4 NOT NULL,
“name” varchar(32) COLLATE “default”,
“geom” “public”.”geometry”,
CONSTRAINT “vector_tile_pkey” PRIMARY KEY (“id”)
)
WITH (OIDS=FALSE)
;

ALTER TABLE “public”.”vector_tile” OWNER TO “postgres”;

CREATE OR REPLACE FUNCTION "public"."test_function"(int4)
  RETURNS "pg_catalog"."int4" AS $BODY$
DECLARE
b_count alias for $1;
BEGIN
while b_count>0 loop
INSERT into testgeo (id,geom,name) VALUES(b_count,ST_GeomFromText('POINT('||(random()*(130-120)+120)||' '||(random()*(37-27)+27)||')',4326),'London'||b_count);
b_count := b_count-1;
end loop;
RETURN b_count;
END;
$BODY$
  LANGUAGE 'plpgsql' VOLATILE COST 100
;

ALTER FUNCTION "public"."test_function"(int4) OWNER TO "postgres";

ALTER TABLE “public”.”vector_tile” OWNER TO “postgres”;`;
2.创建springboot项目,进行相关配置(本人ORM层使用mybaits,使用其他的请自行参考,参考这里写链接内容);
3.实现controller,service层的相关代码,这里需要注意一个问题,前段地图发送过来的是想X,Y,Z的参数代表行列号和地图层级,我们需要将其解算成相应的经纬度出来;
解算方法详情请参考([https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames]
给出相关代码:

  /**
   * 根据经纬度和缩放等级,求得瓦片路径
   * **/
  public static String getTileNumber(final double lat, final double lon, final int zoom) {
    int xtile = (int)Math.floor( (lon + 180) / 360 * (1<<zoom) ) ;
    int ytile = (int)Math.floor( (1 - Math.log(Math.tan(Math.toRadians(lat)) + 1 / Math.cos(Math.toRadians(lat))) / Math.PI) / 2 * (1<<zoom) ) ;
     if (xtile < 0)
      xtile=0;
     if (xtile >= (1<<zoom))
      xtile=((1<<zoom)-1);
     if (ytile < 0)
      ytile=0;
     if (ytile >= (1<<zoom))
      ytile=((1<<zoom)-1);
     return("" + zoom + "/" + xtile + "/" + ytile);
    }


  /**
   * 瓦片获得范围
   * **/ 
  public static TileBox tile2boundingBox(final int x, final int y, final int zoom) {
    //BoundingBox bb = new BoundingBox();
    TileBox bb=new TileBox();
    bb.setYmax(tile2lat(y, zoom));
    bb.setYmin(tile2lat(y + 1, zoom));
    bb.setXmin( tile2lon(x, zoom)); 
    bb.setXmax(tile2lon(x + 1, zoom));
    return bb;
  }

  /**
   * 瓦片转换经度
   * **/
  public static double tile2lon(int x, int z) {
     return x / Math.pow(2.0, z) * 360.0 - 180;
  }

  /**
   *瓦片转换纬度
   * @author zhaoquanfeng 2018年8月13日 下午7:44:08
   * @param y
   * @param z
   * @return
   * @modify {原因} by zhaoquanfeng 2018年8月13日 下午7:44:08
   */
  public static double tile2lat(int y, int z) {
    double n = Math.PI - (2.0 * Math.PI * y) / Math.pow(2.0, z);
    return Math.toDegrees(Math.atan(Math.sinh(n)));
  }

在service层解释XYZ之后,将求得的extent作为参数传入Dao层对应的sql,即可以返回对应范围内的矢量切片数据
注意:mybaits对应文件里面,表示矢量切片的字段请jdbcType=”BINARY”,domain里面使用byte[]类型,这样数据能够对应的解出来;
sql的demo:

 SELECT
        ST_AsMVT (tile, 'points') tile
        FROM
        (
        SELECT 
        st_asmvtgeom (t.geom,
        st_makeenvelope (#{xmin,jdbcType=NUMERIC}, #{ymin,jdbcType=NUMERIC}, #{xmax,jdbcType=NUMERIC},#{ymax,jdbcType=NUMERIC}, 4326),
        4096,
        0,
        TRUE
        ) AS geom
        FROM
        testgeo t
        ) AS tile
        WHERE
        tile.geom IS NOT NULL;

4.前段输入对应矢量切片的服务地址,就可以进行渲染了,本人使用Mapbox事项矢量切片的加载,一百万的点数据速度还可以;

 map.on('load', function loaded() {
            map.addSource('custom-go-vector-tile-source', {
                type: 'vector',
                tiles: ['http://localhost:8052/hgis/vector/tiles/{z}/{x}/{y}']
            });
            // map.addLayer({
            //     id: 'background',
            //     type: 'background',
            //     paint: {
            //         'background-color': 'white'
            //     }
            // });
            map.addLayer({
                "id": "custom-go-vector-tile-layer",
                "type": "circle",
                "source": "custom-go-vector-tile-source",
                "source-layer": "points",
                paint: {
                    'circle-radius': {
                        stops: [
                            [8, 0.1],
                            [11, 0.5],
                            [15, 3],
                            [20, 60]
                        ]
                    },
                    'circle-color': {
                        property: 'v',
                        stops: [
                            [0, '#990055'],
                            [1, '#2a55b9']
                        ]

                    },
                    'circle-opacity': 1
                }
            });
        });

转载自:https://blog.csdn.net/zhaoquanfeng/article/details/81874270

You may also like...