概述:
本文将介紹如何基于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
}
});
});
代碼位址:矢量切片代碼git位址