TopoJSON v2, v3の使い方 -コマンドライン編-

2016年末に[TopoJSON] v2がリリースされ、shpファイルやGeoJSONファイルをTopoJSONに変換する方法が変わったためメモとして記します。

※2017年4月12日追記
2017年4月に[TopoJSON v3]がリリースされました。コマンドラインに関してはv2とv3で差異はないので記事タイトルを「TopoJSON v2の使い方 -コマンドライン編-」から「TopoJSON v2, v3の使い方 -コマンドライン編-」と変更しました。

```shell-session
  $ npm install -g topojson
```

## 概要

[TopoJSON v1]のコマンドラインでは`topojson`というコマンドのみで構成されていましたが、v2ではこれが`geo2topo`、`toposimplify`、`topo2geo`、`topomerge`、`topoquantize`に分割されています。

## TopoJSONとは

[TopoJSON]とはd3.jsの作者Mike Bostock氏が作成したGeoJSONファイルを拡張した地理情報データの規格です。
GeoJSONファイルでは各地物（feature）の中に記されるジオメトリを、TopoJSONでは地物の外に**arcs**という形で記述し、重複するジオメトリを排除することで合理化を図っています。
例えば、A-B-Cで構成されるポリゴン1と、B-C-Dで構成されるポリゴン2があるとすると、GeoJSONでは両方の地物の中に[B, C]が書かれ、ジオメトリが重複してしまいます。TopoJSONでは、これを[B,A,C],[B,C],[B,D,C]というarcsに分割し、各地物は**arcsの集合の中からジオメトリを選択**することで地理情報を定義しています。
これによってTopoJSONファイルは、**他のポリゴンに隣接しているかどうかという情報を持つことができます。**
また、TopoJSONはポリゴンデータや点データなど複数のGeoJSONファイルを、オブジェクトとして一つのTopoJSONファイルに収めることができます。
詳しくは[公式ドキュメント（翻訳版）]をご覧ください。

<img width="320" alt="topojson.png" src="https://qiita-image-store.s3.amazonaws.com/0/154454/7ca7a9be-8f19-1218-bc71-50b85668d390.png">

#### TopoJSONファイルの例

```text:sample.topojson
  {
    "type": "Topology",
    "objects": {
        "objectA": {
            "type": "GeometryCollection",
            "geometries": [Feature, Feature, Feature, Feature, Feature]
            // ここは[Geometry, Geometry, ...]と書くのが正しいのかもしれませんが、
            // Feature(地物)の方が意味合いが近いと思うのでFeatureとしました。
        },
        "objectB": {
            "type": "GeometryCollection",
            "geometries": [Feature, Feature, Feature, Feature, Feature]
        }
    },
    "arcs": [ [arcs], [arcs], [arcs] ],
    "bbox": [xmin, ymin, xmax, ymax],
    "transform": {
        "scale": [scaleX, scaleY],
        "translate": [translateX, translateY]
    }
  }
```

## コマンド

### `geo2topo`

GeoJSONファイルをTopoJSONファイルに変換するコマンド。
[Command Line Reference](https://github.com/topojson/topojson-server#geo2topo)

#### 使い方
```shell-session
  $ geo2topo -q 1e6 sample=input.geojson > output.topojson
```

先ほど複数のファイルを一つのTopoJSONにまとめることができると記しました。`sample=`とついているのはこれら複数のオブジェクトを識別するための名称を定義しているからです。何も定義しないと、名称は`-`となります。

複数のGeoJSONファイルを一つにまとめるときも、一つのGeoJSONファイルを変換するときのように書けば大丈夫です。

```shell-session
  $ geo2topo -q 1e6 mito=mito.geojson iwaki=iwaki.geojson > output.topojson
```

生成したoutput.topojsonという一つのファイルには、mitoとiwakiという二つのオブジェクトが内包されています。

#### 引数

公式の[Command Line Reference](https://github.com/topojson/topojson-server/blob/master/README.md#command-line-reference)に実際に存在しないオプションが記されるなど誤りがあると思われるので、`geo2topo --help`で表示できるオプションを記します。なお、`-h (--help)`と`-V (--version)`は割愛します。

- `-q`,`--quantization`
  変換前に行うジオメトリの量子化の指数を設定します。ジオメトリの量子化については後述します。これを設定することでポリゴンが適切に隣接判定できるようになります。これは**常に設定することをオススメします**。

```shell-session
  $ geo2topo -q 1e6 sample=input.geojson > output.topojson
```

- `-n`,`--newline-delimited`
  入力するファイルが`ndjson`であるときに使います。`ndjson`については後述します。

- `-o`,`--out`
  出力するファイル名を書きます。が、`|`（パイプ）で`toposimplify`や`topoquantize`と繋ぐことを想定されているので、あまり使いません。公式ドキュメントでも`>`（リダイレクト）で記されていることがほとんどです。


### `topo2geo`

TopoJSONファイルをGeoJSONファイルに変換するコマンド。ここでは省略。
[Command Line Reference](https://github.com/topojson/topojson-client/blob/master/README.md#topo2geo)


### `toposimplify`

TopoJSONファイルを簡略化するコマンド。
[Command Line Reference](https://github.com/topojson/topojson-simplify/blob/master/README.md#command-line-reference)

#### 使い方
```shell-session
  $ toposimplify -P 0.1 sample.topojson > simplified.topojson
```

簡略化の仕組みを簡潔に記すと、`[A, B, C, D, E, F]`という座標のパスがあるとしたら、連続する3点でできる三角形、すなわち`[A, B, C], [B, C, D], [C, D, E], [D, E, F]`の**各三角形の面積が閾値より小さくなるとき、真ん中の点を間引く**ことでパスの構成点を削減しています。
例えば2番目と4番目の三角形`[B, C, D], [D, E, F]`の面積が閾値よりも小さくなる場合、点Cと点Eを削除し、新たなパスは`[A, B, D, F]`となります。
Mike Bostock氏による[Line Simplification]のドキュメントに出てくる図を見ていただければご理解いただけるかと思います。

面積の閾値は、`planar area`か`spherical area`のどちらかで指定します。
閾値とファイルサイズの関係ですが、**閾値が大きくなるほど、パスは簡略化され、ファイルのサイズは小さくなります。**

#### 引数

##### planer area

三角形の面積を経度x、緯度yで構成される**平面**として計算する。
地球の歪み、すなわち緯度による経度1度の長さの違いを考慮していないため、多分高速で計算できる…と思います（検証はしていません）。
広範囲の緯度に分布しないような、国内で完結するデータであればこちらでいいと思います。

- `-p` `--planar-area`
  planer areaの閾値を直接指定する。閾値の単位はpx&sup2;。**緯度1度×経度1度で出来る四角形の面積が1px&sup2;**。（←間違ってるかもしれない）

- `-P` `--planar-quantile`
  planer areaの閾値を分位数で決定。[0,1]の間の値を指定する。
  arcsの面積を降順に並べたものの分位数なので**この値が小さいほど、閾値は大きくなり**ファイルのサイズも小さくなる。

##### spherical area

こちらは三角形の面積ではなく、経度x、緯度yに加えてz座標をもつ三次元の三点で構成される立体角[ステラジアン]という単位を閾値として使う。
正直あまり理解していないので説明に自信がないのですが、緯度による経度1度の長さの違いを考慮して計算できる…はずです。
多分世界地図のような範囲の広いデータで有効…なんだと思います。（終始自信なし）

- `-s` `--spherical-area`
  spherical areaの閾値を直接指定する。単位は[ステラジアン]。

- `-S` `--spherical-quantile`
  spherical areaの閾値を分位数で決定。[0,1]の間の値を指定する。
  arcsの面積を降順に並べたものの分位数なので**この値が小さいほど、閾値は大きくなり**ファイルのサイズも小さくなる。

##### フィルタリングオプション

- `-f` `--filter-detached`
  閾値の面積よりも小さく**他の地物に隣接していない**全ての*ring*を削除
  ※ ringはおそらくarcsで構成される輪のこと

- `-F` `--filter-all`
  閾値の面積よりも小さい全ての*ring*を削除

##### 分位数(quantile)について

補足なので、読み飛ばしてしまって構いません。分位数が何かわからなかったので調べたメモです。
結論を先に言うと、`toposimplify`の`-P`、`-S`オプションにおける分位数は**arcsの数を何%に削減するかに等しい**と言えます。
つまり、`toposimplify -P 0.1`とすれば簡略化されたarcsの数は簡略前のarcsの10%の数になります。

d3-arrayに配列の分位数を返す[`d3.quantile(array, p)`]があります。解説をAPIドキュメントから引用します。

```JavaScript
  var a = [0, 10, 30];

  d3.quantile(a, 0); // 0
  d3.quantile(a, 0.5); // 10
  d3.quantile(a, 1); // 30

  d3.quantile(a, 0.25); // 5
  d3.quantile(a, 0.75); // 20
  d3.quantile(a, 0.1); // 2
```

配列の端を0と1すると、0.5は配列の中央値となります。
配列に対応する値がないときは、値の間を線形に補間する値を返してくれます。

```JavaScript
  var b = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 1000];

  d3.quantile(b, 0); // 0
  d3.quantile(b, 1); // 1000
  d3.quantile(b, 0.5); // 50

  d3.quantile(b, 0.6); // 60
  d3.quantile(b, 0.55) // 55
  d3.quantile(b, 0.9); // 90
  d3.quantile(b, 0.95); // 545
```

d3-scaleに`d3.scaleQuantile()`というのがありますが、こういうことだったんですね。
上記の例のように、外れ値に引っ張られないので、東京のような外れ値があるに便利そうです。

`toposimplify`のquantileでは、arcsの面積を**降順**に並べたものの分位数に当たる閾値を返します。

```JavaScript:checkQuantile.js
  const fs = require("fs");
  const topojson = require("topojson");
  const d3 = require("d3-array");

  fs.readFile("./topojson/ibaraki_h28/ibaraki_h28.geojson", (err, json)=>{
    if (err) throw err;

    const data = JSON.parse(json);
    const topology = topojson.topology({ibaraki: data}, 1e6);
    // presimplifyで座標を[x, y, 面積]に変換
    const presimplified = topojson.presimplify(topology);

    // 各arcsの面積を抜き出し、降順に並べる
    const areas = d3.merge(presimplified.arcs).map(d => d[2]).filter(d => d !== Infinity)
                  .sort((a, b)=>{return b - a;});

    // areasの分位数0.2に当たる数値を出力
    console.log(d3.quantile(areas, 0.2));
    // => 1.0353577364878734e-11

    // topojson.quantileで分位数0.2に当たる数値を出力
    console.log(topojson.quantile(topology, 0.2));
    // => 1.0353577364878734e-11

    // areasとareasの分位数0.2を閾値としてフィルタリングした配列の長さの比を出力
    console.log(areas.filter(d => d > d3.quantile(areas, 0.2)).length / areas.length);
    // => 0.19999733532295885
  });

```

ということで、`toposimplify`の分位数による指定は大まかにいうと「arcsをp%に削減する」と等しいという話でした。
閾値の値を直接指定するよりは分位数で指定した方がわかりやすくていいと思います。
ただし分位数によるsimplifyは、元のファイルのarcsの数に依存することを留意しておいたほうがいいかもしれません。


### `topomerge`

TopoJSONファイルの地物を結合するコマンド。
[Command Line Reference](https://github.com/topojson/topojson-client/blob/master/README.md#topomerge)

#### 使い方
```shell-session
  topomerge target=source -k 'd.key' < input.topojson > output.topojson
```

元のオブジェクトを保持したまま、`d.key`によって集約された新たなオブジェクトを追加したTopoJSONファイルを生成します。
例えば[国土数値情報 行政区域データ]のポリゴンを同じ郡ごと（今どき郡がついてる自治体も少なくなっていますが）で結合したい場合は、

```shell-session
  $ topomerge target=source -k 'd.properties.N03_003' \
  < input.topojson > output.topojson
```

となります。output.topojsonにはinput.topojsonにある*source*オブジェクトと、新たに`d.properties.N03_003`によって集約された郡ごとのオブジェクト*target*の2つのオブジェクトが内包されます。

#### 引数

- `-k` `--key`
  地物を集約するキーを指定します。

- `-f` `--filter`
  topomergeを適用する地物を選択します。
  例えば下記のように、[国土数値情報 行政区域データ]の属性N03_004（市区町村の名称）の末尾1文字が「村」と等しいものだけを結合しています。（除外された部分はそのオブジェクトにおいては「空白」になります）

```shell-session
  $ topomerge target=source -k 'd.properties.N03_003' \
  -f 'd.properties.N03_004.slice(-1) === "村"' \
  < input.topojson > output.topojson
```

- `--mesh`
  メッシュオブジェクト（境界線）を作成するオプションです。

topomergeについてはMike Bostock氏によるMediumの記事[Command-Line Cartography, Part 3]がわかりやすくていいかもしれません。


### `topoquantize`

TopoJSONファイルの座標を量子化するコマンド。
[Command Line Reference](https://github.com/topojson/topojson-client/blob/master/README.md#topoquantize)

#### 使い方
```shell-session
  $ topoquantize 1e5 sample.topojson > quantized.topojson
```

量子化とはなんぞやという感じですが、例えば以下のような座標で定義されている地物を、

```JavaScript
[[140.41220426559448,36.345324908827074], [140.27137756347656,36.45860346156702],
 [140.43872594833374,36.39767566585711], [140.53825736045835,36.45722280014421],
 [140.4819416999817,36.37885591035544], [140.41220426559448,36.345324908827074]]
```

1e2(=100)で量子化すると、

```JavaScript
[[52,0], [-52,99], [62,-53], [37,52], [-21,-69], [-26,-29]]
```

となり、新たに`transform`という属性を持つTopoJSONファイルになります。

```JSON
{
  "arcs": [ [ [52,0],[-52,99],[62,-53],[37,52],[-21,-69],[-26,-29] ] ],

  "bbox": [140.27137756347656, 36.345324908827074, 140.53825736045835, 36.45860346156702],

  "transform": {
    "scale": [0.002695755525068516,0.0011442278054540316],
    "translate":[140.27137756347656,36.345324908827074]
  }
}
```

座標を簡単な数値に置き換え、transformを定義することによって文字数の削減をしています。
そういえばDTM用語でクオンタイズってありますがこういうことだったんですね。

### その他

#### shpファイルからtopojsonに変換
→新しいモジュール[`shapefile`]を使う

[TopoJSON v1]の`topojson`コマンドでは、shpファイルを直接TopoJSONに変換することができました。これをTopoJSON v2でおこなうには、コマンドをパイプで繋ぐ必要があります。

```shell-session
$ shp2json --encoding "Shift-JIS" input.shp | geo2topo -q 1e6 > output.topojson
```

#### 地物をフィルタリングする方法
→新しいモジュール[`ndjson-cli`]を使う

```shell-session
$ shp2json -n input.shp | ndjson-filter 'd.properties.ADM0_A3 === "JPN"' \
| geo2topo -n -q 1e6 > output.topojson
```

この辺りをもう少し勉強したら事例集みたいなものを作りたいと思います。

### 参考になりそうなリンク

- [TopoJSON]
- [公式ドキュメント（翻訳版）]
- [Line Simplification]
- [mapshaper]
- [Command-Line Cartography]

[TopoJSON]: https://github.com/topojson
[公式ドキュメント（翻訳版）]: https://github.com/topojson/topojson/wiki/Home.ja
[ステラジアン]: https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%86%E3%83%A9%E3%82%B8%E3%82%A2%E3%83%B3
[Line Simplification]: https://bost.ocks.org/mike/simplify/
[mapshaper]: http://mapshaper.org
[TopoJSON v1]: https://github.com/topojson/topojson-1.x-api-reference/blob/master/Command-Line-Reference.md
[`shapefile`]: https://github.com/mbostock/shapefile
[`ndjson-cli`]: https://github.com/mbostock/ndjson-cli
[`d3.quantile(array, p)`]: https://github.com/d3/d3-array#quantile
[国土数値情報 行政区域データ]: http://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-N03-v2_3.html

[Command-Line Cartography]: https://medium.com/@mbostock/command-line-cartography-part-1-897aa8f8ca2c
[Command-Line Cartography, Part 3]: https://medium.com/@mbostock/command-line-cartography-part-3-1158e4c55a1e

[TopoJSON v3]: https://github.com/topojson/topojson/releases/tag/v3.0.0