2016年末にTopoJSON v2がリリースされ、shpファイルやGeoJSONファイルをTopoJSONに変換する方法が変わったためメモとして記します。
※2017年4月12日追記
2017年4月にTopoJSON v3がリリースされました。コマンドラインに関してはv2とv3で差異はないので記事タイトルを「TopoJSON v2の使い方 -コマンドライン編-」から「TopoJSON v2, v3の使い方 -コマンドライン編-」と変更しました。
$ 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ファイルに収めることができます。
詳しくは公式ドキュメント(翻訳版)をご覧ください。
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
使い方
$ geo2topo -q 1e6 sample=input.geojson > output.topojson
先ほど複数のファイルを一つのTopoJSONにまとめることができると記しました。sample=
とついているのはこれら複数のオブジェクトを識別するための名称を定義しているからです。何も定義しないと、名称は-
となります。
複数のGeoJSONファイルを一つにまとめるときも、一つのGeoJSONファイルを変換するときのように書けば大丈夫です。
$ geo2topo -q 1e6 mito=mito.geojson iwaki=iwaki.geojson > output.topojson
生成したoutput.topojsonという一つのファイルには、mitoとiwakiという二つのオブジェクトが内包されています。
引数
公式のCommand Line Referenceに実際に存在しないオプションが記されるなど誤りがあると思われるので、geo2topo --help
で表示できるオプションを記します。なお、-h (--help)
と-V (--version)
は割愛します。
-
-q
,--quantization
変換前に行うジオメトリの量子化の指数を設定します。ジオメトリの量子化については後述します。これを設定することでポリゴンが適切に隣接判定できるようになります。これは常に設定することをオススメします。
$ geo2topo -q 1e6 sample=input.geojson > output.topojson
-
-n
,--newline-delimited
入力するファイルがndjson
であるときに使います。ndjson
については後述します。 -
-o
,--out
出力するファイル名を書きます。が、|
(パイプ)でtoposimplify
やtopoquantize
と繋ぐことを想定されているので、あまり使いません。公式ドキュメントでも>
(リダイレクト)で記されていることがほとんどです。
topo2geo
TopoJSONファイルをGeoJSONファイルに変換するコマンド。ここでは省略。
Command Line Reference
toposimplify
TopoJSONファイルを簡略化するコマンド。
Command Line Reference
使い方
$ 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²。緯度1度×経度1度で出来る四角形の面積が1px²。(←間違ってるかもしれない) -
-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ドキュメントから引用します。
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は配列の中央値となります。
配列に対応する値がないときは、値の間を線形に補間する値を返してくれます。
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の面積を降順に並べたものの分位数に当たる閾値を返します。
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
使い方
topomerge target=source -k 'd.key' < input.topojson > output.topojson
元のオブジェクトを保持したまま、d.key
によって集約された新たなオブジェクトを追加したTopoJSONファイルを生成します。
例えば国土数値情報 行政区域データのポリゴンを同じ郡ごと(今どき郡がついてる自治体も少なくなっていますが)で結合したい場合は、
$ 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文字が「村」と等しいものだけを結合しています。(除外された部分はそのオブジェクトにおいては「空白」になります)
$ 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
使い方
$ topoquantize 1e5 sample.topojson > quantized.topojson
量子化とはなんぞやという感じですが、例えば以下のような座標で定義されている地物を、
[[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)で量子化すると、
[[52,0], [-52,99], [62,-53], [37,52], [-21,-69], [-26,-29]]
となり、新たにtransform
という属性を持つTopoJSONファイルになります。
{
"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でおこなうには、コマンドをパイプで繋ぐ必要があります。
$ shp2json --encoding "Shift-JIS" input.shp | geo2topo -q 1e6 > output.topojson
地物をフィルタリングする方法
→新しいモジュールndjson-cli
を使う
$ shp2json -n input.shp | ndjson-filter 'd.properties.ADM0_A3 === "JPN"' \
| geo2topo -n -q 1e6 > output.topojson
この辺りをもう少し勉強したら事例集みたいなものを作りたいと思います。