1、阿里巴巴技术专家杨晓明:基于 Hadoop技术进行地理空间分析发表于 2015-02-02 21:46| 4832次阅读| 来源作者投稿| 17 条评论| 作者杨晓明 杨晓明 Hadoop地理信息智能交通摘要:将交通领域产生的海量车辆位置信息和道路进行关联的统计操作是颇为浩大的工作。本文将介绍一种通过使用地理网格进行数据关联,并利用Shuffle过程的二次排序实现高效的统计各条道路上位置点分布情况的方法。 【编者按】交通领域正产生着海量的车辆位置点数据。将这些车辆位置信息和道路进行关联的统计操作则是一项颇为浩大的工作,而随着 Hadoop 技术的成熟和普及,使得在海量数据中进行该统计运算的工作
2、变得相对容易了很多。本文将介绍一种通过使用地理网格进行数据关联,并利用 Shuffle 过程的二次排序实现高效的统计各条道路上位置点分布情况的方法。中华人民共和国交通运输部、中华人民共和国公安部、国家安全生产监督管理总局于 2014 年 1 月 28 日公布了道路运输车辆动态监督管理办法,在该 文件中规定,自 2014 年 7 月 1 日起,国内道路运输车辆须安装卫星定位装置,未按照要求安装卫星定位装置,或者已安装卫星定位装置但未能在联网联控系统正 常显示的车辆,不予发放或者审验道路运输证。随着该文件规定的落实,必将会产生海量的车辆位置点数据。将这些车辆位置信息与地理信息相结合进行统计,则是相
3、关技术行业中常见的统计分析的应用场 景。而在这些统计中,将位置点和道路进行关联的统计操作则属于一种较为复杂的情况。将 TB 级的车辆位置数据按照道路进行区分,并统计每条道路上的位置点分 布情况,需要涉及较复杂的地理空间算法,而且在数据的组织方式上也需要进行更为精巧的设计。在几年前云计算与大数据的技术尚未兴起的条件下,进行这样的操 作将会是一项颇为浩大的工作,既需要考虑分布式并行计算,又需要对地理算法进行尽量高效的设计,还需要兼顾分布式情况下系统的健壮性和可靠性。Hadoop 技术的成熟和普及,使得在海量数据中进行统计运算的工作变得相对容易了很多。作为 Apache 软件基金会的开源分布式计算平
4、 台,Hadoop 提供了分布式文件系统(HDFS)和分布式计算(MapReduce/Yarn)的基础框架支持。在海量数据的分析与处理领 域,Hadoop 以其高可靠性、高效性、高扩展性和高容错性等优势,可以使用户很容易的架构和使用分布式计算平台,可以简便的进行海量数据的存储和检索, 能够轻松的开发和运行处理海量数据的应用程序。在很多 IT 领域,尤其是互联网行业, Hadoop 被广泛应用于用户行为分析、数据挖掘与机器学习、网页抓取 与分析、构建搜索引擎以及推荐广告等与大数据相关的应用之中。由于 Hadoop 的 MapReduce 模型是基于 Key/Value 对的操作,因此在 Key/
5、Value对中如何设计地理数据和位置数据的关联关系 将会成为一个可以使统计性能产生质变的关键点。通过合理的 Key/Value 设计和对 MapReduce 的 Shuffle 过程的优化,将会使统计操作的性 能产生质的飞跃。以下将介绍一种通过使用地理网格进行数据关联,并利用 Shuffle 过程的二次排序实现高效的统计各条道路上位置点分布情况的方法。计算某位置点是否位于某条道路上的一种相对简单的方式是获取道路的轮廓数据(以道路边界的经纬度点组成的多边形顶点经纬度信息)和位置数据的经纬度 信息,然后将道路的轮廓坐标构建一个多边形,并通过判断车辆位置的经纬度坐标是否位于多边形的内部来判断车辆是否
6、位于道路之上。在获取车辆和道路关系信息 后,可以生成一个类似如下结构的二维表数据模型,进而进行分布状态的统计。实现这种统计的一个技术关键点是如何判断一个点是否包含于一个多边形内部,如下图中,如何判断各圆点和多边形的包含关系。 在已知多边形各顶点坐标的情况下,判断任意一个点是否位于该多边形的内部的方法在各种开发语言中均有较多实现,下面以 Java 为例,简述两种常见的方法:方法一:使用顶点坐标构建一个 java.awt.Polygon 对象,该对象具有一个contains(int x,int y)方法,通过将 x 和 y 两个方向的坐标作为该方法的参数进行调用,即可判断该点是否位于多边形内部。
7、方法二:使用第三方空间拓扑关系工具包 JTS Topology Suite 进行判断。该工具包中存在一个抽象类 com.vividsolutions.jts.geom.Geometry。该抽象类作为JTS 的几何元素对象的基类,具有一个 contains(Geometry g)方法,该方法可以用于判断一个几何元素是否位于另一个几何元素内部。com.vividsolutions.jts.geom.Geometry 有一 个表示多边形的子类com.vividsolutions.jts.geom.Polygon 和一个表示位置点的子类 com.vividsolutions.jts.geom.Poin
8、t。使用多边形顶点坐标和位置点坐标分别构建 com.vividsolutions.jts.geom.Polygon 和 com.vividsolutions.jts.geom.Point 对象, 然后根据 Polygon.contains(Point p)方法即可判断位置点是否位于多边形内部。在路网数据和位置点数据量非常巨大的情况下,直接使用这两种方法统计道路-位置点的方法将会遭遇非常严重的性能问题,尤其在统计一个较长时 间段内全国道路上位置点的分布情况时候。对于 TB 级的位置数据和数十 GB 的路网数据,进行空间关系的判断,如果使用单台服务器会涉及大量的磁盘分片读写, 性能会非常底下,而如
9、果使用分布式架构进行统计,网络通信,容错控制,任务管理等工作又会大大增加操作的难度。面对这样的问题,使用 Hadoop 进行统计 操作,则会非常合适。Hadoop 的 MapReduce 编程模型是 Hadoop 体系的分布式并行计算框架。MapReduce 编程模型假设用户需要处理的输入是一系列的 key/value。在此基础上定义了两个函数 Map 和 Reduce。业务逻辑的实现者则需要提供这两个函数的具体实现。Map 函数:输入是一系列 Key/Value 对(k1,v1),经过相应处理之后,Map 函数将会产生中间结果 Key/Value 对(k2,v2)。MapReduce 框架将
10、会对中间结果按照 Reduce 进行分区/汇总 /排序,然后调用 Reduce 函数。Reduce 函数:输入是经过分区/汇总/排序以后的中间结果(k2,list(v2),输出则是最后的输出,可记为 list(v3)。MaprReduce 的大致过程可以描述为:Map(k1,v1)list(k2,v2);Reduce(k2,list(v2) list(v3)。MapReduce 确保每个 reducer 的输入都按键排序。系统执行排序的过程将map 输出作为输入传给 reducer称之为 shuffle。常见的 Shuffle 操作包括PartitionerClass 中的 getPartit
11、ion 方 法,SortComparatorClass 中的 compare 方法和 GroupingComparatorClass 的 compare 方法。 Shuffle 的过程如下图所示,该过程对于 MapReduce 相当重要,适当的优化就可以对整个操作的性能产生质的飞跃。使用 Hadoop 统计道路-位置点分布状态,主要的思路是将路网数据和位置点数据存储在 HDFS 的两个目录下,通过运行 MapReduce 程序,对路网数据和位置点数据执行 Reduce 端的 join 的操作,并在每个 Reduce 函数内进行路网和位置点的关系判断,生成位置 点所属车辆的 ID 和道路 ID
12、以及位置点和时间的关系记录。使用生成的关系记录,再执行一次 MapReduce 的统计,就可以计算出某个时段内每条道路上车 辆的分布状态。这个操作的关键点是在路网数据和位置点数据进行 join 操作并判断路网和位置点关系的这个流程,通过采用地理网格进行 join 并在 Shuffle 阶段以二次排序的方式进行路网和位置点的预排序,经过二次排序后的相同网格内的数据,可以在 Reducer 方法中进行高效的空间关系判断。 执行这一流程的步骤如下:1.确定地理网格的划分方式 划分地理网格的目的是使不同的网格内的路网和位置点数据可以在 Reduce 中并行的执行。适当的划分地理网格的范围,可以使资源更
13、 加合理的调配,提高运行的效率。每个地理网格会被设定一个 ID,这个网格 ID 将会成为路网数据和位置点数据执行 join 操作的依据。例如 ID 为 11025_3810 的网格表示东经110.25 度到 110.30 度,北纬 38.10 度到 38.15 度之间的地理区间。2.Map 阶段 由于 HDFS 中路网数据目录和位置数据的目录都被设定为 MapReduce 的InputPath,因此在 map 阶段,路网数据文件和位置数据文件的每行都会成为map 函数的 value 参数,通过对该行数据格式的判断,可以确定该行数据是路网数据或是位置数据。如果是位置数据,则以该网格的 ID 和”
14、:point” 组成的字符串(如11025_3810:point)作为 key,以经度、纬度、时间、车辆 ID 组成的字符串作为value,生成一组 map 输出的 key/value 对并执行 map 的输出。如果是路线数据,则需要找到与该路线相交的所有地理网格,实现该操作的方法是判断路线外接矩形的四个顶点所位于的地理网格,然后遍历经纬度位于所有顶点网格之间的所有网格。对于每一个网格,都会输出一个 Key/Value 对,Key 是网格 ID 和” :line“组成的字符串(如 11025_3810:line),value 是路线点序列和路线 ID 组成的字符串。该过程的主要步骤大致如下:j
15、ava view plaincopy1. public void map(LongWritable key, Text value, Context context) 2. if (isLine(key) 3. String gridIds = chooseGridIds(key); 4. for (String gridId : gridIds) 5. context.write(new Text(gridId + “:line“),lineValue(value); 6. 7. else 8. String gridId = chooseGridId(key); 9. context.wr
16、ite(new Text(greidId + “:point“), pointValue(value); 10. 11. 3.shuffle 阶段 在 Map 执行阶段,输出记录的 key 的格式是网格 ID+数据文件类型的组合,为了使具有相同网格 ID 的输出进入相同的 Parition,需要对 Partitioner 的getPartition 函数进行重写,将选择分区的方式修改成使用 key 字符串的网格 ID的部分进行 选择。Partition 阶段之后,会在 map 端和 reducer 端分别根据SortComparatorClass 中指定的 compare 规则进行排 序。对于
17、相同的网格 ID,由于”line”字符串的字典排序在”point”字符串之前,而我们也希望排序的最终结果是数据量相对很小的路网数据排列在位 置点的前面,因此 compare 函数制定的排序规则是 v1.toString().compareTo(v2.toString()即可。排序阶段完成 后,会根据 GroupingComparatorClass 中制定的排序规则,确定最后的 Value 序列在reduce 函数中的排序。 GroupingComparatorClass 的 compare 函数会确定最终具有哪些 key 的 value 会出现在同一个 reduce 函数的参数列表 中。根据业
18、务规则,我们希望具有相同的网格 ID 的数据被排列到相同的 reduce 函数中,因此该compare 函数需要从 key 中获取网格 ID 部分,然后 根据字典顺序排序。SortComparatorClass 的 compare 方法为: java view plaincopy1. public int compare(WritableComparable v1, WritableComparable v2) 2. return v1.toString().compareTo(v2.toString(); 3. 4. GroupingComparatorClass 的 getPartitio
19、n 方法为: 5. public int getPartition(Text key, Text value, int num) 6. return (key.toString().split(“:“)0.hashCode() 7. 8. Groupcomparator 的 compare 方法为: 9. public int compare(WritableComparable v1, WritableComparable v2) 10. return v1.toString().split(“:“)pareTo(v2.toString().split(“:“)0); 11. 4.reduc
20、e 阶段 Reducer 中的 reduce 函数每次被调用的时候,函数的第一个参数是一个包含网格 ID 的 key,第二个参数是该网格内所有位 置点以及与该网格相交的所有路线的数据组成的一个 Iterable。通过 Shuffle 过程的优化,在使用 Iterable 遍历数据时,所有路网数据都排 列在位置点数据的前端。这样的排序方式,可以使判断网格内所有路网和位置点的空间关系的操作只需要遍历一次 Iterable。当遍历完位于最前端的路网数 据的时候,就可以将所有的路网数据保存在一个 list 中。遍历的下一步就是开始遍历所有位置点,每遍历一个位置点,都要与保存路网数据的 list 中的每
21、条 数据进行空间关系判断,计算位置点是否位于该路网上。该过程的主要步骤大致如下:java view plaincopy1. public void reduce(Text key, Iterable values, Context context) 2. List roadList = new ArrayList(); 3. boolean isPoint = false; 4. for (Text v : values) 5. if (!isPoint) 6. if (isLine(v) 7. Road road = initRoad(v); 8. lineList.add(road); 9
22、. else 10. isPoint = true; 11. /以下 5 行代码应归属到某个业务方法中 12. Position pos = initPosition(v); 13. for(Road road: roadList) 14. if (road.contains(pos) 15. output(road,pos,context); 16. 17. 18. 19. else 20. /以下 5 行代码应归属到某个业务方法中 21. Position pos = initPosition(v); 22. for (Road road : roadList) 23. if (road.
23、contains(pos) 24. output(road,pos,context); 25. 26. 27. 28. 29. 在数据规模较大的场景下统计位置点和路网的空间关系,由于涉及到两类数据进行 join 操作并执行空间计算的工作,因此传统的基于单 服务器地理信息系统运算的模式会遇到很严重的性能问题,当数据量超过一定规模后,传统的方式甚至无法执行空间数据统计的工作。基于 Hadoop 的 MapReduce 框架,采用以地理网格进行 join 的方式,通过二次排序排列地理类型,可以使统计操作的空间、时间复杂度或是编程的工作量都会大为降 低,使原本非常复杂的工作变成可以在生产环境中被常规操
24、作的工作。采用上述方法进行地理空间分析,对于类似在一个较大地理范围内统计某个时段内车辆在各条道路上的分布情况这样的应用场景可以发挥有 效的作用。这种应用场景对于分析全国交通流量状态,规划路网设计的工作都有非常重要的参考价值。而在 Hadoop 上,使用基于网格的 join 操作,几乎可 以应用于各种数据规模庞大的地理统计分析之中。目前,在实验环境下进行统计测试,对于600G 的车辆轨迹点数据和 300M 的路网数据,在由 10 台 PC 服务 器组成的Hadoop 集群中,完成车辆轨迹点在路网上的分布情况的统计,大约耗时 5 小时可以完成,而采用传统技术对于这种数据规模的轨迹点和路网数据进行
25、空间分析,需要耗费数天的时间。在下一步的工作中,可以将这样的地理空间统计方法推广到性能更加优秀的 Spark 平台,在数据规模不是很大的情况下,这种 方法对于实时计算各条道路上的车辆分布情况,预测交通流量,解决道路拥堵问题,将会发挥很有实际意义的效用。作者:杨晓明,阿里巴巴技术专家,曾担任中交兴路系统架构师。2003 年开始全职从事软件研发工作,2007 年后一直担任架 构师的工作。曾参与民航航空安全管理信息系统、民航总局运行管理中心、中国空中交通流量预测系统、交通部全国货运监管服务平台、雅迅车联网系统、浪淘金全 媒体推荐引擎、位客网、分分网、简单网等包括政府平台、SNS 系统及电子商务平台的开发和架构工作。长期关注新兴技术,目前主要研究可应用于交通领域的高 并发、大数据及数据挖掘相关技术。