地図タイルを ZL １つ分深く作るための覚悟

## はじめに

Web 地図用のタイル（slippy map tilenames 形式）を作成する際に、ズームレベル（ZL）をどのくらいまで作るかは悩みどころです。元になる地図データの精度の他、タイルの容量や総数を考慮しないと管理するのが大変になります。

そのため、タイルの最大 ZL を決める際に考慮しなければならないことを整理してみたいと思います。

### TL;DR

* ZL１つ分深くタイルを作ると、タイル容量・タイル枚数が２倍以上になり、作成時の計算量、データの容量、コピー・転送にかかる時間、閲覧時の転送量・リクエスト数等に大きく影響する。
* そのため、ZL１つ深くタイルを作成するという決定には、**あらゆるコストが２倍以上になるという覚悟**が必要。
* ベクトルタイルの場合は、「精度」「タイル数」だけでなく、タイル容量の「**最大値**」も考慮して最大 ZL を決める必要があるが、うまく設計すれば、**タイル容量・タイル枚数を大幅に抑えることが可能**。


## 地図タイルについて

地図タイルとは、明確な定義はないと思いますが、「あらかじめ分割された地図データ」と考えてよいでしょう。

分割方法は実装次第ですが、Web 地図でよく利用されるのが、一般的に slippy map tilenames や XYZ 形式と呼ばれる形式です。

この slippy map tilenames 形式では、Web メルカトル（Pseudo メルカトルとも）と呼ばれる投影法が用いられています。WGS84で得られた経緯度を、真球を仮定してメルカトル投影を行う図法となります。（詳細は以下の記事で整理を試みています。）

https://qiita.com/mg_kudo/items/089e257570c19aa15100

メルカトル図法は、地球上の角度と紙面上の角度が等しくなるように表現した図法です。経線は紙面上で平行に表されるため、緯度が大きくなるほど、経線間の差が拡大して表示されることになりますから、角度を正しく表示するため、緯線の間隔を、経線の間隔に合わせて拡大して表示しています。ただし、極へ近づくほど、図上距離が無限大に大きくなってしまいます。

そこで、Web メルカトルでは、ちょうど正方形で示すことができる南緯約85度～北緯約85度の範囲を対象としています。

そして、slippy map tilenames 形式では、この正方形の世界を、ズームレベル（ZL）という指標に基づき、タイルへと分割していきます。ZL が１つ大きく（深く）なると、１つのタイルは、縦横２分割ずつ、計４枚のタイルに分割されます。

![ズームレベルとタイル座標（国土地理院HPより）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842832/3170eeae-0760-1c5a-08af-644191a42b82.png)
※国土地理院の「[地理院タイルについて](https://maps.gsi.go.jp/development/siyou.html)」のページより転載

ZL が大きくなるほど、１枚のタイルが示せる領域は狭くなりますが、狭い領域の情報をより詳細に格納できる余地が増えるため、精度が高くなると考えられます。そのため、タイルの世界では、１枚のタイルがカバーする領域の大きさと、精度はトレードオフと言えます。

## タイルの精度

それでは、タイルの精度について考えてみたいと思います。精度を考える場合、ラスタタイルを例にすると分かりやすいかともいます。

ラスタタイルの場合は、１辺がさらに画素（ピクセル）に分割されています。ですので、ラスタタイルは、画素の集合として「メッシュデータ」と考えることができます。この**画素の大きさが、そのラスタタイルで示せる精度の限界**となります。例えば、地理院タイルの場合は、１辺 256 ピクセル（2の8乗）に分割されています。この場合、１ピクセルの大きさは、当該タイルの ZL+8 となるタイルの１辺の大きさに等しくなります。

以下の表は、東京駅周辺のラスタタイル（１辺 256 ピクセル）について、タイル１辺または１ピクセルあたりの地上距離を概算してみた結果になります。１ピクセルの大きさは、ZL14では10 m、ZL15では5 mメッシュのオーダー感となります。

![タイル1辺または1ピクセルあたりの地上距離（東京駅）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842832/c20ab549-93a8-347a-6c95-db07f7a93645.png)

ラスタタイルを作成する場合は、特に、元データがメッシュデータの場合は、この１ピクセル当たりの大きさを考慮して、どのZLまで作成するかどうか決める必要があるでしょう。

例えば、国土地理院の[標高タイル](https://maps.gsi.go.jp/development/demtile.html)を見てみましょう。元になる標高モデルデータは、格子状のメッシュデータとなりますが、5 mメッシュは ZL 15（東京駅周辺では１ピクセル 3.9 m）、10 mメッシュは ZL 14 (7.8 m)、500 m メッシュは ZL 8 (497 m)と、タイルのピクセルの大きさを考慮して最大 ZL が決められているように思われます。

![標高タイルの最大ZL.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842832/1b8309ed-83a9-c789-6008-dd0dde5c8c05.png)
※国土地理院「[標高タイルの作成方法と地理院地図で表示される標高値について](https://maps.gsi.go.jp/development/hyokochi.html)」のページより転載


補足ですが、メルカトル図法では、緯度が高くなるほど、引き延ばされて表示されるため、タイルのカバーする領域は狭くなります。そのため、沖縄と北海道で精度（１つのタイルがカバーする領域）は1.2倍くらい異なることになります。

![緯度毎のタイルの大きさ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842832/6de74ae1-984e-3517-7fbb-3f066eefe1ab.png)

## タイルセットのファイル数・容量

ZL が高くなるほど、タイルの精度は高くなります。一方、ZL が大きくなるほど、タイルセット全体としてのファイル数や容量が増加することになります。

タイルの枚数は、１つ ZL を上げるごとに、指数的に増加します。単純に考えると、ZL が n のタイル枚数は、ZL が n-1 のタイル枚数の4倍になります。これを ZL 0 からの累計で考えてみると、以下の表のように、ZL が n のタイル枚数は、ZL が 0 ～ n-1 までのタイル数の総数よりも約３倍大きくなることが分かります。

![タイルの枚数と累計（全世界）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842832/4cfa8ecd-7335-1001-2d90-d24830df1db0.png)

:::note
ZL が 0 ～ n-1 のタイルの総数については、初項 a=1、公比 r=4、第 n 項までの等比数列の和になりますから、[公式](https://ja.wikipedia.org/wiki/%E7%AD%89%E6%AF%94%E6%95%B0%E5%88%97#%E7%AD%89%E6%AF%94%E6%95%B0%E5%88%97%E3%81%AE%E5%92%8C)より、
```math
総数 = \frac{a・(r^n - 1)}{(r - 1)} = \frac{1・(4^n - 1)}{(4 - 1)} 
= \frac{1}{3} (4^n - 1)
```
となり、ZL が n のタイル枚数 $4^n $ の約 1/3 となります。
:::

実際は、ZLが上がるとタイル領域に地物がなくなり、タイルを作る必要がなくなる場合もあります。そのため、地理院タイル（標準地図）を例として、実際のラスタタイルの枚数を確認してみました。理論値通りの3倍には届かないものの、ある ZL のタイル総数は、１つ下の ZL までのタイル累計数よりもずっと多くなることが分かります。

ZL を１つ増やす場合、全域をカバーする地図データ一式が増えることにもなりますので、当然データ容量も大きくなります。

タイルの容量については、ZL に応じた地図編集（省略・総描等）が入るため、一概に言うのは難しいですが、地理院タイル（標準地図）を観察してみると、ZL が１つ大きくなると、容量がおおむね２倍に増えことがわかります。ある ZL のタイル容量は、１つ下の ZL までのタイルの累計容量とほぼ同じとなっています。

![ラスタタイルのファイル容量+ファイル数の例.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842832/896cdf6d-76b1-1eea-8d99-0abf325d1361.png)

## タイル作成の際の注意点

ZLを１つ大きくする場合、タイル容量・タイル枚数は２倍以上となることがわかりました。

１枚ごとのタイルが１ファイル（PNG 等）である場合、ファイル数が非常に多くなり、データ生成・出力の他、コピーや転送に多くの時間がかかることになります。タイル容量が増え、ストレージを圧迫することも問題ですが、それ以上にデータの生成・移動時間が必要になります。これは、迅速なデータ提供を行う上で、大きな障害になると考えられます。

タイル１つ１つのサイズは小さいとしても、タイルセットのファイル数が多くなると、作成時の計算量、ファイルの出力時間、データの容量、コピー・転送にかかる時間、閲覧時の転送量・リクエスト数……等、あらゆるコストに影響してきます。ZL の深さはタイルの精度や付きまとうコストも考えて、慎重に決めなければならないです。

以下は、細かいファイルの転送が遅くなる点に言及した記事・資料等です。

https://learn.microsoft.com/ja-jp/troubleshoot/windows-server/networking/slow-smb-file-transfer

https://qiita.com/yamamorisoba/items/889c934b2b7dbc629b3d


## 結論その１

* ZL１つ分深くタイルを作成するという決定には、**あらゆるコストが2倍以上になるという覚悟**が必要。

## 元データの精度が足りないが、深い ZL で表示させたい場合

方法１：そもそも表示すべきではない、と結論付ける

精度が足りないにも関わらず、詳細が分かる ZL でデータを表示させてしまうと、誤解が生じる可能性がありますので、表示する ZL の深さにも慎重になるべきです。

例えば、リスク情報の精度が250 m メッシュレベル（ZL9相当）であるにもかかわらず、深い ZL で表示させてしまうと、数メートル単位の各戸建て住宅（ZL15相当）のリスクを判定しようとしてしまう等、誤った利用をされてしまう場合があります。

方法２：オーバーズーミングさせる

それでも深い ZL で表示させたいという場合、タイルを拡大して、より深い ZL で表示させる「オーバーズーミング」という手法を使えばよいでしょう。ラスタタイルでオーバーズーミングする場合、使用する地図ライブラリによってはぼやけるかもしれませんが、それは元データの精度の限界なので、割り切ってもよいかと思います。

方法３：別のフォーマットの採用を検討する

たとえば、メッシュの境界を厳密に表現しなければならない場合、深い ZL で表現することが必要になります。その際は、必要な精度が出るまで、ZL を上げて作成するのはやむなしかと思います。ただし、ラスタタイルではなく、より精度を保持できるベクトルタイル等を利用できないか、検討する価値があると思います。

## ベクトルタイルの場合

これまでの議論は、ラスタタイルを主に想定して議論をしてきました。ここで、ベクトルタイルの場合についても考えてみましょう。

ラスタタイルは、画像の断片であり、１つあたりのタイルの容量の最大値は限度があります。一方、ベクトルタイルは、元のGISデータの一部分であり、タイルがカバーする領域のデータをそのまま保持することもできてしまいます。

そのため、何も考えずにタイルを作成すると、非常に大きなサイズのタイルが生じてしまいます。そのため、適切に各 ZL で表示する地物を選んでタイルを作成する必要性が強くなります。ラスタタイルで考慮すべき **「精度」「タイル数」だけでなく、各タイルのファイルサイズ「最大値」も考慮する必要** があると言えます。

一方、ベクトルタイルの場合は、様々な情報を１つのタイルに格納できるので、サーバ側で管理すべきタイルの種類（つまり、ファイルの数）を削減できる可能性があります。

面白い例として、[国土地理院のベクトルタイル](https://github.com/gsi-cyberjapan/gsimaps-vector-experiment)では、ZL17のデータを ZL16のタイルに格納することで、タイル数の削減に成功しています。そして、ZL17では、ZL16のタイルをオーバーズーミングして表示し、ZL17用のデータのみを表示させています。国土地理院で採用しているベクトルタイルの形式（Mapbox Vector Tile）では、内部座標があるため、精度を考慮する必要はありますが、オーバーズーミングを活用している有用な事例だと思います。

さらに、国土地理院の[最適化ベクトルタイル](https://github.com/gsi-cyberjapan/optimal_bvmap)では、データの取捨選択等により、各 ZL 帯のタイルの最大値を一定以下の容量に抑えている模様です。

以下に、ラスタタイル（地理院タイル（標準地図））とベクトルタイル（国土地理院最適化ベクトルタイル）のファイル容量・ファイル数を比較した結果を示します。ベクトルタイルの方が、大幅にファイル数や容量の削減に成功している様子が分かります。

![ラスタタイルとベクトルタイルのファイル容量+ファイル数の比較例.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2842832/9e75b217-4131-aaf0-da10-474c91c978d0.png)

:::note warn
* 国土地理院のベクトルタイルの場合、ラスタタイルに相当する情報が１つ小さい ZL のタイルに格納されているため、ZL18のタイルを作成する必要がなくなっています。
* ベクトルタイルは、gzip 圧縮された後のサイズです。ラスタタイルも PNG 形式であり、実質的に圧縮されているため、そのまま比較して問題ないと判断しています。
* ラスタタイルとベクトルタイルの表示デザインは異なるため、単にファイルサイズ・ファイル数のみの比較だけでは優劣は決められないことは申し添えておきます。
:::

参考ですが、国土地理院のベクトルタイルのサイズの特徴については、以下の記事で調査・考察しています。

https://qiita.com/mg_kudo/items/f6d3a1ec5c0117237fd3

https://qiita.com/mg_kudo/items/9f87fca0c579fd2b645d

## 結論その２

* ベクトルタイルでは、「精度」「タイル数」だけでなく、**ファイルサイズの「最大値」** も考慮する必要がある。
* ベクトルタイルは、**上手く設計することで、タイルの容量やサイズを大幅に削減可能**。

## タイルセット一式を１つのファイルで扱う

最後に、タイルセット一式を１つのファイルで扱う技術が進んできていることもご紹介いたします。

例としては、以前記事にした PMTiles というデータ形式が挙げられます。

https://qiita.com/mg_kudo/items/3179639c79c1734fd373

詳細は、上記記事中の解説やリンク先をご覧いただければと思いますが、以下のような技術になります。

* １つのファイルの中にタイルデータ一式が格納されている。
* ユーザは、HTTP 範囲リクエストにより、PMTiles の中の必要な部分だけ取得することで、タイルを消費。
* 管理者は、多量のファイルを移動させるコストを払わずに済むほか、HTTP 範囲リクエストに対応していれば、サーバ側の特別な実装（プログラム）も不要（サーバレスを実現でき、クラウド環境と相性が良い）。

タイルセット一式が詰め込まれているため、データ更新があると全体の作り直しが必要ですが、更新が低頻度の場合や都度タイル一式を作り直している場合等には有効な技術だと思います。


## おわりに

タイル管理にまつわる苦労を思い出しながら、今回の記事を作成しました。

私はタイルのホスト先として、GitHub の gh-pages をもっぱら利用しています。ブラウザ経由でファイルをアップしていますが、タイルセットのファイル数の影響でアップロードに苦戦することが多いです（そのため、PMTiles を利用した際は、感動したのを覚えています。ただ、そうすると今度はファイルサイズの壁にぶつかるわけですが……）。

いったんサーバにホストした後は、クライアントが必要な分のタイルのファイルを配信するだけ、というタイルの仕組みの恩恵を享受して、それにまつわるコストをいったん忘れることができます。ですが、いざ、タイルを更新したり、バックアップをとろうとしたりすると、再びファイル数との戦いになります。

一度決めた ZL は、その後の工程のどこまでも付きまとってきますので「迷うくらいなら作らない」というのを自分の中では方針としていきたいです。

最後に、結論をもう一度記載しておきます。

* ZL１つ分深くタイルを作成するという決定には、**あらゆるコストが2倍以上になるという覚悟**が必要。
* ベクトルタイルでは、「精度」「タイル数」だけでなく、**ファイルサイズの「最大値」** も考慮する必要がある。
* ベクトルタイルは、**上手く設計することで、タイルの容量やサイズを大幅に削減可能**。