ベクトルタイルを作成する方法
はじめに
maplibreなどjavascriptの地図ライブラリで読み込めるベクトルタイルを作成する方法をC#で行う方法とPostGISで行う方法の2パターン紹介します
下準備
PostGISの設置
簡単にdocker-composeを使用してPostGISを用意します
services:
postgis:
image: postgis/postgis
environment:
POSTGRES_DB: vector_tile_example
POSTGRES_USER: example
POSTGRES_PASSWORD: example
ports:
- "5432:5432"
テストデータ作成
ツールでPostGISに接続し、下記のSQLを実行しサンプルデータを作成します
create table
points (
id serial,
name varchar(100),
location geometry,
primary key (id)
);
insert into
points
(
name,
location
)
values
('東京駅', st_geomfromtext('POINT(139.76769903733918 35.68139335470638)', 4326)),
('東京タワー', st_geomfromtext('POINT(139.74540410167575 35.658607404547354)', 4326)),
('東京スカイツリー', st_geomfromtext('POINT(139.81069924696237 35.710000524374095)', 4326))
プロジェクト作成と必要パッケージのインストール
いずれの方法にせよPostgreSQLを扱うパッケージが必要なので、Nugetでインストールします
Npgsql
以上で準備は完了です
C#でベクトルタイルを作成する方法
処理の流れとしては、データベースで空間検索を行い取得したデータを元にベクトルタイルを作成します
この方法のメリットはPostGIS以外のデータベース(SQLServerやMySQLなど)でもベクトルタイルが作成可能な点です
必要なパッケージ
Nugetで以下のパッケージをインストールします
NetTopologySuite.IO.VectorTiles.Mapbox
処理詳細
かなり大雑把な例として下記のようなアクションを作成します
[Route("tile/test1/{z}/{x}/{y}")]
public IActionResult Test1(int x, int y, int z)
{
var tile = new Tile(x, y, z);
var vectorTile = new VectorTile(){ TileId = tile.Id };
var layer = new Layer(){ Name = "points"};
using var connection = new NpgsqlConnection("server=localhost;port=5432;username=example;password=example;database=vector_tile_example");
connection.Open();
using var cmd = connection.CreateCommand();
cmd.CommandText =
"SELECT ST_ASTEXT(location) AS geom, name FROM points WHERE ST_INTERSECTS(location, ST_TRANSFORM(ST_TILEENVELOPE(@z, @x, @y), 4326))";
cmd.Parameters.Add(new NpgsqlParameter("x", x));
cmd.Parameters.Add(new NpgsqlParameter("y", y));
cmd.Parameters.Add(new NpgsqlParameter("z", z));
var wktReader = new WKTReader();
using var reader = cmd.ExecuteReader();
while (reader.Read())
{
var geom = wktReader.Read((string)reader["geom"]);
var attribute = new AttributesTable(new Dictionary<string, object>()
{
{ "name", (string)reader["name"] }
});
layer.Features.Add(new Feature(geom, attribute));
}
vectorTile.Layers.Add(layer);
using var ms = new MemoryStream();
vectorTile.Write(ms);
return File(ms.ToArray(), "application/pbf", $"{z}.{x}.{y}.pbf");
}
最初のこの部分でベクトルタイルのインスタンスを作成します
ここでレイヤー名をpointsとしていますが、実際にmaplibreから読み込むときにこの名前が必要となります
var tile = new Tile(x, y, z);
var vectorTile = new VectorTile(){ TileId = tile.Id };
var layer = new Layer(){ Name = "points"};
次にこの部分では、タイルに含まれるデータを空間検索しています
ここでポイントとなるのはST_TILEENVELOPE
です
x,y,zで矩形のポリゴンを作成してくれます
ただし、SRIDが3857なので今回のケースではST_TRANSFORM
で4326に変換する必要があります
using var connection = new NpgsqlConnection("server=localhost;port=5432;username=example;password=example;database=vector_tile_example");
connection.Open();
using var cmd = connection.CreateCommand();
cmd.CommandText =
"SELECT ST_ASTEXT(location) AS geom, name FROM points WHERE ST_INTERSECTS(location, ST_TRANSFORM(ST_TILEENVELOPE(@z, @x, @y), 4326))";
cmd.Parameters.Add(new NpgsqlParameter("x", x));
cmd.Parameters.Add(new NpgsqlParameter("y", y));
cmd.Parameters.Add(new NpgsqlParameter("z", z));
var wktReader = new WKTReader();
using var reader = cmd.ExecuteReader();
最後に取得したデータからGeometryを作成しFeatureを作成し、前段で作成したlayerに追加していきます
バイナリとして出力すれば完了です
今回はGeometry型のカラムのlocationを一度ST_ASTEXT
で文字列として取得し、再度WKTReader
でGeometryに復元するという二度手間のような処理をしていますが、EntityFrameworkなどを使用し直接Geometryとして取得できる場合はこの処理は不要です
while (reader.Read())
{
var geom = wktReader.Read((string)reader["geom"]);
var attribute = new AttributesTable(new Dictionary<string, object>()
{
{ "name", (string)reader["name"] }
});
layer.Features.Add(new Feature(geom, attribute));
}
vectorTile.Layers.Add(layer);
using var ms = new MemoryStream();
vectorTile.Write(ms);
return File(ms.ToArray(), "application/pbf", $"{z}.{x}.{y}.pbf");
PostGISでベクトルタイルを作成する方法
SQLクエリだけで完結するのでC#で作成する場合よりも簡潔です
追加で必要なパッケージもありません
処理詳細
C#の場合と同様、かなり大雑把な例として下記のようなアクションを作成します
[Route("tile/test2/{z}/{x}/{y}")]
public IActionResult Test2(int x, int y, int z)
{
using var connection = new NpgsqlConnection("server=localhost;port=5432;username=example;password=example;database=vector_tile_example");
connection.Open();
using var cmd = connection.CreateCommand();
cmd.CommandText =
"SELECT ST_AsMvt(t.*, 'points') FROM (SELECT ST_AsMVTGeom(location, ST_TRANSFORM(ST_TILEENVELOPE(@z, @x, @y), 4326)) AS geom, name FROM points WHERE ST_INTERSECTS(location, ST_TRANSFORM(ST_TILEENVELOPE(@z, @x, @y), 4326))) AS t";
cmd.Parameters.Add(new NpgsqlParameter("x", x));
cmd.Parameters.Add(new NpgsqlParameter("y", y));
cmd.Parameters.Add(new NpgsqlParameter("z", z));
using var reader = cmd.ExecuteReader();
var data = new List<byte>();
while (reader.Read())
{
if (reader[0] is byte[] b)
{
data.AddRange(b);
}
}
return File(data.ToArray(), "application/pbf", $"{z}.{x}.{y}.pbf");
}
データベースから取得したデータを結合してクライアントに返します
ポイントとなるのはSQLクエリのみです
SELECT
ST_AsMvt(t.*, 'points')
FROM
(
SELECT
ST_AsMVTGeom(location, ST_TRANSFORM(ST_TILEENVELOPE(@z, @x, @y), 4326)) AS geom,
name
FROM
points
WHERE
ST_INTERSECTS(location, ST_TRANSFORM(ST_TILEENVELOPE(@z, @x, @y), 4326))
) AS t
空間検索(where句)の部分はC#の場合と全く同じです
ポイントは ST_AsMVTGeom と ST_AsMvtです
ST_AsMVTGeom
でタイルでGeometryを切り抜きしつつベクトルタイルの空間座標に変換します
その後ST_AsMvt
でベクトルタイルとして読み込めるバイナリに変換します
第2引数のpointsはレイヤー名です
動作確認
簡単なHTMLを作成します
C#版を表示するようにしています
コメントアウトを外してPostGIS版を確認することもできます
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.js"></script>
<link href="https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.css" rel="stylesheet" />
</head>
<body>
<div id="map" style="width: 500px; height: 500px"></div>
<script>
var map = new maplibregl.Map({
container: 'map',
style: {
version: 8,
sources: {
bg: {
type: 'raster',
tiles: ['https://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png'],
tileSize: 256,
attribution:
'<a href="http://www.gsi.go.jp/kikakuchousei/kikakuchousei40182.html" target="_blank">地理院タイル</a>',
},
csharp: {
type: 'vector',
tiles: ['http://localhost:5269/tile/test1/{z}/{x}/{y}']
},
postgis: {
type: 'vector',
tiles: ['http://localhost:5269/tile/test2/{z}/{x}/{y}']
}
},
layers: [
{
id: 'bg',
type: 'raster',
source: 'bg',
minzoom: 0,
maxzoom: 18,
},
// C#版
{
id: 'csharp',
type: 'circle',
source: 'csharp',
'source-layer': 'points',
minzoom: 0,
maxzoom: 18,
paint:{
'circle-color': '#BB0000',
'circle-radius': 10
}
},
// PostGIS版
/*{
id: 'postgis',
type: 'circle',
source: 'postgis',
'source-layer': 'points',
minzoom: 0,
maxzoom: 18,
paint:{
'circle-color': '#BB0000',
'circle-radius': 10
}
},*/
],
},
center: [139.76769903733918, 35.68139335470638], // starting position [lng, lat]
zoom: 9 // starting zoom
});
</script>
</body>
</html>
最後に
C#でベクトルタイルを作成する場合はPostGISでなくても可能です
またGeoJSONなどテキストファイルでも可能です
一方、PostGISで作成する場合はPHPやRubyなどC#以外の言語でも可能です
状況に合わせて使い分けることが可能です
今回のソースはGitHubに置いてあります
Discussion