（メモ）nodejsで 正六角形のポリゴン(ベクトルタイル、GeoJSON sequence)を作った 

# はじめに
世の中には、たとえば Pixel Map Generator　( https://pixelmap.amcharts.com/ 下図 ) のように、正六角形のポリゴンを使って、世界地図を表現しているものがあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/f28a62f8-0ce4-d7e0-c021-e6f91796ac88.png)
（Pixel Map Generator）

一方で、背景に使うことのできる正六角形のポリゴンデータが手元になかったので、作れるか少し確認しました。


# 作業環境
* Windows PC
* Docker （ベクトルタイルを作るときにunvt/nanbanでtippecanoeを使う）
* nodejs

# 作業手順
## 作業1. web mercatorの座標範囲の整理
https://epsg.io/3857 によれば、座標範囲はXmin, Ymin, Xmax, Ymaxの順に -20037508.34, -20048966.1, 20037508.34, 20048966.1 となっています。

X方向（東西方向）に約 40,075 km、Y方向に約 40,098 km です。この範囲に六角形を並べていきます。並べる角度をどうしようかなと考えましたが（例えば下の２つのパターン）、簡単のために下の左のパターンにしようと思います。
ウェブメルカトルの投影法では東西と南北で正方形になりますが、実際距離は東西約40,000kmに対し、南北約20,000km（本当は85度以北はないので、これより短い。19,000kmくらい？）で、さらに投影による歪みを考えると、中心点の配置感覚が東西方向に短いほうがいいかなぁ、、、と考えました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/68e66e24-25d3-e368-0881-8033a33a5094.png)　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/d2313efe-a502-4d71-8e14-272c85e74cf1.png)

## 作業2. スクリプトを考える。
六角形の中心座標を(cx, cy)　、一片の長さをr とすると、6つの点は以下のように与えられると思います。これを反時計回りに並べればポリゴンができます。
```json
let p1 = [cx, cy + r]
let p2 = [cx - Math.sqrt(3) * r /2 , cy + r/2]
let p3 = [cx - Math.sqrt(3) * r /2 , cy - r/2]
let p4 = [cx, cy - r]
let p5 = [cx + Math.sqrt(3) * r /2 , cy - r/2]
let p6 = [cx + Math.sqrt(3) * r /2 , cy + r/2]
f.geometry.coordinates = [[p1,p2,p3,p4,p5,p6]]
```

中心点が並ぶyは以下のように与えます。赤道から北にまずy座標をリストしていき、そのあと赤道の下から南に向かってリストしていきます。

```js
let ylist = []
let row = 0
let y = 0

while (y < ymax - r){
    ylist.push(y)
    row ++
    y = y + 3*r/2
}

y = -3*r/2

while (y > ymin + r){
    ylist.push(y)
    row ++
    y = y - 3*r/2
}
```

中心のy座標（cy）によって、中心のx座標（cx）の配置がかわるので、xについては以下のようにリストします。
```js
for (let cy of ylist){
    if ( cy % r == 0 ){
        let xlist = []
        let x = 0
        console.log(`${cy} is A`)

        while (x < xmax - Math.sqrt(3)*r/2){
            xlist.push(x)
            x = x + Math.sqrt(3)*r
        }

        x= - Math.sqrt(3)*r

        while (x > xmin + Math.sqrt(3)*r/2){
            xlist.push(x)
            x = x - Math.sqrt(3)*r
        }

        for ( let cx of xlist){
            id ++
            f.properties._id = id
            f.geometry.type = 'Polygon'
            let p1 = [cx, cy + r]
            let p2 = [cx - Math.sqrt(3) * r /2 , cy + r/2]
            let p3 = [cx - Math.sqrt(3) * r /2 , cy - r/2]
            let p4 = [cx, cy - r]
            let p5 = [cx + Math.sqrt(3) * r /2 , cy - r/2]
            let p6 = [cx + Math.sqrt(3) * r /2 , cy + r/2]
            f.geometry.coordinates = [[p1,p2,p3,p4,p5,p6]]
            f.tippecanoe.layer = layer 
            f.tippecanoe.minzoom = minz
            f.tippecanoe.maxzoom = maxz

            stream.write(JSON.stringify(f))
            stream.write(', \n')
        }
    } else {
        let xlist = []
        let x = Math.sqrt(3)*r/2

        console.log(`${cy} is B`)
        while (x < xmax - Math.sqrt(3)*r/2){
            xlist.push(x)
            x = x + Math.sqrt(3)*r
        }

        x= - Math.sqrt(3)*r/2

        while (x > xmin + Math.sqrt(3)*r/2){
            xlist.push(x)
            x = x - Math.sqrt(3)*r
        }

        for ( let cx of xlist){
            id ++
            f.properties._id = id
            f.geometry.type = 'Polygon'
            let p1 = [cx, cy + r]
            let p2 = [cx - Math.sqrt(3) * r /2 , cy + r/2]
            let p3 = [cx - Math.sqrt(3) * r /2 , cy - r/2]
            let p4 = [cx, cy - r]
            let p5 = [cx + Math.sqrt(3) * r /2 , cy - r/2]
            let p6 = [cx + Math.sqrt(3) * r /2 , cy + r/2]
            f.geometry.coordinates = [[p1,p2,p3,p4,p5,p6]]
            f.tippecanoe.layer = layer 
            f.tippecanoe.minzoom = minz
            f.tippecanoe.maxzoom = maxz

            stream.write(JSON.stringify(f))
            stream.write(', \n')
        }
    }
}
```

## 作業3. GeoJSON sequenceに出力する
上記の方針で、nodejsのスクリプトを書きました。

* index.js: https://github.com/ubukawa/hex/blob/main/index.js
* config file: https://github.com/ubukawa/hex/blob/main/config/default.hjson

config/default.hjson のなかで、一片の長さ、ベクトルタイル用の情報（レイヤ名、最大・最小ズーム )、もともとの投影法（ウェブメルカトル）のX方向の長さ、y方向の長さを入れておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/8ca7f020-a727-64c7-933a-c90f96d1928c.png)
将来のために正六角形の隙間をつくるgapも作っていますが、実際には使っていません。（中心点から点を配置するとき、r=(1-gap)*r）で少し縮めるイメージでいました）

そして、index.jsを実行すると、GeoJSON Sequenceで出力できます。

```bash
node index.js
```




## 作業4. tippecanoeでベクトルタイルに変換する
できたGeoJSON sequence ファイルをfelt/tippecanoeでベクトルタイルに変換します。ビュワーでですぐに確認したいので、pmtilesにすることにします。

```bash
tippecanoe -o docs/hex70.pmtiles --no-tile-compression --no-feature-limit --no-tile-size-limit --projection=EPSG:3857 --force test-400.=EPSG:3857 --force test-400.geojsons
```

# 結果

作ったタイル(pmtiles)をPMTilesViewerで見てみました。今回、六角形の一辺が400km、200km、100kmなどのパターンで作ってみました。用途によって大きさを使い分ければよさそうですね。

* 400km
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/0b9e8959-db80-3cb7-c466-ee11592a19a3.png)

* 200km
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/2bf6a559-ab57-1f22-95a3-765370953d08.png)

* 100km
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679143/696f850b-f4d4-c715-f4b6-890823c6b11c.png)


# まとめ
今回は六角形ポリゴンを作るところまでで終わりにしましたが、GeoJSON seqence から ベクトルタイルに変換するとこまでここに書きました。

どこかに使える六角形データはあると思いますが、自分で、一片の長さを決められる、ベクトルタイルの最大ズームと最小ズームを調整できる等の理由から自分でも作成してみました。

今後、発展させるとすると、、、
* GeoJSON seqをGeoJSON 又はシェープファイルにして、空間解析を行い結果をベクトルタイルとして公開する。
* 六角形ポリゴンにコード（例：国や地域など）をつけて、六角形のピクセルマップとして利用する。（最初に同僚が考えていた利用法）

などが考えられると思います。


# 参考ページ
* 今回の作業レポジトリ: https://github.com/ubukawa/hex
* Pixel Map Generator: https://pixelmap.amcharts.com/
* epsg.io (ウェブメルカトル): https://epsg.io/3857