【入門者向け・日本語】unvt/charitesでベクトルタイル地図のスタイル作成

この記事は、週末に書いた記事[**Vector tile styling with unvt/charites**](https://qiita.com/T-ubu/items/6588ebff2af537f6a9b5)の日本語版です。すべてを正確に訳したわけではないですが、日本語でもメモしておきます。

# はじめに
unvt/charites は簡単、直感的、効率的なコマンドラインツールで、ベクトルタイル地図を作るため、[Mapbox](https://docs.mapbox.com/mapbox-gl-js/style-spec/) や [MapLibre](https://maplibre.org/maplibre-gl-js-docs/style-spec/)のスタイル仕様に適合したスタイルをYAMLファイルで作成することができます。 
YAMLファイルは読みやすく、またcharitesのライブプレビュー機能はスタイル編集を助けてくれます。

この記事では、Windows PCでcharitesをインストールする方法と、その使い方を紹介します。unvt/charitesと、さらにはスタイル仕様をよく理解できるようにいくつかの練習があります。簡単なものもあるかもしれませんが、unvt/charitesを使ってベクトルタイルのスタイルを作成することに興味があればご覧いただけれるとうれしいです。

**この作業の大半は国連オープンGISイニチアチブのドメイン作業部会７に対する私の活動として行ったものに基づいています。** 
**本稿はもともとは同僚の M.T., O.M.S., P.F.のために書いたものです。**


# 私の実験環境

- Windows 10
- git
- nodejs version 18.12.1
- npm version 8.19.2
- Windows PowerShell


# Exercise 1. unvt/charites のインストール

## 1-1. nodejs と npm のバージョン確認（又はインストールされているか確認）
nodejs インストールされているか確認しましょう。もしされていない場合は、[nodejs ](https://nodejs.org/en/) からダウンロードしましょう。バージョン14以降を使うことが奨励されます。特に12．20.20より古いバージョンだとcharitesのインストールは失敗すると思います。

Windows PowerShell　を開いて、以下のコマンドを実行するとバージョンを確認できます。

```powershell
node --version
npm --version
```
## 1-2. npm パッケージ（@unvt/charites）のインストール
nodejsとnpmがあることを確認したら、以下のコマンドを実行し unvt/charites の npm パッケージをインストールします。うまくインストールするとcharites --versionでcharitesのバージョンを確認できます。

```powershell
npm install -g @unvt/charites
charites --version
charites --help
```
![windows-guide-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/a88b49ad-9644-3936-dd5b-7449df44f08d.png)

これでunvt/charitesを使えます。おめでとうございます！

:::note info
もし "charites.ps1 cannot be loaded because running scripts is disabled on this system,"　というようなエラーが出たときは、 charites をコマンドプロンプトで使うと動くと思います。以下のように、ExecutionPolicy が Restructed だとcharitesはPowerShellで実行できません。可能なら、ポリシーをRemoteSigned 等にしておくと大丈夫です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/c0514857-bf95-015f-edda-43a10843f8c1.png)
:::


# Exercise 2. スタイルの編集
charites でベクトルタイルのスタイルを編集します。

## 2-1. 新しいスタイルをはじめから作る
もしも既存のスタイルJSONを持っていれば、charitesでインポートするとよいので簡単です。しかし、もし初めからスタイルを作るのであれば、私が準備したテンプレートを使ってみます。私のレポジトリからテンプレートをコピーしてきます。以下のようなコマンドで出来ます。

```
git clone https://github.com/ubukawa/charites-init
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/39955cb6-7f2e-40cd-b909-fb2d2c5add05.png)

クローンした "charites-init" のなかにはstyle.ymlが入っています。layersフォルダもありますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/8a3862c5-9189-7e4a-012e-3ebf4d105cb2.png)

## 2-2. style.yml を編集して、sources, sprites, glyphs を指定する。
何もしていないとき、style.yml は以下のような感じですが、これを編集していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/8d43b187-0514-0ed7-17e8-92ae7c7444d9.png)

最初にstyle.ymlをなんでもよいのでテキストエディタで開きます。MicroSoft Visual Studio Codeで編集するのも便利でよいと思います。コメントタグ（#）を削除して必要な事項を以下の方針で記入していきます：

* sprite
  * sprite は、地図記号や塗りのパターンを与えるための画像ファイルとJSONファイルです。 
  * 今回の練習では、 "https://ubukawa.github.io/clearmap-style2/sprite_un/sprite_S" を使います。
* glyphs
  * glyphs にはpbf形式のテキストフォントの場所を指定します。 
  * 今回の練習では "https://ubukawa.github.io/clearmap-style2/font/{fontstack}/{range}.pbf" を使います。
* sources
  * ここにデータソースを指定することができます。ベクトルタイル、ラスタタイル、WMS、raster-demなど指定できます。今回は以下のyamlファイルにあるように3つのベクトルタイルソースを指定します。YAMLファイルを確認してみてください。
 
```YAML:style.yml
version: 8
sprite: https://ubukawa.github.io/clearmap-style2/sprite_un/sprite_S
glyphs: https://ubukawa.github.io/clearmap-style2/font/{fontstack}/{range}.pbf
sources:
  ne: #change the name if needed
    type: vector
    attribution: NaturalEarth   #add if needed
    minzoom: 0 #add if needed
    maxzoom: 5 #add if needed
    tiles:
    - https://ubukawa.github.io/graticules-vt/zxy/{z}/{x}/{y}.pbf
  carto: #change the name if needed
    type: vector
    attribution: Cartotile - Test data   #add if needed
    minzoom: 0 #add if needed
    maxzoom: 2 #add if needed
    tiles:
    - https://ubukawa.github.io/cartotile/tile/{z}/{x}/{y}.pbf
  cme: #change the name if needed
    type: vector
    attribution: UN_CLEARMAP_VT(powered by esri)
    minzoom: 0 #add if needed
    maxzoom: 5 #add if needed
    tiles:
    - https://pro-ags2.dfs.un.org/arcgis/rest/services/Hosted/Clearmap_Webplain/VectorTileServer/tile/{z}/{y}/{x}.pbf
layers:
   - !!inc/file layers/background.yml
   # add any layer if needed
```

## 2-3. charites ライブプレビュー開始
これでstyle.ymlを編集しましたので、ローカルホストでcharitesのライブプレビューを立ち上げることが出来ます。ただし、現時点ではバックグラウンドのレイヤしかありませんので（コピーしてきたパッケージに含まれていた）、地図のレイヤは何も見えないはずです。

```powershell
cd charites-init
charites serve style.yml  #(Ctrl + c to stop)
```
ライブプレビューは http://localhost:8080 から見られます。 
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/7f65f60d-799e-d4a6-2a3f-c761a1990e65.png)

## 2-4. background color の変更の練習 (色の表現にも慣れてみましょう)
**試しに layers/background.yml を編集して、 backgound-color を変えてみましょう。テキストエディタで編集できます**。 色の指定は index color (e.g. '#D6EBF5')でも, rgb color (e.g. rgb(0,0,100))でも, rgba color (e.g. rgba(10,0,100,0.7))でもできます。また、 "red", "green", "blue"などの表現でも大丈夫です (参照： https://maplibre.org/maplibre-gl-js-docs/style-spec/types/#color )。 ただし、QGIS plugin や Esrc's ArcGIS API for Javascriptなどでは、それぞれ使える色の表現が違う可能性があるので気を付けましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/70bf5bdd-0102-5bf8-9bb7-089f5ea59d51.png)

## 2-5. レイヤ（layer）を足してみよう: グリッドライン（緯度経度の線）
charites ライブプレビューを実行したまま作業を続けます。まず、ラインのレイヤを足してみます。NaturalEarthの30度の経緯度線を足してみます。style.yml中ではneとしたソースです。編集を簡単にするため、charites-init/layers/layer_samplesの中に私が準備しておいたサンプルのyamlファイルを使います。**"ls-line-sample.yml" を "ls-grid30.yml" としてコピーして、それを編集します。**
charites ライブプレビューが走ったままなので、新しいコマンドラインを立ち上げないといけないかもしれません。
 
```powershell
cd charites-init/layers
cp layer_samples/ls-line-sample.yml ls-grid30.yml
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/deb6bcd8-505b-f66a-6cc9-23a347e6007f.png)

上のコマンドのようにコピーしてきたら、 "ls-grid30.yml" を以下のように編集します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/8ef0efdd-998c-2cda-23de-656ad0932a27.png)
テンプレートにはコメントタグがいくつかありますが、内容は以下のYAMLファイルのようにシンプルです。

```yaml:ls-grid30.yml
id: ls-grid30
minzoom: 0
maxzoom: 5
paint:
  line-color: '#999999'
  line-width: 0.7
source: ne
source-layer: grid30 
type: line
```

**しかし、ここまで作業しても、ライププレビューには何も変化が見られないでしょう。この後に、style.ymlを編集してlayersのリストに一行追加する必要があります**。 style.ymlの中で参照されているlayersの一覧の中に、一行以下のように追加してください。リストの中で下に来るレイヤほどウェブ地図の中では上に来ることに注意しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/5fa558d0-8382-11ad-4938-4e4e652671a8.png)
charites はファイルの更新を見つけて、変更を自動でライブプレビューに反映してくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/372ac6a2-afac-a6b2-753f-1a6b52c40f0d.png)

うまくいくと以下のような画面になります。おっと、私が作ったグリッドのベクトルタイルはズームレベルが１～５だったようです。**ですので、レイヤのスタイルで最大最小ズームを０ー５にしても、ウェブ地図ではズームレベル 0 から 1 未満で線が出てこないことを確認してください。**
この失敗から、ソースとなるベクトルタイルの構造をよく理解しておくことの重要性がわかります。スタイルで最大最小ズームを定義しなくても、ソースの最大最小ズームレベルでコントロールすることができます。（また、オーバーズーミングがどのように効いてくるかということも、ここでは説明しませんが理解しておくとよいと思います。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/4c986016-67f2-14de-c9d4-d14d3ca7f7df.png)

## 2-6. レイヤを足してみよう:  ポリゴン（Fill）を足す
次に、陸地のポリゴンを足してみます。 Esri's Feature Service で次のところから提供されているポリゴンです: https://geoservices.un.org/arcgis/rest/services/Hosted/UN_Geodata_Stylized/FeatureServer/3?f=pjson .
2022年12月上旬にこのURLにアクセスして、データをベクトルタイルに変換しておいたものがあります。(方法は [こちら](https://qiita.com/T-ubu/items/17cb65754a54aa5ffb21) ).

同じように、テンプレートの "pg-fill-sample.yml" を "pg-landmass.yml" としてコピーして編集します。
```powershell
cp layer_samples/pg-fill-sample.yml pg-landmass.yml
```

```yaml:pg-landmass.yml
id: pg-landmass
minzoom: 0
maxzoom: 5
paint:
  fill-color: rgb(237,237,237)
source: carto
source-layer: bnda 
type: fill
```
そして、style.yml にレイヤを追加するのも忘れないようにしましょう。backgroundより上ですが、グリッドラインよりは下にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/7a3041c5-a1a6-08a7-afd3-797143794fd4.png)

こうすると、以下の図のような地図を見られると思います。以下のスクリーンショットを取った時の私のcharitesのバージョンは古かったのですが、もしcharitesのバージョンが0.3.0であれば左下にレジェンドのコントロールなどもあると思います。なお、追加した陸地ポリゴンはデザイン地図の用途につかっていたもので詳細地図用途ではないので座標値などはあまり正確ではありません。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/1e1da4da-f350-ecec-ed4e-9e042f1b8a76.png)

## 2-7. レイヤを足してみよう:　もう一つのラインレイヤを "filter"を使って加える
次に、もう一つのラインを足してみます。ここでは、レイヤに対して"filter"を使ってみます。ライン用のテンプレートを"ls-coastline.yml"としてコピーして、以下のように編集します。style.ymlにレイヤを足すことも忘れないようにしましょう。

```yaml:ls-coastline.yml
filter:
- '=='
- - get
  - bdytyp
- 0
id: ls-coastline
minzoom: 0
maxzoom: 5
paint:
  line-color: '#0000FF'
  line-width: 1
source: carto
source-layer: bndl
type: line
```

ここでは、フィルターを使って、bdytyp が 0 のものだけを表示するようにしています。例えば、0を1に変えてみて実験してみてください。
filterの使い方についてはいろいろな方法があります。もし興味があれば、私の別の記事（[こちら](https://qiita.com/T-ubu/items/961176fb92fb66a927e0)）もご覧ください。

また、使う地図ライブラリによって、フィルタの表現を選ぶ必要もあると思いますので注意しましょう。

* 我々が今回使ったフィルタ (MapLibre/MapBoxで奨励されている): ["==",["get","bdytyp"],0]
* ArcGIS Online でよく使われているフィルタ(例): ["==","bdytyp",0]

ここまでで、こんな地図が見られます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/05ba6ec8-5e05-1a35-ed5a-de7ecb381e05.png)

## 2-8. レイヤを足してみよう:  symbolをたす (filter と matchを使う)
最後にシンボルのレイヤも試してみます。ここでも **filter** を使いますし、今度は **match** という表現を使って、データの属性に応じた地図記号アイコンを割り振ります。

テンプレート(pt-symbol-sample.yml) を "pt-city.yml" としてコピーします。そして以下のように編集します。編集が終わればstyle.ymlにレイヤを追加します。グリッドラインと海岸線ラインの間に入れましょう。

```yaml:pt-city.yml
filter:
- match
- - get
  - _symbol
- - 0 
  - 1 #Alternative
  - 2 #Territorial
- true
- false
id: pt-city
minzoom: 4
maxzoom: 5
layout:
  icon-allow-overlap: true
  icon-image:
  - match
  - - get
    - _symbol
  - 1
  - PPL_Alternative
  - 2
  - PPL_Territorial
  - PPL_National # for _symbol==0
  icon-size: 1
  text-allow-overlap: true
  text-optional: true
  text-field: '{_symbol}' #or [get, text_field] would be good.
  text-font: 
  - Open Sans Regular #text font should be from glyphs
  text-size: 10
  text-anchor: bottom
  text-offset:
   - 1
   - 1
  #text-letter-spacing: 0.1
  #text-max-width: 50
paint:
  text-color: '#000000'
  text-halo-color: '#FFFFFF'
  text-halo-width: 0.8
source: cme
source-layer: Populated Places_SYMBOLS #polygon or line can be also used for labelling 
type: symbol
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/4f46308d-62ab-7dae-29b6-a1c9105acf34.png)

少しだけ上のYAML (pt-city.yml)について説明します:
* フィルターが使われていますが、"_symbol" が [0, 1, 2] のいずれかであるものを選んでいます。"in"という論理演算子を使った表現にすることも可能ですが、今回は"match"を使っています。
* icon-imageにかんして、"match"表現を使うことで、"_symbol"属性に応じてアイコンを変えています。この"match"表現はMapLibre GL JS や Mapbox GJ JS でうまく働きますが、ArcGIS API for JavaScript や QGIS plugin では対応していない可能性があります (最新バージョンでは確認していません。)
* text-field では単純に"_symbol" を表示するようにしています。これはソースのベクトルタイルでシンボル用のベクトルタイルレイヤとラベル用のベクトルタイルが分かれていたためです。一つのベクトルタイルレイヤであれば、アイコンとラベルを一つのスタイルレイヤで表現することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/d511d73e-8784-3825-81e8-9e5de65cd06a.png)

symbol レイヤについて、今回はポイントのベクトルタイルを使いましたが、ラインやポリゴンのベクトルタイルレイヤを使うことも可能です。

ここまでの練習で、線、ポリゴン、点のデータのスタイル作成を練習できました。


## 2-9. おまけの練習
最新の unvt/charites はMapLibre GL JSの 2.4.0を使っているので、３D Terrainの地図もプレビューできます。データソースとしてraster-demを指定して、style.tml中にterrainを追加します。ちょっと難しいですが、応用問題の練習として以下のような事例も紹介しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/0be2874a-340d-b9ba-ced3-02fbc2a1bd32.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/c1bc7ea2-2d5b-2d39-e338-fa68d6cc72b0.png)


# Exercise 3. YAMLファイルからJSONファイルへの出力
ここまで出来たらスタイル編集を終わりにし、JSONファイルに出力することも練習します。
ライブプレビューを止め、コマンドラインに戻りましょう。JSONへの出力は簡単で以下のようなコマンドで出来ます。

```
charites build charites-init/style.yml out-style.json
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/7b1e00e0-e5ee-a72c-d142-7170ce1623a3.png)

おめでとうございます。これでスタイル編集が終わったスタイルをJSON形式で得ることができました。

（もしも、スタイルの表現にエラーがあるとここでJSONが作成されません。）


# まとめ
この記事ではどうやってMapbox/MapLibre向けのスタイルをUNVTのcharitesツールで編集するかを紹介しました。
ツールの使い方のみならず、Mapbox/MapLibreスタイルを理解する一助になれば幸いです。

# 謝辞
unvt/charitesの開発者の皆様、貢献者の皆様に感謝します。

# Refernces
* Tools
  * unvt/charites: https://github.com/unvt/charites 
* Repository
  * the files used in this practice: https://github.com/ubukawa/style-ex01 
* Others
  * UN Open GIS Initiative: http://unopengis.org/unopengis/main/main.php
  * UN Open GIS Initaitive - DWG7 (smart maps): https://github.com/UNopenGIS/7
  * Mapbox style specification: https://docs.mapbox.com/mapbox-gl-js/style-spec/
  * MapLibre style specification: https://maplibre.org/maplibre-gl-js-docs/style-spec/
  * nodejs: https://nodejs.org/en/