Mapbox を利用して 1km メッシュ別に東京都の 2050 年における人口を可視化してみた

はじめに

はじめまして、プラットフォーム事業本部の神谷と申します。 普段は、LANDLOG プラットフォームにおける 2/3D の地形プロジェクトの開発のマネージメントを担当しています。

さて、今回は開発者にも人気があり、最近ではゼンリンとの提携を発表したなど話題の多い、Mapbox を利用してデータの可視化を行いましたのでデータの準備含めて一連の流れを紹介したいと思います。

GIS とは

Mapbox は GIS の一種ですので、はじめに GIS について簡単に紹介したいと思います。

実世界に存在するデータを地図上に表示させたり、そのデータに対して分析をしたりする場合には、あまり馴染みがない方も多いと思いますが GIS という技術が使用されています。

GIS とは、「Geographic Information Syetem」の略で日本語では、地理情報システムと呼んでいます。
GIS は、実世界をコンピュータ上でモデル化し、「地図」という視覚的に分かりやすい形で情報を整理したものになります。

下記の図は GIS におけるデータモデルの例で、コンビニなどの店舗をポイント(点)、バスなどの路線を線(ライン)、土地利用などの都市の区画を面(ポリゴン)の3種類の形状で表すことができます。それらを統合したデータが GIS データとなり、ファイル形式では、Shape ファイルや GeoJSON などが良く利用されますが、もちろん、データベースとして管理することも可能です。

https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20190513/20190513190824.png

GIS を利用するメリットですが、情報同士における地理的な関係性や傾向にも気づくことができたり、地図上に可視化することで視覚的にも分かりやすくなります。また、紙地図と違い、大量の情報の分析や解析もできるため、多くの分野で GIS が活用されています。特に最近は、IoT などの普及によって移動体の位置情報が簡単に取得できたりと、地図を活用するニーズが増えてきています。

Mapbox でデータ可視化

Mapbox を利用して、今回は 1km メッシュ別に東京都の 2050 年 における人口(男女合計)を可視化してみました。
23 区に人口が集中していることが分かるかと思います。データの属性値は 2015 年から持っているため、例えばどのエリアの人口が一番減少しているかなども表現したりできます。
こちらのデモは GitHub にも公開していますので確認していただけます。

https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20190513/20190513190839.png

1. Mapbox とは

Mapbox は Google Maps APIs にも似た Web の地図サービスです。2010 年からサービスは開始されており、特徴は地図の高いカスタマイズ性をで、Foursquare、Pinterest、Evernote、the Financial Times、Uber Technologies 等といった著名な企業の Web サイト等でも活用されています。

Mapbox を使うことで、自分だけのオリジナルな地図を作成したり、地図上に自分が欲しい情報やユーザーに提供したい情報を加えてカスタマイズしたりできます。

Mapbox でカスタマイズした地図の事例は以下のサイトで確認できます。

www.mapbox.com

2. データの準備

データは、国土数値情報で提供されている「5. 各種統計」の1 kmメッシュ別将来推計人口(H30 国政局推計)(shape 形式版) のデータを利用しました。

国土数値情報とは、国土に関する電子地図用のデータのことで、地形や土地利用など、国土に関する基礎的かつ汎用的なデータを GIS データとして整備して提供しています。汎用的なデータに関しては無償データとして公開されているため、利用用途にもよりますが、無償で利用することができます。

1km メッシュ別将来推計人口(H30国政局推計)のデータは、GeoJSON に変換して利用しました。通常 GIS データで利用されるフォーマットは、Shape ファイルや座標が入った CSV 等の利用が多いですが、Webアプリケーションで GIS データを利用する場合は GeoJSON が一番手軽に利用することができます。 GeoJSON は、JSON の拡張版のフォーマットで地理空間情報を手軽に利用することが可能です。

1kmメッシュ別将来推計人口(H30国政局推計)の Shape ファイル形式から GeoJSON への変換には、GDAL というフリーの GIS ツールを利用しました。GDLA は GIS データを扱う上でとても便利なツールです。データフォーマットの確認やデータ変換など多くの機能に対応しています。

www.gdal.org

GDAL の org2ogr を利用します。
$ ogr2ogr -f GeoJSON -t_srs crs:84 1km_mesh_2018_13.geojson 1km_mesh_2018_13.shp
というコマンドを実行することで、1km_mesh_2018_13.shp の Shape ファイル形式から 1km_mesh_2018_13.geojson の GeoJSON へと変換されます。

今回変換した 1km_mesh_2018_13.geojson の GeoJSON は GitHub に登録して利用しました。

補足ですが GeoJSON は GitHub に登録すれば GitHub 上でデータの属性情報含めて Mapbox の地図で確認することができます。

help.github.com

3. Mapbox JS GL で可視化

データの準備ができましたので最後はいよいよ可視化の部分です。データの可視化ライブラリとして、Mapbox GL JS を利用しました。 Mapbox GL JS は、WebGL を利用したオープンソースのマップクライアントライブラリです。Mapbox GL JS を利用して、2. のデータの準備で用意した「 1 kmメッシュ別将来推計人口(H30 国政局推計)」を GeoJSON で読み込み、背景地図は Mapbox が標準で提供している地図サービスを利用しました。

次のコードは、Mapbox GL JS における可視化部分の内容です。

// mapboxgl.Map を利用して、マップオブジェクトを設定
// ID タグ名の map、初期表示背景地図、中心位置の緯度経度、初期ズームレベル、地図の初期ピッチ(傾斜)を設定
const map = new mapboxgl.Map({
  container: 'map', 
  style: 'mapbox://styles/mapbox/light-v10',
  center: [139.657125, 35.661236],
  zoom: 10,
  pitch: 40
});

// 地図のコントロールを追加
map.addControl(new mapboxgl.NavigationControl());

let scale = new mapboxgl.ScaleControl({
  maxWidth: 250,
  unit: 'metric'
});

map.addControl(scale);

// mapboxgl.Popup を利用してポップアップのオブジェクトを設定
let popup = new mapboxgl.Popup({
  closeButton: false,
  closeOnClick: false
});


// マウスポインタがメッシュの領域に入ったら属性情報をポップアップで表示
map.on("mousemove", "2DmeshLayer", function(e) {

  map.getCanvas().style.cursor = 'pointer';

  popup.setLngLat(e.lngLat)
    .setHTML(
      "<div><b>市区町村コード &nbsp;</b>" + e.features[0].properties.SHICODE + "</div>" + 
      "<div><b>将来推計人口 2050年 (男女計)</b></div>" + 
      "<div>" + Math.round(e.features[0].properties.PT0_2050) + " 人</div>")
    .addTo(map);

});

// マウスポインタがメッシュの領域から離れたらポップアップをクリア
map.on("mouseleave", "2DmeshLayer", function() {
  map.getCanvas().style.cursor = '';
  popup.remove();
});

// 2050年の人口から10段階のカテゴリ別に分類するためのフィルタ
let PT0_2050_1 = ["<", ["get", "PT0_2050"], 4000];
let PT0_2050_2 = ["all", [">=", ["get", "PT0_2050"], 4000], ["<", ["get", "PT0_2050"], 8000]];
let PT0_2050_3 = ["all", [">=", ["get", "PT0_2050"], 8000], ["<", ["get", "PT0_2050"], 12000]];
let PT0_2050_4 = ["all", [">=", ["get", "PT0_2050"], 12000], ["<", ["get", "PT0_2050"], 16000]];
let PT0_2050_5 = ["all", [">=", ["get", "PT0_2050"], 16000], ["<", ["get", "PT0_2050"], 20000]];
let PT0_2050_6 = ["all", [">=", ["get", "PT0_2050"], 20000], ["<", ["get", "PT0_2050"], 24000]];
let PT0_2050_7 = ["all", [">=", ["get", "PT0_2050"], 24000], ["<", ["get", "PT0_2050"], 28000]];
let PT0_2050_8 = ["all", [">=", ["get", "PT0_2050"], 28000], ["<", ["get", "PT0_2050"], 32000]];
let PT0_2050_9 = ["all", [">=", ["get", "PT0_2050"], 32000], ["<", ["get", "PT0_2050"], 36000]];
let PT0_2050_10 = ["all", [">=", ["get", "PT0_2050"], 36000], ["<", ["get", "PT0_2050"], 40000]];

// 色の設定
let colors = ['rgb(215, 25, 28)',   'rgb(232, 91, 58)', 
              'rgb(249, 158, 89)',  'rgb(254, 201, 128)',
              'rgb(255, 237, 170)', 'rgb(237, 247, 201)',
              'rgb(199, 230, 219)', 'rgb(157, 207, 228)',
              'rgb(100, 165, 205)', 'rgb(44, 123, 182)'] 


const meshToMap = (meshdata) => {
 
  // map.addSource に 読み込んだ GeoJSON を設定 
  map.addSource('meshdata',{
    type: 'geojson',
    data: meshdata,
  });
  
  // map.addLayer で メッシュの色や幅、塗り潰し等の見た目を設定して、メッシュを表示
  // メッシュの色分け表示は、case オプションにより人口の属性値で分けて表示するように設定
  map.addLayer({
    'id': '2DmeshLayer',
    'type': 'fill',
    'source': 'meshdata',
    'layout': {},
    'paint': {
        "fill-color": 
          ["case",
            PT0_2050_1, colors[0],
            PT0_2050_2, colors[1],
            PT0_2050_3, colors[2],
            PT0_2050_4, colors[3], 
            PT0_2050_5, colors[4], 
            PT0_2050_6, colors[5], 
            PT0_2050_7, colors[6], 
            PT0_2050_8, colors[7], 
            PT0_2050_9, colors[8], 
            PT0_2050_10, colors[9], 
            colors[9]
          ],
        "fill-outline-color": "white"
      }
  });
};

//  1km メッシュ GeoJSON
let meshGeoJsonURL = 'https://raw.githubusercontent.com/valuecreation/mapbox-prj/b014b62e2c4db92726ca35ca8ec9a52b2acd5f28/data/1km_mesh_2018_13.geojson';

const handleGetData = (err, meshdata) => {
  meshToMap(meshdata);
}

d3.queue()
  .defer(d3.json, meshGeoJsonURL)
  .await(handleGetData);

今回紹介した内容以外にも Mapbox JS GL では多くの可視化パターンがあります。以下のサンプルサイトではデモとコードの内容が確認できます。

docs.mapbox.com

最後に

今回は Mapbox を使ってデータの準備から可視化までの流れを紹介しました。今回紹介した以外にも 3D やヒートマップ、クラスタリングなど地図上で色々な可視化を行うことができます。また、私が携わっている LANDLOG では IoT のプラットフォームとして、緯度経度を含んだ位置情報も API で簡単に登録できたりできます。次回は LANDLOG の IoT プラットフォームと連携して、地図でデータの可視化を行うまでの流れが紹介できればとも思っています。

オプティムでは、地図に限らず Web アプリケーションエンジニアやフロントエンジニアなども募集していますので、興味のある方は是非一度お話しましょう!

www.optim.co.jp