d3.js (v5) を使って白地図を描く


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 と同じように、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というデータ形式についてキチンと理解する必要がある。
そのへんの説明はまた後日まとめたいと思う。