D3.jsとTopoJsonライブラリを使って地理情報を動的に簡素化する。
クライアントサイドのtopojsonには読み込んだ地理データを簡素化するpresimplifyという機能があります。
今回はこの機能を使った地形データの動的な簡素化のサンプルをつくりました。
スライダーを右に動かすと地形が簡素化されます。
解説
全く持って上手く説明できる自信がないのですが、ただコードを置いておくのもなんなので一応解説っぽいものをかいてみました。
ジオストリーム
D3を用いて地理情報を描画する際、projection関数とpath関数を作成して描画処理を行います。
1 2 3 4 5 6 7 8 9 10 11 12 |
//緯度経度をpixcel座標に変換するリスナー var projection = d3.geoMercator() projection([139.0032936, 36.3219088]); -> [480, 320] //pixcel座標のコレクションをpath要素のd属性に渡すパスデータ形式に変換する関数 var path = d3.geoPath().projection(projection); path(geojson); -> "M318.33499585167647,350.924745060654,L318.5161692703732,350.51.199698 …… Z" |
D3では地理情報がもつ緯度経度をpixcelの座標へと変換するデータの流れを「ジオストリーム」と呼びます。
そしてそのジオストリームに対して変換処理を行う関数をストリームリスナーと呼びます。
d3.geoMercatorは、メルカトル図法を基準にして緯度経度をpixcel座標へと変換するストリームリスナーを生成しています。
今回はこのメルカトル図法のストリームリスナーをラップしてgeometoryを簡素化するストリームリスナーを作りました。
topojson.presimplify
topojson.presimplifyメソッドは、引数で渡されたtopojsonに簡素化に執拗なジオメトリ情報を追加したコピーを返します。
1 |
const presimplify = topojson.presimplify(topojson); |
presimplifyメソッドを実行するとgeometoryの各点ごとに隣接した三点を結んだ三角形の面積が付加されます。この面積の値に閾値を設定してそぎ落とすことでgeometoryの簡素化を行うことができます。
詳しいアルゴリズムについては以下を参照してください。
簡単に説明すると「隣接する三点を結んで作成した三角形の面積が小さい座標点は、隣接する座標点との変化が少ない点なので、無くしちゃっても問題ないよね!」みたいな感じです。
カスタムストリームリスナー
d3.geo.transformを利用するとジオストリーム(緯度経度をpixcel座標に変換するプロセス)を途中でフックし、独自のストリームリスナーを生成することができます。
topojson.presimplifyメソッドで付加した面積の値はストリームリスナーに引数として渡されるので、閾値を設定しgeometoryをフィルタリングしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//緯度経度をpixcel座標に変換する関数(ジオストリームリスナー) var projection = d3.geoMercator() //カスタムストリームリスナー var simplify= d3.geoTransform({ point: function(x, y, z) { //隣接した3点を結んだ面積が1を超えるものだけを残す if (z >= 1) this.stream.point(projection([x, y])); } }); //パスジェネレーターを生成 var path = d3.geoPath().projection(simplify); |
カスタムストリームリスナーの中で、隣接する三点を結んだ三角形の面積が閾値を超える座標点のみを変換対象とし、それ以外の座標は切り捨ててしまうことで、座標点を減らし簡素化した図形を描画しています。