produce-gsc-osm（PostGISから大縮尺のOSMベクトルタイル作成）を理解する

# はじめに
[こちらの記事](https://qiita.com/k96mz/items/be74755c97f51f4d3e2b)でunデータのズームレベル6~15のベクトルタイルを作成するproduce-gsc-unレポジトリを解説しました。この記事では、オープンストリートマップ(OSM)データのズームレベル6~15のベクトルタイルを作成する[produce-gsc-osmレポジトリ](https://github.com/unvt/produce-gsc-6/tree/main/produce-gsc-osm)を解説します。

作業レポジトリは[こちら](https://github.com/k96mz/20241017produce-gsc-osm)です。

# unデータのズームレベル6~15のベクトルタイルを作成する
参照しているレポジトリには、「index_day01.js」や「index_everyday.js」などがありますが、参照する設定ファイルなどが少し異なるのみで、コードとしてはほぼ同じなので、「index_everyday.js」を使用することにします。index_everyday.jsのコードはこれまで解説してきたコードとほぼ同じなので、解説は省略します。

## modify.js
それぞれのテーブルに対して、レイヤ名、最小ズームレベル、最大ズームレベルの付与、属性の削除などを行っています。「@mapbox/geojson-area」モジュールが使用されています。解説は[こちらの記事](https://qiita.com/k96mz/items/1e0fd6133e45fd3513d2)にあります。新しく関数flapが使用されているので、こちらを解説します。

```javascript:modify.js
const geojsonArea = require('@mapbox/geojson-area');

const flap = (f, defaultZ) => {
  switch (f.geometry.type) {
    case 'MultiPolygon':
    case 'Polygon':
      let mz = Math.floor(19 - Math.log2(geojsonArea.geometry(f.geometry)) / 2);
      if (mz > 15) {
        mz = 15;
      }
      if (mz < 6) {
        mz = 6;
      }
      return mz;
    default:
      return defaultZ ? defaultZ : 10;
  }
};
```

・引数
f：地理データ（フィーチャ）オブジェクト
defaultZ：デフォルトのズームレベル

・switch (f.geometry.type) {
    case 'MultiPolygon':
    case 'Polygon':
  }
地理データのタイプが MultiPolygon または Polygon の場合、面積に基づいてズームレベルを計算します。それ以外のジオメトリタイプ（例えば Point や LineString）の場合は、デフォルトのズームレベルを返します。

・let mz = Math.floor(19 - Math.log2(geojsonArea.geometry(f.geometry)) / 2);

geojsonArea.geometry(f.geometry) は、@mapbox/geojson-area モジュールを使って、f.geometry の面積を計算しています。
Math.log2(面積) で面積の対数（ベース2）を計算し、それを 2 で割り、その結果を 19 から引くことでズームレベルを計算しています。
Math.floor() で結果を切り捨て、小数点以下を取り除きます。

例えば、Math.log2(8) は 3 を返します。なぜなら 2^3 = 8 だからです。つまり、100平方メートルの面積を持つポリゴンだと、Math.log2(100) ≒ 6.64であり、2で割ると3.32、これを19から引くと15.68となります。Math.floor(15.68)は15なので、mz = 15となります。

・default:
    return defaultZ ? defaultZ : 10;
default ブロックでは、もしジオメトリタイプが MultiPolygon または Polygon でない場合、defaultZ が指定されていればその値を返し、指定されていない場合は 10 を返します。

まとめると、関数flapがあることで、最小ズームレベルをポリゴンの面積に基づいて設定することが出来ます。面積が小さい地物に関して、ズームレベルが大きくなってから表示されることで、効率的にウェブ地図を表示出来ます。

### default.hjson
「index_everyday.js」を見ながら、参照したレポジトリのdefault/default-sample.hjsonから不必要な部分を削除して作成しました。

※その他
「npm init」を実行して、package.jsonを作成し、必要なモジュールをインストールします。@mapbox/tilebeltはバージョン1.0.2をインストールすることに注意します。

```
npm install @mapbox/geojson-area @mapbox/tilebelt@1.0.2 better-queue cli-spinner config hh-mm-ss hjson pg prettysize winston winston-daily-rotate-file    
```

## プログラムの実行
ローカルでファイルなどを用意したので、次にLinuxPCで作業をします。
すべての範囲でプログラムを回すと時間がかかり過ぎるので、6-35-31(z-x-y)のデータのみで実行します。中央アフリカ共和国の首都バンギ辺りのデータです。設定ファイルはdefault_shortList.hjsonとしました。

LinuxPC上で、npm installとして必要なモジュールをインストールしてから、以下のコードを実行します。

```
sudo node produce-gsc-osm/index_everyday.js
```

今回もPMTiles形式のベクトルタイルを作成するために、index_everyday_pmtiles.jsというファイルも用意して、プログラムを実行しました。

PMTilesビューワで見ると、以下の地図が表示されます。

![スクリーンショット 2024-10-17 22.41.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684675/1b243810-594c-baf8-0af3-7a05dda9787d.png)

次は、以下の３つの領域で実行してプログラムが回るか確かめます。
5-14-13
5-14-14
5-15-13

設定ファイルはdefault_shortList2.hjsonとしました。
mbtiles形式とpmtiles形式の両方で作成出来ました。

# tile-join
前回の記事でUNベクトルタイルを、今回の記事でOSMのベクトルタイルを作成しました。ここでは、複数の地図タイルセットを結合するためのコマンドであるtile-joinを使用して、２つのベクトルタイルをマージさせたいと思います。tile-joinの使い方は[こちら](https://github.com/felt/tippecanoe/tree/main?tab=readme-ov-file#tile-join)に記載されています。
今回は、pmtilesファイルを作成しました。

```
tile-join --no-tile-size-limit -f -o large_tiles/unosm/tile_every/6-35-31.pmtiles produce-gsc-un/mbtiles/un_tile/6-35-31un.pmtiles produce-gsc-osm/mbtiles/osm_tile_every/6-35-31osm.pmtiles;
```

出力されるpmtilesファイル
- large_tiles/unosm/tile_every/6-35-31.pmtiles

入力されるpmtilesファイル
- produce-gsc-un/mbtiles/un_tile/6-35-31un.pmtiles
- produce-gsc-osm/mbtiles/osm_tile_every/6-35-31osm.pmtiles

:::note info
Linux PCにおいては、以下のコマンドで実行出来ます。
 sudo /usr/local/bin/tile-join --no-tile-size-limit -f -o large_tiles/unosm/tile_every/6-35-31.pmtiles produce-gsc-un/mbtiles/un_tile/6-35-31un.pmtiles produce-gsc-osm/mbtiles/osm_tile_every/6-35-31osm.pmtiles;
:::


オプション
--no-tile-size-limit: 500kb以上のタイルをスキップしない
-f: アウトプットファイルが存在しても上書きして処理を実行
-o: 出力するアウトプットファイルを指定

作成したpmtilesファイルをpmtilesビューワで見ると以下のとおりです。unデータとosmデータがマージされて、どちらも表示されています。

![スクリーンショット 2024-10-21 10.19.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684675/7f010cbf-be44-05e3-48b6-fc689acc3408.png)

# 6-35-31(z-x-y) 中央アフリカ共和国の首都バンギ辺りのデータ

pmtilesビューワ(UN Data)
https://pmtiles.io/#url=https%3A%2F%2Fk96mz.github.io%2F20241017produce-gsc-osm%2Fproduce-gsc-un%2Fmbtiles%2Fun_tile%2F6-35-31un.pmtiles&map=6.54/3.345/19.624&showTileBoundaries=true

pmtilesビューワ(OSM data)
https://pmtiles.io/#map=6.7/3.257/18.717&url=https%3A%2F%2Fk96mz.github.io%2F20241017produce-gsc-osm%2Fproduce-gsc-osm%2Fmbtiles%2Fosm_tile_every%2F6-35-31osm.pmtiles&showTileBoundaries=true

pmtilesビューワ(UN+ OSM data)
https://pmtiles.io/#url=https%3A%2F%2Fk96mz.github.io%2F20241017produce-gsc-osm%2Flarge_tiles%2Funosm%2Ftile_every%2F6-35-31.pmtiles&map=6.52/2.811/19.688&showTileBoundaries=true



# scpコマンドでmbtilesファイルをリモートサーバにコピー

下記のようなコマンドを実行することで、mbtilesファイル等をリモートサーバにコピー出来ます。LinuxPCで試してみたところ、無事にコピー出来ました。

scp -i XXX(path to your ssh key) -r ./large_tiles/unosm/tile_every/* (username)@(hostingserver):(path)/mbtiles/unosm

scpコマンドの説明は[こちらの記事](https://qiita.com/k96mz/items/928690dc97cfba09c6b7#scp%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%A8%E3%81%AF)にあります。

# crontabを利用した定期的なプログラムの実行
参照している[こちらのレポジトリ](https://github.com/unvt/produce-gsc-6/tree/main)にあるシェルスクリプトを実行するために、crontabが使用されています。
crontabの使い方は[こちら](https://qiita.com/k96mz/items/668bee0c6d751a444b5c#%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90)の記事にあります。

```
crontab -l
```
として、現在設定されているcronジョブをリスト表示してみると以下のとおり表示されます。

```
03 7 * * * cd /home/vectortile/data_conversion/produce-gsc-6; ./work_every.sh > /home/vectortile/data_conversion/cron-log/everyday-log.log 2>&1
00 14 * * 1 cd /home/vectortile/data_conversion/produce-gsc-6; ./work_day01.sh > /home/vectortile/data_conversion/cron-log/day01-log.log 2>&1
00 14 * * 2 cd /home/vectortile/data_conversion/produce-gsc-6; ./work_day02.sh > /home/vectortile/data_conversion/cron-log/day02-log.log 2>&1
00 14 * * 3 cd /home/vectortile/data_conversion/produce-gsc-6; ./work_day03.sh > /home/vectortile/data_conversion/cron-log/day03-log.log 2>&1
00 14 * * 4 cd /home/vectortile/data_conversion/produce-gsc-6; ./work_day04.sh > /home/vectortile/data_conversion/cron-log/day04-log.log 2>&1
00 14 * * 5 cd /home/vectortile/data_conversion/produce-gsc-6; ./work_day05.sh > /home/vectortile/data_conversion/cron-log/day05-log.log 2>&1
00 14 * * 6 cd /home/vectortile/data_conversion/produce-gsc-6; ./work_day06.sh > /home/vectortile/data_conversion/cron-log/day06-log.log 2>&1
00 14 * * 7 cd /home/vectortile/data_conversion/produce-gsc-6; ./work_day07.sh > /home/vectortile/data_conversion/cron-log/day07-log.log 2>&1
## twice a week
00 22 * * 3 cd /home/vectortile/data_conversion/produce-gsc-6; ./work_day01.sh > /home/vectortile/data_conversion/cron-log/day01-log.log 2>&1
00 22 * * 4 cd /home/vectortile/data_conversion/produce-gsc-6; ./work_day02.sh > /home/vectortile/data_conversion/cron-log/day02-log.log 2>&1
00 22 * * 5 cd /home/vectortile/data_conversion/produce-gsc-6; ./work_day03.sh > /home/vectortile/data_conversion/cron-log/day03-log.log 2>&1
00 22 * * 6 cd /home/vectortile/data_conversion/produce-gsc-6; ./work_day04.sh > /home/vectortile/data_conversion/cron-log/day04-log.log 2>&1
00 22 * * 7 cd /home/vectortile/data_conversion/produce-gsc-6; ./work_day05.sh > /home/vectortile/data_conversion/cron-log/day05-log.log 2>&1
00 22 * * 1 cd /home/vectortile/data_conversion/produce-gsc-6; ./work_day06.sh > /home/vectortile/data_conversion/cron-log/day06-log.log 2>&1
00 22 * * 2 cd /home/vectortile/data_conversion/produce-gsc-6; ./work_day07.sh > /home/vectortile/data_conversion/cron-log/day07-log.log 2>&1
```

例えば、1行目は、毎日7時3分に work_every.sh スクリプトを実行し、出力ログを everyday-log.log に保存することを示しています。
2行目から8行目では、毎週月曜日から日曜日の14:00にそれぞれ異なるスクリプトを実行しています。
10行目以降は、夜22:00にスクリプトを実行しています。
例えば、work_day01.shは月曜14時と、水曜22時の週2回実行されます。
実行にかかる時間は、過去の資料によるとosmの変換に最大7時間、マージに4時間程度です。
上記コマンドの通り、osmデータは定期的にアップデートされますが、UNデータはされないので、必要に応じて手動でアップデートする必要があります。


# まとめ
オープンストリートマップ(OSM)データのズームレベル6~15のベクトルタイルを作成するproduce-gsc-osmレポジトリを解説しました。さらにtile-joinを利用したmbtilesファイル等のマージ、scpコマンドを用いたファイルをリモートサーバにコピーする方法、crontabを利用した定期的なプログラムの実行について記載しました。

# Reference

https://qiita.com/k96mz/items/be62463594b28a21e73e

https://github.com/unvt/produce-gsc-6/tree/main/produce-gsc-osm

https://qiita.com/k96mz/items/1e0fd6133e45fd3513d2

https://github.com/felt/tippecanoe/tree/main?tab=readme-ov-file#tile-join

https://qiita.com/k96mz/items/928690dc97cfba09c6b7