Mapbox Blog のこの記事にインスパイアードされて、試しに以下のようなものを作ってみたという話。
気づき
Mapbox GL JS v0.27.0 あたりから 3D オブジェクトをレンダリングする機能が追加されてビルディングを立体的に表現することが可能になったわけですが、これはビルディングに限らず任意のポリゴンを 3D で表現できます。つまり任意の統計情報を高さ付きポリゴンで表現することで、データのビジュアライゼーションが出来るではないか!という発想に至りました。
データの準備
都道府県のshapeデータ
Natural Earth からデータを取得し、以下手順で GeoJSON 形式に変換して作成しました。
$ curl -LO 'http://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_1_states_provinces.zip'
$ unzip ne_10m_admin_1_states_provinces.zip
$ ogr2ogr -f GeoJSON -where 'geonunit = "Japan"' japan.geojson ne_10m_admin_1_states_provinces.shp
ogr2ogr
コマンドは $ brew install gdal
でインストールしています。
都道府県別人口データ
e-Stat から取得した都道府県別人口データを手編集でプログラムから簡単に扱えるように編集しました。
以下コマンドで取得して、
$ curl -O 'http://www.stat.go.jp/data/nihon/zuhyou/n160200200.xls'
以下の形に編集。全データは GitHub をご参考。
北海道,5400
青森,1321
岩手,1284
宮城,2328
秋田,1037
:
都道府県ポリゴンと人口をマージした GeoJSON データ
都道府県のshapeデータと都道府県別人口データを、以下のスクリプトでマージして Mapbox GL JS で読み込む GeoJSON データを作成します。
require "csv"
require "json"
# ヒートマップ的なカラーコード値を生成
def gen_color(val, max)
i = ((val.to_f / max.to_f ) * 255).to_i
r = case i
when 0..127 then 0
when 128..190 then (i - 127) * 4
else 255
end
g = case i
when 0..63 then i * 4
when 64..191 then 255
else 256 - (i - 191) * 4
end
b = case i
when 0..64 then 255
when 65..126 then 255 - (i - 64) * 4
else 0
end
sprintf("#%02x%02x%02x", r, g, b)
end
# 都道府県別人口をロード
populations = CSV.read("./populations.csv").to_h
max_population = populations.to_a.map{|i| i[1].to_i}.max
# 都道府県ポリゴンをロード
japan = JSON.load(File.open("./japan.geojson"))
# 人口とポリゴンをマージ
japan["features"].delete_if {|feature| feature["properties"]["name"].nil? }
japan["features"].each {|feature|
if feature["properties"]["name"] == "Shizuoka" # 静岡だけname_localが空...
name = "静岡"
else
name = feature["properties"]["name_local"].sub(/[県都府]$/, '')
end
population = populations[name].to_i
feature["properties"] = {
name: name,
color: gen_color(population, max_population),
population: population,
population_height: ((population.to_f / max_population.to_f) * 65535).to_i
}
}
puts japan.to_json
以下の要領で実行します。
$ ruby merge.rb > japan_with_populations.geojson
HTML実装
Mapbox GL JS で準備した GeoJSON データを読み込み、APIでスタイルも設定してしまいます。
<!DOCTYPE html>
<html>
<head>
<script src='https://api.mapbox.com/mapbox-gl-js/v0.28.0/mapbox-gl.js'></script>
<link href='https://api.mapbox.com/mapbox-gl-js/v0.28.0/mapbox-gl.css' rel='stylesheet' />
</head>
<body style='margin: 0;'>
<div id='map' style='width: 100%; position: absolute; top: 0; bottom: 0;'></div>
<script>
mapboxgl.accessToken = 'pk.eyJ1Ijoiay1uYWthbXVyYSIsImEiOiJjaXVqZWd6OTMwMGhjMnlxaXZrcGhzczJtIn0.LQwDcl6aZFQRx8dPfneFHQ';
var map = new mapboxgl.Map({
container: 'map',
center: [135, 35],
zoom: 6,
pitch: 60,
style: 'mapbox://styles/mapbox/streets-v9'
});
map.on('load', function() {
map.addSource("populations", {
'type': 'geojson',
'data': './japan_with_populations.geojson?_=' + Date.now()
});
map.addLayer({
'id': 'population-extrusion',
'type': 'fill-extrusion',
'source': 'populations',
'paint': {
'fill-extrusion-color': {
'property': 'color',
'type': 'identity'
},
'fill-extrusion-height': {
'property': 'population_height',
'type': 'identity'
},
'fill-extrusion-opacity': 0.8
}
});
});
map.on('data', function(event) {
if (event.dataType == 'source' && event.source.id == 'populations') {
var attrib = document.querySelector(".mapboxgl-ctrl-attrib");
attrib.innerHTML = attrib.innerHTML +
' | <a href="http://www.e-stat.go.jp/" target="_blank">政府統計の総合窓口(e-Stat)</a>' +
' | <a href="http://www.naturalearthdata.com/" target="_blank">Made with Natural Earth.</a>';
}
});
</script>
</body>
</html>
ハマりポイント
レンダリングされる高さの値は読み込む GeoJSON 側に保持しており、以下の population_height
が該当します。
"features": [
{
"type": "Feature",
"properties": {
"name": "広島",
"color": "#00d4ff",
"population": 2833,
"population_height": 13865
},
"geometry": {
Mapbox GL JS に指定する高さの単位はメートル、人口の値の単位は千人です。それをそのままメートルとして適用すると迫力が出ないと思い、最大値の比率を変えて適用してたところ、10万などを指定するとレンダリングされず...。レンダリングされる/されないの閾値を確認したところ 65536 以上になるとレンダリングされないということで、 unsigned short int
的な幅があるみたいです。
Ruby のコードでは以下のような計算を行い、高さの最大値(東京都)が 65535
となるように調整しています。
population_height: ((population.to_f / max_population.to_f) * 65535).to_i
まとめ
- Mapbox GL JS の 3D オブジェクトをレンダリングする機能を利用したビジュアライゼーション面白いかも
- ただし新しめのブラウザ版だけなので注意!
- Mapbox GL JS
- 0.27.0 以上
- iOS SDK
- Not yet supported
- Android SDK
- Not yet supported
- Mapbox GL JS
- マピオンベクターも Mapbox を利用しているのでこういったデータビジュアライゼーションにチャレンジしたい!