LoginSignup
11
6

More than 5 years have passed since last update.

Mapbox GL JSで都道府県別人口のビジュアライゼーション

Last updated at Posted at 2016-11-28

Mapbox Blog のこの記事にインスパイアードされて、試しに以下のようなものを作ってみたという話。

Untitled.gif

気づき

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 データを作成します。

merge.rb
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 オブジェクトをレンダリングする機能を利用したビジュアライゼーション面白いかも :thumbsup:
  • ただし新しめのブラウザ版だけなので注意!
    • Mapbox GL JS
      • 0.27.0 以上
    • iOS SDK
      • Not yet supported
    • Android SDK
      • Not yet supported
  • マピオンベクターも Mapbox を利用しているのでこういったデータビジュアライゼーションにチャレンジしたい!
11
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
6