国土地理院のバイナリベクトルタイルを OpenLayers で表示

[2019年7月29日に国土地理院が提供実験を開始したバイナリ形式のベクトルタイル][地理院地図Vector（仮称）提供実験のソース]を JavaScript の地図ライブラリである [OpenLayers] で表示する方法です。

まずは[作成したデモ][デモ3]をどうぞ

![example3_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154454/82fb0f1b-8d6d-ae08-d69d-699bfca5c096.png)

## ベクトルタイル概要

| 項目 | 説明 |
|---|---|
| URL  | https://cyberjapandata.gsi.go.jp/xyz/experimental_bvmap/{z}/{x}/{y}.pbf |
| データソース  | 数値地図（国土基本情報）- 地図情報　等  |
| ズームレベル  | 5～17  |
| 提供範囲  | 20万分1地勢図「宇都宮」「水戸」「甲府」「東京」「千葉」の範囲（縮尺によっては、その周辺も提供）  |
| 提供開始  | 令和元年7月29日 |

[地理院地図Vector（仮称）提供実験のソース]

従来のベクトルタイルは GeoJSON 形式でしたが、今回新たに提供実験が始まったベクトルタイルはシリアライズされた pbf という形式のファイルです。

また公開に合わせて [Mapbox GL JS] ベースの [地理院地図Vector] の試験公開も始まりました。

## 開発環境

Webpack + TypeScript 環境を想定しています。

```shell
$ mkdir ol-vectortile
$ cd ol-vectortile
$ yarn init -y
$ yarn add --dev webpack webpack-cli webpack-dev-server
$ yarn add --dev css-loader style-loader
$ yarn add ol
// TypeScript を使う場合
$ yarn add --dev typescript ts-loader
$ yarn add @types/ol
```

### バージョン

```txt
- ol@5.3.3
```

### ディレクトリ構成

```txt
.
├── dist
│   └── index.html
├── src
│   └── index.ts
├── .gitignore
├── .prettierrc
├── tsconfig.json
├── webpack.config.js
├── package.json
└── yarn.lock
```

## バイナリベクトルタイル表示の最小限のコード

OpenLayers でバイナリベクトルタイルを表示する最小限のコードです。

### ./dist/index.html

```html:./dist/index.html
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>OpenLayers Vector Tiles</title>
    <style>
      html, body {
        width: 100%;
        height: 100%;
        margin: 0;
        padding: 0;
      }
      .map {
        width: 100%;
        height: 100%;
        margin: 0;
        padding: 0;
      }
    </style>
  </head>
  <body>
    <div class="map" id="map">
    <script src="./bundle.js"></script>
  </body>
</html>
```

### ./src/index.ts

```ts:./src/index.ts
import Map from 'ol/Map';
import View from 'ol/View';
import VectorTileLayer from 'ol/layer/VectorTile';
import VectorTileSource from 'ol/source/VectorTile';
import MVTFormat from 'ol/format/MVT';
import { fromLonLat } from 'ol/proj';

import 'ol/ol.css';

// ベクトルタイルの olレイヤを定義
const vt = new VectorTileLayer({
  source: new VectorTileSource({
    format: new MVTFormat(),
    url: 'https://cyberjapandata.gsi.go.jp/xyz/experimental_bvmap/{z}/{x}/{y}.pbf',
    attributions: [
      '<a href="https://github.com/gsi-cyberjapan/gsimaps-vector-experiment" target="_blank" rel=”noopener noreferrer”>国土地理院</a>',
    ],
  })
});

new Map({
  target: 'map',
  view: new View({
    center: fromLonLat([140.46, 36.37]),
    zoom: 10
  }),
  layers: [vt]
});
```

このコードで作成したデモは[こちら][デモ1]です。

![example1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154454/adf813ba-4bd3-c416-9523-db28275bacd0.png)


このデモではベクトルタイルの **olレイヤ** `vt` にスタイルを設定していないので、全ての地物にデフォルトのスタイルが適用されています。

さて、ここからはベクトルタイル内に含まれる地物のレイヤを扱いますが、これを OpenLayers のレイヤクラスと区別するため、OpenLayers のレイヤクラスを **olレイヤ** と表記します。

webpack guide: [TypeScript](https://webpack.js.org/guides/typescript/ "webpack guide TypeScript")
OpenLayers API: [ol/layer/VectorTile](https://openlayers.org/en/latest/apidoc/module-ol_layer_VectorTile.html "OpenLayers API ol/layer/VectorTile")
OpenLayers API: [ol/source/VectorTile](https://openlayers.org/en/latest/apidoc/module-ol_source_VectorTile.html "OpenLayers API ol/source/VectorTile")

## より地図らしくしていく

先ほどのコードで国土地理院のバイナリベクトルタイルを OpenLayers で表示することに成功しました。ここからは「レイヤの選択」と「スタイルの設定」で表示する地図を、より地図らしくしていく作業になります。

まずは `src` ディレクトリに `layers` ディレクトリと `styles` ディレクトリを作成し、`src` ディレクトリの構成を以下のように変更します。

```txt
src
├── index.ts
├── layers
│   └── vt.ts
└── styles
    └── vtstyle.ts
```

### レイヤを選択して描写

バイナリベクトルタイルは、GeoJSON 形式のベクトルタイルとは違って、タイル内にレイヤ情報を含んでいます。全てのレイヤをスタイリングするのは非常に手間がかかるため、ここではレイヤの選択をおこないます。

レイヤの選択は、`VectorTileSource` オブジェクトの `format` プロパティに設定した `MVTFormat` オブジェクトを編集することで実現できます。`layers` プロパティに選択するレイヤ名の配列を設定します。

次の例では、道路(road)、鉄道(railway)、海岸線(coastline)、河川(river)、湖池(lake)、水域(waterarea) のみを選んで描写しています。

#### ./src/layers/vt.ts

```ts:./src/layers/vt.ts
import VectorTileLayer from 'ol/layer/VectorTile';
import VectorTileSource from 'ol/source/VectorTile';
import MVTFormat from 'ol/format/MVT';

// 次の項で作成する StyleFunction
import vtstyle from '../styles/vtstyle.ts';

const vt = new VectorTileLayer({
  source: new VectorTileSource({
    format: new MVTFormat({
      // 取得するレイヤ名を配列に記述する
      layers: ['river', 'coastline', 'lake', 'waterarea', 'road', 'railway']
    }),
    url: 'https://cyberjapandata.gsi.go.jp/xyz/experimental_bvmap/{z}/{x}/{y}.pbf',
    attributions: [
      '<a href="https://github.com/gsi-cyberjapan/gsimaps-vector-experiment" target="_blank" rel=”noopener noreferrer”>国土地理院</a>'
    ]
  }),
  style: vtstyle
});

export default vt;
```

レイヤを取捨選択することで、必要に応じた様々な地図を作成することができます。
レイヤの一覧は[地物コード及び表示ズームレベル一覧]で確認できます。

OpenLayers API: [ol/format/MVT](https://openlayers.org/en/latest/apidoc/module-ol_format_MVT.html "OpenLayers API ol/format/MVT")

### レイヤごとにスタイルを作成

olレイヤにスタイルを適用していきます。
olレイヤのスタイルの指定方法は以下の3通りです

1. `Style` オブジェクトを指定 `Style`
2. `Style` オブジェクトの配列を指定 `Style[]`
3. `StyleFunction` を指定 `(RenderFeature, number) => Style | Style[]`

ここからは `StyleFunction` を作成してベクトルタイルのレイヤに合わせたスタイル指定の方法を行ないます。
`StyleFunction` は `RenderFeature` と `resolution (number)` を引数にとり、`Style` オブジェクトあるいは `Style` オブジェクトの配列を返します。

#### ./src/styles/vtstyle.ts

```ts:./src/styles/vtstyle.ts
import Style from 'ol/style/Style';
import Stroke from 'ol/style/Stroke';
import Fill from 'ol/style/Fill';
import RenderFeature from 'ol/render/Feature';

export default function vtStyle(
  feature: RenderFeature,
  resolution: number
) {
  // RenderFeature から properties を取得
  const properties = feature.getProperties();
  // properties に格納されているレイヤ名からスタイルを書き分ける
  if (properties.layer === 'road') {
    return new Style({
      stroke: new Stroke({
        color: '#ddd',
        width: 2
      }),
      zIndex: 1
    });
  } else if (properties.layer === 'railway') {
    return new Style({
      stroke: new Stroke({
        color: 'gray',
      }),
      zIndex: 2
    });
  } else if (properties.layer === 'coastline') {
    return new Style({
      stroke: new Stroke({
        color: 'skyblue',
        width: 2
      }),
      zIndex: 0
    });
  } else if (properties.layer === 'river' || properties.layer === 'lake' || properties.layer === 'waterarea') {
    return new Style({
      fill: new Fill({
        color: 'skyblue',
      }),
      zIndex: 0
    });
  } else {
    // 空の new Style() を返すと何も描写しない
    return new Style();
  }
}
```

このスタイルで作成したのが[デモ2]です。

![example2_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154454/209525cb-70b5-2dad-faba-ee19b59b6931.png)

![example2_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154454/4fe5421b-fb9a-c8eb-c1ab-60cccc15c153.png)

![example2_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154454/cb3601a5-c014-2d1a-f19d-4b6794cf326d.png)

水域が水色で塗られ、道路がシルバー、鉄道がグレーで描かれています。

この例では、ベクトルタイル内のレイヤ名のみでスタイルを書き分けましたが、地物の属性(properties)から道路の区分や幅員を取得することで、さらに複雑なスタイリングを施すことができます。
[地物コード及び表示ズームレベル一覧]と[地物の属性一覧]を読んで、どのようなレイヤ、属性があるか確認してください。

OpenLayers API: [ol/render/Feature](https://openlayers.org/en/latest/apidoc/module-ol_render_Feature.html "OpenLayers API ol/render/Feature")
OpenLayers API: [ol/style/Style](https://openlayers.org/en/latest/apidoc/module-ol_style_Style.html "OpenLayers API ol/style/Style")

## プロジェクト作成例

![example3_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154454/82fb0f1b-8d6d-ae08-d69d-699bfca5c096.png)
GitHub に OpenLayers + TypeScript プロジェクト作成例のリポジトリを作成しました。
このリポジトリを clone すれば、Webpack 周りの初期設定をせずに OpenLayers で国土地理院ベクトルタイルを使った開発が可能になります。

国土地理院ベクトルタイル + OpenLayers + TypeScript Example
https://github.com/cieloazul310/ol-vectortile/

![example3_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154454/4fbd82cc-ab76-f805-4ec1-7f6f64a5b9ea.png)

![example3_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154454/04f601fb-670f-3699-87d0-56b0a0f30be8.png)


## まとめ

以上、OpenLayers で国土地理院のバイナリベクトルタイルを使う上での基礎的な内容でした。

バイナリベクトルタイルの利用は、 [Mapbox GL JS] を使うのが一番良い方法だとは思いますが、バイナリベクトルタイルへの対応を見ると OpenLayers もまだまだ捨ててもんじゃないなと思いました。

今までは煩雑だと思っていた OpenLayers の書き方も、TypeScript や Dart といった静的型付け言語を勉強して、その理屈がよくわかりました。
今回初めて OpenLayers を TypeScript で書いてみたのですがやはり相性がよかったです。

## リンク

- [ベクトルタイルとその提供実験について](https://maps.gsi.go.jp/development/vt.html "ベクトルタイルとその提供実験について") 国土地理院
- [地理院地図Vector（仮称）提供実験のソース] GitHub
- [地理院地図Vector]
- [国土地理院ベクトルタイル + OpenLayers + TypeScript Example](https://github.com/cieloazul310/ol-vectortile/) GitHub
- [OpenLayers]

<!-- References -->
[地理院地図Vector（仮称）提供実験のソース]: https://github.com/gsi-cyberjapan/gsimaps-vector-experiment "地理院地図Vector（仮称）提供実験のソース"
[地物の属性一覧]: https://maps.gsi.go.jp/help/pdf/vector/attribute.pdf "属性解説書"
[地物コード及び表示ズームレベル一覧]: https://maps.gsi.go.jp/help/pdf/vector/dataspec.pdf "データ仕様"

[地理院地図Vector]: https://maps.gsi.go.jp/vector/ "地理院地図Vector"
[Mapbox GL JS]: https://docs.mapbox.com/mapbox-gl-js/ "Mapbox GL JS"

[デモ1]: https://observablehq.com/d/6e452908195b323a "デモ1"
[デモ2]: https://observablehq.com/d/8dbac5ce864a2bca "デモ2"
[デモ3]: https://cieloazul310.github.io/ol-vectortile/ "デモ3"

[OpenLayers]: http://openlayers.org/ "OpenLayers"

[TypeScript]: https://www.typescriptlang.org "TypeScript"
[Dart]: https://www.dartlang.org "Dart"