概要
通常、ベクトルタイルはGeoJSONなどから変換した静的なファイルとして配信されることが多いです(.pbfファイルの配信)。パフォーマンスやサーバーのコストの観点ではベストな方法と言えますが、いくつか典型的な問題があります。
- 変換に大きなコストがかかる(特にplanet級の、タイル数が膨大な場合に問題となる)。
- データの追加・削除や更新が苦手
そんなわけで、データベースから動的にタイルを配信出来ることはとても重要です。しかしこのアプローチにもパフォーマンス面などで課題があるため、どの方法をなぜ採用するのか、よく検討する必要があります。
FOSS4Gの世界のデータベースといえばPostGISです。世間では、PostGISのテーブルからベクトルタイルを動的に配信するためのアプリケーションがいくつか実装されています。
martinとpg_tileservはPostGISのST_AsMVT
関数を使っていて、ほかはそうではないというのが、大きな違いのひとつです。martinとpg_tileservは他より新しい実装であるためです。
本記事ではこのうち、tegolaとmartintとpg_tileservを比較します。
PostGIS + ベクトルタイルサーバーを設定
PostgreSQL
Dockerを使って、PostgreSQLを起動、データを投入します。
(以降、断りなくdocker-compose.yml形式で記述します)
postgis:
image: kartoza/postgis:12.4
environment:
- POSTGRES_USER=docker
- POSTGRES_PASS=docker
- POSTGRES_DB=postgres
ports:
- "5432:5432"
volumes:
- postgis-data:/var/lib/postgresql
- ./postgres:/usr/src/app
networks:
- default
healthcheck:
test: [ "CMD-SHELL", "pg_isready" ]
interval: 10s
timeout: 5s
retries: 5
PostgreSQLを起動...
docker-compose up -d postgis
次にデータを投入します。今回はgeofabrikの関東の建物・道路データを利用しました。
wget https://download.geofabrik.de/asia/japan/kanto-latest-free.shp.zip
unzip kanto-latest-free.shp.zip
ogr2ogr PG:postgresql://docker:docker@localhost:5432/postgres gis_osm_roads_free_1.shp
ogr2ogr PG:postgresql://docker:docker@localhost:5432/postgres gis_osm_buildings_a_free_1.shp -nlt MultiPolygon
投入が完了しました。
SELECT * FROM gis_osm_roads_free_1 LIMIT 10;
SELECT * FROM gis_osm_buildings_a_free_1 LIMIT 10;
以降は、関東の建物・道路データをベクトルタイルで配信する設定をしていきます。
tegola
tegola:
image: gospatial/tegola
ports:
- "8080:8080"
depends_on:
postgis:
condition: service_healthy
volumes:
- ./tegola:/opt/tegola_config
command: --config /opt/tegola_config/config.toml serve
networks:
- default
tegola
はconfig.toml
なる設定ファイルを使用します。この設定ファイルの定義に基づき、PostgreSQLのテーブルをベクトルタイルとして配信します。
# /tegola/config.toml
[webserver]
port = ":8080"
CORSAllowedOrigin = "*"
# register data providers
[[providers]]
name = "japan" # provider name is referenced from map layers (required)
type = "postgis" # the type of data provider. currently only supports postgis (required)
host = "postgis" # postgis database host (required)
port = 5432 # postgis database port (required)
database = "postgres" # postgis database name (required)
user = "docker" # postgis database user (required)
password = "docker" # postgis database password (required)
srid = 4326 # The default srid for this provider. If not provided it will be WebMercator (3857)
[[providers.layers]]
name = "roads"
geometry_fieldname = "wkb_geometry"
id_fieldname = "ogc_fid"
tablename = "gis_osm_roads_free_1"
[[providers.layers]]
name = "buildings"
geometry_fieldname = "wkb_geometry"
id_fieldname = "ogc_fid"
tablename = "gis_osm_buildings_a_free_1"
[[maps]]
name = "japan"
center = [139.72120, 35.73273, 11.0] # set the center of the map so the user is auto navigated to Bonn
[[maps.layers]]
provider_layer = "japan.buildings"
min_zoom = 5
max_zoom = 20
[[maps.layers]]
provider_layer = "japan.roads"
min_zoom = 5
max_zoom = 20
これらの設定により、ベクトルタイルがhttp://localhost:8080/maps/japan/{z}/{x}/{y}.vector.pbf
として配信されます。
martin
martin:
image: urbica/martin
restart: unless-stopped
ports:
- 3000:3000
depends_on:
postgis:
condition: service_healthy
networks:
- default
volumes:
- ./martin:/opt/martin_config
command: martin --config /opt/martin_config/config.yaml
martinもtegolaと同じような設定ファイルを用います。
# The socket address to bind [default: 0.0.0.0:3000]
listen_addresses: '0.0.0.0:3000'
# Database connection string
connection_string: 'postgres://docker:docker@postgis/postgres'
# Maximum connections pool size [default: 20]
pool_size: 20
# Connection keep alive timeout [default: 75]
keep_alive: 75
# Number of web server workers
worker_processes: 8
# If a spatial table has SRID 0, then this default SRID will be used as a fallback
default_srid: 4326
# Enable watch mode
watch: false
# Trust invalid certificates. This introduces significant vulnerabilities, and should only be used as a last resort.
danger_accept_invalid_certs: true
# Associative arrays of table sources
table_sources:
public.buildings:
# Table source id (required)
id: public.gis_osm_buildings_a_free_1
# Table schema (required)
schema: public
# Table name (required)
table: gis_osm_buildings_a_free_1
# Geometry SRID (required)
srid: 4326
# Geometry column name (required)
geometry_column: wkb_geometry
# Feature id column name
id_column: ogc_fid
# An integer specifying the minimum zoom level
minzoom: 0
# An integer specifying the maximum zoom level. MUST be >= minzoom
maxzoom: 30
# The maximum extent of available map tiles. Bounds MUST define an area
# covered by all zoom levels. The bounds are represented in WGS:84
# latitude and longitude values, in the order left, bottom, right, top.
# Values may be integers or floating point numbers.
bounds: [-180.0, -90.0, 180.0, 90.0]
# Tile extent in tile coordinate space
extent: 4096
# Buffer distance in tile coordinate space to optionally clip geometries
buffer: 64
# Boolean to control if geometries should be clipped or encoded as is
clip_geom: true
# Geometry type
geometry_type: GEOMETRY
# List of columns, that should be encoded as tile properties (required)
properties:
ogc_fid: integer
name: string
fclass: string
public.roads:
id: public.gis_osm_roads_free_1
schema: public
table: gis_osm_roads_free_1
srid: 4326
geometry_column: wkb_geometry
id_column: ogc_fid
minzoom: 0
maxzoom: 30
bounds: [-180.0, -90.0, 180.0, 90.0]
extent: 4096
buffer: 64
clip_geom: true
geometry_type: GEOMETRY
properties:
ogc_fid: integer
name: string
fclass: string
maxspeed: smallint
ベクトルタイルはhttp://localhost:3000/public.roads,public.buildings/{z}/{x}/{y}.pbf
で配信されます.
pg_tileserv
pg_tileserv:
image: pramsey/pg_tileserv
container_name: pg_tileserv
environment:
- DATABASE_URL=postgres://docker:docker@postgis/postgres
depends_on:
- postgis
ports:
- 7800:7800
pg_tileservはゼロコンフィグで起動でき、自動的にPostgreSQLのテーブルをベクトルタイルとして配信してくれることは、特筆すべき点です。
ベクトルタイルはhttp://localhost:7800/public.gis_osm_buildings_a_free_1,public.gis_osm_roads_free_1/{z}/{x}/{y}.vector.pbf
で配信されます.
比較
それではベクトルタイルサーバーを起動しましょう。
docker-compose up
QGISで、下記のようにベクトルタイルレイヤーを追加することができます。
- Vector Tilesで「新規一般接続」
- データの存在する領域を表示
- ベクトルタイルレイヤーを追加
3つのレイヤーは全く同じ見た目で表示されます(色は自動で設定されますが)。全て同一のデータ(PostgreSQLのテーブル)を参照しているので当然といえば当然です。しかしながら、martinとpg_tileservはtegolaより遥かに高速にレスポンスされることがわかると思います。
パフォーマンスの差の原因は?
tegolaやmartin/pg_tileservの詳細な実装の違いはわからないですが、martin/pg_tileservはST_AsMVT
を使っていて、おそらくこの関数が速いのだと思います。
調べた限り、ST_AsMVT
は2018年に実装されたのち、2019年にパフォーマンスの改善があったらしいです。
https://www.crunchydata.com/blog/waiting-for-postgis-3-st_asmvt-performance
(最新のtegolaではST_AsMVT
に対応しているらしいのですが、エラーが解消出来ませんでした)
加えて、martinは明らかにpg_tileservよりも速いです。原因は定かではないですが、martinはRustで書かれていてかつ、Actixという高速なwebサーバーで動作しているというのが、理由のひとつかもしれません。
結論
- PostGISベクトルタイルサーバーではmartinが最速
- 次点はpg_tileserv、ゼロコンフィグでとても使いやすい
- これらはローカルで実行している限りは実用的な速度が出るが、タイル化するズームレベルは調整が必要でしょ
ソースコード