这是一个 step by step 的教程,不讲解原理,想深入了解请咨询google和github。
1,首先,我们需要地图坐标
Nature earth 可以下载地图坐标,这里有1:10m,1:50m,1:110m三种精度的数据可选,第一种最详细,文件尺寸也最大,后面两种文件较小,但是不包含省市信息(除了北美)。
在这个页面下载 1:10m 的地图
国家: Download countries (5.11 MB) version 3.1.0
省:Download states and provinces (13.97 MB) version 3.0.0
省会: Download populated places (1.46 MB) version 3.0.0
【这里不演示省会信息,但是处理方法相同】下载后解压缩可以看到多种数据格式,我们需要 .shp 文件。
也就是这两个文件:
ne_10m_admin_0_countries.shp【8.8M】
ne_10m_admin_1_states_provinces.shp【21.6M】
2,转换坐标文件
下载的文件包含全球所有国家和地区,我们现在只需要中国的地图数据。
现在需要用到 Geospatial Data Abstraction Library – GDAL 的 ogr2ogr 工具把shp文件转换为GeoJSON。
假设你用 OS X 并且安装了 brew,运行:
> brew install gdal
安装后运行:
> ogr2ogr -f GeoJSON -where "SU_A3 = 'CHN' OR SU_A3='TWN'" countries.json ne_10m_admin_0_countries.shp
同样处理省市文件:
> ogr2ogr -f GeoJSON -where "gu_a3 = 'CHN'" states.json ne_10m_admin_1_states_provinces.shp
转换后的文件就可以用 d3.js 来生成地图啦。
3,压缩坐标文件
但是有个问题,经过上面转换后,文件尺寸有
countries.json【651KB】
states.json 【2.6MB】
不可能直接用到网页中啊。还好已经有人做过这一步工作了, http://www.mapshaper.org/上传上面的文件,会展示出来地图信息,拖动滚动条调整精度,国家地图我选择4.0%,省市地图我选择 25%。
然后选择导出GeoJSON。
countries.json【651KB】 -> china_countries_min.json 【25KB】
states.json 【2.6MB】 -> states_min.json【767KB】
减少了很多,不过还是有点大,我们需要用另外一个工具 topojson 进一步去除不必要的信息,假设你用 OS X 并且安装了 Node.js,运行:
> npm install -g topojson
分别处理以上两个文件:
> topojson --id-property SU_A3 -p name=NAME -p name -o china_countries_topo.json china_countries_min.json
> topojson --id-property adm1_cod_1 -p name -o states_topo.json states_min.json
处理后:
countries.json【651KB】 -> china_countries_min.json 【25KB】-> china_countries_topo.json【6KB】
states.json 【2.6M】 -> states_min.json【767KB】-> states_topo.json【104KB】
文件小到网页加载可以接受了,不过,在 mapshapher.org 这一步,可以试试调成精度更低的省市级别的文件,进一步减小文件尺寸。
3,生成地图
终于来到了最后一步,展示地图,废话不多说直接看代码:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
#map {
background-color: #fff;
border: 1px solid #ccc;
overflow:hidden;
}
.background {
fill: none;
pointer-events: all;
}
#countries, #states {
fill: #cde;
stroke: #fff;
stroke-linejoin: round;
stroke-linecap: round;
}
#countries .active, #states .active {
fill: #89a;
}
#countries .active:hover,
#states .active:hover {
fill: #EAEAEA;
}
.province:hover{
fill: #bad6fc;
}
#cities {
stroke-width: 0;
}
.city {
fill: #345;
stroke: #fff;
}
pre.prettyprint {
border: 1px solid #ccc;
margin-bottom: 0;
padding: 9.5px;
}
</style>
<body>
<div id="map"></div>
<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script>
var m_width = $("#map").width(),
width = 960,
height = 800,
centered;
var projection = d3.geo.mercator()
.center([104, 36])
.scale(850)
.translate([width/2, height/2]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("#map").append("svg")
.attr("preserveAspectRatio", "xMidYMid")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("width", m_width)
.attr("height", m_width * height / width);
svg.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height)
.on("click", clicked);
var g = svg.append("g");
d3.json("./json/china_countries_topo.json", function(error, us) {
g.append("g")
.attr("id", "countries")
.selectAll("path")
.data(topojson.feature(us, us.objects.china_countries_min).features)
.enter().append("path")
.attr("d", path)
.on("click", clicked);
});
d3.json("./json/china_states_topo.json", function(error, us) {
g.append("g")
.attr("id", "states")
.selectAll("path")
.data(topojson.feature(us, us.objects.states_min).features)
.enter().append("path")
.attr("d", path)
.attr("class", "province")
.on("click", clicked);
});
function clicked(d) {
console.log(d);
var x, y, k;
if (d && centered !== d) {
var centroid = path.centroid(d);
x = centroid[0];
y = centroid[1];
k = 4;
centered = d;
} else {
x = width / 2;
y = height / 2;
k = 1;
centered = null;
}
g.selectAll("path")
.classed("active", centered && function(d) { return d === centered; });
g.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
}
</script>
</body>
4,说好的互动呢
上面的代码就可以互动啊,可以放大缩小。
d3.js 可以给地图节点绑定多种事件,并传入当前节点信息。svg地图也可以由css、javascript控制样式,可以做很多操作。
最终效果演示:http://900m.pro/demo/geo/
参考阅读及用到的工具:
Interactive Map with d3.js
【 D3.js 入门系列 — 10 】 地图的绘制
1:10m Cultural Vectors
http://www.mapshaper.org/
国家地区代码
d3.js
topojson