336 lines
11 KiB
Markdown
336 lines
11 KiB
Markdown
---
|
||
title: PostGIS 矢量瓦片
|
||
date: 2020-09-11
|
||
author: ac
|
||
tags:
|
||
- vector tile
|
||
categories:
|
||
- Database
|
||
---
|
||
|
||

|
||
|
||
### Martin - 基于PostGIS的矢量瓦片服务器
|
||
|
||
### 1. 简介
|
||
|
||
目前流行的矢量瓦片的切图方案:
|
||
|
||
- `mapbox gl` + [tippecanoe](https://github.com/mapbox/tippecanoe) :v2收费,tippecanoe是mapbox官方推荐的矢量瓦片静态生成工具 ,适用于大数据量场景,且不频繁更新的空间数据;
|
||
- `openlayers` + `geoserver` :开源,使用geoserver的矢量瓦片扩展,增加矢量瓦片的输出格式;
|
||
- `maplibre`+ `Martin` +[postgis](https://www.postgis.net/docs/manual-dev/en/ST_AsMVT.html) :开源,martin是矢量切片服务器,通过postgis的函数动态生成矢量瓦片。
|
||
- `maptiler`:收费
|
||
|
||
> mapbox v2 必须使用 access token 才能初始化 Map 对象。进行token计算,每月50000免费次数。
|
||
|
||
### 2. Martin
|
||
|
||
`Martin`是一个开源的PostGIS矢量切片服务器,可以从任何PostGIS表或视图中创建`MVT`矢量切片,也可以从 PMTile 和MBTile文件中动态生成矢量瓦片,是使用 `Rust`编写,针对切片速度和大流量进行了优化,是极快且轻量级的切片服务器。
|
||
|
||
#### 2.1 安装
|
||
|
||
> 如果将Martin和PostgreSQL一起使用,PostGIS版本必须为v3.0+
|
||
|
||
Martin支持Linux、macOS、windows平台和docker环境,本例采用windows环境。先从github上下载[martin-Windows-x86_64](https://github.com/maplibre/martin/releases/latest/download/martin-Windows-x86_64.zip) 。解压后会发现这两个exe:
|
||
|
||
```yaml
|
||
─martin-Windows-x86_64
|
||
├─martin.exe
|
||
└─mbtiles.exe
|
||
```
|
||
|
||
windows平台可以直接使用`martin.exe`来启动切片服务器,可以先在命令行查看一下参数:
|
||
|
||
```shell
|
||
D:\tools\vectorTileTool\martin-Windows-x86_64>martin.exe --help
|
||
Blazing fast and lightweight tile server with PostGIS, MBTiles, and PMTiles support
|
||
|
||
Usage: martin.exe [OPTIONS] [CONNECTION]...
|
||
|
||
Arguments:
|
||
[CONNECTION]... Connection strings, e.g. postgres://... or /path/to/files
|
||
|
||
Options:
|
||
-c, --config <CONFIG>
|
||
Path to config file. If set, no tile source-related parameters are allowed
|
||
--save-config <SAVE_CONFIG>
|
||
Save resulting config to a file or use "-" to print to stdout. By default, only print if sources are auto-detected
|
||
-s, --sprite <SPRITE>
|
||
Export a directory with SVG files as a sprite source. Can be specified multiple times
|
||
-k, --keep-alive <KEEP_ALIVE>
|
||
Connection keep alive timeout. [DEFAULT: 75]
|
||
-l, --listen-addresses <LISTEN_ADDRESSES>
|
||
The socket address to bind. [DEFAULT: 0.0.0.0:3000]
|
||
-W, --workers <WORKERS>
|
||
Number of web server workers
|
||
-b, --disable-bounds
|
||
Disable the automatic generation of bounds for spatial PG tables
|
||
--ca-root-file <CA_ROOT_FILE>
|
||
Loads trusted root certificates from a file. The file should contain a sequence of PEM-formatted CA certificates
|
||
-d, --default-srid <DEFAULT_SRID>
|
||
If a spatial PG table has SRID 0, then this default SRID will be used as a fallback
|
||
-p, --pool-size <POOL_SIZE>
|
||
Maximum connections pool size [DEFAULT: 20]
|
||
-m, --max-feature-count <MAX_FEATURE_COUNT>
|
||
Limit the number of features in a tile from a PG table source
|
||
-h, --help
|
||
Print help
|
||
-V, --version
|
||
Print version
|
||
```
|
||
|
||
#### 2.2 使用
|
||
|
||
> 本例pg安装postgis是v3.3.3.1
|
||
|
||
`martin`连接pg可以用`connection_string`(命令行参数)和配置文件两种形式。
|
||
|
||
PostgreSQL连接字符串的形式:
|
||
|
||
```shell
|
||
# martin.exe postgresql://user:password@host/db
|
||
D:\tools\vectorTileTool\martin-Windows-x86_64>martin.exe postgresql://postgres:123@127.0.0.1:5433/postgres
|
||
[2023-08-08T03:50:11Z INFO martin] Starting Martin v0.8.7
|
||
[2023-08-08T03:50:11Z INFO martin] Config file is not specified, auto-detecting sources
|
||
[2023-08-08T03:50:11Z INFO martin::pg::pool] Connecting to postgresql://postgres:123@127.0.0.1:5433/postgres
|
||
[2023-08-08T03:50:11Z INFO martin::pg::configurator] Discovered source zhujiang_river from table public.zhujiang_river with geom column (MULTIPOLYGON, SRID=4326)
|
||
[2023-08-08T03:50:11Z INFO martin] Use --save-config to save or print Martin configuration.
|
||
[2023-08-08T03:50:11Z INFO martin] Martin has been started on 0.0.0.0:3000.
|
||
[2023-08-08T03:50:11Z INFO martin] Use http://0.0.0.0:3000/catalog to get the list of available sources.
|
||
|
||
```
|
||
|
||
配置文件的形式 config.yaml:
|
||
|
||
```yaml
|
||
# Connection keep alive timeout [default: 75]
|
||
keep_alive: 75
|
||
|
||
# The socket address to bind [default: 0.0.0.0:3000]
|
||
listen_addresses: '127.0.0.1:3000'
|
||
|
||
# Number of web server workers
|
||
worker_processes: 8
|
||
# Database configuration. This can also be a list of PG configs.
|
||
postgres:
|
||
# Database connection string. You can use env vars too, for example:
|
||
# $DATABASE_URL
|
||
# ${DATABASE_URL:-postgresql://postgres@localhost/db}
|
||
connection_string: 'postgresql://postgres@localhost:5433/postgres?sslmode=disable&user=postgres&password=123'
|
||
|
||
# Same as PGSSLCERT for psql
|
||
#ssl_cert: './postgresql.crt'
|
||
# Same as PGSSLKEY for psql
|
||
#ssl_key: './postgresql.key'
|
||
# Same as PGSSLROOTCERT for psql
|
||
#ssl_root_cert: './root.crt'
|
||
|
||
# If a spatial table has SRID 0, then this SRID will be used as a fallback
|
||
default_srid: 4326
|
||
|
||
# Maximum connections pool size [default: 20]
|
||
pool_size: 20
|
||
|
||
# Limit the number of table geo features included in a tile. Unlimited by default.
|
||
max_feature_count: 1000
|
||
|
||
# Control the automatic generation of bounds for spatial tables [default: false]
|
||
# If enabled, it will spend some time on startup to compute geometry bounds.
|
||
disable_bounds: false
|
||
```
|
||
|
||
```shell
|
||
D:\tools\vectorTileTool\martin-Windows-x86_64>martin.exe -c config.yaml
|
||
[2023-08-08T03:54:43Z INFO martin] Starting Martin v0.8.7
|
||
[2023-08-08T03:54:43Z INFO martin] Using config.yaml
|
||
[2023-08-08T03:54:43Z INFO martin::pg::pool] Connecting to postgresql://postgres@localhost:5433/postgres?sslmode=disable&user=postgres&password=123
|
||
[2023-08-08T03:54:43Z INFO martin::pg::configurator] Discovered source zhujiang_river from table public.zhujiang_river with geom column (MULTIPOLYGON, SRID=4326)
|
||
[2023-08-08T03:54:44Z INFO martin] Use --save-config to save or print Martin configuration.
|
||
[2023-08-08T03:54:44Z INFO martin] Martin has been started on 127.0.0.1:3000.
|
||
[2023-08-08T03:54:44Z INFO martin] Use http://127.0.0.1:3000/catalog to get the list of available sources.
|
||
```
|
||
|
||
本例采用的是配置文件的形式启动服务器,服务地址是`http://127.0.0.1:3000`。
|
||
|
||
从启动的日志可以看到`Martin`将所连接的pg库中的`zhujiang_river`表发布为数据源。
|
||
|
||
> Martin 会将所有表发布为数据源(如果它们至少有一个几何列)。如果 SRID 为 0,则必须设置默认 SRID,否则该地理列/表将被忽略。所有非几何表列都将发布为矢量切片要素标签(属性)。
|
||
|
||

|
||
|
||
打开目录可以看到已发布的数据源:
|
||
|
||

|
||
|
||

|
||
|
||
数据表的元数据是符合`TileJSON 3.0.0`标准的JSON文件。
|
||
|
||
`Martin`为每张要素表都提供一个`TileJSON` endpoint。
|
||
|
||
> TileJSON是表示地图元数据的开放标准,endpoint这个词以前经常被用来描述进程间通信。
|
||
|
||
|
||
|
||
#### 2.3 mapbox
|
||
|
||
发布完数据源后,使用mapbox gl 来调用一下:
|
||
|
||
```html
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Document</title>
|
||
<script src="https://unpkg.com/mapbox-gl@1.9.1/dist/mapbox-gl.js"></script>
|
||
<link href="https://unpkg.com/mapbox-gl@1.9.1/dist/mapbox-gl.css"/>
|
||
<style>
|
||
#map{
|
||
height: 100vh;
|
||
width: 100%;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="map"></div>
|
||
<script>
|
||
|
||
const TIANDITU_URL = 'http://t0.tianditu.gov.cn';
|
||
const tdtKey = "your_token";
|
||
const tdtConfig = {
|
||
code: 'tdt_img',
|
||
name: '天地图影像',
|
||
source: {
|
||
type: 'raster',
|
||
tiles: [
|
||
`${TIANDITU_URL}/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=${tdtKey}`
|
||
],
|
||
tileSize: 256,
|
||
minzoom: 0,
|
||
maxzoom: 22
|
||
},
|
||
layer: {
|
||
id: 'tdt-img-tiles',
|
||
type: 'raster',
|
||
minzoom: 0,
|
||
maxzoom: 22,
|
||
layout: { visibility: 'visible' }
|
||
}
|
||
}
|
||
let map = new mapboxgl.Map({
|
||
container:'map',
|
||
style:{
|
||
"version":8,
|
||
"sources":{},
|
||
"layers":[],
|
||
glyphs: "mapbox://fonts/mapbox/{fontstack}/{range}.pbf",
|
||
},
|
||
center: [112.39747, 22.908823], // 广东
|
||
zoom: 8, // starting zoom 地图初始的拉伸比例
|
||
pitch: 0, // 地图的角度,不写默认是0,取值是0-85度,一般在3D中使用
|
||
bearing: 0, // 地图的初始方向,值是北的逆时针度数,默认是0,即是正北
|
||
antialias: true, // 抗锯齿,通过false关闭提升性能
|
||
minZoom: 3,
|
||
maxZoom: 17.36
|
||
});
|
||
map.on('load', () => {
|
||
// 天地图底图加载
|
||
addLayerConfig(tdtConfig);
|
||
addMvt();
|
||
});
|
||
function addLayerConfig(layerConfig){
|
||
const {code, source,layer} = layerConfig;
|
||
map.addSource(code, source);
|
||
const layerParams = {
|
||
...layer,
|
||
source: code
|
||
};
|
||
// 添加图层
|
||
map.addLayer(layerParams);
|
||
}
|
||
function addMvt(){
|
||
map.addLayer({
|
||
id: 'lines',
|
||
type: 'fill',
|
||
source: {
|
||
type: 'vector',
|
||
url: 'http://localhost:3000/zhujiang_river'
|
||
},
|
||
'source-layer': 'zhujiang_river',
|
||
paint: {
|
||
'fill-color': 'red'
|
||
}
|
||
});
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
加载效果:
|
||
|
||

|
||
|
||
|
||
|
||
#### 2.4 openlayers
|
||
|
||
> **由于postgis 切片生成的是mvt格式图片,所以只能使用web墨卡托坐标系**
|
||
|
||
|
||
|
||
### 3.应用
|
||
|
||
#### 3.1 docker安装
|
||
|
||
```shell
|
||
[root@localhost martin]# pwd
|
||
/home/martin
|
||
# 下载martin镜像
|
||
[root@localhost martin]# docker pull ghcr.io/maplibre/martin:main
|
||
main: Pulling from maplibre/martin
|
||
7264a8db6415: Pull complete
|
||
8b9d286dcf48: Pull complete
|
||
ada398a88aa0: Pull complete
|
||
Digest: sha256:13b38133bc15fd1d8b9b9444ca28e0380146799f1faa4c1f418090f067d48474
|
||
Status: Downloaded newer image for ghcr.io/maplibre/martin:main
|
||
ghcr.io/maplibre/martin:main
|
||
|
||
|
||
```
|
||
|
||
|
||
|
||
|
||
|
||
### 4. 遇到的问题
|
||
|
||
|
||
|
||
局限性:只能连接一个pg库。
|
||
|
||
对于有分库的系统,可以使用Nginx来转发。
|
||
|
||
#### 4.1 表没有空间索引
|
||
|
||
`Table public.table_name has no spatial index on column geom`
|
||
|
||
```sql
|
||
CREATE INDEX table_name_geom_idx
|
||
ON table_name
|
||
USING GIST (geom);
|
||
```
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
### 参考文章
|
||
|
||
[1] Martin https://martin.maplibre.org/
|
||
|
||
[2] Martin Tile Server Documentation https://maplibre.org/martin/installation.html |