d3.js (v5) を使って、最短で白地図を描く方法について、メモ。
- 前提として基本的なd3.jsでのデータの扱い方は知っていることとする。(こういうやつ http://bl.ocks.org/alansmithy/e984477a741bc56db5a5)
- d3は v3 -> v4 のアップデートでめちゃくちゃ名前空間が変わっている & v4 で callback書く系の関数がv5ではPromiseを返すようになっているため、過去記事のコードはほとんどそのままでは動かない。
目次
シンプルな白地図を描くために、最低限必要な以下の2つのステップについて説明する。
- 地図のGeoJSONデータを用意する
- GeoJSONをsvg形式に変換するd3関数 d3.geoMercator() と d3.geoPath() を理解する
この2つができれば、以下のExampleのような短いスクリプトで世界地図が描けるようになる。
ちなみに、
- 白地図ではなく、地名や人口密度などの情報も重ねて表示したい場合は、GeoJSONのデータ形式をちゃんと理解する必要がある。
- 描画を高速化したい場合、GeoJSONから無駄な情報を除いたり、TopoJSONに変換してファイルサイズを削減したりする必要がある。
これらについては後日まとめたいと思う。
Shapefileをダウンロードし、GeoJSONに変換する
Natural Earth から無料で世界地図のshapefileがダウンロードできる。
例えば、1:50mサイズ (http://www.naturalearthdata.com/downloads/50m-cultural-vectors/) の中から、国の境界のshapefile (Admin 0 – Countries の Download coutries)をダウンロードしてくる。
そして[チュートリアル Part 1] (https://medium.com/@mbostock/command-line-cartography-part-1-897aa8f8ca2c) と同じように、shp2jsonを使えばGeoJSONに変換できる。
npm install -g shapefile # shp2jsonのインストール
cd [shpファイルのあるフォルダ]
shp2json ne_50m_admin_0_countries.shp -o hogehoge.json # 変換
GeoJSONをブラウザ上でsvgに変換して描画
d3.geoMercator(): 点から点への変換
d3.geoMercator()は、「緯度経度の点座標」を「svg上の点座標」に変換する関数。デフォルトでは世界地図をw960×h500のsvg領域に収めるような変換が設定されている。
>> var projection = d3.geoMercator();
>> console.log(projection([135, 40])); // 経度・緯度を入力すると
[840.375, 133.31457058917871] // svgの座標になる
世界地図ではなく特定の地域などを描画したい場合は、以下のパラメータを設定する。
- translate: svgの中心座標
- center: svgの中心(translate)に表示する緯度経度
- scale: 地図の縮尺。メルカトル図法の場合は[svgの幅]/[表示したい経度幅のラジアン値]を入力すれば良いらしい...が、この辺は試行錯誤で数値を変えれば良い。
d3.geoMercator() はメルカトル図法で地図を描くための関数だが、他にも 色々なprojection関数 が提供されているので、描きたい地図に合わせて適切なものを選ぶとよい。
d3.geoPath(): 線から線への変換
d3.geoPath()は、GeoJSONのgeometryの情報を、svgのpath要素のd属性の形式に変換する関数。
イメージとしては、d3.geoMercator()が点から点への変換関数だったのに対して、d3.geoPath()は線から線への変換関数となっている。
>> var projmerc = d3.geoMercator();
>> var path = d3.geoPath(projmerc);
>> console.log(path({"type": "LineString", "coordinates": [[102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]]})) // GeoJSONのgeometry情報
"M752.2833333333333,250L754.9527777777778,247.3304200186872L757.6222222222223,250L760.2916666666667,247.3304200186872" // path要素のd属性の形式
いよいよd3を使って地図を描く
例えば日本周辺の地図を描く場合、以下のようにする。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
<script>
// svgのサイズ
var width = 400;
var height = 800;
// 描きたい緯度経度の領域(日本周辺)
var west = 120;
var east = 150;
var north = 46;
var south = 24;
// svg要素を追加
var svg = d3.select("body").append("svg").attr("width", width).attr("height", height);
// projectionを定義
var projection = d3.geoMercator()
.center([(west + east) / 2, (north + south) / 2]) // 緯度経度領域の中心
.translate([width / 2, height / 2]) // svgの中心
.scale(width / (2 * Math.PI * ((east - west) / 360)));
// pathを定義
var path = d3.geoPath(projection);
// GeoJSONを読み込み、描画. ここでthenを使うのがv4との違い.
d3.json("hogehoge.json").then(function(json) {
svg.append("g").selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path) // GeoJSONのgeometryの情報をpath関数で変換
.attr("stroke", "black") // 線の色
.attr("fill", "none"); // 塗りつぶしの色
}
)
</script>
</body>
</html>
Next Step
白地図を描くために必要な最低限の手順をまとめたが、地図にもっと情報を付け加えようとすると色々課題が出てくる。例えば、
- 県境まで描画したい -> 1:10mのprovinceデータを使うことになるが、上にまとめた方法だと、地図が表示されるまで30秒くらい時間がかかってしまう。原因は主に以下の2つ。
- 日本以外の見えない領域の描画にも時間を費やしてしまっている。
- GeoJSONのファイルが大きすぎて読み込みに時間がかかる。
- 人口密度や地名を表示したい -> GeoJSONに人口密度や地名のデータを追加する必要がある。
このへんを解決するためには、
- GeoJSONから不必要な情報を削除する
- GeoJSONに必要な情報を追加する
- GeoJSONを軽量なTopoJSON形式に変換する
などの作業をする必要があるため、GeoJSONとTopoJSONというデータ形式についてキチンと理解する必要がある。
そのへんの説明はまた後日まとめたいと思う。