d3.js (v6) でブラウザに地図を描く方法

d3.js とは

d3.js はデータビジュアライゼーションJavaScriptライブラリであり、棒,円,折れ線グラフなんてものは序の口で様々なデータ可視化ができる高機能ライブラリ。その実力は 紹介されているギャラリー(サンプル) を見れば一目瞭然。前からこれを使って、GeoDataで地理情報を可視化して遊んでいたのですが、バージョンが上がるごとに若干作法が変わるので定期的にウオッチが必要。今回は現在(2021.5)の最新であるv6をキャッチアップするとともに、最近、コロナ禍で色々な地域データの可視化も盛んなので改めてその方法をまとめておく

地図データの準備

d3.js で地図を描く場合、そのデータをGeoJson形式で準備する必要がある。さらに、一般的にはデータサイズが大きくなりがちのGeoJsonを軽量化したtopojson形式のデータを用意しインプットとしても良い。

一般的にはこの手のデータはシェープファイル形式で配布されていることが多い。今回は、ESRIジャパンが提供する全国市区町村界データを用いる。その他にも国土数値情報ダウンロードサービスより「行政区域データ」など、国が提供しているものなど様々なシェープファイルが入手可能である。

ESRIサイトからダウンロードし展開すると以下のようなファイル群を得られる。拡張子shpがシェープファイルファイルであり、QGISなどのシェープファイルを閲覧できるソフトウエアであればこのファイルを開くことでその内容を描画することができる

% ls -ltr
total 19912
-rw-r--r--@ 1 hiyuzawa  staff      145  4 12 16:04 japan_ver83.prj
-rw-r--r--@ 1 hiyuzawa  staff   100372  4 12 16:45 japan_ver83.shp.xml
-rw-r--r--@ 1 hiyuzawa  staff    15356  4 12 17:10 japan_ver83.shx
-rw-r--r--@ 1 hiyuzawa  staff  8818912  4 12 17:10 japan_ver83.shp
-rw-r--r--@ 1 hiyuzawa  staff      612  4 12 17:10 japan_ver83.sbx
-rw-r--r--@ 1 hiyuzawa  staff    17876  4 12 17:10 japan_ver83.sbn
-rw-r--r--@ 1 hiyuzawa  staff  1215081  4 12 17:10 japan_ver83.dbf
-rw-r--r--@ 1 hiyuzawa  staff     6249  4 27 09:53 Readme.txt
QGISでシェープファイルを開いた状態

shp to GeoJson

さて、このシェープファイル(shp)をGeoJsonに変換し、さらにtopojson に変換する。シェープファイルをGeoJsonに変換するには gdal というソフトを利用する。gdalの説明は省略する。 mac であれば brew install gdal でインストールが可能である. 利用する実行ファイルは ogr2ogr だがこれも難しいので、機械的に変換方法を覚えればひとまずOK。

% ogr2ogr -f GeoJSON japan.geojson japan_ver83.shp
% ls -l japan.geojson
-rw-r--r--  1 hiyuzawa  staff  24600043  5 18 22:46 japan.geojson
% cat japan.geojson | jq . | more
{
  "type": "FeatureCollection",
  "name": "japan_ver83",
  "crs": {
    "type": "name",
    "properties": {
      "name": "urn:ogc:def:crs:EPSG::6668"
    }
  },
  "features": [
    {
      "type": "Feature",
      "properties": {
        "JCODE": "01101",
        "KEN": "北海道",
        "SICHO": "石狩振興局",
        "GUN": null,
        "SEIREI": "札幌市",
        "SIKUCHOSON": "中央区",
        "CITY_ENG": "Sapporo-shi, Chuo-ku",
        "P_NUM": 238198,
        "H_NUM": 144196
      },
      "geometry": {
....

上記のように変換を行うと、GeoJsonがシェープファイルから得られる。GeoJsonはJsonファイルなので中身も確認できる. ogr2ogr にさらに -where 引数で属性値を絞り込むなどをするとシェープファイルから一部を抜き出してGeoJson化も可能である。

条件を指定してGeoJson作成

上の例にあげたシェープファイルの各要素の属性としてGeoJson化して中身をみると分かりやすいが、JCODE, KEN, SICHO, … 等の値をシェープファイルは保有していることが分かる。例えば、KEN=東京都 のみを抽出したい場合は以下のように ogr2ogr コマンドを実行すれば良い。 (演算子はイコール(=)以外にも in (x, y, z) 等も利用できる)

% ogr2ogr -f GeoJSON -where "KEN = '東京都'" tokyo.geojson japan_ver83.shp

GeoJson to topojson

GeoJson形式のままd3.js に読み込ませることは可能であるが、先程、GeoJson化したファイルサイズを確認すると元のshpファイルが8.4Mに対して、変換したGeoJsonが23MBとサイズが大きくなり、これをクライアントに毎回ダウンロードさせるのは現実的ではない。

% ls -ltrh
-rw-r--r--@ 1 hiyuzawa  staff   8.4M  4 12 17:10 japan_ver83.shp
-rw-r--r--  1 hiyuzawa  staff    23M  5 18 22:46 japan.geojson
-rw-r--r--  1 hiyuzawa  staff   407K  5 19 09:32 tokyo.geojson

よってこのGeoJsonをスリム化する処理がtopojson化である。topojsonで実際に何をしているかというと、例えば、GeoJsonで線分のPATHの頂点が A→B→C→D→E→… とある場合に ABC, BCD, CDE, … という三角形の面積が閾値以下となった場合はその頂点を間引く処理を行いスリム化します。以下のサイトは英語ですがわかりやすくこの部分が説明されています。

topojson は node 環境で動作するので npm でインストールします

% npm -g install topojson  # -g とするかは各自の環境で判断

インストールが完了するとgeo2topo のコマンドが実行できる (昔は topojson というコマンドだったが最近のバージョンで役目ごとにコマンドが別れました) GeoJsonからの変換は以下のように行う。

% geo2topo -q 1e6 japan=japan.geojson > japan.topojson
% geo2topo -q 1e6 tokyo=tokyo.geojson > tokyo.topojson

1e6 という部分が先程説明したスリム化の閾値で一般的には1e6がよく使われているが1e5, 1e4と変化させるとどうなるのか試してみるのも面白い。実際のファイルサイズを確認してもかなり削減されていることがわかる

-rw-r--r--@ 1 hiyuzawa  staff    23M  5 18 22:46 japan.geojson
-rw-r--r--  1 hiyuzawa  staff   3.8M  5 19 09:49 japan.topojson
-rw-r--r--  1 hiyuzawa  staff   407K  5 19 09:50 tokyo.geojson
-rw-r--r--  1 hiyuzawa  staff    85K  5 19 09:50 tokyo.topojson

d3.jsで地図を描画

d3.jsで地図を描画するにあたり、VSCode + TypeScript を環境として用いる。この環境整備に関しては以前に記事にしたので必要あれば参考いただきたい

TypeScriptを使ときの自己流な準備(備忘録)

今回の作業で必要な npm モジュールは4つでこれをまずは追加でインストールする. (2つはTypeScriptの型定義なので実質2つ)

npm install --save-dev d3 topojson-client @types/d3 @types/topojson-client

基本的には以下のモジュールを一つ生成すれば良い。Projectionを変えれば地図の図法を変えることができる。基本な部分は 26行目でJsonを読み込み (distフォルダに topojsonファイルを保存しておく) 、28行目でtopojson形式で読み込む。29行目以降はd3.js の流儀てきなものなのでしっかり覚えるならd3.jsのドキュメントを参照する。とりあえずならコピペでOK. on でイベントをキャプチャできるので例えばポリコン内にマウスが入ったことを検知して色を塗ったりその属性値を表示したりすることができる(下記の例ではその2つの処理をしている)

import * as d3 from "d3"
import * as topojson from "topojson-client";

export default class D3Map {

    svg: any
    projection: any
    path: any
    center: [number, number] = [139.5, 35.6]
    scale: number = 40000

    static readonly TOKYO_GEOJSON = "./tokyo.topojson"

    constructor(width: number, height: number, elm: string = "main") {
        this.svg = d3.select(elm)
            .append("svg")
            .attr("width", width)
            .attr("height", height)

        this.projection = d3.geoMercator()
            .center(this.center)
            .scale(this.scale)
            .translate([width/2, height/2])
        this.path = d3.geoPath(this.projection)

        d3.json(D3Map.TOKYO_GEOJSON).then( (data: any) => {
            var map = this.svg.selectAll("path")
                .data((topojson.feature(data, "tokyo") as any).features)
                .enter()
                .append("path")
                .attr("stroke", "#888")
                .attr("fill", "#fff")
                .attr("d", this.path)
                .on("mouseover", (e: any, n: any)  => {
                    d3.select(e.currentTarget)
                        .attr("fill", "red")
                    d3.select("#info")
                        .html(n.properties.JCODE + ": " + n.properties.KEN + " " + n.properties.SIKUCHOSON)
                })
                .on("mouseout", (e: any, n: any) => {
                    d3.select(e.currentTarget)
                        .attr("fill", "#fff")
                })
        })
    }
}

Main.tsやindex.htmlはあとはそれを使うものなのでシンプルなものでOK. Main.tsは以下の4行

import D3Map from "./D3Map"
window.onload = (e: any) => {
    const  d3Map = new D3Map(800, 600, "#map")
}

index.htmlは今回の例ではbodyの内に以下の2つのタグを設けておく. (地図を描画する部分とマウスオーバで情報表示する部分)

<div id="map"></div>
<div id="info"></div>

まとめ

d3.jsで地図を描画するチュートリアルでした。上にも述べたがシェープファイルはたくさん公開してあり、例えば、全国の鉄道網や駅といったものも国より公開されている手順は全く同じでこれらをブラウザに表示することができるだけでなく、それに紐づく別の情報を組み合わせて画面上に表示すことも容易にできる。

現状、コロナ禍において感染者数などが公開されているがやはり地図と組み合わせて可視化することが重要でありそういう意味ではこういう簡単に取り扱えるビジュアライズの手法を一つ武器として覚えておくと良いと思います。

※ D3.js をGoogleMapの上に載せる記事を別に記載しました。これでインタラクティブな地図操作がD3.jsでも簡単に実現できます

d3.jsで描いた地図をGoogleMapの上に載せる方法