はじめに
最近になってd3jsを触り始めて楽しさを再認識しました。
世界地図をブラウザでぐりぐりするのが目標です。
開発環境はMacOSです。
最終目標までの道は遠いです。
QGISもnpmだってインストールしてゴリゴリ進めます。
この辺りを大分参考にさせていただきました。
http://www.tam-tam.co.jp/tipsnote/javascript/post6174.html
http://y-uti.hatenablog.jp/entry/2014/02/09/071953
http://qiita.com/sawamur@github/items/ec32237bcbaaba94108d
http://ja.d3js.node.ws/blocks/mike/map/
http://blog.hitsuji.me/make-topojson-file-and-show-map/
やること
- シェープファイルの取得
- Shape→GeoJSON
- GeoJSON→topoJSON
- d3jsで地図を表示
使うもの
- d3.js
- QJIS
- GDAL
- node.js
地図データの取得
シェープファイルと言われる地図データを取得します。
ここを参考にさせていただきました。
http://y-uti.hatenablog.jp/entry/2014/02/09/071953
シェープファイルとは
シェープファイルというのは、おおまかに言うと
- ポリゴンと点のようなエリアや地点を表すベクター情報のファイル(.shp)
- 国名、県名、番号、あるいは、トイレ、井戸というような属性データ(.dbf)
- それらの対応関係を示すファイル(.shx)
のセットでできている地図データの形式です。多分。
http://www.pasco.co.jp/recommend/word/word028/
http://www.esrij.com/getting-started/learn-more/shapefile/
NatualEarthからダウンロード
シェープファイルはNaturalEarthというところから落としてきます。
こんなリッチなデータをフリーで使わせていただけるなんてすごすぎる。
http://www.naturalearthdata.com/
今回はグリグリするのが目的で、そこまで詳細な地図は必要ないかなという判断で
国境の世界地図のシェープファイルをダウンロードしました。
800KBのzipなのでダウンロードは一瞬。
Medium scale data, 1:50m > Caltural > Admin 0 – Countries > Download countries
落としてきたzipは解凍。
QGISのインストール
こちらにYosemiteにQGISインストールするまでについての
記事を書きましたので参考になりましたら是非。
ちなみに、私もQGISで中身を見て気づきましたが
県境データだと思っていたこれはアメリカとオーストラリアとブラジルしか県境がありませんでした。
Medium scale data, 1:50m > Caltural > Admin 1 - States, provinces > Download states and province
紫芋っぽい色がAdmin 0 – Countries
ミントっぽい色がAdmin 1 - States, provinces
です。
Shape→GeoJSON
GeoJSONとは?
geojsonの例
{ "type": "FeatureCollection",
"features": [
{ "type": "Feature",
"geometry": {"type": "Point", "coordinates": [102.0, 0.5]},
"properties": {"prop0": "value0"}
},
{ "type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]
]
},
"properties": {
"prop0": "value0",
"prop1": 0.0
}
},
{ "type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],
[100.0, 1.0], [100.0, 0.0] ]
]
},
"properties": {
"prop0": "value0",
"prop1": {"this": "that"}
}
}
]
}
こんな形式で地理的データ構造をエンコードするためのフォーマットです。
多分色んな所で使えて便利なので位置情報クラスタの常識なんだと思います。
ちなみに、今回利用するd3.jsで地図を描画できるのはGeoJSON形式とTopoJSON形式です。
GDALのインストール
Shape形式の地図データをGeoJSON形式に変換するためにogr2ogrというツールを利用します。
ogr2ogrはGDALにバンドルされているのでGDALをインストールします。
このあたりに詳しく書いてあります。
http://qiita.com/sawamur@github/items/ec32237bcbaaba94108d
http://www.geopacific.org/opensourcegis/gdal-ogr/ogr-commands
QGISインストール済みの方
GDALはQGISのインストールのために必要なので、すでに入っているかもしれません。
もし「すでに入れたはずなのにコマンドラインツールが見つからないよ!」という方がいらっしゃったら
パスが通ってないせいかもしれないので、以下を.bash_profileに追記してパスを通しましょう。
export PATH=/Library/Frameworks/GDAL.framework/Programs:$PATH
QGIS未インストールの方
まだ入れていない場合は、今回はMacなのでbrewからGDALをインストールすると簡単かもしれません。
GDALをインストール
$ brew install gdal
これで完了。
ogr2ogrでGeoJSONに変換
shp→geojsonに変換
$ cd ne_50m_admin_0_countries
$ ogr2ogr -f GeoJSON countries.geojson ne_50m_admin_0_countries.shp
GeoJSON形式にフォーマットを指定して変換するだけです。
今回利用したシェープファイルは小さかったので0.5秒くらいで終わります。
参考にさせていただいたこちらには、
出力ファイルがsjisになるので気をつけて。
と書いてありましたが、私の手元ではutf8で出力されていました。ラッキー。
GeoJSON→topoJSON
topoJSONとは?
こちらで 「データの変換」 の章に以下の様な説明がありました。
GeoJSONをより小さく、効率的にするために、トポロジーをエンコードして拡張した。
という感じでしょうか。
TopoJSON(※) は GeoJSON の拡張形式であり、トポロジーをエンコードしたものです。座標計算に固定精度エンコーディングを用いることにより TopoJSON ファイルのサイズは GeoJSON よりずっと小さくなります。今作っている地図は、GeoJSON では 536KB ですが、TopoJSON ではたったの 80KB、つまり 85% も削減されています(gzip圧縮後の比較でもずっと小さくなります)。単にディスク必要量を 削減するだけではなく、TopoJSON に含まれるトポロジー情報を使えば、境界線の自動計算や こうした面白いアプリケーションを作ることもできます。
topojson(npm)をインストール
GeoJSON→topoJsonの変換にはnode.jsにバンドルされているtopoJSONのコンバータを利用します。
そのために、node.jsのパッケージ管理ツールであるnpmをインストールして、
npmからtopojsonをインストールします。
node.jsのパッケージ管理はnodebrewというのが流行っているらしいです。
ここを参考にさせていただきました。
http://qiita.com/ryosy383/items/2ea9c92a792302b159b0
nodebrewをインストール(すぐ終わった
$ brew install nodebrew
node.jsの最新版をインストール(結構かかった
$ nodebrew install latest
latest版を利用する設定
$ nodebrew use latest
nodeのパスを通す。
viで.bash_profileに以下を追記する。
export PATH=$PATH:$HOME/.nodebrew/current/bin
npmが使えるようになったか確認のためにヘルプを見る
$ npm help
これでnpmが使えるようになる!
最後にtopojsonのインストール
$ npm install -g topojson
topojsonがインストールできたか確認
$ which topojson
準備完了!
GeoJSON→topoJSON
topojsonに変換(一瞬
$ cd ne_50m_admin_0_countries
$ topojson --id-property su_a3 -p NAME=name -p name -o worldmap.topojson countries.geojson
これでokです。
topojsonはいくつかのgeoJSONを結合して1つのjsonにすることもできるので
最後にgeoJSONの引数を幾つか追加すると結合されたtopoJSONを取得できます。
今回は1つのgeoJSONから1つのtopoJSONを生成。
d3.jsで地図を表示
やっときました!
ここからが本番
ここからはこのチュートリアルが手腕をふるいます。
素晴らしいチュートリアル。
詳しいことはだいたい説明してくれているので端折ります。
index.htmlを配置
いい感じのディレクトリを作成して、index.htmlを配置します。
$ mkdir worldmap
$ cd worldmap
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>world map</title>
<style>
/* CSS goes here. */
</style>
</head>
<body>
hello world map
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v0.min.js"></script>
<script>
/* JavaScript goes here. */
</script>
</body>
</html>
ローカルでWebサーバを起動
Webブラウザではローカルのjsonなどを利用するためにはwebサーバを起動する必要があります。
(詳しくはクロスドメインで検索)
http://blog.qaramell.com/?p=11971
ここではMacではお手軽で有名なpythonさんでワンライナーでローカルにWebサーバを起動します。
$ cd worldmap
$ phthon -m SimpleHTTPServer 8008 &
たったこれだけで、
ローカルホストにWebサーバが起動されて、さっき作った /wolrdmap/index.html を
ブラウザで確認できます!
http://localhost:8008
まだこれでは"hello world map"と表示されるだけですが、もうすこしのはず。
topoJSONを描画してみる
index.htmlがあるディレクトリにdataディレクトリを作成して生成したtopojsonを配置しました。
$ cd worldmap
$ cd mkdir data
$ cp (さっき生成したところ)/ne_50m_admin_0_countries/worldmap.topojson ./data/
topojsonを配置したら
/* JavaScript goes here. */
のところにこんなかんじで書いてみます。
var width = 1600;
height = 900;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
/* JavaScript goes here. */
d3.json("data/worldmap.topojson", function(error, worldmap) {
svg.append("path")
.datum(topojson.object(worldmap, worldmap.objects.countries))
.attr("d", d3.geo.path().projection(d3.geo.mercator()));
});
これで更新してみるとこんな感じ!
70度より北らへんがスパット切れちゃってるので、
場所を動かしたりスケールを変えたりしましょう。
国ごとのパスを分割する
さっきの状態ではすべてのパスがひとつなぎになってしまっているので
国ごとに色を変えるみたいなことができません。
なので次のように変更します。
ついでにprojectionでスケールも変えてみました。
var width = 1600;
height = 900;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
/* JavaScript goes here. */
d3.json("data/worldmap.topojson", function(error, worldmap) {
// topojsonからgeojsonオブジェクトを生成
var countries = topojson.object(worldmap, worldmap.objects.countries);
// 投影方法
var projection = d3.geo.mercator()
.scale(150)
.translate( [width / 2, height / 2] );
// svgのパス要素
var path = d3.geo.path()
.projection( projection );
// 各国のパスを作成
svg.selectAll(".countries")
.data( countries.geometries )
.enter()
.append("path")
.attr("class", function(d) { return "country " + d.id; } )
.attr("d", path)
.style("fill", function(d){return rand_color();} );
});
function rand_color(){
var r = Math.floor( Math.random() * 255 ).toString(16);
var g = Math.floor( Math.random() * 255 ).toString(16);
var b = Math.floor( Math.random() * 255 ).toString(16);
return "#" + r + g + b;
}
こんなかんじにすると国のパスが1つづつに別れて便利になりました。