Jupyter Notebook上で3Dコロプレスマップを描写

# はじめに
- 値に応じて市区町村および都道府県が浮き上がる3DコロプレスマップをNotebook上で描写してみた
- 参考資料が少なくちょっと手間取ったので備忘録としてまとめておく
- 今回使用したコードは[github](https://github.com/Dr-Sin1/choroplethMap3D)にアップロードしておく

## ポイント
- cesiumpyというラッパーを使うことでNotebook上でCesimu.jsを利用
  - https://github.com/sinhrks/cesiumpy
- Cesimu.jsにより下記のサイトのGeoJsonを読み込み高さを設定して描写
  - https://github.com/niiyz/JapanCityGeoJson

## 描写例
![figure.GIF](https://qiita-image-store.s3.amazonaws.com/0/23691/b9671fec-ee23-2a89-9c22-2acb2845dc1f.gif)

# 動かしてみた
- 今回の動作環境
    - クライアント（Windows 10 Pro）
        - ブラウザ : Google Chrome 68.0.3440.106<br />（Microsoft Edge, Firefoxでは動作しなかった）
    - サーバ（Ubuntu 18 on VMware Workstation 12 Player）
        - JupyterLab : 0.33.11
        - python : 3.6
        - cesiumpy : 0.3.3

## 準備
- cesiumpyのインストール
- GeoJsonのダウンロード
- （Jupyter Notebookの環境は構築済みとする）

```python
! pip install cesiumpy
import cesiumpy
```

## 群馬県を描写（デフォルト設定）
```python
#群馬
v = cesiumpy.Viewer()

#geojsonを読み込み座標棟を取得
res = cesiumpy.io.read_geojson('./geojson/prefectures/10.json')

#グラフの値をextrudedHeightとすることで値に応じてポリゴンを浮き出させる
myExtrudedHeight = 10e3

#エンティティーへ設定
v.entities.add(res, extrudedHeight=myExtrudedHeight)

#描写
v
```

![1.GIF](https://qiita-image-store.s3.amazonaws.com/0/23691/d74c259a-c827-40d8-1771-a08260f4350c.gif)

## 群馬県を描写（カスタム）
- 本家のCesium.jsと同様に各種プロパティを変更可能
- ラッパーが対応していない機能も一部あるようなので注意

```python
#ウィジェットの有無は辞書型で渡すので注意
options = dict(\
               animation=False,\
               timeline=False,\
               fullscreenButton=False,\
              )
```

```python
#グンマ
v = cesiumpy.Viewer(**options)
res = cesiumpy.io.read_geojson('./geojson/prefectures/10.json')
myExtrudedHeight = 10e3
#各種プロパティの設定
myName = '群馬県'
myDescription = 'I love Gunma'
myMaterial = cesiumpy.color.Color.fromCssColorString('pink').withAlpha(0.8)
myOutlineColor = cesiumpy.color.Color.fromCssColorString('#FF0000')
v.entities.add(\
               res,\
               name = myName,\
#おそらくdescriptionは非対応
               description=myDescription,\
               extrudedHeight = myExtrudedHeight,\
               material = myMaterial,\
               outline  =True,\
               outlineColor = myOutlineColor,\
              )
v
```

![2.GIF](https://qiita-image-store.s3.amazonaws.com/0/23691/94242e45-2384-6eab-fd65-4efba15e9ac5.gif)

## 群馬県を描写（市区町村別）
- 市区町村単位でGeoJsonを読み込めばよい
- 元データは行政区域単位で用意されており、行政区域コードがファイル名である
- 市区町村名と行政区域コードを対応付けたJsonファイルを用意しておくと管理しやすい

```python
import json
import os
import random
```

```python
#市区町村名と行政区域コードを対応付けたJsonファイルを読み込む
with open('./myList.json') as f:
    ml = json.load(f)
f.close()
```

```python
#行政区域コードからディレクトリを判別する関数
def setDir(code):
    n = int(code)
    if (n > 1000):
        return code[0:2]
    else:
        return "mergedPrefectures"
```

```python
#Entitiyを1つ追加する関数（引数:行政区域コード,値,色,輪郭色）
def setEntitiy(code,val,rgb1,rgb2):
    path = './geojson/'+setDir(code)+'/'+code+'.json'
    res = cesiumpy.io.read_geojson(path)
    myName = ml[code]
    myMaterial = cesiumpy.color.Color.fromCssColorString(rgb1)
    myOutlineColor = cesiumpy.color.Color.fromCssColorString(rgb2)
    v.entities.add(res,\
                   name=myName,\
                   extrudedHeight=val,\
                   material=myMaterial,\
                   outline=True,\
                   outlineColor=myOutlineColor)
```

```python
#ディレクトリ内のgeojsonをすべて描写する関数（値も色もランダム）
def setRandomEntities(code):
    list = []
    path = 'geojson/'+code
    for file in os.listdir(path):
        base, ext = os.path.splitext(file)
        if ext == '.json':
            list.append(base)
    list.sort()
    for i in list:
        val = random.randrange(10e3)
        clr = '#C'+str(val).zfill(5)
        setEntitiy(i,val,clr,clr)
```

```python
#グンマー
v = cesiumpy.Viewer(**options)
setRandomEntities('10')
v
```

![3.GIF](https://qiita-image-store.s3.amazonaws.com/0/23691/e372e310-ea5e-1e26-a160-bab8cd926e83.gif)

## 日本全国を描写
### 描写失敗
- GeoJsonのデータが大きすぎてブラウザが停止
![error.GIF](https://qiita-image-store.s3.amazonaws.com/0/23691/4f1021a2-2729-ec19-be44-ff866866ec9d.gif)

### GeoJsonのサイズダウン
#### A) 都道府県境のみを抽出
   - 都道府県のグラフであれば市区町村の区分は不要
   - QGISというGISソフトウェアを利用して結合
   - 手順
     1. QGISにGeoJsonファイルをドラッグ&ドロップ
     2. [ベクター]>[空間演算ツール]>[ディゾルブ]をクリック![QGIS1.png](https://qiita-image-store.s3.amazonaws.com/0/23691/be14681e-8d68-de50-3011-4de7dc58e0dc.png)
     3. [パラメーター]>[入力レイヤ]>[...]へ変換したいGeoJsonのレイヤを設定
     4. [パラメーター]>[融合]>[...]>[Save to File]から出力先及びファイル形式（geojson）を設定![QGIS2.png](https://qiita-image-store.s3.amazonaws.com/0/23691/683ec630-cb55-4f18-46f6-23035b73bdf4.png)
     5. [バックグラウンドで実行]をクリック<br />（複数ある場合には[Run as Batch Process]から一括処理も可能）
     6. 拡張子を「geojson」から「json」に書き換える  

#### B) GeoJsonの簡素化
   - 結合してもデータ量は大きい
   - ポリゴンの点数そのものを減らすことで処理速度の向上
   - [mapshaper](http://mapshaper.org/)サイトというが目視して調整でき利用しやすい（QGISでも簡素化可能）
     1. [mapshaper]にGeoJsonファイルをドラッグ&ドロップ![mapshaper1.png](https://qiita-image-store.s3.amazonaws.com/0/23691/38dbaec9-371e-fea6-cf68-3216861d645f.png)
     2. 必要な[Option]を選択して[Import]![mapshaper2.png](https://qiita-image-store.s3.amazonaws.com/0/23691/0ae417bc-2a49-d14a-cc27-090466a81121.png)
     3. [Simplify]をクリックして、必要な[Simplification menu]を選択![mapshaper3.png](https://qiita-image-store.s3.amazonaws.com/0/23691/7b6b9a7b-cb8e-edae-543f-b5939c17e8e1.png)
     4. [Settings]の倍率を調整して整形
     5. [Export]をクリック、[GeoJson]を選択![mapshaper4.png](https://qiita-image-store.s3.amazonaws.com/0/23691/a8ff2687-9304-1f94-cf91-f0382628fb1b.png)
     6. 完了

### 描写成功
```python
#日本
v = cesiumpy.Viewer(**options)
for i in range(1,48):
    val = random.randrange(10e4)
    clr = '#E'+str(val).zfill(5)
    setEntitiy(str(i).zfill(2),val,clr,clr)
v
```

![4.GIF](https://qiita-image-store.s3.amazonaws.com/0/23691/b43ef35a-52b9-6424-f644-4af43a002cf9.gif)

# 終わりに
- GeoJsonをサイズダウンすることで3Dコロプレスマップを描写することができた
- Notebook上で動作することが最大のメリットである
  - スクレイピングなどと併用するとその真価が発揮する
  - 集計から整形、描写までNotebook（Python）で完結する