地理院地図Vector（仮称）や外部のベクトルタイルをArcGIS Onelineで使う（Tilemapつき）

日曜日の朝に早起きしたので、最近試したArcGIS Onlineでのベクトルタイル利用をQiitaにメモします。

## はじめに
これまでに『自分で生産、ホストしたベクトルタイルをArcGIS Onlineで使う方法』を調べてきました。
ArcGISサーバー以外でホストしている外部ベクトルタイル（注:Esriサービスの外部ということで、便宜的にここではこのような表現にしています）を、ArcGISで利用するのは少しハードルが高いです。（どうやってレイヤをインポートするのか、オーバーズーミングはどうやるのか？等）

ここでは、nodejsサーバーに簡単なインターフェースを用意して、ArcGIS Onlineで **地理院地図ベクター（仮称）** （国土地理院の運用するサーバーからpbfタイルが提供されている）を利用したみた経験を書きます。自分で作ったpbfファイルをArcGISオンラインで使ってみたい方の参考になれば幸いです。
私の、内部環境と独自データでうまくいったので、経験を共有するためにもオープンな地理院地図Vector（仮称）でやってみます。


* 地理院地図Vectorや、自分で作ってGitHubページでホストしているようなベクトルタイルをArcGISで使えるやり方をためします。（オーバーズーミングも可能。）
    * なお、オーバーズーミングとは、タイルデータの最大ズームレベル以上でそれより小さいズームレベルのタイルを使うことを指します。ArcGIS OnlineではTilemapを使って実現しています。
* nodejs/expressを使って、そのためのインターフェースを準備します。
    * localhostでhttpのホスティングをしてもテストはできます。
    * nodejs/expressサーバーを自分で建てられる環境がない人は、オーバーズーミングがなしですが（あるいは小縮尺だけ）GitHubページでやる方法もありますのでそちら（[前の記事](https://qiita.com/T-ubu/items/d2454cd7b018820fa8b1)）を試してください。


## 環境
* レンタルVPSサーバー
    * ハード： CPU 仮想２Core、メモリ1GB、SSD 50GB
    * CentOS Linux release 7.8.2003
    * nodejs v16.13.1
    * npm version 8.1.2
    * pm2 version 4.4.0 (短時間の実験ならなくてもよい。サーバーをデーモンプロセスで走らせるため)

なお、今回の取り組みでは直接データをホスティングするものではないので、ストレージは少なくても大丈夫だと思います。作る機能もルーティングとかjsonを返す機能くらいなので、そんなに重たい処理はしないと思います。

開発はWindowsPCでやりました。

## 使ったデータ
* 地理院地図Vector（仮称）
    * タイルURL：　https://cyberjapandata.gsi.go.jp/xyz/experimental_bvmap/{z}/{x}/{y}.pbf
    * 利用について（[地理院地図Vector（仮称）提供実験](https://github.com/gsi-cyberjapan/gsimaps-vector-experiment)・一部抄）：
        * 本提供実験のデータは、[国土地理院コンテンツ利用規約](https://www.gsi.go.jp/kikakuchousei/kikakuchousei40182.html)に従って利用できます。データを利用する際は、「国土地理院ベクトルタイル提供実験」などと、出典の明示を行ってください。
   * スタイル：　公開されているのはレイヤ数が多く複雑なので、自分で作ったテスト用のものをベースにして加工しました。（[もとになったスタイルはこちらのGitHubページにあります](https://ubukawa.github.io/plib-mlb01/test-style.json)）

## 手順
### Step 0: GitHubレポジトリ
今回の作業のレポジトリは https://github.com/ubukawa/piedmontite にあります。必要な方はご参照ください。

### Step1: 必要な機能の整理
作業を始める前に、これまでの経験([過去記事１](https://qiita.com/T-ubu/items/6360252ece6c792732c7)、[過去記事２](https://qiita.com/T-ubu/items/d2454cd7b018820fa8b1)、[unvt/marble](https://github.com/unvt/marble) など)を踏まえて、実装したい機能を考えます。以下の３つを作ることにしました。ただし、最後のタイルマップは完全なものでなく、必要最低限の機能にします（後述）。

* ArcGISオンライン用のベクトルタイル（サーバー）概要（index.json）を返す機能。
    * index.jsonは事前に用意しておく。
* Style情報（json形式）を返す機能。
    * style.json（リクエストはroot.jsonでくる）は事前に用意しておく。
* オーバーズーミングのために、タイルの存在を教える機能（Tilemap）
    * ArcGIS REST APIを完全に実装するのは困難なので必要最低限の機能。

なお、実際のベクトルタイルデータ（PBF形式）、地図記号（sprites）やフォント（glyphs）は、サーバーでルートを準備しなくても外部URLを直接指定すれば動くことを確認しているので準備しません。

また、上記の機能は、提供するデータセットごとに作りたいですから、サーバーの中にESRI用のインターフェース(esriIF)を作って以下のようなパスで実現することにしました。以下の（データセット名）は実際には、gsimaps-vector-experimentしています。

|サービス  |パス  |
|---|---|---|
|ベクトルタイルサーバー概要  | /esriIF/(データセット名)/VectorTileServer |
|スタイル  | /esriIF/(データセット名)/VectorTileServer/resources/styles |
|タイルマップ  | /esriIF/(データセット名)/VectorTileServer/tilemap |

:::note info
ここで小ネタです。ArcGIS REST APIのページのスタイルの説明ではスタイルの場所は .../resources/style と書いてありますが、実際のArcGISサーバーやLiving Atlasなどのデータを見てみると、.../resources/styles となっていますから、私もstylesで実装します。ちょっとつまづいたところなのでメモしておきます。

ArcGIS REST APIの記載：
https://developers.arcgis.com/rest/services-reference/enterprise/vector-tile-style.htm
LivingAtlasの例：https://basemaps.arcgis.com/arcgis/rest/services/World_Basemap_v2/VectorTileServer/resources/styles/
:::


### Step 2: サーバーの準備
簡単なnodejsサーバーのコードを準備しました。以下の[app.js](https://github.com/ubukawa/piedmontite/blob/main/app.js)です。ローカルではhttp、サーバーではhttpsにできるように、httpsも準備しますがコメントアウトしておきます。
コンフィグ情報をconfig/default.hjsonからよんで、ログの設定をして、expressを立ち上げます。esriのインターフェースはroutes/esriIF.jsというファイルを作ることにするので、app.jsのほうではルーティングを設定しておきます（変数の定義とapp.use('/esriIF', esriIFRouter)の追加。 ）。CORSも設定しておきます。
esriインターフェース関係の2行を消すと、htdocsを静的にホスティングしている簡単なサーバーとして動きます。

```js:app.js
const config = require('config')
const express = require('express')
const cors = require('cors') 
const morgan = require('morgan')
const winston = require('winston')
const DailyRotateFile = require('winston-daily-rotate-file')

// config constants
const morganFormat = config.get('morganFormat')
const htdocsPath = config.get('htdocsPath')
const port = config.get('port') 
const logDirPath = config.get('logDirPath')

// logger configuration
const logger = winston.createLogger({
  transports: [
    new winston.transports.Console(),
    new DailyRotateFile({
      filename: `${logDirPath}/server-%DATE%.log`,
      datePattern: 'YYYY-MM-DD'
    })
  ]
})

logger.stream = {
  write: (message) => { logger.info(message.trim()) }
}

// app
const app = express()
var esriIFRouter = require('./routes/esriIF') //esri interface (tilemap, etc..)
app.use(cors())
app.use(morgan(morganFormat, {
  stream: logger.stream
}))
app.use(express.static(htdocsPath))
app.use('/esriIF', esriIFRouter) //esri interface

//for http
app.listen(port, () => {
    console.log(`Running at Port ${port} ...`)
})

/* for https
const fs = require('fs')
const spdy = require('spdy') //for https
const privkeyPath = config.get('privkeyPath') //for https
const fullchainPath = config.get('fullchainPath') //for https
spdy.createServer({
  key: fs.readFileSync(privkeyPath),
  cert: fs.readFileSync(fullchainPath)
}, app).listen(port)
*/
```

コンフィグ設定は以下のようにしています（hjson形式）。ここではlogの場所やesri関係のjsonファイルをしまう場所(esri)を指定しています。localでやるときはポートの番号443を変更しましょう（8836,3000など）。**また、Tilemapで使うために、ここで読み込むデータセットとその最大ズームレベル、最小ズームレベルを指定します。**

```config/default.hjson(httpsの場合)
{
 morganFormat: tiny
 htdocsPath: htdocs
 privkeyPath: ./（場所）/privkey.pem 
 fullchainPath: ./（場所）/cert.pem
 logDirPath: log
 port: 443
 esriDir: esri
 esri-tilemap-min:{
   gsimaps-vector-experiment: 4
 }
 esri-tilemap-max:{
  gsimaps-vector-experiment: 17
 }
}
```


### Step 3: esri関係インターフェース
routes/esriIF.jsを準備しました。前のstepでルーティングの設定をしたので、.../eriIFときたリクエストはここで処理します。以下では、ポイントを説明していきますが、コードを見たい人は https://github.com/ubukawa/piedmontite/blob/main/routes/esriIF.js をどうぞ。

#### 3-1. モジュール、変数の読み込みなど 
最初のパートですが、使うモジュールを呼び出したあと、サーバー中のesri用のディレクトリの場所、データセットの最大ズーム、最小ズームを呼び込みます。そして、expressをスタートしています。

```js:routes/esriIF.jsの最初の部分
var express = require('express')
var router = express.Router()
const config = require('config')
const fs = require('fs')
const cors = require('cors')

// config constants
const esriDir = config.get('esriDir')
const maxTiles = config.get('esri-tilemap-max')
const minTiles = config.get('esri-tilemap-min')

// variables
let busy = false
var app = express()

app.use(cors())
```
#### 3-2. VectorTileServer概要を返すところ 
次のパートでは、esriIF/(データセット名)/VectorTileServerというように来たリクエストに対して、サーバーにあるesri/（データセット名）/index.json を返すようにしています。
（ここには書きませんが、たまにesriIF/(データセット名)/VectorTileServer/**index.json**というパスでリクエストする人もいるので、もう一度getのところを.../indexにして同じことをしています。）

```js:routes/esriIF.jsの一部
//Returing an index of VectorTileServer --> make sure that the index.json is ready
router.get(`/:t/VectorTileServer`,  
 async function(req, res) {
  busy = true
  const t = req.params.t
  var indexjsonPath = `${esriDir}/${t}/index.json`

  if(fs.existsSync( indexjsonPath )){   
    res.sendFile('index.json', { root: `./${esriDir}/${t}` })
    busy = false
  } else {
    console.log(indexjsonPath)
    res.status(404).send(`index.json not found: esriIF/${t}/VectorTileServer`)
    busy = false
  }
}
)
```

#### 3-3. Styleを返すところ
使っている仕組みは3-2と一緒ですが、esriIF/(データセット名)/VectorTileServer/resources/stylesというように来たリクエストに対して、サーバーにあるesri/（データセット名）/style.json を返すようにしています。パスに ?f=pjsonなどがついてくることもありますが、これで大丈夫です。

```js:routes/esriIF.jsの一部
//Returing a style file --> make sure that the style.json is ready
router.get(`/:t/VectorTileServer/resources/styles`,  //need to think about other request f=json, index.json etc
 async function(req, res) {
  busy = true
  const t = req.params.t
  var stylejsonPath = `${esriDir}/${t}/style.json`

  if(fs.existsSync( stylejsonPath )){   
    res.sendFile('style.json', { root: `./${esriDir}/${t}` })
    busy = false
  } else {
    console.log(stylejsonPath)
    res.status(404).send(`style.json (root.json) not found: esriIF/${t}/VectorTileServer/resources/style`)
    busy = false
  }
}
)

router.get(`/:t/VectorTileServer/resources/styles/root.json`,  //need to think about other request f=json, index.json etc
 async function(req, res) {
  busy = true
  const t = req.params.t
  var stylejsonPath = `${esriDir}/${t}/style.json`

  if(fs.existsSync( stylejsonPath )){   
    res.sendFile('style.json', { root: `./${esriDir}/${t}` })
    busy = false
  } else {
    console.log(stylejsonPath)
    res.status(404).send(`style.json (root.json) not found: esriIF/${t}/VectorTileServer/resources/style`)
    busy = false
  }
}
)
``` 

:::note info
そして、ここでまた小ネタです。スタイルのルーティングでも同じようなブロックが２つあります。../resouces/stylesとリクエストする人だけでなく、ArcGIS Onlineからは ../resouces/styles/root.json とリクエストされるのでこのようにしています。これを入れておかないとArcGIS Onlineで読み込めませんので注意しましょう！
:::
↓pm2のログ。赤で囲んだところをみるとroot.jsonにリクエストが来ていることがわかります。
![2022-01-09-pm2-test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/9615fbf6-8c1d-57e9-0aef-48167e43894c.png)

#### 3-4. タイルマップ
ArcGIS REST APIを見るとわかるように、タイルマップのリクエストは、.../tilemap/（ズームレベル）/（row→行、範囲の上端）/column（列、範囲の左端）/（幅）/高さ）となっています。
以前、ArcGIS Online観察したところでは、タイルマップへのリクエストには以下の特徴がありました。

* 特徴1：　幅と高さはいつも32（つまり32×32なので区画内の1024のタイルの存在の有無を聞いている）
* 特徴2：　トップとレフトは32の倍数（0,32,64,...）。ZL0-5ではトップとレフトは0しか使わない。

ArcGIS REST APIは、本来ならば任意の位置の任意の区画に対してタイルマップを返せるAPIなのですが、私の実装では簡単にするため、観察で見つけた上記2点の特徴をもったリクエストしか来ないという条件で準備することにしました。
また、それぞれのタイルの有無を個別に指定するのは難しいので、ベクトルタイルデータセットの存在するズームレベルの範囲内であれば、タイルマップは全て1（タイルがあるという意味）を、ズームレベルの範囲外ならタイルがないというcode 422を返すことにしました。なお、地理院地図Vector(仮称)ではZL4～17の範囲にデータがあります。

データがない場合は、問い合わせ位置の情報は不要ですが、データがある場合には、トップとレフトの位置なども入れるので、パスから読み込んだ変数も使ってjsonを返します。



```json:タイルがある場合のタイルマップ（tilemap/tilemap/8/64/96/32/32の例）
{"adjusted":false,"location":{"left":96,"top":64,"width":32,"height":32},"data":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]}
```

```json:タイルがない場合のタイルマップ
{"error":{"code":422,"message":"Tiles not present","details":[]}}
```

注意点ですが、ZL0-4までは、各ZL全体のタイル数が1024未満なので、(top, left)= (0, 0)からはじまる1024個のタイルの問い合わせに対して、1とすべき範囲が異なるのでdataを別に準備します。（ZL5以上は全て1でよい。）

ということで、以下の様な書き方をしてみました。

```js:routes/esriIF.jsの一部
//Tilemap function- t,left,top,m,ny are extracted from the path
router.get(`/:t/VectorTileServer/tilemap/:z/:row/:column/:width/:height`,  //need to think about other request f=json, index.json etc
 async function(req, res) {
  busy = true
  const t = req.params.t
  const z = parseInt(req.params.z)
  const row = parseInt(req.params.row)
  const column = parseInt(req.params.column)
  const width = parseInt(req.params.width)
  const height = parseInt(req.params.height)
  var maxTileZ = maxTiles[t]
  var minTileZ = minTiles[t]

  if(z > maxTileZ || z < minTileZ){ //if Z is larger than the max tile ZL or smaller than the min tile ZL, there is no tile.
      res.json({"error":{"code":422,"message":"Tiles not present","details":[]}})
  } else {
  if( maxTiles[t] && width==32 && height==32){ //in the future, we may add a detailed condition.
//  if(fs.existsSync( tmPath )){  // you can think about deliverying pre-existing json if you want. 
      var tmapdataFull = { 
        //Please note that at each zoom level, we use the same values for any tilemaps.
        //If we have a real tile mapping, we can consider extracting the requested tile map from the whole array.
        0:[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],
        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,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],
        2:[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],
        3:[1,1,1,1,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,1,1,1,1,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,1,1,1,1,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,1,1,1,1,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,1,1,1,1,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,1,1,1,1,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,1,1,1,1,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,1,1,1,1,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],
        4:[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,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],
        5:[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
      }
      var tmapdata = {}
      if (z < 5){
        tmapdata = tmapdataFull[z]
      }else{
        tmapdata = tmapdataFull[5]
      }
    res.json({ adjusted: false , location: {left: column, top: row, width: width, height: height}, data: tmapdata})
    busy = false
  } else {
    res.status(404).send(`tilemap not found: esriIF/${t}/VectorTileServer/tilemap/${z}/${row}/${column}/${width}/${height}`)
    busy = false
  }
}
}
)
```
最後のにこのjsをモジュールとしてexportしたら完了です。

### Step4 JSONファイルの準備
Step3までで、サーバーの準備ができました。esri/gsimaps-vector-experimentというフォルダを作って、その中にindex.jsonとstyle.jsonを準備します。

#### 4-1 index.json
ベクトルタイル（サーバー）の概要を記載するものです。このindex.jsonについて、ArcGIS REST APIにサンプルはあるのですが、各要素の説明はないので、いろいろな例を見ながら自力で作成してみました。


```json:index.json
{
 "currentVersion": 10.9,
 "name": "GSImaps Vector test",
 "copyrightText": "地理院地図Vector（仮称）",
 "capabilities": "TilesOnly,Tilemap",
 "type": "indexedVector",
 "tileMap":"tilemap",
 "defaultStyles": "resources/styles",
 "tiles": [
  "https://cyberjapandata.gsi.go.jp/xyz/experimental_bvmap/{z}/{x}/{y}.pbf"
 ],
 "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": 282.124294,
 "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
   },
   {
    "level": 9,
    "resolution": 152.874056570279,
    "scale": 577790.5542885
   },
   {
    "level": 10,
    "resolution": 76.4370282852055,
    "scale": 288895.2771445
   },
   {
    "level": 11,
    "resolution": 38.2185141425366,
    "scale": 144447.638572
   },
   {
    "level": 12,
    "resolution": 19.1092570712683,
    "scale": 72223.819286
   },
   {
    "level": 13,
    "resolution": 9.55462853563415,
    "scale": 36111.909643
   },
   {
    "level": 14,
    "resolution": 4.777314267817075,
    "scale": 18055.9548215
   },
   {
    "level": 15,
    "resolution": 2.388657133974685,
    "scale": 9027.977411
   },
   {
    "level": 16,
    "resolution": 1.19432856698734,
    "scale": 4513.9887055
   },
   {
    "level": 17,
    "resolution": 0.597164283427525,
    "scale": 2256.9943525
   },
   {
    "level": 18,
    "resolution": 0.2985821417799085,
    "scale": 1128.4971765
   },
   {
    "level": 19,
    "resolution": 0.1492910708238085,
    "scale": 564.248588
   },
   {
    "level": 20,
    "resolution": 0.07464553541190416,
    "scale": 282.124294
   }
  ]
 },
 "maxzoom": 20,
 "resourceInfo": {
  "styleVersion": 8,
  "tileCompression": "gzip",
  "cacheInfo": {
   "storageInfo": {
    "packetSize": 128,
    "storageFormat": "compactV2"
   }
  }
 },
 "minLOD": 0,
 "maxLOD": 0,
 "exportTilesAllowed": false,
 "maxExportTilesCount": 100000,
 "supportedExtensions": ""
}
```

index.jsonを書いたときの注意点は以下の通りです。

* currentVersionはオフィスで使っているデータと同じ10.9にしておきました。
* nameやコピーライトテキストは適宜記入します。
* capabilityは,タイルマップもあるのでTilesOnlyに加えてTilemapもつけています。
* typeはindexedFlatでも良いかもしれないですが、indexedVectorにしておきます。
* tileMapにはタイルマップのパスを書きます。相対パスでかきます。トリッキーですが、ここは"tileMap":"tilemap"です。なんでMを大文字にしているのかはわかりませんが、そういうことになっています。（私は引っかかりました。気をつけましょう。）
* defaultStylesの場所も書いておきます。（フォルダの指定ですが、その中のroot.jsonを読みに行きます。）
* tilesも本来は相対パスでtileと書けばいいのですが、**外部のベクトルタイルを使うためURLを記載します（**。esriさんのタイルの順番zyxとtippiecanoeで作ったタイルの順番zxyは違うので注意。
* InitialExtent、fullExtentはWebメルカトルで指定します。全球データと同じエクステントにしています。
* minScaleとmaxScaleは、その下のlodsに記載したスケールから拾ってきます。
* lodsはlevels of detailだと思いますが、各ズームレベルで解像度とスケールは決まった値を使っています。他の例から選んできてZL20まで記入しました。
* maxzoomが実際にArcGISオンラインで表示される最大ズームレベルになるようです。20までにしました。
* resourceInfoはpbfのことだと思うのですが、どのサンプルでも同じ書きぶりだったので同じようにしました。
* minLODやmaxLODは最初、ここでオーバーズーミングを決めるのかなと思っていたのですがあまり関係ないようです。他の例でも0にしているものが多く、ここは0にしておけば良さそうです。（タイルの有無はtilemapが伝えるので）


#### 4-2 style.json
そして、スタイルも作成します。
まずは簡単な地図で表示されるか試したかったので、以下の様な簡単なスタイルをつくりました。特に、注意することは、ソースの指定をtileではなくて、urlで指定することです。

* spritesとglyphsは、ArcGISサーバーではベクトルタイルサーバーのresouces中に入れていることが多いようですが、外部のものをURLを記入しても動きました。
* 私の経験の範囲では、ソースを2つ以上にすると上手くいったことがありませんので、ソースは1つにしておくとよいと思います。
* 最後の_sslが何をしているのかわからないですが、他のサンプルでも入っているので踏襲しています。

```json:style.json
{
  "version": 8,
  "name": "GSI Vector -simple test",
  "sources": {
    "v": {
      "type": "vector",
      "url": "../../",
      "attribution":"<a href=\"https://maps.gsi.go.jp/vector/\" target=\"_blank\">地理院地図Vector（仮称）</a>"
    }
  },
  "sprite": "https://gsi-cyberjapan.github.io/gsivectortile-mapbox-gl-js/sprite/std",
  "glyphs": "https://maps.gsi.go.jp/xyz/noto-jp/{fontstack}/{range}.pbf",
  "layers": [
    {
      "id": "background",
      "type": "background",
      "paint": {"background-color": "#D6EBF5"}
    },
    {
      "id": "ls-coastline",
      "type": "line",
      "source": "v",
      "source-layer": "coastline",
      "minzoom": 4,
      "maxzoom": 9,
      "layout": {"visibility": "visible"},
      "paint": {"line-color": "#636566"}
    },
    {
      "id": "pg-watera",
      "type": "fill",
      "source": "v",
      "source-layer": "waterarea",
      "minzoom": 4,
      "maxzoom": 7,
      "paint": {"fill-color": "rgba(46, 0, 255, 1)"}
    },
    {
      "id": "ls-boundary-pref",
      "type": "line",
      "source": "v",
      "source-layer": "boundary",
      "minzoom": 6,
      "maxzoom": 8,
      "filter": ["all", ["==", "ftCode", 51212]],
      "layout": {"line-cap": "square"},
      "paint": {"line-color": "#000000", "line-dasharray": [10, 2, 1, 2]}
    },
    {
      "id": "ls-boundary-cty",
      "type": "line",
      "source": "v",
      "source-layer": "boundary",
      "minzoom": 4,
      "maxzoom": 8,
      "filter": ["all", ["==", "ftCode", 51221]],
      "layout": {"line-cap": "square"},
      "paint": {"line-color": "rgba(34, 24, 21, 1)"}
    }
  ],
  "_ssl": true
}
```

:::note info
(2022.1.10 追記)
ここで小ネタです。上のスタイルでソース中のurlを相対パス(../../)で書いていますが、これをやるとなぜかArcGIS Onlineで「コンテンツ」に登録したレイヤーを「マップ」で開くときに読み込みのエラーになります。ですので、urlは絶対パスで指定した方が良いと思います。VectorTileServerで終わるパスをいれますが、最後にスラッシュをいれるとそれもエラーになる（コンテンツからスタイルを読めない）ので気をつけましょう。
:::


### Step5　サーバーをスタートする
ここまでで出来たものはGitHubのレポジトリにいれましたが、外部サーバーでgit cloneしてcongig設定やhttpsの設定をしました。
shスクリプトを書いて、pm2でapp.jsをスタートしました。

```bash:pmserve.sh
pm2 stop piedmontite; pm2 delete piedmontite; pm2 start app.js --name piedmontite; pm2 monit
```

次の実験をスタートするまで、以下のURLで試験公開しておこうと思います。
[https://www.unvt-test.com/](https://www.unvt-test.com/)

![2022-01-09-hp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/159669df-76cb-5904-85b7-2d409bd7a161.png)

これで、地理院地図Vector（仮称）のベクトルタイル（サーバー）に関する情報がインターネット上でアクセスできるようになりました。
[https://www.unvt-test.com/esriIF/gsimaps-vector-experiment/VectorTileServer](https://www.unvt-test.com/esriIF/gsimaps-vector-experiment/VectorTileServer)

### Step6　ArcGIS Onlineでみる。
ArcGIS OnlineのMapでAddボタンをおして、Add Layer from Webで作成したURLを入れます。
![2022-01-09-map01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/be2d9f9b-1244-898e-a2c6-b6c078e45125.png)

そうするとベクトルタイルの地図が見られるようになりました。コンテンツとして保存したい場合には、レイヤをクリックしてコンテンツとして保存します。
![2022-01-09-map02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/1368778a-e0bb-80db-f938-27626dded18e.png)

## まとめ
ここでは、ArcGIS Onlineで地理院地図Vectorのデータを表示する方法を練習しました。
今回の方法では、オーバーズーミングが対応できるようになっています。
スタイルについては、今後取組みを進めます。


## References

* [作業したレポジトリ https://github.com/ubukawa/piedmontite](https://github.com/ubukawa/piedmontite)
* [地理院地図Vector（仮称）提供実験](https://github.com/gsi-cyberjapan/gsimaps-vector-experiment)
* [国土地理院コンテンツ利用規約](https://www.gsi.go.jp/kikakuchousei/kikakuchousei40182.html)
* [ArcGIS REST API](https://developers.arcgis.com/rest/services-reference/enterprise/vector-tile-style.htm)
* https://qiita.com/T-ubu/items/d2454cd7b018820fa8b1