スマホでPMTilesを利用する

これはFOSS4G Advent Calendar 2023の記事です。

野外調査のための地図アプリ「EcorisMap」を作ってます。そのアプリでPMTilesを読み込めるようにしたので、それについてのお話です。

なぜPMTilesが必要か?

 調査で使う地図は、地理院地図のような下図に調査地点など必要なデータを重ねて持っていきます。下図は、地理院さんが全国レベルでタイル配信しており、ありがたくアプリでも利用させてもらっています。一方、調査地点などのデータは、自分で用意するもので、ファイルサイズも大きくならないのでGeoJSONにしてアプリにインポートすることで利用することができます。
 しかし、問題はそのどちらでもないデータで、ファイルサイズもある程度大きいもののタイル配信されていないデータです。例えば、植生図のポリゴンとか河川距離標のラインデータなどです。こういうデータは市町村〜県レベルの範囲で整備されており、そのままGeoJSONとしてインポートすると重くてアプリが動作しなくなってしまいます。調査に必要な範囲だけを切り出せば良いのですが、調査範囲が広い場合はそうもいきません。
 そういう場合はどうするかと言うと、ベクタデータをラスタ形式の地図タイルに変換します。手順としては、まず、ベクタデータをQGISで色を付けたりラベルを表示させたりしてスタイリングします。次にそれを一枚のGeoTIFFとして書き出します(地図は重ねずデータ以外は透過)そしてgdal2tilesで必要なズームレベルでタイルに切り出し、サーバーにアップします。この方法でデータを地図タイルとして読むことはできますが、ファイルの数とサイズが増えてしまいサーバーのストレージも消耗してしまいます。

PMTilesとは?

 そこでPMTilesの出番です。PMTilesとは、簡単に言うとズームレベルごとにベクタデータをベクタ形式のままタイル状に切り出して一つのファイルにまとめたものです。似たようなものにmbtilesと言うものがあるのですが、mbtilesはデータの配信に専用のタイルサーバーが必要になりますが、PMTilesはファイルサーバーに置くだけで良いです。
 PMTilesだとベクタ形式のままなのでファイルサイズは大きくならずファイル数も一つなので取り扱いも簡単です。そして専用のサーバーも不要なのでホームページ用のファイルサーバーやGithub PagesやAmazon S3のような静的ホスティングサービスだけで配信できます。
 アプリではPMTilesから表示範囲だけのベクタタイルを取得してレンダリングの処理をすれば良いので、広範囲のデータであっても重くなりません。さらに、色やラベルなどのスタイルはユーザー側で設定できるようになります。
 気になるのは、ベクタタイルをPMTilesから取得する仕組みですが、HTTP Range RequestsというHTTPでファイルの一部分だけを取得する方法を利用しています。PMTilesは、各ズームレベルのX,Yタイルをヒルベクト曲線に基づいてバイナリファイルに配置しています。なので、z/x/yが分かれば、逆にバイナリの位置を計算することができ、それをRange Requestで取得しています。

https://docs.protomaps.com/pmtiles/
https://github.com/protomaps/PMTiles/blob/main/spec/v3/spec.md


PMTilesを使ってみよう!

前置きはこれぐらいにして、スマホでPMTilesを使ってみましょう。使用するデータはこちらです。GeoJSONのデータをPMTilesに変換して、Github Pagesに置いてあります。
github.com

1. EcorisMapを起動して、左下のボタンを押します。

2. 地図選択の画面で新規追加ボタンを押します。

3. 地図の名称とpmtilesのURLとスタイルファイルのURLを入力してOKを押します。(スタイルファイルを指定しないとデフォルトの色になります。)
https://tmizu23.github.io/santa/santa.pmtiles
https://tmizu23.github.io/santa/style.json


4. これでサンタクロースのPMTIlesを読み込むことができました。クリックすると、属性を表示できます。

スタイルを変更したい場合は、上記のstyle.jsonをコピーして、色などを変更したのち、自分のサイトに置いてスタイルファイルのURLを変更してください。

ということで良いクリスマスを!

補足1 PMTilesの作り方

PMTilesのデータは、go-pmtilesを使ってmbtilesから変換するか、tippecanoeを使ってGeoJSONから変換して作成します。GeoJSONからPMTilesへの変換は、以下のコマンドでできます。データによっては、変換オプションを指定して最適化する必要があります。詳しい説明は、また期を改めて。

tippecanoe -o santa.pmtiles santa.geojson

ちなみに、GeoJSONのデータは「サンタの絵を書いて、それをポリゴンにして、スケールと座標は、東北地方ぐらいの仙台付近、GeoJSONでダウンロードしたい!」とChatGPTに言って作ってもらいました。

補足2 スタイルファイルについて

スタイルファイルは、ベクタタイルに色やラベルをつけるための設定ファイルです。そのルールは基本的にはMapLibre Style Spec
に準拠していますが以下の点が異なります。

  • {layers:[]}のみが必須項目です。
  • id、sourceはダミーでOKです。
  • source-layerはPMTilesのレイヤ名と対応させる必要があります。
  • filter関連は、sampleにある形式のみ対応しています。(matchと==だけ)

(今後、少しずつ色々なfilterに対応するように実装しようと思ってます)

補足3 react-native-mapsへのPMTilesの実装方法

 EcorisMapはreact-native-mapsを利用して作成しています。しかしreact-native-mapsはPMTilesはもとより、ベクタータイルもサポートしていません。なので、新機能が欲しければ自分で実装するしかありません。React Nativeは、Typescriptを書けばAndroid用とiOS用のアプリを同時に作成できるのが良いところなのですが、裏を返せば新機能を追加するためには、Android用のJavaiOS用のObjective-Cの両方のコードを書く必要が出てきます。Objective-Cは見たこともないし、Javaは25年前にアプレットを作ったきりです。
 こういう状況なので、去年までならPMTilesを実装しようなどとは、露ほども考えなかったのですが、ChatGPTが状況を変えました。PMTilesのGithubには、Cとjavascriptの実装コードと仕様(spec)が置いてあります。これらを関数ごとにコピペして「javaObjective-Cに移植して!」とお願いすることでコードが作成されます。それをコンパイルしてエラーが出たら再びChatGPTに修正してもらいます。これを繰り返すことでなんとか実装できました。
 ベクタータイルの実装も同様の方法ですが、Maplibreのようにグラフィックライブラリで高速に描画を行う工夫がされているのではなく、単純にCanvasへの描画です。ただ、野外調査で使うデータのように少ないレイヤに色やラベルを付けるだけで良い場合は、十分速く描画することができます。

補足4 代替手段の検討

MapLibre GL JS

MapLibre GL JSでWeb用のサイトを作ってスマホのブラウザで見る方法です。もしかすると、これでも良いかもしれません。ただ、アプリじゃないとできないこととか、パフォーマンスが気になります。あと、地理院地図以外の地図や衛星画像を使うためにはアクセス数に応じて課金が発生するサービスの利用が必要になります。(もしくは、自分で配信)

MapLibre Native

GitHub - maplibre/maplibre-native: MapLibre Native - Interactive vector tile maps for iOS, Android and other platforms.

上記と違ってスマホ用のアプリを作ることになるのでパフォーマンスは問題なさそうです。ただ、開発にはAndroid用にはKotlin、iOS用にはSwiftを使用するようです。両方に対応したアプリを作る場合は大変になります。

MapLibre GL SDK for React Native

github.com

これが本命?試してないので分かりませんが、react-native-maps+ベクタータイルの代替には、これが良さそうです。ただ、react-native-mapsだと、Maps SDK for Android, iOSをベースにしているので、googleの地図と衛星画像が無課金で利用できます。それが使えなくないのは弱点です。