はじめに
「サーバーレスなタイルサーバー」とかいう1行で矛盾しているタイトルでお送りします。
さて、最近話題に事欠かないPMTilesですが、そのユースケースは大体、クライアントからPMTilesが配信されているサーバーへ直接リクエストしてタイルデータを得る、というパターンです。最もシンプルにタイル配信出来るので、間違いなくベストプラクティスと言えるでしょう。
本記事ではあえて、PMTilesを用いた「タイルサーバー」を作る方法と意図を述べます。
タイルサーバーとは
本記事のタイルサーバーとは、{z}/{x}/{y}
のリクエストに対してタイルデータをレスポンスするサーバーのことを指すこととします。
たとえば所定のディレクトリ構造で配置したタイルデータを配信するウェブサーバーは、まさにタイルサーバーと言えます。PMTiles以前の、静的タイルの配信のベストプラクティスでした。
全球など広範囲のタイルになるとファイルの数が膨大になってしまい、ファイルの生成・アップロードに大きなコストを要するため、MBTilesを作成してタイルを配信するサーバーを実装することもありました。サーバーレスの文脈では、ジオロニアさんのtileserverlessが記憶にあります。
PMTilesでサーバーレスなタイルサーバー
tileserverlessはAWS Lambdaを用いたサーバーレス実装ですが、MBTilesを利用しているのでEFSが必要なのがネックです(実体がSQLiteでありファイルシステムが必要であるため)。PMTilesなら、たとえばS3に置いておき、LambdaからRange-Requestを行うことが出来ます。EFSがいらなくなって、もっとサーバーレスになってうれしい。以下はHonoによる実装例(かなり端折ってますが本質部分は読めるはず)。
import { Hono } from 'hono';
import { PMTiles } from 'pmtiles';
const app = new Hono();
app.get('/tiles/:id/:z/:x/:y', async (c) => {
const id = c.req.param('id'); // タイルの識別子
const z = Number(c.req.param('z'));
const x = Number(c.req.param('x'));
const y = Number(c.req.param('y'));
const url = await getPresignedUrl(id); // S3の署名付きURLを得る
const pmtiles = new PMTiles(url);
const tile = await pmtiles.getZxy(z, x, y);
if (tile === undefined) return c.text('tile not found', 404);
return c.body(Buffer.from(tile.data), 200, {
'Content-Type': 'application/vnd.mapbox-vector-tile',
});
});
他のサービスと組み合わせると以下のような構成になります。
Lambda->S3はけっこう速いので、タイルのレスポンスも速いです、うれしい。CloudFrontでキャッシュしてあげるともっとうれしくなります。CloudFromt->Lambda
という動線は、Function URLを使うと実現出来ます。API Gatewayが必要なくなりうれしいです。
上記はNode.js + Lambdaのコード例ですが、筆者はCloudflare WorkersとR2でも動かしたりしています。安くて良いです(が、AWSの方が速いです)。
PMTilesなのになぜサーバー?という問いに対しての回答:
- クライアントにPMTilesの実装を要求する
- キャッシュとか認証とか入れたい
前者はわかりやすくて、例えばPMTilesを読む事例ではMapLibre GL JSがよく登場しますが、MapLibre GL JSはPMTilesをサポートしていません(プラグインが実装されているだけ)。同じく、QGISもまだPMTilesを読むことは出来ません(いずれ出来るようになるでしょうけど)。特殊な実装を要求するという点は明確なデメリットです。
後者はあまり思いつかなかったのでこじつけですが、サーバー実装が必要なときってありますよね。
追記:
PMTilesの実体を隠せるので、タイルデータ全体をダウンロードされてしまうとかいう事態を避けられることもメリットのひとつですね