参考:
https://bost.ocks.org/mike/map/
用 D3 和 TopoJSON做一个地图
1,安装转换工具
brew install gdal
npm install -g topojson@1
检查安装结果
which ogr2ogr
which topojson
2,获取数据
http://www.naturalearthdata.com/
国家:http://www.naturalearthdata.com/downloads/10m-cultural-vectors/10m-admin-0-details/
省:http://www.naturalearthdata.com/downloads/10m-cultural-vectors/10m-admin-1-states-provinces/
城市:http://www.naturalearthdata.com/downloads/10m-cultural-vectors/10m-populated-places/
除了还有50m和110m数据
3,数据转换
参考:http://calmhawk.iteye.com/blog/2026798
ogr2ogr -f GeoJSON -where “ADM0_A3 IN (‘CHN’,’HKG’,’MAC’,’TWN’)” subunits.json ne_10m_admin_0_map_subunits/ne_10m_admin_0_map_subunits.shp
ogr2ogr -f GeoJSON -where “ISO_A2 = ‘CN’ AND SCALERANK < 8” places.json ne_10m_populated_places/ne_10m_populated_places.shp
ogr2ogr -f GeoJSON -where “ADM0_A3 = ‘CHN’” cn_province.json ne_10m_admin_1_states_provinces/ne_10m_admin_1_states_provinces.shp
topojson -o uk.json –id-property SU_A3 –properties name=NAME – subunits.json places.json cn_province.json
参考:
https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3
http://blog.csdn.net/zhou452840622/article/details/49058395
中国编号
CHN China TWN Taiwan, Province of China
MAC Macao HKG Hong Kong
CHINA CN
4,视图转换,大小缩放
参考:https://en.wikipedia.org/wiki/Albers_projection
中国数据:
var projection = d3.geo.albers()
.scale(800)
.translate([width / 2, height / 2])
.rotate([-105, 0])
.center([0, 36])
.parallels([27, 45]);
解释:
中间有一个关键的projection,投影.文中的albers projection中文名称叫等积圆锥投影,搜了一下参数,用如下code即可,简单原理就是
- 先放大图形
- 然后转换长宽到自己想要的大小
- 然后旋转使自己的区域所在经度居中,以0°为基准,西经为正数,东经为负数,参数文中中国正中为105°:
- 然后变换中心使自己的区域所在维度居中.
- 标称维度,参数文中为27°和45°.
原文翻译:
作为一个公众性的选择,GeoCommons是一个分享地理数据集的平台。集成(integrated)搜索以及简介使得它容易被浏览。但是,尽管这里有很多有用的数据,你应该警惕没有被检验的来源,至少对于新闻业而言。对于直接获取数据来说,它比政府机构或者其他可引用来源来得容易。
显而易见的,最方便的地理数据来源是Natural Earth,它是制图员Nathaniel Vaughn Kelso以及其他成员的倾力之作。Natural Earth 提供了大量的文化,物理以及栅格数据集。Shapefiles被漂亮的简化为不同的分辨率以便需要对应分辨率的应用使用。我们将使用1:1e7的Natural Earth数据集为这张地图的制作。
Admin 0 -Details - map subunits
Populated Places
第一个包括了国家的多边形(polygons),而第二个则包括了著名地点的名字的位置。这两个文件囊括了整个世界,所以我们的第二部是筛选出我们所需要的数据子集。
安装工具
地理数据文件对于手动清理和转换来说来说几乎总是工作量太大。幸运的是,存在一个充满活力的地理开源社区,其提供了很多强大而且免费的工具来进行数据的操纵与格式转换。
最知名的综合工具是Geospatial Data Abstraction Library,经常被称为GDAL,它包括了OGR Simple Features Library以及Ogr2org 库,我们将用来操作Natural Earth 的shapefiles。这里有一些官方GDAL库覆盖了大多数平台,但是如果你在Mac,你应该使用Homebrew:
brew install gdal
接下来你将需要TopoJSON,这要求Node.js (你可以安装Node通过Homebrew,或者official installers也可以),在安装Node之后,运行下面的命令安装TopoJSON:
npm install -g topojson
为了验证这两个安装成功了,尝试:
which ogr2og2
which topojson
它应该打印出 /usr/local/bin/ogr2ogr 以及 /usr/local/bin/topojson
转换数据
现在我们准备好了,我们将合并两个shapefiles到一个单独的TopoJSON文件。我们首先筛选出shapefile,使得它只包含我们所需要的UK信息。然后我们将shapefiles先转换成中间件GeoJSON然后再生成TopoJSON。
为什么出现两种JSON格式?事实上,它们是兄弟。TopoJSON是GeoJSON的一个拓扑编码扩展。通过对坐标进行固定精度编码,TopoJSON通常比GeoJSON小得多。我们的地图的GeoJSON有536KB之大,而TopoJSON只有80KB,一个85%的削减。(这个削减比例甚至在gzip压缩后仍然保持!)进一步的,在TopoJSON中的拓扑信息允许自动计算边界线以及其他令人感兴趣的应用,即使这消耗了更多的存储空间。
将下载的ne_10m_admin_0_map_subunits.shp作为输入,使用ogr2ogr转换成subunits.json GeoJSON文件:
ogr2ogr
-f GeoJSON
-where “ADM0_A3 IN (‘GBR’, ‘IRL’)”
subunits.json
ne_10m_admin_0_map_subunits.shp
-where参数指示了筛选规则:只有ADMO_A3属性值为GBR与IRL的项才会被输出到GeoJSON中。在此,ADM0表示Admin-0,官方边界的最高等级,以及A3表示ISO 3166-1 alpha-5国家代码。尽管只画联合王国的地图,但我们需要所有的爱尔兰数据,另一方面,我们应当指出爱尔兰只是个地方并不是一个国名!
接下来我们筛选出著名地点,其中place属性有点不同(也许设定的太随意了),所以我们使用ISO_A2取代之。而SCALERANK筛选将把标签限制在大城市级别。
ogr2ogr
-f GeoJSON
-where “ISO_A2 = ‘GB’ AND SCALERANK < 8”
places.json
ne_10m_populated_places.shp
最后我们组合subunits.json与places.json到一个单独的uk.json文件中。这一步包括一个最小化变换来固定原数据中的坐标,重命名NAME属性为name,以及将SU_A3属性变为对象id。
topojson
-o uk.json
–id-property SU_A3
–properties name=NAME
–
subunits.json
places.json
尽管在这个地图中并不需要,但ogr2ogr有很多强有力的特性你也许用地上。-clipdst参数,作为例子,调整shapefile到一个bounding box,对于显示特性中的一小部分是有用的。如果你的shapefile使用网格坐标系统(如UTM),使用 -t_src EPSG:4326 可以将它转回经纬度系统。阅读ogr2ogr手册了解更多。
#读取数据
为了简洁的一瞥如何用命令行操纵地理数据,我们返回web开发、在我们之前转换出来的东西的基础上,我将假设你对HTML以及JavaScript非常熟悉,如果并不是,花一点时间阅读 Scott Murray’s introduction to D3.在uk.json所在的目录下,创建index.html以下属模板进行。
<!DOCTYPE html>
然后,启动一个本地服务器来显示你的例子,我使用http-server,但是任何服务器都可以:
http-server -p 8008 &
如果你访问 http://localhost:8008,你应该看到一个光荣的空白页面:
很可能这并不是你希望的那样激动人心!但是我们可以很快改变这一切,在主script tag里(就是说JavaScript goes here那个地方),调用d3.json函数来加载TopoJSON文件:
d3.json("uk.json", function(error, uk) {
if (error) return console.error(error);
console.log(uk);
});
现在如果你看一下你的JavaScript命令行,你应该可以看到一个topology对象其代表联合王国的官方边界以及著名地点。
# 显示 Polygons
存在不同的方法来渲染二维几何对象在浏览器中,但是有两个主要标准是SG以及Canvas。D3 3.0两个都支持。我们将使用SVG对于这个例子,因为你可以对SVG通过CSS施加样式,并且声明样式是简单的。首先创建根SVG元素:
我建议这些在主脚本里进行而非在d3.json函数的参数回调函数中进行。这是因为d3.json是异步的:页面的剩余部分江北渲染当我们等待TopoJSON文件被下载。创建一个空的SVG根元素在页面加载的同时避免了在地理数据到达后又反过来去做这些事情。
我们还需要两个东西来渲染地理数据,一个projection(投影)以及一个path generator(路径产生器)。正如它的名字所暗示的,projection将球坐标系投影到笛卡尔坐标系上。这对于将其显示到2D的屏幕上是必要的,你可以跳过这一步如果你之后想以3D全息(holographic)投影显示它。path generator持有2D投影并且将它格式化为适当的SVG或者Canvas形态。
所以准备好做地图了!替换下面代码作为d3.json函数的回调函数像这样:
d3.json("uk.json", function(error, uk) {
if (error) return console.error(error);
svg.append("path")
.datum(topojson.feature(uk, uk.objects.subunits))
.attr("d", d3.geo.path().projection(d3.geo.mercator()));
});
你应该看到一个小的,黑的,熟悉的痕迹:
少量绘图者现在将宣布工作已经完满解决然后回家来上一杯啤酒。但是我们应做的比这个更多。无论如何,如果我解释那三行代码做了什么将是有帮助的。。。
回顾之前两个地位相近的JSON地理数据格式:GeoJSON以及TopoJSON。当我们的数据被以更有效率的TopoJSON储存,我们必须将它转回GeoJSON来进行显示。拆分出这一部让它更清晰:
var subunits = topojson.feature(uk, uk.objects.subunits);
类似的,我们可以提取投影的定义来使得代码更干净:
var projection = d3.geo.mercator()
.scale(500)
.translate([width / 2, height / 2]);
以及这样的path generator:
var path = d3.geo.path()
.projection(projection);
将path元素绑定GeoJSON数据,然后使用selection.attr设置"d"属性给格式化过的path数据。
svg.append("path")
.datum(subunits)
.attr("d", path);
对于形成的这段结构化的代码,我们可以修改投影的设置来使其更适合联合王国。Albers equal-area conic projection是一个好的选择,并且控制所绘制区域为50N到60N。我们旋转经度4.4度并且设置中心到0W,55.4N,所以真正的中心是4.4W 55.4N,这个地方在苏格兰。
var projection = d3.geo.albers()
.center([0, 55.4])
.rotate([4.4, 0])
.parallels([50, 60])
.scale(6000)
.translate([width / 2, height / 2]);
我们地图变成了这个样子
# 对Polygon施加样式
像我之前提到的那样,SVG的一个好处是我们可以作用CSS样式,我们可以对邦国进行染色通过赋予fill属性。无论如何,我们首先需要给定每个国家一个他们自己的path元素,而不是共享一个。如果不这样做,就没法分别染色。
在TopoJSON文件uk.json内部,Admin-0地图subunits被以feature collection(特征集)的方式表征。通过取出feature数组,我们可以计算data join(数据匹配,即数据多余时产生更多节点适应数据)以创建每个feature的path元素。
svg.selectAll(".subunit")
.data(topojson.feature(uk, uk.objects.subunits).features)
.enter().append("path")
.attr("class", function(d) { return "subunit " + d.id; })
.attr("d", path);
函数设定的"class"值基于ISO-3166 alpha-3国家码标准,这使得我们可以分别作用fill样式给每个国家:
.subunit.SCT { fill: #ddc; }
.subunit.WLS { fill: #cdd; }
.subunit.NIR { fill: #cdc; }
.subunit.ENG { fill: #dcd; }
.subunit.IRL { display: none; }
样式将爱尔兰完全隐藏了,不过之后我们在画边界的时候又会把它弄回来。下面是地图现在的样子:
# 显示边界
为了让polygon不至于连起来,我们需要一些线。这里有两种线,一种是英格兰,苏格兰,威尔士的边界,另一个爱尔兰的的海岸线。
我们将使用topojson.mesh来计算边界。这要求两个参数,topology以及constituent geometry object.一个可选的筛选器可以缩减返回的边界的集合,其持有的两个参数a和b表示边界两边的特征。如果是外部边界,如海岸线,则a和b是一样的。通过a===b和a!==b这样的表达式,我们可以给不同的边界以不同的渲染。
英格兰-苏格兰以及英格兰-威尔士边界是内部边界,我们可以排除爱尔兰和北爱尔兰边界通过id筛选:
svg.append("path")
.datum(topojson.mesh(uk, uk.objects.subunits, function(a, b) { return a !== b && a.id !== "IRL"; }))
.attr("d", path)
.attr("class", "subunit-boundary");
这只留下了爱尔兰的外部边界
svg.append("path")
.datum(topojson.mesh(uk, uk.objects.subunits, function(a, b) { return a === b && a.id === "IRL"; }))
.attr("d", path)
.attr("class", "subunit-boundary IRL");
增加一点样式
.subunit-boundary {
fill: none;
stroke: #777;
stroke-dasharray: 2,2;
stroke-linejoin: round;
}
.subunit-boundary.IRL {
stroke: #aaa;
}
# 显示地点
就像国家polygon一样,著名地点也是一个feature集合,所以我们可以再次转换TopoJSON到GeoJSON并且使用d3.geo.path来进行渲染:
svg.append("path")
.datum(topojson.feature(uk, uk.objects.places))
.attr("d", path)
.attr("class", "place");
这将在每个城市所在地点画一个小圆,我们可以调整其半径通过设置path.pointRadius,并且通过CSS赋予样式。但是我们还想要label,所以我们需要data join来产生文本元素。通过计算transform属性通过投影地点的坐标,我们可以转换坐标到希望到位置。
svg.selectAll(".place-label")
.data(topojson.feature(uk, uk.objects.places).features)
.enter().append("text")
.attr("class", "place-label")
.attr("transform", function(d) { return "translate(" + projection(d.geometry.coordinates) + ")"; })
.attr("dy", ".35em")
.text(function(d) { return d.properties.name; });
适当的打Label实际上是有难度的,特别是如果你想要将标签自动防止。我们在这个简单的地图上无视了这个问题,因为我们已经通过SCALERANK跳出了我们想要的标签。一个方便的技巧是使用右对齐标签到地图的左边,并且左对齐标签在地图的右边,这时使用了1W作为阈值:
svg.selectAll(".place-label")
.attr("x", function(d) { return d.geometry.coordinates[0] > -1 ? 6 : -6; })
.style("text-anchor", function(d) { return d.geometry.coordinates[0] > -1 ? "start" : "end"; });
正如你在下面看到的,看上去已经令人能够理解了,尽管存在一些重叠的标签。如果你想抵制这种情况你可以使用特殊发布可选对齐,或者你可以简单的移除覆盖的标签,你甚至可以使用simulated annealing 或者 forcedirected layout来布局,但是我会在1之后演示automatic label placement.
# 国家标签
我们地图遗失了一个重要的部分:我们还诶有标记国家!我们可以使用Natural Earth的Admin-0 label points,但是我们可以只是简单的使用projected centroid计算出国家标签应该在的位置:
svg.selectAll(".subunit-label")
.data(topojson.feature(uk, uk.objects.subunits).features)
.enter().append("text")
.attr("class", function(d) { return "subunit-label " + d.id; })
.attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; })
.attr("dy", ".35em")
.text(function(d) { return d.properties.name; });
国家标签被样式化成比城市标签显示的更大。通过设置它们的transparent,它们被压在城市标签下面:
.subunit-label {
fill: #777;
fill-opacity: .5;
font-size: 20px;
font-weight: 300;
text-anchor: middle;
}
【Topojson是一种使用拓扑编码方式的GeoJSON扩展,而不是代表着离散的几何图形(也可以称为几何体,因为json中的数据实际上是立体的),几何图形在TopoJSON中被一种叫做arcs(弧线,也是TopoJSON中的结构)的共享连接线联系在一起。Arcs实际上是点的集合,把图形用线连接起来可以叫做弧线。每条弧线只会被定义一次(这一点跟GeoJSON不通,GeoJSON的边界可能会被多次重绘),但是可以在不通的形状中多次引用,因此减少了数据冗余而缩小了文件大小。另外,TopoJSON促使应用程序使用拓扑结构,例如使用保留拓扑结构(topology-preserving)的简单形状、自动着色(d3中常用)、统计地图等。TopoJSON说明的引用实现是可行的,作为一个命令行工具将GeoJSON(或者ESRI形状文件)转换成TopoJSON,然后在客户端Javascript库重新将TopoJSON转回GeoJSON。】