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

さて、このシェープファイル(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形式のまま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 を環境として用いる。この環境整備に関しては以前に記事にしたので必要あれば参考いただきたい

今回の作業で必要な 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でも簡単に実現できます
