D3.js で地図情報を表示する †
地図情報のJSONでの表し方
GeoJSON : 地図上のポリゴン(緯度・経度で与えられた点を結んでできる多角形)を定義するデータ形式
TopoJSON : GeoJSON の改良型。容量が 1/5 くらいになる
D3.js は、地図上(lon,lat)のポリゴン を SVG(x,y)に変換して、画面に表示する。
いろいろな投影図法を使える
D3.js 自体は GeoJSON 形式しか扱えない。最近は topojson plugin を使って、TopoJSON 形式のデータを使うのが主流のようだ
ひとまず、チュートリアルをなぞってから、いろいろやってみる
TopoJSON 形式のデータ作成環境の構築 †
Python のカレントバージョンを 2.7 にする (現行バージョンは 3 系統だが、node.js のライブラリをビルドする gyp が python v2 を必要とする)
[~]$ port select python
Available versions for python:
none
python25-apple
python26-apple
python27
python27-apple
python32
python33 (active)
[~]$ sudo port -n upgrade --force python27
[~]$ sudo port select --set python python27
Password:
Selecting 'python27' for 'python' succeeded. 'python27' is now active.
[~]$ python
Python 2.7.5 (default, May 19 2013, 13:26:46)
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> quit()
昔 2 系統を使っていて、今は 3 系統を使ってる場合には、念のため port upgrade をしておいたほうがいい。
もちろん、そもそも python をインストールしていなかった場合は 「$ port install python27」でインストール
gcc も多分いるはず。「$ port install gcc」。gcc は最新版で OK
Mac Port で、必要なアプリケーションをインストール
[~]$ sudo port install gdal py27-gdal nodejs npm qgis grass
node.js の topojson モジュールをインストール
[~]$ npm install -g topojson
インストールの確認
[~]$ which ogr2ogr
/opt/local/bin/ogr2ogr
[~]$ which topojson
/opt/local/bin/topojson
Linuxの場合は yum なり apt-get で同じようにやれば OK.
Windowsでは諦めたほうがいいかな → Windowsで topojson を使う
地図データの取得 †
Chrome で JSON を読み込むには、Webサーバが必要 †
地図を書いてみる + Mouse over で県名表示 †
Your borwser is not supporting object tag. Please use one of the latest browsers. Go to /D3JSExam/D3JS-map1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3 JP Map</title>
<style type="text/css">
.subunit {
fill : lightgreen;
stroke : black;
stroke-width : 0.3;
}
#unitName {
position: absolute;
padding: 10px;
color: black;
font-size: 12px;
background: white;
border-radius: 10px;
border-style:solid;
border-width:2px;
border-color: red;
opacity: 0.8
}
#unitName.hidden {
display: none;
}
</style>
<script type="text/javascript" src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script type="text/javascript" src="https://d3js.org/topojson.v1.min.js" charset="utf-8"></script>
</head>
<body>
<div id="unitName" class="hidden"></div>
<script type="text/javascript">
var width = 800;
var height = 800;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("rect").attr({x:0, y:0, width:width, height:height, stroke:"black", fill:"lavender"});
var projection = d3.geo.mercator()
.scale(1600)
.center([135, 35])
.translate([width / 2, height / 2]);
var path = d3.geo.path().projection(projection);
var url = "jp.json";
d3.json(url, function(error, data){
var subunits = topojson.feature(data, data.objects.subunits);
svg.selectAll(".subunits")
.data(subunits.features)
.enter()
.append("path")
.attr("class", "subunit")
.attr("d", path)
.on("mouseover", function(d) {
var mXY = d3.mouse(document.body);
d3.select("#unitName")
.style("left", (mXY[0] + 10) + "px")
.style("top", (mXY[1] + 10) + "px")
.classed("hidden", false)
.text(d.properties.name);
})
.on("mouseout", function() {
d3.select("#unitName")
.classed("hidden", true)
.text("");
});
});
</script>
</body>
</html>
GeoJSON を SVG に変換する function の準備
var projection = d3.geo.mercator()
.scale(1600)
.center([135, 35])
.translate([width / 2, height / 2]);
var path = d3.geo.path().projection(projection);
https://github.com/mbostock/d3/wiki/Geo-Projections
projection で、地図情報の S(λ,Φ) を、投影したあとの P(f(λ,Φ),g(λ,Φ)) に変換する
path で、P を SVG 上の座標に変換する
projection で使うのは、次の3つくらいかな
d3.geo.equirectangular (正距円筒図法)
d3.geo.mercator (メルカルトル図法)
d3.geo.orthographic (球)
projection のオプション
scale() : スケール。デフォルトは 1:150
center([x,y]) : 地図の中心座標。デフォルトは(0°,0°)
translate([gx,gy]) : 地図の中心のグラフィック座標。デフォルトは [480,250]
TopoJSONから、GeoJSON 形式の県形状情報を取得する
var subunits = topojson.feature(data, data.objects.subunits);
これでもとの GeoJSON に戻る (ただし、TopoJSON に変換した際の誤差あり)
もとの GeoJSON で、県1つ分の情報は、subunits.features 配列の1つ分 (前の章の「できた GeoJSON 形式のデータ」を参照)
これをさっきの path (d3.geo.path().projection(projection)) に食わせると SVG のポリゴンに変換される
県名は subunits.features[i].properties.name
境界線を破線にする、主要都市を表示する、経線・緯線を表示する †
Your borwser is not supporting object tag. Please use one of the latest browsers. Go to /D3JSExam/D3JS-map2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3 JP Map</title>
<style type="text/css">
.subunit {
fill : #ccffcc;
stroke : none;
}
.subunit-boundary {
fill: none;
stroke: gray;
stroke-dasharray: 2,2;
stroke-linejoin: round;
}
.subunit-sea-boundary {
fill: none;
stroke: gray;
stroke-width : 0.3;
}
#unitName {
position: absolute;
padding: 10px;
color: black;
font-size: 12px;
background: white;
border-radius: 10px;
border-style:solid;
border-width:2px;
border-color: red;
opacity: 0.8
}
#unitName.hidden {
display: none;
}
.place,
.place-label {
fill: #444;
}
text {
font-family: monospace;
font-size: 12px;
pointer-events: none;
}
.graticule {
fill: none;
stroke: #777;
stroke-width: .5px;
stroke-opacity: .5;
}
</style>
<script type="text/javascript" src="https://d3js.org/d3.v3.min.js" charset="utf-8">
</script>
<script type="text/javascript" src="https://d3js.org/topojson.v1.min.js" charset="utf-8">
</script>
</head>
<body>
<div id="unitName" class="hidden"></div><script type="text/javascript">
var width = 1200;
var height = 1200;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("rect").attr({x:0, y:0, width:width, height:height, stroke:"black", fill:"lavender"});
var projection = d3.geo.mercator()
.scale(2500)
.center([135, 35])
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection)
.pointRadius(2);
var graticule = d3.geo.graticule();
// var url = "index.php?plugin=attach&pcmd=open&file=jp.json&refer=HTML%20D3.js%20Geo";
var url = "jp.json";
d3.json(url, function(error, data){
var subunits = topojson.feature(data, data.objects.subunits);
// 経線、緯線
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
// 県
svg.selectAll(".subunits")
.data(subunits.features)
.enter()
.append("path")
.attr("class", "subunit")
.attr("d", path)
.on("mouseover", function(d) {
var mXY = d3.mouse(document.body);
d3.select("#unitName")
.style("left", (mXY[0] + 10) + "px")
.style("top", (mXY[1] + 10) + "px")
.classed("hidden", false)
.text(d.properties.name);
})
.on("mouseout", function() {
d3.select("#unitName")
.classed("hidden", true)
.text("");
});
// 県境 (他のポリゴンと共有)
svg.append("path")
.datum(topojson.mesh(data, data.objects.subunits, function(a, b) { return a !== b; }))
.attr("d", path)
.attr("class", "subunit-boundary");
// 海岸 (他のポリゴンと共有しない)
svg.append("path")
.datum(topojson.mesh(data, data.objects.subunits, function(a, b) { return a === b; }))
.attr("d", path)
.attr("class", "subunit-sea-boundary");
// 主要都市 (点) 大きさは d3.geo.path() のオプションで指定
svg.append("path")
.datum(topojson.feature(data, data.objects.places))
.attr("d", path)
.attr("class", "place");
// 主要都市 (ラベル)
var leftCities = ["Nagasaki","Kobe","Yamagata","Otaru","Hirosaki","Hiroshima","Matsue","Asahikawa"];
svg.selectAll(".place-label")
.data(topojson.feature(data, data.objects.places).features)
.enter()
.append("text")
.attr("class", "place-label")
.attr("transform", function(d) { return "translate(" + projection(d.geometry.coordinates) + ")"; })
.attr("dy", ".35em")
.attr("x", function(d) { return leftCities.indexOf(d.properties.NAME) > -1 ? -6 : 6; })
.style("text-anchor", function(d) { return leftCities.indexOf(d.properties.NAME) > -1 ? "end" : "start"; })
.text(function(d) { return d.properties.NAME; });
}); // end of d3.json
</script>
</body>
</html>
経線・緯線 https://github.com/mbostock/d3/wiki/Geo-Paths
var graticule = d3.geo.graticule();
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
県境
// 県境 (他のポリゴンと共有)
svg.append("path")
.datum(topojson.mesh(data, data.objects.subunits, function(a, b) { return a !== b; }))
.attr("d", path)
.attr("class", "subunit-boundary");
// 海岸 (他のポリゴンと共有しない)
svg.append("path")
.datum(topojson.mesh(data, data.objects.subunits, function(a, b) { return a === b; }))
.attr("d", path)
.attr("class", "subunit-sea-boundary");
これ、TopoJSON のすごいところ
topojson.meth のセレクタで、他のポリゴンと共有している境界か、そうでないかを判別している。viva 位相幾何学
主要都市
// 主要都市 (点) 大きさは d3.geo.path() のオプションで指定
svg.append("path")
.datum(topojson.feature(data, data.objects.places))
.attr("d", path)
.attr("class", "place");
TopoJSON にまとめた、県のポリゴンと主要都市情報のうち、主要都市情報を表示する。表示する点は path のオプションで設定する
主要都市では、点ひとつひとつを別扱いする必要はないので .datum() で追加している。必要があれば、県のポリゴンと同様に .data() をつかってもよい
HTML / GIS