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のような短いスクリプトで世界地図が描けるようになる。

* http://bl.ocks.org/almccon/fe445f1d6b177fd0946800a48aa59c71

ちなみに、

* 白地図ではなく、地名や人口密度などの情報も重ねて表示したい場合は、GeoJSONのデータ形式をちゃんと理解する必要がある。
* 描画を高速化したい場合、GeoJSONから無駄な情報を除いたり、TopoJSONに変換してファイルサイズを削減したりする必要がある。

これらについては後日まとめたいと思う。


## Shapefileをダウンロードし、GeoJSONに変換する
[Natural Earth](http://www.naturalearthdata.com/) から無料で世界地図の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関数](https://github.com/d3/d3-geo#azimuthal-projections) が提供されているので、描きたい地図に合わせて適切なものを選ぶとよい。

### 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>
```
![Screen Shot 2018-09-11 at 1.53.32.png](https://qiita-image-store.s3.amazonaws.com/0/256327/0275711c-5282-1390-8132-eacf630d0cb8.png)

## Next Step
白地図を描くために必要な最低限の手順をまとめたが、地図にもっと情報を付け加えようとすると色々課題が出てくる。例えば、

* 県境まで描画したい -> 1:10mのprovinceデータを使うことになるが、上にまとめた方法だと、地図が表示されるまで30秒くらい時間がかかってしまう。原因は主に以下の2つ。
    * 日本以外の見えない領域の描画にも時間を費やしてしまっている。
    * GeoJSONのファイルが大きすぎて読み込みに時間がかかる。
* 人口密度や地名を表示したい -> GeoJSONに人口密度や地名のデータを追加する必要がある。

このへんを解決するためには、

* GeoJSONから不必要な情報を削除する
* GeoJSONに必要な情報を追加する
* GeoJSONを軽量なTopoJSON形式に変換する

などの作業をする必要があるため、GeoJSONとTopoJSONというデータ形式についてキチンと理解する必要がある。
そのへんの説明はまた後日まとめたいと思う。