読者です 読者をやめる 読者になる 読者になる

StatsFragments

Python, R, Rust, 統計, 機械学習とか

Cesium.js を Python から使うパッケージを作った

3D 地図を表示する JavaScript ライブラリである Cesium.jsPython から簡単に使いたい。Cesium.js についてはこちらを。

sinhrks.hatenablog.com

上に記載した方法は、可視化したい内容に応じて JavaScript のテンプレートを作成し、Python からデータを埋め込むというものだった。が、都度 テンプレートを作るのはさっと可視化したい場合にはめんどくさい。

ということでこれを Python のみ / JavaScript なしで利用できるパッケージを書いた。

github.com

使い方

サンプルデータは前と同じく こちらのエントリのものを利用する。作成した DataFrame は変数 df に入っているとする。

インストール

pip で。

$ pip install cesiumpy

地図の表示

以降の操作は Jupyter Notebook 上で行う。装飾のない地図を表示したいだけなら Viewer インスタンスを作成すればよい。地図は Jupyter のセルに埋め込まれて表示される。

import cesiumpy
cesiumpy.__version__
# '0.1.1'

cesiumpy.Viewer()

f:id:sinhrks:20160109221713p:plain

グラフの描画

Cesium.js では以下のような図形 ( Entity ) が描画できる。cesiumpy を使うとこれらを Python のみで作成 / 描画できる。

  • Point
  • Label
  • Box
  • Ellipse
  • Cylinder
  • Polygon
  • Rectangle
  • Ellipsoid
  • Wall
  • Corridor
  • Polyline
  • PolylineVolume
  • Billboard

ここではそのうちいくつかを例として記載する。ドキュメントには全ての Entity の画像と作り方を簡単に記載している。

Cylinder

上の例と同じく、Cylinder で 3D 棒グラフを描く。Entity を描画するには、まず当該のインスタンスを作成し、Viewer.entities.add メソッドに渡せばよい。

v = cesiumpy.Viewer()
for i, row in df.iterrows():
    l = row['Recreation Visitors (2014)[5]']
    cyl = cesiumpy.Cylinder(position=[row['lon'], row['lat'], l / 2.], length=l,
                            topRadius=10e4, bottomRadius=10e4, material='aqua')
    v.entities.add(cyl)
v

f:id:sinhrks:20160109221725p:plain

Point

Point クラスを使うと バブルチャートが描ける。

また、右上に並んだアイコンで投影法や地面の画像 ( Imagery ) が変更できる。ここでは Open Street Map を表示している。Imagery は スクリプトからも変更できる

v = cesiumpy.Viewer()
for i, row in df.iterrows():
    l = row['Recreation Visitors (2014)[5]']
    p = cesiumpy.Point(position=[row['lon'], row['lat'], 0],
                       pixelSize=np.sqrt(l / 10000), color='blue')
    v.entities.add(p)
v

f:id:sinhrks:20160109221736p:plain

Billboard ( Pin )

Leaflet のように ピン を表示する場合は BillboardPin を使う。ピンの色やラベルを変更することもできる。

v = cesiumpy.Viewer()
pin = cesiumpy.Pin()
for i, row in df.iterrows():
    l = row['Recreation Visitors (2014)[5]']
    b = cesiumpy.Billboard(position=[row['lon'], row['lat'], 0], image=pin, scale=0.4)
    v.entities.add(b)
v

f:id:sinhrks:20160109221749p:plain

scipy.spatial の利用

また、cesiumpyscipy.spatial に含まれる以下のような図を地図上に重ねて描くことができる。

まずはデータを合衆国本土のみにフィルタする。

filtered = df[(-130 < df['lon']) & (df['lon'] < -60) & (20 < df['lat']) & (df['lat'] < 50)]
filtered.shape
# (47, 10)

フィルタしたデータから、各レコードの座標 (経度, 緯度) からなる tuplelist を作成する。

pos = filtered[['lon', 'lat']].values
pos = [tuple(p) for p in pos]
pos
# [(-68.129999999999995, 44.210000000000001),
#  (-109.34, 38.409999999999997),
#  ...
#  (-119.3, 37.5),
#  (-113.03, 37.18)]

まず scipy で書くとこのような処理になる。詳細は以下のドキュメントに記載されているが、プロットの見た目を変更したり、各領域の座標を再利用できる形に変換するのは少し手間だ。

from scipy.spatial import Voronoi, voronoi_plot_2d
vor = Voronoi(pos)
vor
# <scipy.spatial.qhull.Voronoi at 0x107dbab50>

voronoi_plot_2d(vor)

f:id:sinhrks:20160109221809p:plain

一方、cesiumpy でやるとこんな感じ。cesiumpy.spatial.Voronoi.get_polygons() は、各座標を 対応するボロノイ領域を含む Polygon のリストに変換する。これに適当に色付けして地図上に表示すればよい。

ボロノイ領域は元の座標と同じ順序になっているため、元座標に応じて色分けすることもできる。

colors = [cesiumpy.color.BLUE, cesiumpy.color.RED, cesiumpy.color.YELLOW, cesiumpy.color.ORANGE,
          cesiumpy.color.PURPLE, cesiumpy.color.GREEN, cesiumpy.color.WHITE, cesiumpy.color.AQUA,
          cesiumpy.color.NAVY, cesiumpy.color.PINK, cesiumpy.color.MAGENTA]

v = cesiumpy.Viewer()
polygons = cesiumpy.spatial.Voronoi(pos).get_polygons()
colors = np.random.choice(colors, len(polygons))

for p, c in zip(polygons, colors):
    p.material = c.set_alpha(0.1)
    p.outline = True
    v.entities.add(p)
v

f:id:sinhrks:20160109221818p:plain

まとめ

cesiumpy を使うと Cesium.js を利用した可視化が JavaScript なしで書ける。

  • Entity を利用したグラフの描画、可視化
  • ボロノイ図や凸包の描画

また、上には含めなかったが下のような処理もできる。