都道府県の画像アセットを全自動生成しよう。

f:id:puhitaku:20161222002423p:plain

やったこと

47都道府県をSlack絵文字にしたかったので、Natural EarthのShapefileからPNGアセットを自動生成させるコードをPythonで書いた。 コードの工夫次第で何にでも応用が利き便利そうなのと、加えてKartograph.pyの情報が少なかったため備忘録を書いておく。

悲しいかなKartograph.pyはメンテされておらずPython 2.7でしか動かないため、本記事に登場するコードは全部Python 2.7向けに書いている。 探せばもっと優れたライブラリもあるはずだ。

手順

「Pythonで地理データを読む」〜「縮小処理」までは単なる手順の備忘録なので、とりあえず出力したい場合は「ライブラリのインストール」まで済ませて末尾のGistに載ってるコードを動かすだけでOK。私が出力したデータもZIPにして同じく末尾に置いているので、それを落としてもらってもよい。

  • 地理データを入手
  • ライブラリをインストール
  • Pythonで地理データを読む
  • 都道府県別に切り出す
  • 見た目を整える
  • PNG出力
  • 縮小処理
  • 完成!

地理データを入手

パブリックドメインで地形データを配布しているNatural EarthからShapefileをダウンロードする。今回は参考ページに従い1:10,000,000 (1:10m) スケールの都道府県単位で記録されているデータをダウンロードした。

www.naturalearthdata.com

「Large scale data」 → 「Cultural」→「Admin 1 – States, Provinces」からダウンロードできる。

ライブラリをインストール

手頃だったのでMacの手順しか書いていないのはお許しを。まずbrewでGDALを入れる。

$ brew install gdal

次節で使うPythonライブラリ「Kartograph.py」はGDALのPythonラッパーを使う。本来はKartographのrequirementsで入れるのだが、公式のインストール方法は変なためここは自分でインストールした方がいい。

公式のインストール手順の「Alternative way if GDAL install fails」にGDALのPythonラッパーにPYTHONPATHを張る部分があるが、 PYTHONPATHはいじらないのが普通 なので手でsite-packagesへコピーする。brewで入れた場合は /usr/local/Cellar 以下に保存されているはずで、私の場合は /usr/local/Cellar/gdal/1.11.3_1/lib/python2.7/site-packages/ 以下に入っていた。

$ cp -r /usr/local/Cellar/gdal/1.11.3_1/lib/python2.7/site-packages/* /path/to/site-packages/

あとは公式の言うとおり requirements-nogdal.txt をpipに食わせてからKartographを入れればOK。

$ pip install -r https://raw.github.com/kartograph/kartograph.py/master/requirements-nogdal.txt
$ pip install https://github.com/kartograph/kartograph.py/zipball/master

importできるかチェック。

$ python -c "from kartograph import Kartograph"

何も出なければKartographはインストール完了だ。あとで使うのでPillowとCairoSVGも入れておこう。

$ pip install pillow cairosvg

Pythonで地理データを読む

公式ドキュメント曰く、Shapefileに詰まった要素と属性の対から、属性でフィルタリングした結果をSVGに出せるらしい。

日本の都道府県をフィルタリングする方法は上でも載せた参考ページに載っていた。曰く adm0_a3 という要素が JPN になっているらしい。とりあえず

lambda d: d['adm0_a3'] == 'JPN'

でfilterしたらこのような出力になった。

f:id:puhitaku:20161221212428p:plain

なんかナナメってる。なんで?

これは世界地図を二次元に投影する際に、仮想的な視点の目の前に日本がなかったのが理由だ。Projectionの指定方法を参考に、Kartograph.jsの動作サンプルを使ってパラメータを決めよう。

平行投影 ortho でもいいが、せっかくなので?ここはお馴染みのメルカトル図法で出力した。以下のように、configのdictに設定を追加する。

cfg = {
    'layers': {
        'japan': {
            'src': 'ne_10m_admin_1_states_provinces.shp',
            'filter': lambda d: d['adm0_a3'] == 'JPN'
        }
    },
    'proj': {
        'id': 'mercator',
        'lon0': 140
    }
}

lon0 はlongitude、つまり経度のことだ。日本がちょうどど真ん中に来るようにしている。

こうして出力したのが以下だ。

f:id:puhitaku:20161221214328p:plain

わーい!

都道府県別に切り出す

では都道府県別で切り出すにはどうすればいいのか?

ここで、先ほどの adm0_a3 のような判別に使える値を探してみる。shpファイルはそのままではバイナリだが、 GDALの中に入っている ogrinfo を使うとプレーンテキストに書き下してくれる。

これをgrepしてみるとnameという要素を発見。

$ ogrinfo -al ne_10m_admin_1_states_provinces.shp | grep 'Okayama'

f:id:puhitaku:20161221231147p:plain

これを元に岡山県を切り出してみる。

cfg = {
    'layers': {
        'japan': {
            'src': 'ne_10m_admin_1_states_provinces.shp',
            'filter': lambda d: d['name'].decode(encoding='utf-8') == 'Okayama'
        }
    },
    'proj': {
        'id': 'mercator',
        'lon0': 140
    }
}

f:id:puhitaku:20161221221031p:plain

わーい!

ちなみにこの name で絞り込む方法は 万能ではない 。静岡県だけ何故か name の値が存在せず、filterできないのだ。そこで実際のコードでは adm0_a3, name, woe_label の3つの値で確実にfilterする関数を使っている。

見た目を整える

あとは見た目だ。都道府県なんだし、地図っぽく深緑の塗りにして出力したい。

SVGの中身はXMLなので、見た目の変更はCSSを文字列で直接渡してやることで対応できる。id名がよくわからないのでChromeで見てみよう。

f:id:puhitaku:20161221222711p:plain

configで japan と書いているせいか、idjapan のようだ。わかりやすい。というわけで以下のように書いて、generate()に渡してみる。

f:id:puhitaku:20161221223023p:plain

やったー!

PNG出力

Pythonのコード内でSVG→PNG変換するならCairoSVGが楽ちん。generate()で出力したSVGをまんま渡すだけで変換完了だ。

import cairosvg
cairosvg.svg2png(url='mikumiku.svg', write_to='pinkorblack.png')

実際のコードではgenerate()の後にこれを置き変換させている。

縮小処理

Slackの絵文字は128x128[px]以下でなければならないので、縮小処理をかける。

正方形にするため、辺の長さを長い方に揃えてから128x128[px]に縮小している。

完成!

かくして都道府県絵文字の作成に成功した。出力したデータ(大きいPNG・絵文字サイズPNG・SVG)をまとめたZIPはこちら。SVGはそのまま印刷物などのアセットとしても使える。

www.dropbox.com

今までもちらちら出ていたが、完成品のコードはGistにパブリックドメインとして上げているので自由に利用して欲しい。

gist.github.com

参考ページ

qiita.com

Kartograph.py Docs