这是一个 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