D3学习之地图

(2017.03.09-03.11)

地图的意义

在可视化领域中,将数据点投影和关联到地理区域上,是一个非常关键的内容(体现了可视化中利用读者自身知识常识从而加速吸收信息的原则)。

GeoJSON and TopoJSON

GeoJSON是用于描述地图空间信息的数据格式。GeoJSON不是一种新的格式,其语法规范是符合JSON格式的,只不过对其名称进行了规范,专门用于表示地理信息。GeoJSON里的对象也是由名称/值对的集合构成,名称总是字符串,值可以是字符串、数字、布尔值、对象、数组、null。内部结构的话,每一个GeoJSON都必有一个type属性,表示对象的类型,如Point(点)、LineString(线)、Feature(特征)等。



上图展示的就是一个标准GeoJSON文件的内容,可以看到type值为FeatureCollection(特征集合),则该对象必须要一个名称为features的成员(就在type下一行),features是一个数组,数组每一项都是一个特征对象,如果是中国地图的话,则每一项描述一个省的地理信息。

TopoJSON是GeoJSON按拓补学编码后的扩展形式,TopoJSON中的每一个几何体都是通过将共享边整合后组成的,从而消除了部分冗余数据,同时地理坐标使用整数,因此文件大小较GeoJSON缩小了80%左右。同时,TopoJSON的语法规范也是符合JSON格式的。

获取地图数据

首先介绍一个非常好的地理数据网站Natural Earth,其中提供了大量免费的地理数据下载内容,包括中国地图,那么在网站上下载了相应的zip包后,进行解压:



其中的shp文件,我们需要从中提取出需要的地理信息,并保存为中文形式,标准的工具室ogr2ogr,但是工具需要使用命令行操作,而且需要VS进行编译,较为麻烦,因此我们使用一个基于ogr2ogr开发的图形化软件:ogr2gui,下载地址为ogr2gui,下载完成后打开工具,进行下图操作:(切记!!导入的文件和导出的文件路径中一定不能出现汉字,不然会导致导出失败并且没有任何错误提示!

完成后生成china.geojson文件,那么我们通过在线工具来看看我们获取的数据绘制成地图后长什么样,浏览http://mapshaper.org,导入我们的geojson文件:

地图绘制得不错,然而不能接受的是地图里没有台湾省。。。。所以最后我在网上下载了一个更合适的地图:

mapshaper这个网站还有一个很重要的功能就是简化地图的边界,原始地图数据通常非常大,因为其中包含了大量地图的细微边界变化数据,而其中一些我们并不需要,因此可以进行简化,下图就是简化后的效果:



可以发现适当的简化并不影响地图的整体效果(并且有时候,舍弃基本的地理信息可以让我们展示更真实的数据,见我的博客《数据可视化之美阅读》)。

使用D3绘制地图

那么我们最终的目的当然是使用D3来绘制地图,GeoJSON和TopoJSON格式都可以绘制地图,然而TopoJSON具有文件大小更小的优势,所以尽可能都是用TopoJSON,但TopoJSON的缺点在于它标准是由D3作者制定,目前还不是世界范围内承认的标准。下面的内容,我们来分别使用两种方式来绘制地图。

GeoJSON

首选无论使用GeoJSON还是TopoJSON,都需要先定义地图的投影和地理路径生成器,具体代码和注释如下:

 var projection = d3.geo.mercator()
.center([107, 31])   //设置地图中心位置,前经度后纬度
.scale(850) //设置缩放量
.translate([width/2, height/2]);    //设置平移量 //定义地理路径生成器
var path = d3.geo.path()      //应用上面生成的投影,每一个坐标都会先调用此投影函数,然后才产生路径值
.projection(projection);

然后,通过d3.json请求文件china.geojson,并添加足够数量的path(svg的path,svg是d3的基础),每一个path用于绘制一个省的路径。

//颜色比例尺
var color = d3.scale.category20();
//请求china.geojson,把文件的json内容传递给root对象
d3.json("../geojson/china.geojson", function(error, root) { if (error)
return console.error(error);
console.log(root.features); svg.selectAll("path")
.data( root.features )
.enter()
.append("path")
.attr("stroke","#aaa") //svg边线属性定义,这里是颜色
.attr("stroke-width",1)  //这里是宽度
.attr("stroke-dasharray",10,10) //svg stroke虚线
.attr("fill", function(d,i){ //每一块的颜色填充
return color(i);
})
.attr("d", path )
.on("mouseover",function(d,i){ //两个交互,鼠标放置和鼠标移开
d3.select(this)
.attr("fill","yellow");
})
.on("mouseout",function(d,i){
d3.select(this)
.attr("fill",color(i));
});
});

至此,地图绘制成功,步骤非常简洁,并且带有部分交互效果,我们来看看效果:

如上图所示,鼠标箭头停留处对应的省份颜色会变成黄色,实现了一定程度的交互效果(虚拟机截图关系,看不到鼠标箭头。。)。

查看网页源代码,看看地图的HTML格式:

每一个path对应一个省份,并且都在svg元素内。

TopoJSON

使用上文提到的mapshaper网站,可以将GeoJSON文件转为TopoJSON文件后导出。首先需要明确的一点是,我们使用D3虽然导入的是topojson文件,但D3通过将TopoJSON对象转换为GeoJSON再绘制地图,所以实质还是使用GeoJSON对象绘制地图,和上面的操作并不多少不同。我们主要来看看对象转换过程:

d3.json("../geojson/china.topojson",function(error,toporoot){
if(error)
return console.error(error); //输出china.topojson的对象
console.log(toporoot); //将topoJSON对象转换为GeoJSON,保存在georoot中
//然而需要注意的是,实际上在绘制地图时,还是使用了GeoJSON对象。 //feature方法返回GeoJSON的特征(Feature)或特征集合(FeatureColleciton)
var georoot = topojson.feature(toporoot,toporoot.objects.china); console.log(georoot);

后面绘制过程和使用GeoJSON并不差别,所以不贴代码了。

那么为什么我们要使用TopoJSON呢,除了它文件相比GeoJSON会小很多外,它还能实现一些有趣的功能。

①合并地区

举个例子,要将东南各省合并在一起用一个颜色表示,那么就可以使用topojson.merge( )方法来返回一个合并后的几何体对象,并且其中只保存着我们所需要的几个省的几何信息。代码如下:

var southeast = d3.set([
"广东", "海南", "福建", "浙江", "江西",
"江苏", "台湾", "上海", "香港", "澳门"
]);
//所有省份
var georoot = topojson.feature(toporoot,toporoot.objects.china);
//合并东南各省
var mergePolygon = topojson.merge(toporoot, toporoot.objects.china.geometries
.filter(function (d) {
return southeast.has(d.properties.name); //只有集合中名字相称的省份才会留下,其他会被filter过滤
}
));

在绘制的时候我们分两步来绘制,一、绘制除东南各省外的其他省份;二、绘制东南各省,代码如下:

 //先不绘制选中的那几块
svg.selectAll("path")
.data(georoot.features.filter(function(d){
return !southeast.has(d.properties.name); //筛选掉东南各省,不绘制
}))
.enter()
.append("path")
.attr("class","province")
.style("fill","#ccc")
.attr("d",path); //绘制东南各省
svg.append("path")
.datum(mergePolygon)
.attr("class","province")
.style("fill","blue") //用蓝色标注
.attr("d",path);

绘制后,看看结果:



效果不错~

②绘制边界线

假设我们现在需要用蓝色标注新疆的西藏的边界,使其更加显眼,那该怎么做呢,下面代码展示了如何使用topojson做到边界线的绘制:

d3.json("../geojson/china.topojson",function(error,toporoot){
if(error)
return console.log(error); //获取西藏和新疆的边界线
var boundary = topojson.mesh(toporoot,toporoot.objects.china,function (a,b) {
//经尝试发现a和b的取值存在顺序关系,参数相反的话无法识别,所以正反条件都加上了
return (a.properties.name ==="西藏" && b.properties.name ==="新疆")
or (b.properties.name ==="西藏" && a.properties.name ==="新疆");
}); console.log(boundary); var georoot = topojson.feature(toporoot,toporoot.objects.china);
//绘制整体地图
svg.selectAll("path")
.data(georoot.features)
.enter()
.append("path")
.attr("class","province")
.style("fill","#ccc")
.attr("d",path); //绘制特殊边界线
svg.append("path")
.datum(boundary) //boundary为topojson.mesh方法生成的几何对象
.attr("class","boundary")
.style("fill","none") //path如果不设置fill为none的话会自带黑色填充,导致无法呈现为一条线
.style("stroke","blue")
.style("stroke-width",3)
.attr("d",path); });

效果如图:



③查找相邻区域

TopoJSON除了可获取两省份的边界线之外,还可以计算与一个省份相邻的省份,需要用到topojson.neighbors( )方法,代码如下:

d3.json("../geojson/china.topojson",function(error,toporoot){

    //通过topojson.neighbors计算所有省份的相邻省份,保存在数组neighbors里
//数组neighbors保存有各省份的邻省序号
var neighbors = topojson.neighbors(toporoot.objects.china.geometries);
var georoot = topojson.feature(toporoot,toporoot.objects.china); paths = svg.selectAll("path")
.data(georoot.features)
.enter()
.append("path")
.style("fill","#ccc")
.attr("class","province")
.attr("d",path); console.log(paths);
svg.selectAll("path").each(function (d,i) {
//为每一个元素添加相邻省份的选择集
d.neighbors = d3.selectAll(
neighbors[i].map(function(j){ //使用map方法通过序号返回邻省的path对象
return paths[0][j];
})
);
}).on("mouseover",function (d,i) {
//鼠标移入后,变色
d3.select(this).style("fill","red");
d.neighbors.style("fill","steelblue");
}).on("mouseout",function(d,i){
//鼠标移出后,恢复原来的颜色
d3.select(this).style("fill","#ccc");
d.neighbors.style("fill","#ccc");
});
});

效果如图:

网格生成器

作为地图,有时候需要我们添加经纬线,我们可以使用网格生成器来绘制:

var width = 1000, height = 1000;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(0,0)"); var eps = 1e-4; //防止网格没有边界线,不过因为我们是对中国区域画网格,并不影响 //创建一个经纬度网格生成器,设置经度和纬度范围以及步长
var graticule = d3.geo.graticule()
.extent([[71,16],[137+eps,54]])
.step([3,3]); //生成网格数据
var grid = graticule();
var projection = d3.geo.mercator()
.center([107,31])
.scale(800)
.translate([width/2,height/2]);
var path = d3.geo.path().projection(projection); d3.json("../geojson/china.topojson",function(error,toporoot){
if(error)
return console.error(error);
var georoot = topojson.feature(toporoot,toporoot.objects.china);
svg.append("path")
.datum(grid)
.attr("class","graticule")
.style("stroke","steelblue")
.style("stroke-width","2")
.attr("d",path);
svg.selectAll("path.province")
.data(georoot.features)
.enter()
.append("path")
.attr("class","province")
.attr("fill", "#ccc")
.style("stroke","steelblue")
.attr("d", path );
});

效果如下:

总结

地图会成为我毕设后续代码编写的一块主要内容,通过这几天的学习初步掌握了地图绘制的方式,在以后的代码编写中再来巩固。

D3学习之地图的更多相关文章

  1. D3 学习

    D3 学习笔记 D3简介 D3全称是Data-Driven Documents数据驱动文档,是一个开源的javascript库,可以用于数据可视化图形的创建,但不仅仅只是这些.可以查看d3帮助文档还有 ...

  2. D3学习笔记一

    D3学习笔记一 什么是D3? D3(全称Data Driven Documents)是一个用来做Web数据可视化的JavaScript函数库.D3也称之为D3.js. D3是2011年由Mike Bo ...

  3. D3学习之动画和变换

    D3学习之动画和变换 ##(17.02.27-02.28) 主要学习到了D3对动画和缓动函数的一些应用,结合前面的选择器.监听事件.自定义插值器等,拓展了动画的效果和样式. 主要内容 单元素动画 多元 ...

  4. iOS学习笔记-地图MapKit入门

    代码地址如下:http://www.demodashi.com/demo/11682.html 这篇文章还是翻译自raywenderlich,用Objective-C改写了代码.没有逐字翻译,如有错漏 ...

  5. d3学习之路

    d3学习历程: 轻量化编译器:HbuiderXHbuiderX使用教程   理解HTMl js CSS 三者关系   学习html js css :1)w3school           2)moo ...

  6. D3学习之:D3.js中的12中地图投影方式

    特别感谢:1.[张天旭]的D3API汉化说明.已被引用到官方站点: 2.[馒头华华]提供的ourd3js.com上提供的学习系列教程,让我们这些新人起码有了一个方向. 不得不说,学习国外的新技术真的是 ...

  7. 【D3】D3学习轨迹-----学习到一定层度了再更新

    1.  首先了解SVG的基本元素 http://www.w3school.com.cn/svg/ 2.  了解d3的专有名词  http://www.cnblogs.com/huxiaoyun90/p ...

  8. 前端使用d3.js调用地图api 进行数据可视化

    前段时间自己研究了demo就是把某个区域的某个位置通过经纬度在地图上可视化.其实就是使用了第三方插件,比现在比较火的可视化插件d3.js echart.js.大致思路就是,把要用到的位置的geojso ...

  9. D3学习之画布制作

    最近大半个月都和d3斗争,学习艰辛(呜呜……)如果觉得作者写的对你有用,可以打赏作者哦!owo 起言:结合自己的学习之路,我认为要想使用d3画图搞清楚布局很重要,层次分明,就给了你很大的灵活性,写起代 ...

随机推荐

  1. 面试题思考:interface和abstract的区别

    抽象类(abstract) 含有abstract修饰符的class即为抽象类,abstract 类不能创建的实例对象. 含有abstract方法的类必须定义为abstract class,abstra ...

  2. Hibernate 中多对多(many-to-many)关系的查询语句

    两个对象: 学生表:Student 课程表:Course 两者的关系是多对多,当查询Student对象,并以Course对象作为条件时的sql语句写法如下: select pa from Studen ...

  3. oracle批量update

    我个人觉得写的很好 http://blog.csdn.net/wanglilin/article/details/7200201 需求: 将t2(t_statbuf)表中id和t1(T_Mt)表相同的 ...

  4. 【IDEA】项目中引入Spring MVC

    一.原文说明: IntelliJ idea创建Spring MVC的Maven项目 - winner_0715 - 博客园 https://images2015.cnblogs.com/blog/82 ...

  5. css 特殊处理样式记录

    1.解决任何盒子的垂直横向 居中显示 display: -webkit-box; -webkit-flex: 1; -webkit-box-orient: vertical; -webkit-box- ...

  6. myeclipse10.7导出war包时出错解决办法

    myeclipse10.7的版本破解后,导出war包时报“SECURITY ALERT: INTEGERITY CHECK ERROR”的错误. 选中项目->export->java ee ...

  7. SpringMVC是单例的,高并发情况下,如何保证性能的?

    首先在大家的思考中,肯定有影响的,你想想,单例顾名思义:一个个排队过...  高访问量的时候,你能想象服务器的压力了... 而且用户体验也不怎么好,等待太久~ 实质上这种理解是错误的,Java里有个A ...

  8. tornado requesthandler可以重写的方法

    一 :RequestHandler 一般我们继承tornado.web.RequestHandler 1,RequestHandler.initialize()一般用于初始化,第三个字典参数传入 cl ...

  9. 转载一篇pandas和,mysql

    http://pandas.pydata.org/pandas-docs/stable/comparison_with_sql.html#compare-with-sql-join http://bl ...

  10. Leetcode 之 Exclusive Time of Functions

    636. Exclusive Time of Functions 1.Problem Given the running logs of n functions that are executed i ...