Pythonを用いたベクトル形式データのラスタ化と描画

# はじめに

地理情報データは，ベクトル形式（ライン，ポリゴンなど）データとラスタ（グリッドベース）データに大別される．ベクトルデータを用いたラスタデータのマスキングやフィルタリングをおこなうに際し，ベクトルデータをラスタデータとしてあらかじめ変換しておくと処理が楽な場合がある．ということで，今回はrasterioモジュールを用いて，geopandasで読み込んだベクトルデータをラスタデータに変換する．
また，覚書的に，ベクトルデータとラスタデータを同じ図の上に描画するコードについてもまとめた．

# 使用モジュール

Python3.7系にて操作を行った．

```py:
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from functools import partial
import pyproj
from shapely.ops import transform
import geopandas as gpd
import rasterio as rasio
import rasterio.features as rasioftr
```

# 世界地図ポリゴンの読込

今回は，geopandasモジュールで一般公開されている世界地図データを使用する．
変換する解像度は1度×1度（180グリッド×360グリッド）とした．

```py:
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
print(world.head())
```
![v2r_table1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187118/7cc99d42-238a-3e2d-6303-42b3039480e0.png)

# 変換行列の設定

rasterioモジュールの変換ツールを用いて，緯度経度および配列サイズ情報を用いて変換行列を作成する．東西南北の順番および緯度経度長さの順番に注意．

```py:
nlatlon = (180, 360)
WSEN_ar = (-180, -90, 180,  90)
WSEN_trans = rasio.transform.from_bounds(*WSEN_ar, *nlatlon[::-1])
print(WSEN_trans)
'''
| 1.00, 0.00,-180.00|
| 0.00,-1.00, 90.00|
| 0.00, 0.00, 1.00|
'''
```

# 既存のカラム値を挿入してラスタ化

世界地図データには，各地域の人口とGDPのデータが付与されている．ラスタ化する際にいずれの値を保持するかを決定する必要がある．

## 各地域の人口

rasterio.featureクラス下の`rasterize()`関数を使用して，shapely形式のベクトルデータをnumpy arrayに変換する．関数内の使用可能な変数については[リンク](https://rasterio.readthedocs.io/en/stable/api/rasterio.features.html#rasterio.features.rasterize)を参照のこと．下記では，基本的なアイテム（`shapes`，`out_shape`，`transform`）に加え，ポリゴン外の配列値`fill`，境界部分の設定`all_touch`を設定している．ここでも，東西南北の順番および緯度経度長さの順番に注意．

```py:
item = 'pop_est'
shps = [(s, i) for i, s in zip(world[item], world['geometry'])]
result = rasioftr.rasterize(
    shps,
    out_shape=nlatlon,
    transform=WSEN_trans,
    fill=np.nan,
    all_touched=True)
plt.imshow(result)
plt.colorbar(orientation='horizontal')
```
![v2r_thumb_pop.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187118/c469f883-b472-7f50-36c2-215ca63089e9.png)

## 各地域のGDP(百万ドル)

人口とまったく同様に変換を行う．

```py:
item = 'gdp_md_est'
shps = [(s, i) for i, s in zip(world[item], world['geometry'])]
result = rasioftr.rasterize(
    shps,
    out_shape=nlatlon,
    transform=WSEN_trans,
    fill=np.nan,
    all_touched=True)
plt.imshow(result)
plt.colorbar(orientation='horizontal')
```
![v2r_thumb_gdp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187118/847c9ea8-4275-53c2-d65b-f870c9b20212.png)

# 新規カラムを挿入してラスタ化

先述のアイテムに加えて，geopandas内の関数で計算できるポリゴン面積と，文字列データカラムを利用した大陸州ごとのラスタ化を実施した．

## 各地域の面積

[この記事](https://qiita.com/HidKamiya/items/c27c9fe7d8533cfe8a73)を基にして計算を行った．

```py:
def CalcArea(geom):
    geom_swap = Swap_xy(geom)
    area = geom_to_area(geom_swap)
    return area * 1e-6 # m2 -> km2 

world['area_km'] = world['geometry'].map(CalcArea)
print(world.head())
```
![v2r_table2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187118/c87f2d67-5c13-584d-6762-9b711be5699d.png)


あとは全く同様．

![v2r_thumb_area.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187118/98c984b7-9560-edbc-6a63-b56323f36a07.png)

## 各地域の大陸州ごとに値を変えたラスタ化

まず，新たなカラムを加える．

```py:
world['new_id'] = 0
print(world.head())
```
![v2r_table3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187118/53d8ec69-af13-9a85-f011-d27cd21da341.png)

次に，既存のカラム`continent`の項目ごとに数値を割り振る．

```py:
for i, name in enumerate(world['continent'].unique()):
    print(i, name)
    world.loc[world['continent'] == name, 'new_id'] = i + 1
print(world.head())
```
![v2r_table4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187118/d6b9bbd5-c4e2-3739-5797-df36cab59ddc.png)

あとは同様．

![v2r_thumb_cont.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187118/ed51accc-562a-adaf-7b77-4f6876f49923.png)

# 南→北向きにラスタ化

上記までのラスタ化では，緯度次元（0次元）が北から南向きとなっている．しかしながら，緯度が増える方向（南から北向き）のほうが都合のいいことが多く，その際は下記のように東西南北の順番を変更する必要がある．

```py:
WNES_ar = (-180,  90, 180, -90)
WNES_trans = rasio.transform.from_bounds(*WNES_ar, *nlatlon[::-1])
```

この変換行列を用いて描画した図は，上と全く同様に`imshow()`を用いて描画すると上下が反転する．関数内で`origin='lower'`を付け加えることで南→北向きの正しい方向の図を描画できる．

![v2r_thumb_cont_s2n.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187118/1d80a02d-1854-e61b-2985-2ef508e059ce.png)

# ポリゴンとラスタの描画

ここからは，作成したラスタデータと元のベクトルデータとを描画するコードを示す．
ここでも，東西南北の順番に注意．

```py:
WESN_ar = (-180, 180, -90,  90)
WESN_w  = (-180, 180, -90,  90)
# WESN_w  = ( 130, 150,  30,  50)

fig = plt.figure(figsize=(14, 7))
ax = fig.add_subplot(111, projection=ccrs.PlateCarree())
ax.set_extent(WESN_w, crs=ccrs.PlateCarree())
im = ax.imshow(result, extent=WESN_ar, origin='upper') #, origin='lower')
world.plot(ax=ax, facecolor='none', edgecolor='k')
```
![v2r_glob_cont.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187118/2121d959-f90c-b266-ef0f-b26b5aa74e4c.png)

上記のWESN_w（`ax.set_extent()`にて使用）を置き換えると，描画範囲を変更することができる．

![v2r_japan_cont.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187118/fb797200-1be4-0c3c-0e81-fefa75d6d318.png)

# グリッド中心を基準としたラスタ化

最後に，`rasterize()`において`all_point=False`とした際の図を示す．グリッドの中心がポリゴン内に含まれるか否かで判定がなされるため，上図と比較してマスクされた範囲が多くなる．

![v2r_japan_cont_untouch.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187118/4415744e-8bec-c8b1-0602-aef05d09b5b8.png)