GitHubページでArcGIS Onlineでも表示できるベクトルタイルサーバーを作る（オーバーズーミングつき）

## はじめに
これまでに、ArcGIS Onlineでの外部ベクトルタイル（ArcGISサーバー以外のという意味）の表示を試したり（[こちらの記事](https://qiita.com/T-ubu/items/ac4c1aef293e4b253111)）、ArcGIS Onlineでのベクトルタイル表示を観察（[こちらの記事](https://qiita.com/T-ubu/items/6360252ece6c792732c7)）してきました。

今回は、ArcGIS Onlineで使えるベクトルタイルサーバーをGitHubページを使って作ってみます。具体的には、簡単なベクトルタイルを作成しGitHubページにホスティングします。そして、このベクトルタイルをArcGIS Onlineで消費できるように、簡単なスタイルや必要なjsonファイルを準備してきます。特に今回はオーバーズーミング（ベクトルタイルのMax Zoomより大きいズームレベルで少し小さいベクトルタイルを使う）のためのtilemapについても簡単な実装を試してみました。

## 環境
#####ローカル環境
* Windows 10 (Enterprise)
* Docker version 20.10.8　　（ベクトルタイル作成だけにつかいました）
* PowerShell version 5.1.19041.1237　　（ベクトルタイル作成だけにつかいました）
* テキストエディタ　（JSONファイルの作成等に使いました。）

#####ウェブ上のもの
* GitHub （レポジトリを作って、GitHubページにしました。）
* ArcGIS online　（読み込みを試しました。ArcGIS EnterpriseのArcGIS onlineだと思います。バージョンはいくつかわかりません。）


##### 使ったデータ
パブリックドメインとして使うことができる [NaturalEarth](https://www.naturalearthdata.com/) のデータを使いました。小縮尺のデータをいくつかダウンロードしました。

  

## 手順
### Step 1:　ベクトルタイルの作成
NaturalEarthのサイトからダウンロードしてきたシェープファイルを作業フォルダに保存します。その後、Docker（[unvt/nanban](https://hub.docker.com/r/unvt/nanban)）を立ち上げ、シェープファイルからGeoJSONに変換し（gdal）、その後にtippicanoeを使ってベクトルタイルを作成しました。ベクトルタイルはpbf形式で作成しました。
この方法については、以前の記事[（こちら）](https://qiita.com/T-ubu/items/76a60bfd4a27d11521e2)に詳細を書いてあります。
![2021-11-26-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/8d759e40-9dbc-e8f0-5038-daf3cd9f7ed4.png)
ベクトルタイルの構造は以下の通りです。

|  SourceName (from NaturalEarth)  | VT Layer Name  |  Min Zoom  | Max Zoom  |
| ---- | ---- | ---- | ---- |
|  ne_110m_coastline.shp  |  coastl  |  0  |  2  |
|  ne_110m_land.shp  |  landmass  |  0  |  2  |
|  ne_110m_rivers_lake_centerlines.shp  |  riverl  |  0  |  2  |
|  ne_110m_graticules_30.shp  |  graticules30  |  0  |  2  |

私はWindows環境で作業しているので、データ変換にDocker (unvt/nanban) を使いました。Windows環境でも、Dockerを使うことでtippecanoeを簡単に使うことができる環境を作ることができます。

注意：2021年の8月にDockerのポリシーが変わったので注意しましょう（[Updated Docker Subscription and Licensing Questions](https://www.docker.com/pricing/faq)）。


### Step 2: GitHubページでのホスティング 
GitHubに新しいレポジトリをつくりました（[ここ](https://github.com/ubukawa/VTserverTest)です）。Esriさんの一般的なベクトルタイルサーバーの構造に習い、レポジトリの中にVectorTileServerというフォルダを作って、その中にtileというフォルダを作りました。tileフォルダの下に、z/x/y.pbfの構造で作成したベクトルタイルを保存しました。
（Esriさんのベクトルタイル（vtpk等）では、ベクトルタイルをズームレベルごとのbundleファイルで保存していると思いますが、ここではpbfファイルを使います。）
![2021-11-26-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/490baf69-f427-9cb4-cb5c-6bc57fec2e75.png)
GitHubのsettingからGitHubページの設定を行って、ベクトルタイルをホストしました。これでベクトルタイルが　[https://ubukawa.github.io/VTserverTest/VectorTileServer/tile/{z}/{x}/{y}.pbf](https://ubukawa.github.io/VTserverTest/VectorTileServer/tile/{z}/{x}/{y}.pbf)　からアクセスできるようになりました。（なお、pbfタイルがすでにどこかにあるのであれば、必ずしも自分でホストしなくても大丈夫です。）

### Step 3: スタイルファイル作成 
アップしたベクトルタイルを使った地図を見るため、簡単なスタイルを作成しました（[こちら](https://ubukawa.github.io/VTserverTest/VectorTileServer/resources/styles/root.json) ）。mapboxスタイル仕様（MapLibreスタイル仕様）に基づき、以下のような5つのレイヤのスタイルを作成しました。オーバーズーミング機能を確認するため、緯度経度線はZL2までにして、他のものはZL6まで表示できるようにしています。
このくらいのスタイルなのでテキストエディタで書けますが、maputnikも少し使って色の雰囲気などを確認しながらやると早いと思います。

|  スタイルレイヤid  | タイプ  | ソースレイヤ  |  Min Zoom  | Max Zoom  |
| ---- | ---- | ---- | ---- | ---- |
|  background  |  background  |  na  |  na  |  na  |
|  landmass  |  Fill  |  landmass  |  0  |  6  |
|  river  |  line  |  landmass  |  0  |  6  |
|  coastl  |  line  |  coastl  |  0  |  6  |
|  graticules  |  line  |  grativules30  |  na  |  2  | 

maptunik で見てみたところ、以下のような地図になりました。
![2021-11-26-03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/6f7ac1e5-40ce-bcb8-a7a6-20fa0d379163.png)

注意点
スタイルファイルの中でsourcesの書き方には注意が必要です。私はいつも（MapLibre GL JS, mapbox gl js, maputnik　等）ならば、tilesでタイルのパスを指定するのですが、ArcGIS Onlineでの消費のために、urlでベクトルタイルサーバーのURLを指定します。（ArcGIS Onlineで、既存のスタイルファイルを編集する際にはtilesでの書き方でも大丈夫なのですが、ArcGIS Onlineに新たにコンテンツを追加する場合はこの方が良いようです。）

```json:スタイルファイル・普段のソースの書き方
  "sources": {
    "naturalearth": {
      "type": "vector",
      "tiles": [
        "https://ubukawa.github.io/VTserverTest/VectorTileServer/tile/{z}/{x}/{y}.pbf"
      ],
      "minZoom": 0,
      "maxzoom": 2
    }
  },
```

```json:スタイルファイル・今回のArcGIS&#x20;Online用ソースの書き方
  "sources": {
    "naturalearth": {
      "type": "vector",
      "url": "https://ubukawa.github.io/VTserverTest/VectorTileServer/",
      "minZoom": 0,
      "maxZoom": 2
    }
  },
```

### Step 4: スタイルファイルのホスティング
作成したスタイルファイルは、レポジトリの中の VectorTileServer/resources/stylesのフォルダの中に、root.jsonという名前にして保存しました。この構成もesri社の一般的なベクトルタイルサーバーにあわせています。

（参考）
resourcesの中にはsprite（地図記号）やglyphs（フォント）が入る場合がありますが、今回は使っていません。（styleでこれらを参照する場合でもstyles/root.jsで外部のURLを指定すれば大丈夫です。）

### Step 5: ベクトルタイルサーバー情報の作成
レポジトリのVectorTileServerフォルダの中に、index.jsonというファイルを作ります。[ArcGIS REST API](https://developers.arcgis.com/rest/services-reference/enterprise/vector-tile-service.htm)を参照して以下のようなstyle.jsonを作成しました（[こちら](https://ubukawa.github.io/VTserverTest/VectorTileServer/index.json)）。

```json:index.json
{
 "currentVersion": 10.9,
 "name": "ServerTest",
 "capabilities": "TilesOnly,Tilemap",
 "copyrightText": "NaturalEarth",
 "type": "indexedVector",
 "tileMap": "tilemap",
 "defaultStyles": "resources/styles",
 "tiles": [
  "https://ubukawa.github.io/VTserverTest/VectorTileServer/tile/{z}/{x}/{y}.pbf"
  ],
 "exportTilesAllowed": false,
 "initialExtent": {
  "xmin": -2.0037507842788246E7,
  "ymin": -2.0037508342787E7,
  "xmax": 2.0037507842788246E7,
  "ymax": 2.0037508342787E7,
  "spatialReference": {
   "wkid": 102100,
   "latestWkid": 3857
  }
 },
 "fullExtent": {
  "xmin": -2.0037507842788246E7,
  "ymin": -2.0037508342787E7,
  "xmax": 2.0037507842788246E7,
  "ymax": 2.0037508342787E7,
  "spatialReference": {
   "wkid": 102100,
   "latestWkid": 3857
  }
 },
 "minScale": 2.958287637957775E8,
 "maxScale": 1155581.1085775,
 "tileInfo": {
  "rows": 512,
  "cols": 512,
  "dpi": 96,
  "format": "pbf",
  "origin": {
   "x": -2.0037508342787E7,
   "y": 2.0037508342787E7
  },
  "spatialReference": {
   "wkid": 102100,
   "latestWkid": 3857
  },
  "lods": [
   {
    "level": 0,
    "resolution": 78271.516964,
    "scale": 2.958287637957775E8
   },
   {
    "level": 1,
    "resolution": 39135.75848199995,
    "scale": 1.479143818978885E8
   },
   {
    "level": 2,
    "resolution": 19567.87924100005,
    "scale": 7.39571909489445E7
   },
   {
    "level": 3,
    "resolution": 9783.93962049995,
    "scale": 3.6978595474472E7
   },
   {
    "level": 4,
    "resolution": 4891.96981024998,
    "scale": 1.8489297737236E7
   },
   {
    "level": 5,
    "resolution": 2445.98490512499,
    "scale": 9244648.868618
   },
   {
    "level": 6,
    "resolution": 1222.992452562495,
    "scale": 4622324.434309
   },
   {
    "level": 7,
    "resolution": 611.496226281245,
    "scale": 2311162.2171545
   },
   {
    "level": 8,
    "resolution": 305.74811314069,
    "scale": 1155581.1085775
   }
  ]
 },
 "maxzoom": 8,
 "resourceInfo": {
  "styleVersion": 8,
  "tileCompression": "gzip",
  "cacheInfo": {
   "storageInfo": {
    "packetSize": 128,
    "storageFormat": "compactV2"
   }
  }
 },
 "minLOD": 0,
 "maxLOD": 2,
 "maxExportTilesCount": 100000,
 "supportedExtensions": ""
}
```
注意した点は以下の通りです。

* capabilities:　今回はtilemapも試すのでTilemapも入れています。
* tiles:　ここでベクトルタイル（pbf）へのフルパスを指定しておきます。スタイルではこのパスが指定されていないのでここでしっかり指定します。
* extentについては、ウェブメルカトルでの座標範囲（単位はメートル）になっています。
* LOD（Level of Detail）でズームレベル 0－8を範囲の指定にしています。これはArcGIS Onlineで見られるウェブ地図のズームレベルだろうと理解しています。タイルデータはズームベレル0－2しかないのでminLODとmaxLODでそれぞれ指定しています。（LODでもっと縮尺をあげて見たい方はEsriさんのLiving Atlasなどにあるベクトルタイルサーバーなどを参考にすると大縮尺のズームレベルの値を確認できます。）

GitHubページにて公開されますが、このファイルは以下のどのURLでも同じファイルにアクセスできます。
https://ubukawa.github.io/VTserverTest/VectorTileServer/index.json
https://ubukawa.github.io/VTserverTest/VectorTileServer/
https://ubukawa.github.io/VTserverTest/VectorTileServer/?f=pjson

### Step 5: tilemapの作成
tilemapはMapLibre GL JSやMapbox gl jsでは耳にしない機能ですが、ArcGIS REST APIに含まれていて、どうやらタイルの有無を返すAPIの様です。
ArcGIS Onlineを観察したところ（[過去の記事](https://qiita.com/T-ubu/items/6360252ece6c792732c7)はこちら）、オーバーズーミングを判断する際にtilemapを使って 32×32の範囲のタイルについてその有無を問い合わせているということがわかっています。今回はオーバーズーミングを行うためにとりあえずZL0～6までのtilemapを作成してみます。（ZL0-5までは各ズームレベルで最低1つのjsonファイルを準備すればいいのですが、ZL6では4個、ZL7では16個、ZL8では64個となるので、とりあえずZL6まで試しました。）

基本方針は以下のものにしました。

* ベクトルタイルはZL0-2まであるので、0-2まではタイルがあるという情報を返し、ZL3以降はタイルがないという情報を返す。
* tilemapでは、~~tilemap/ズームレベル/列（左端の位置）/行（上端の位置）/ 横幅 / 縦幅~~　**(2022.1.7修正) tilemap/ズームレベル/行（上端の位置）/列（左端の位置）/ 横幅 / 縦幅** で問い合わせを行っている。ArcGIS Onlineの観察では問い合わせの範囲は常に32×32、上端・左端も32の倍数のみだったので、特定のパスのフォルダを準備して、そこにindex.jsonファイルを作っておいておくことにする（index.jsonという名前にしておくことで、ディレクトリまでの指定でjsonが返せるようにする）。

:::note warn
（2022.1.7 修正）
タイルマップのtopとleftの順番に気を付けましょう。ZL0-5までのタイルマップでは、(top, left) は (0, 0)しかないので、間違っても問題がおきませんが、ZL6からはパスと返すタイルマップのtop、leftが反対になってしまう結果になるとtilemapがうまく働きません。
このページの例では、ZL6のタイルマップはデータなしということを返すのでtopとleftが関係ないので、最初間違っていても問題なくいっていました。
:::

ZL0-5までは、次の5つのファイルを準備しました。実際のファイルは以下の通りです。32×32の範囲の問い合わせなので、1024の数字が帰ってきます。ZL0だとタイルは一つなので最初は1であとは全部0でよいですが、ZL1になると4つのタイルになるので110000・・・・110000・・・という数字の列になります。ZL2になると16のタイルが存在するので、11110000・・・11110000・・・11110000・・・11110000・・・・となります。ZL3以降はタイルがないのでタイルがないということをjsonファイルに書いておきます。

 * [tilemap/0/0/0/32/32/index.json](https://ubukawa.github.io/VTserverTest/VectorTileServer/tilemap/0/0/0/32/32/)
 * [tilemap/1/0/0/32/32/index.json](https://ubukawa.github.io/VTserverTest/VectorTileServer/tilemap/1/0/0/32/32/)
 * [tilemap/2/0/0/32/32/index.json](https://ubukawa.github.io/VTserverTest/VectorTileServer/tilemap/2/0/0/32/32/)
 * [tilemap/3/0/0/32/32/index.json](https://ubukawa.github.io/VTserverTest/VectorTileServer/tilemap/3/0/0/32/32/)
 * [tilemap/4/0/0/32/32/index.json](https://ubukawa.github.io/VTserverTest/VectorTileServer/tilemap/4/0/0/32/32/)
 * [tilemap/5/0/0/32/32/index.json](https://ubukawa.github.io/VTserverTest/VectorTileServer/tilemap/5/0/0/32/32/)


```json:tilemap/0/0/0/32/32/index.json
{"adjusted":false,"location":{"left":0,"top":0,"width":32,"height":32},"data":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}
```
```json:tilemap/1/0/0/32/32/index.json
{"adjusted":false,"location":{"left":0,"top":0,"width":32,"height":32},"data":[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}
```
```json:tilemap/2/0/0/32/32/index.json
{"adjusted":false,"location":{"left":0,"top":0,"width":32,"height":32},"data":[1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}
```
```json:tilemap/3/0/0/32/32/index.json
{"error":{"code":422,"message":"Tiles not present","details":[]}}
```
```json:tilemap/4/0/0/32/32/index.json
{"error":{"code":422,"message":"Tiles not present","details":[]}}
```
```json:tilemap/5/0/0/32/32/index.json
{"error":{"code":422,"message":"Tiles not present","details":[]}}
```

ZL6になると、32×32のタイルが4つ分あるので、以下の4つのファイルを用意します。中身はいずれもエラーメッセージです。

 * [tilemap/6/0/0/32/32/index.json](https://ubukawa.github.io/VTserverTest/VectorTileServer/tilemap/6/0/0/32/32/)
 * [tilemap/6/0/32/32/32/index.json](https://ubukawa.github.io/VTserverTest/VectorTileServer/tilemap/6/0/32/32/32/)
 * [tilemap/6/32/0/32/32/index.json](https://ubukawa.github.io/VTserverTest/VectorTileServer/tilemap/6/32/0/32/32/)
 * [tilemap/6/0/32/32/32/index.json](https://ubukawa.github.io/VTserverTest/VectorTileServer/tilemap/6/32/32/32/32/)

```json:エラーメッセージ
{"error":{"code":422,"message":"Tiles not present","details":[]}}
```

以上で、tilemapの準備は終わりです。Esriさんのベクトルタイルパッケージ（vtpk）ではtilemapのフォルダの中（直下）にroot.jsonが一つあるのですが、この書き方はまだよくわからないので今回は準備しません。（ArcGISServerのAPIではtilemapを返すときに利用されると思います、今回は問い合わせがあるであろうパスを事前に用意しておいたのでとりあえずは不要と考えました。）

### Step 6: 確認
ここまででで作成したものを振り返ってみます。以下があることを確認して次に進みました。

* ベクトルタイル VectorTileServer/tile/z/x/y.pbf （他の外部URLでも代替可能）
* ベクトルタイルのスタイル VectorTileServer/resources/styles/root.json
* ベクトルタイルサービス概要 VectorTileServer/index.json
* ベクトルタイルのタイルマップ　VectorTileServer/tilemap/z/32n/32m/32/32/ (n, m は整数)

### Step 7: ArcGIS Onlineでの読み込み
以前紹介した記事にあるとおり、ArcGIS Onlineではマップを開いてからコンテンツを読み込み、そしてそれをセーブするという手順で進めます。


ArcGIS Onlineで新しいMapを開きます。AddボタンからAdd Layer from Webを選んで、今回作ったサーバーのURLを入れます。
![2021-11-26-05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/81855287-7609-7be0-fa45-f87925740f59.png)

すると、ベクトルタイルの地図が読み込まれました。（この画面では動作確認のためベースマップとして読み込みました。）
![2021-11-26-04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/2a565a51-9351-a5bc-7d66-6bc5992b9f50.png)

拡大してみると、緯度経度の線（最大ズーム2）が消えて、他のレイヤ（最大ズーム6）が見える状況が確認できます。つまり、オーバーズーミングが機能しているようであると確認できました。
![2021-11-26-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/78dd7e24-e32d-b329-9ad0-be3ee861172f.png)

通信状況を見てみても、32というファイルを見ています（以下の例ではZL4のtilemapを見ています）。オーバーズーミング成功です。
![2021-11-26-07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/253c0eea-8b9b-ca5d-63a9-0bda83ccce75.png)

最後に、レイヤをコンテンツとして保存しました（ベースマップとして読み込んでいるとできません）。
![2021-11-26-08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/2d11379e-5da9-d73f-bbf9-eb02cdfdd0b7.png)


## まとめ
今回は、オーバーズーミングも含めたArcGIS Onlineようのベクトルタイルサーバーを作成しました。
例えば地理院地図Vector（仮称）などのベクトルタイルも、ArcGIS Onlineで利用できるようになる（with　オーバーズーミング）かもしれません。

今後、もう少し大きなズームレベルでのtilemapの準備（ZLが上がるとファイル数が多くなるので少し工夫の余地あり）なども考えていきたいです。

## 謝辞
今回の取組みはArcGIS REST APIや、ESRIさんのLiving Atlasを参考にさせていただきました。ありがとうございます。また、前回同様に、[David Hollema(2020)の記事](https://support.woolpert.io/hc/en-us/articles/360047005294-Vector-Tiles-on-Google-Cloud-Storage-Web-Clients)も今回の記事の参考になっています。


## 参考文献
* [ArcGIS REST API](https://developers.arcgis.com/rest/services-reference/enterprise/vector-tile-service.htm)
* [ArcGIS.com Vector Basemaps](https://www.arcgis.com/home/group.html?id=30de8da907d240a0bccd5ad3ff25ef4a#overview)
* [Vector Tiles on Google Cloud Storage: Web Clients (David Hollema, 2020)](https://support.woolpert.io/hc/en-us/articles/360047005294-Vector-Tiles-on-Google-Cloud-Storage-Web-Clients)