一般社団法人OpenStreetMap Foundation Japanではさくらインターネットからコミュニティ支援を受け、さくらのクラウド上にコミュニティ向けのOpenStreetMap Japanサイトおよびタイルサーバを構築、運用をしています。今回はタイルサーバおよびその構築方法について解説します。

タイルサーバとは何か

地図業界ではタイルサーバというと、一般的に地図データを配信するサーバを指します。地図をタイルという単位で分割することでREST APIで地図データを取得し、JavaScriptライブラリやネイティブ向けライブラリを使って簡単に地図を表示することが可能となります。

ただ、「タイル」といっても「タイル」自体にいろいろな仕様があります。OpenStreetMap Foundation Japanが提供しているタイルは「zxyタイル」(またはXYZ)と呼ばれるもので、なんとこれは今のところ「標準仕様がない」ものとなります。仕様がないというのはどういうことでしょうか?

まず、地図タイルで使われている「仕様」では、以下の二つがあります。

  • TMS(Tile Map Service)
    • Open Source Geospatial Foundationが作成した仕様
    • 特徴としてはY軸が数学的な座標系になっている
  • WMTS(Web Map Tile Service)
    • Open Geospatial Consortiumが作成した仕様
    • 様々なリクエスト形式(KVP, REST, SOAP)などをサポートしている

現在のところ、きちんとした標準仕様としてWMTSがありますが、WMTSは様々なメタデータのサポートが必要で、比較的複雑になります。TMSはWMTSに比べて軽量な実装となりますが、問題となるのがY軸の扱いです。Y軸が数学的な座標系を取るため、原点となる箇所が左下となり、コンピュータで地図を扱うには少し変です。

そこでzxyタイルではTMSのY座標だけコンピュータ座標、つまり原点が左上から始まるようにしたものを採用しています。ここが「現在仕様がない」という部分になります。とはいえ、現状zxyタイルは最も広く使われていて、デファクトスタンダードといってよいレベルになっています。

日本国内の地図インフラを提供するところも多くがzxyタイルを提供しています。国土地理院の地理院地図やONE COMPATHのMapion地図などが有名なところとなります。OpenStreetMap本家もこの仕様をベースにして世界のタイルサーバを配信しています。OpenStreetMapではY-fliped TMSと呼んでいて、TMSのY座標を逆にしたものですという説明をしています。

実際の仕様

では、zxyタイルの仕様というのはどのようなものでしょうか?
これは以下のように定義されます。

  • EPSG:3857(Web Mercator Projection)の座標系を使う
  • REST APIを提供

EPSGとはEuropean Petroleum Survey Groupの略ですが、一般的には世界中の測地系、座標系、地図投影法などをコード体系にしたものを指します。例えばGPSで使われている座標系はEPSG:4326(WGS:84とも言われます)で、これは世界中で広く使われています。

日本の測地系はいろいろありますが、現在では東日本大震災の際にずれた値を補正したEPSG:6668(JGD2011)が緯度経度の標準として使われています。

EPSG:3857はもともとGoogle Mapが採用した座標系がベースとなっています。かつてはGoogleをもじってEPSG:900913というコードをプログラムで扱うことがありました。この座標系はコンピュータ上に配信したタイル画像を扱うために使われています。特徴としては南緯85.06より南、および北緯85.06より北をカットするというのがあります。これは南極点や北極点は利用しないという割り切りをしています。

次にREST APIでの提供とありますが、タイルでは『{z}/{x}/{y}.{format}』という形式でタイルを提供するようにします。zはzoom level、xはX座標、yはY座標を指します。formatは後述するラスタータイルかベクタータイルかによって変わります。OpenStreetMap Foundation Japanのタイルサーバではラスタータイルではpngを、ベクタータイルではmvtというフォーマットを提供しています。なお、提供する画像の種類によってラスタータイルのフォーマットは変わるのが一般的で、例えば衛星写真を提供するタイルサーバであればjpgなどを使います。

zoom levelは画像の拡大するレベルを指します。例えばzoom level = 0では1枚の画像で世界中を表現します。

zoom level = 0の画像

zoom levelが1つあがる(zoom up)と解像度が縦横それぞれ2倍になります。つまり、zoom level = 1では2 x 2 = 4枚の画像で世界中を表すことになります。

インフラ系の人なら、「これを静的にファイル持ってたら死ぬんじゃない?」って気づくかもしれません。実際のところ静的なファイルとしてホスティングしてしまうとファイルシステムの限界に当たってしまいます。今回作成しているタイルサーバではtileserver-glおよびmbtilesというファイルを使うことでこれを解決しています。なお、地理院地図ではCDNにホスティングをしてこの問題を解決しています。

ラスタータイルとベクタータイル

今回作成しているタイルサーバはラスタータイルとベクタータイルの両方をサポートしています。

ラスタータイルとは地図データを画像に落とし込んだものをタイル上に配信する仕組みとなります。多くは256px x 256px の画像データで、サーバによってはRetinaディスプレイに対応した画像(つまり512px x 512px や768px x 768pxの画像)を配信可能なものもあります。

ベクタータイルとは地図データそのものをタイル上に配信する仕組みとなります。今回作成しているタイルサーバではOpenMapTilesを利用して作成したタイルを配信しています。

ここでtileserver-glについて説明をしておきます。tileserver-glはベクタータイルおよびラスタータイルの配信用サーバです。

ベクタータイルの配信は、デザインファイルやフォント、スプライト画像といった、クライアントサイドでのレンタリングに必要なファイルとベクタータイルを配信することで、クライアント上でレンタリングが可能になります。

ベクタータイル配信の仕組み

ラスタータイルの配信はベクタータイルの仕組みをそのまま使い、tileserer-gl上でレンタリングしたものを返します。また、OpenStreetMap Foundation Japanのサーバではvarnishでキャッシュしながら配信をします。

ラスタータイル配信の仕組み

なお、ラスタータイルのレンタリングを除いたtileserver-gl-lightというものもあります。現状はまだラスタータイルの方が主流ですが、ベクタータイル中心のプロダクトを作るのであれば、tileserver-gl-lightを検討してみるとよいでしょう。

Mapbox Vector Tileについて

ベクタータイルの仕様はMapbox社が開発、公開をしているMapbox Vector Tileを利用しています。これは地図データそのものをProtocol Buffersでエンコードしたフォーマットになります。

Mapbox社自体がOpenStreetMapの世界中のデータを商用サービスとして展開しているのもあり、Mapbox Vector Tile自体がOpenStreetMapのデータ構造と相性がよい仕組みになっています。その他、データをZigzagエンコーディングすることでgzip圧縮の効率をあげたりするなど工夫をしています。

OpenMapTilesはMapbox Vector Tileを自分たちで作るための仕組みです。筆者もコミッターをしています。

OpenMapTilesではアウトプットとしてMBTiles形式のファイルを出力します。MBTilesはこれもまたMapbox社が作った仕組みなのですが、簡単に言うとSQLiteにタイルのデータを格納することで取り回しを簡単にする仕組みです。

mbtilesの仕組み

MBTilesを使うと、中身がSQLiteなので簡単にプログラムを作ることができて便利です。またファイルが一つで済むので、前述したファイルを大量に扱うときのファイルシステムの限界などを考慮しなくて済むという利点があります。

なお、MBTilesではY座標がzxyタイルの逆になっています。というのもMBTilesはTMSの仕様をベースとしてしまったためです。開発者はちょっと後悔しているっぽいです。

タイルサーバを作ってみよう

では、ここからタイルサーバを実際に構築してみましょう。

今回はOSMFJが公開しているDocker環境を用いて構築をします。このDockerイメージはDocker for Macなどでは動作しないため、Linuxのインスタンス(仮想サーバ)が必要です。このDocker環境にはいくつかデザインとvarnishを含んだ環境を整理しています。なお、httpsに対応する場合は別途nginxなどでリバースプロクシを通してください。

とりあえず下記のセットアッププログラムを動かしてみましょう。なお、1.8GBほどダウンロードするので、できればさくらのクラウドなどで実行するとよいでしょう。

git clone https://github.com/osmfj/tileserver-gl-site.git
cd tileserver-gl-site
cd data
wget https://file.smellman.org/japan-20210308.mbtiles
mv japan-20210308.mbtiles japan.mbtiles
cp japan.mbtiles asia.mbtiles # asiaは30GBぐらいあるので日本のものを代わりに使う
cd ..
docker-compose up

あとは`http://your-ip:8000`にアクセスできたらOKです。

localhostでのテスト例

タイルサーバの構造を簡単に説明します。

$ tree -L 1
.
├── Dockerfile
├── config.json
├── data
├── default.vcl
├── docker-compose.yml
├── fonts
├── source
├── sprites
└── styles

5 directories, 4 files

config.jsonはtileserver-glの設定ファイルです。

Dockerfileとdefault.vclがvarnishのDockerfileおよび設定となります。default.vclでは10台のtileserver-glのインスタンスと接続しようとしています。

docker-compose.ymlはDocker Composeの設定ファイルで、varnishのインスタンスと10台のtileserver-glを立ち上げています。なお、10台としているのはさくらのクラウドで20coreのマシンを使っていて、かつtileserver-glが1インスタンスあたり2coreを使うのでそれに合わせています。

data, fonts, sprites, stylesは、tileserver-glが利用するディレクトリです。なお、ディレクトリはconfig.jsonで変更ができます。

dataはMapbox Vector Tileを格納したmbtilesファイルを置くディレクトリです。デフォルトでhoppo.mbtilesとtakeshima.mbtilesがあります。これは北方領土問題と竹島問題を雑に対応するために作っています。ちなみに、元ネタのファイルはsourceディレクトリにあり、tippecanoeというプログラムで変換しています。

sytlesディレクトリにはベクタータイルを利用するためのデザインファイルがあります。osm-bright, maptiler-basicおよびmaptiler-tonerを、それぞれ英語版と日本語版で作っています。

fontsディレクトリはフォントファイルをProtocol Buffers形式に変換したものを展開しています。maptiler社が提供しているものにM+1フォントを追加したものとなります。

spritesディレクトリはスプライト画像を展開しています。スプライト画像はCSSスプライトのような画像とそれを参照するためのJSONファイルで構成されています。

アプリケーションに組み込んでみる

タイルサーバができたらアプリケーションに組み込んでみましょう。

一つ注意があり、ベクタータイルを組み込む場合はHTTPSが必須となります。というのもスタイルファイルがjsonで提供されていて、外部のドメインからparseするときにHTTPSではないとセキュリティの問題でブラウザがエラーを出します。前述した通り、nginxなどでリバースプロクシを通してHTTPS対応してください。

ラスタータイルの組み込みは非常に簡単です。基本的には以下のURLを使います(例としてOpenStreetMap Foundation Japan提供のタイルサーバを使います)。

https://tile.openstreetmap.jp/{style}/{z}/{x}/{y}.png

sytleはtileserver-glでの`identify`を指定します。なお、tileserver-gl上でXYZをクリックするとURLが出てくるので、それをコピーしてもよいです。

XYZをクリック

では、実際のコードを見てみましょう。次のコードはLeafletでの例です。(言語はTypeScriptです)

import L from 'leaflet'
import 'leaflet/dist/leaflet.css'
import './index.css'

const map = new L.Map('mapid')
L.tileLayer(
  'https://tile.openstreetmap.jp/styles/maptiler-basic-ja/{z}/{x}/{y}.png',
  {
    maxZoom: 18,
    attribution: `© 
      <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>
      contributors`,
  }
).addTo(map)
map.setView([36.104611, 140.084556], 10)

LeafletではL.tileLayerが標準でzxyタイルに対応をしています。mapオブジェクトが地図自体になるので、L.tileLayerを作成してそれをmapオブジェクトに追加するという仕組みになります。

注意すべきはattributionをきちんと書くという点です。attributionはデータの元ネタがどこなのかを記載するところです。OpenStreetMapを使う場合は`© OpenStreetMap contributors`を必ずいれてください。

では、次にベクタータイルの例を見てみます。ベクタータイルもスタイルのURLは以下のようになります。

https://tile.openstreetmap.jp/styles/{style}/style.json

こちらもtileserver-gl上でURLを取得できます。

GL Styleを選択

スタイルファイルはベクタータイルへの参照の仕方を記述しています。実際はTileJSONという形式のものへの参照となっています。そのため、ベクタータイルを使う場合は実際のzxyタイルのURLを書かずに済みます。

ベクタータイルのJavascriptライブラリとしてはMapbox GL JSが有名ですが、2.xからOpenSourceではなくなった上にアカウントの登録が必須となり、Mapboxのサービスを使わなくても使用状況に応じて課金されるという状態になりました。そのため、今回はMapbox GL JSのv1.xからのフォークであるMapLibre GL JSを使います。コード例を示します。(言語はTypeScriptです)

import './index.css'
import 'maplibre-gl/dist/maplibre-gl.css'
import { Map } from 'maplibre-gl'

new Map({
  container: 'mapid',
  style: 'https://tile.openstreetmap.jp/styles/maptiler-basic-ja/style.json',
  center: [140.084556, 36.104611],
  zoom: 10,
})

ここではattributionがありません。これはMBTiles上にattributionの情報があり、TileJSONがその情報を記述してくれるため、記載する必要がありません。ただし、Mapbox GL JSやMapLibre GL JSでラスタータイルを使う場合はもちろんattributionの記載は必要になります。

ラスタータイルは他にもOpenLayers(Javascript)、Mapkit(iOS)、Maps SDK for Androidなどが対応をしていてネイティブアプリも標準で対応しています。ベクタータイルはMapbox GL nativeもしくはMapLibre GL nativeを使うことになりますが、Mapbox社のライブラリはJS同様注意が必要です。

スタイルをいじってみよう

最後にベクタータイルならではの楽しみとして、スタイルを編集して独自のスタイルを作成してみましょう。

OSSのスタイルエディタとしてMaputnikがあります。これはWebベースのアプリケーションで、エディタに行くとスタイルの編集ができます。

まず、HTTPS対応をしたあとにMaputnikでOpenを押して`Load from URL`からスタイルのURLを入力します。

Load from URLからスタイルのURLを入力

次にレイヤーの`water`グループを選択して、中の`water`を選択します。あとは、`Paint properties`から色を変えて遊んでみましょう。

水面の色を変えてみたところ

このような形で簡単にデザインを変更することができます。よいデザインができれば是非公開をしてみてください。