利用Power BI制作分级地图报表
最近用到Power BI来做数据报表的呈现,这个报表的目的是展现车辆在业务使用过程中的消息发送的时延。服务器端接收车辆上报的消息的时候,会根据消息生成的时间戳和接收到消息的时间戳来计算传输时延。对于计算后的时延,按照每小时的粒度来进行聚合,并且呈现在地图上。
这个报表会把数据展现在地图中,地图可以分级来进行聚合呈现,例如地图可以按照区域来进行指标的呈现,当点击区域的时候,可以进入到第二级,按照网格(例如200*200米)来进行呈现。
这里我采用了Power BI的Mapbox控件来进行开发。
1. 地图数据的准备
需要准备两个级别的地图数据。第一个级别是区域的数据,第二个级别是网格的数据
1.1 区域数据的准备
首先准备区域的数据,这里我以瑞典首都斯德哥尔摩市为例,从wiki page https://en.wikipedia.org/wiki/Districts_of_Sweden#Stockholm可以查到斯市共分为了14个区,然后可以上https://osm-boundaries.com/Map这个网站找到对应的这些区域的多边形的坐标数据(在这个网站中斯市只有13个区),下载之后得到geojson格式的文件,里面定义了每个区的多边形的坐标点,按照逆时针的顺序来排序。打开这个文件,可以看到里面的Feature的geometry的类型是polygon,这里需要更改为MultiPolygon的类型,并且把coordinates的数组增加多一个[]层级,不然Mapbox处理会有问题。
区域的Geojson文件准备好之后,就可以登录上Mapbox的网站https://studio.mapbox.com/tilesets/,用mapbox studio来创建新的tileset了。
1.2 网格数据的准备
现在准备第二个级别的网格数据。这里用到了http://turfjs.org/这个工具,提供了javascript来方便的对地图进行网格切分,只需要指定一个BBOX,网格大小,就可以进行切分,切分的网格有多种不同的形状,正方形,六角形,三角形等等。非常方便。
以下代码是调用turf的javascript脚本来进行网格切分的示例。
<!DOCTYPE html>
<html>
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><meta name="viewport" content="initial-scale=1.0, user-scalable=no" /><style type="text/css">body, html,#allmap {width: 100%;height: 100%;overflow: hidden;margin:0;font-family:"微软雅黑";}</style><script src='https://unpkg.com/@turf/turf/turf.min.js'></script><title>turf</title>
</head>
<body><script type="text/javascript">var bbox = [14.29, 57.669, 19.124, 60.555];var cellSide = 0.2;var options = {units: 'kilometers'};var squareGrid = turf.squareGrid(bbox, cellSide, options);console.log(squareGrid.features.length);console.log(JSON.stringify(squareGrid));</script>
</body>
把这个HTML文件放到Web服务器,然后打开这个HTML,在浏览器的控制台可以看到切分的网格数据的输出。把这些数据拷贝下来,保存为Geojson格式的文件。这里同样需要对Geojson文件进行修改,把Polygon的类型改为Multipolygon,以及给Coordinates数组增加多一个列表层级。
1.3 道路数据的准备
因为车辆是跑在道路上的,因此发送的位置坐标都应该是落在道路上的。因此我们需要把这些区域内的道路数据抓取出来。这里用到Openstreetmap的数据来提取道路信息。
在Openstreetmap上下载斯德哥尔摩的地图数据,数据格式是XML,里面的节点如果tag是node,表示是一个具体的点和对应的坐标,如果tag是way,需要进一步看其属性是否是highway,如果是就代表是一条道路,再获取其包含的node,这样就可以提取出这条道路的所有坐标点了。
为了之后能方便进行地图区域的处理,采用Google的S2地理库来进行处理,把提取出来的道路坐标点会转换为s2point的对象,然后把这条道路转换为s2polyline。以下python代码是处理过程
tree = ET.ElementTree(file = 'stockholm/stockholm_map.osm')
root = tree.getroot()
nodes = {}
for child in root:if child.tag=='node':nodes[child.attrib['id']] = (float(child.attrib['lat']), float(child.attrib['lon']))
ways = {}
ways_type = {'motorway':[], 'trunk':[], 'primary':[], 'secondary':[]}
for child in root:if child.tag=='way':waytag = child.findall("tag")isRoad = FalseroadPosition = []for item in waytag:if item.attrib['k'] == 'highway':if item.attrib['v'] in ['motorway', 'trunk', 'primary', 'secondary']:isRoad = Trueways_type[item.attrib['v']].append(child.attrib['id'])breaks2points = []if isRoad:nds = child.findall("nd")ways[child.attrib['id']] = []for nd in nds:s2point = s2.S2LatLng.FromDegrees(nodes[nd.attrib['ref']][0], nodes[nd.attrib['ref']][1]).ToPoint()s2points.append(s2point)s2polyline = s2.S2Polyline()s2polyline.InitFromS2Points(s2points)ways[child.attrib['id']] = s2polyline
1.4 区域,网格和道路的处理
需要把区域,网格以及道路的信息都关联起来,就是说根据区域来判断有哪些网格是落在区域里面的,然后判断哪些网格是覆盖道路的。
首先是把区域的数据转换为S2的s2loop
with open('stockholm/district/district.geojson', 'r') as f:all_district = json.loads(f.readlines()[0])s2districts = {}
for feature in all_district['features']:coords = feature['geometry']['coordinates'][0][0]s2points = [s2.S2LatLng.FromDegrees(a[1],a[0]).ToPoint() for a in coords]s2loop = s2.S2Loop(s2points[:-1])s2loop.Normalize()s2districts[feature['properties']['name']] = s2loop
把网格的数据也转换为s2loop,然后判断哪些网格是包含在区域里面的,对这些在区域里面的s2loop,转换为s2polygon
with open('stockholm/stockholm_all_grids.geojson', 'r') as f:all_grids = json.loads(f.readlines()[0])selected_grids = {}
s2grids = []
gridid = 0
for feature in all_grids['features']:coords = feature['geometry']['coordinates'][0]s2points = [s2.S2LatLng.FromDegrees(a[1],a[0]).ToPoint() for a in coords]s2loop = s2.S2Loop(s2points[:-1])s2loop.Normalize()for districtname in s2districts:districtloop = s2districts[districtname]if districtloop.Contains(s2loop):feature['properties']['name'] = districtnamefeature['properties']['gridid'] = 'grid'+str(gridid)feature['s2polygon'] = s2.S2Polygon(s2loop)selected_grids['grid'+str(gridid)]=featuregridid += 1
判断上一步得到的网格中,哪些是覆盖道路的,这里用到了s2polygon的IntersectWithPolyline方法,来判断道路的polyline和网格的polygon是否相交,如果相交,那么返回的就是相交的polyline。
new_selected_grids = []
for k,v in tqdm(ways.items(), total=len(ways)):for grid in selected_grids:if len(selected_grids[grid]['s2polygon'].IntersectWithPolyline(v))>0:new_selected_grids.append(selected_grids[grid])del selected_grids[grid]break
最后就是把这些网格写入为一个geojson的文件,这个文件制作完成之后,同样需要用Mapbox studio来创建新的tileset。
way_grids = {}
way_grids["type"] = "FeatureCollection"
way_grids["features"] = []
for grid in new_selected_grids:feature = grid.copy()feature["geometry"]["type"] = "MultiPolygon"feature["geometry"]["coordinates"] = [feature["geometry"]["coordinates"]]del feature["s2polygon"]way_grids["features"].append(feature)
way_grids_str = json.dumps(way_grids)
with open("stockholm/way_grids_1209.geojson", "w") as f:f.write(way_grids_str)
2. 测量数据的准备
有了网格和区域的数据之后,我们就可以准备测量数据了。这里模拟随机生成了一些测量数据,并关联到网格和区域中。因为我们是要制作二级地图,因此数据里面需要包括三个字段,分别是区域名称,网格名称和测量值。以下代码是随机生成一些测量数据
simulate_data = 'district,gridid,latency'
for grid in way_grids["features"]:district = grid["properties"]["name"]gridid = grid["properties"]["gridid"]latency = random.randint(60, 150)simulate_data += "\n"simulate_data += district+","+gridid+","+str(latency)
with open("stockholm/simulate_data_1209.csv", "w") as f:f.write(simulate_data)
3. 报表的制作
PowerBI里面有多个地图控件,例如Azure Map, ArcGIS map, Filled Map等等,但是能做分级地图的似乎只有Mapbox以及Drilldown Choropleth,其中Mapbox功能更强一些,因此这里选用Mapbox。Mapbox提供了比较友好的收费模式,在地图访问量小于50000的时候是免费的。
打开PowerBI desktop,默认是没有mapbox的,在Visualizations里面选择Get more visuals,然后在maps里面查找mapbox并安装。
点击Mapbox控件,点击Get Data,选择我们之前制作的模拟数据。然后在Mapbox的Fields里面,把数据的district和grid两个字段拖动到location,把latency字段拖动到color
点击Format,关闭Circle,开启Choropleth,Number of levels选择为2,然后选择level 1,Data Level选择Custom Tileset,Vector URL选择在Mapbox创建的区域Tileset的名称,Source Layer name选择Tileset的layter,Vector property输入district。之后按照同样的过程来设置level 2,只是换成网格Tileset。
最后的效果如下
stockholm
利用Power BI制作分级地图报表相关推荐
- 利用Power BI制作RFM客户分析模型
RFM客户分析模型 RMF模型是一种常见的客户分析模型.RFM是三个英文单词的简称,分别是最近一次消费 (Recency), 消费频率 (Frequency)以及消费金额(Monetary).RMF模 ...
- 利用Power BI形状地图制作着色中国地图
由于Power BI自带的地图是全球地图,看中国的业务会不太方便,如何利用 Power BI 自带的形状地图来做着色的中国地图 步骤: 1 在地图选择器中下载中国地图json文件 地图选择器 2 需要 ...
- power bi 地图_如何使用Power BI创建地理地图-填充地图和气泡地图
power bi 地图 该项目 (The project) This is the first article of a series dedicated to discovering geograp ...
- 利用Power BI自定义图表,原来图片还可以这么玩
Power BI 制作可视化报告时,为了展示效果,有时候需要用图片来展示,在 Power BI 中,关于图片的自定义视觉对象主要有下面三个,利用他们可以很轻松的进行图片可视化. 下面来看看这些视觉对 ...
- 利用Power BI计算组,设计个性化数据标签
利用Power BI计算组,设计个性化数据标签 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/405532292
- 用Power BI 做ERP系统报表(小微企业管家)
ERP系统在生产型企业使用很广泛,主要以订单为中心.这是ERP围绕的核心,销售行为产生订单,生产行为处理订单,发货行为完成订单.所以,这三种行为都属于事实表,其他相关表为维度表,如员工表,客户表. 要 ...
- 利用Power BI行级安全性,限制用户访问权限
Power BI报告有多种分享协作方式,不仅可以让用户查看报告的所有数据,还可以根据用户角色进行数据限制,让特定用户只查看部分与之相关的数据. 按角色分享用到的就是PowerBI中的行级安全性(Row ...
- Power BI Paginated Reports分页报表
为什么有时候使用分页报表,不直接使用power bi的报表.在SQL Server Reporting Services(SSRS)里就有分页报表的功能. 分页报表最适合需要为打印或 生成 PDF形式 ...
- powerbi python词云图_使用Power BI制作可爱的词云图
不少星友曾问起PowerBI是否可以生成词云图,足见该图的流行度,在PowerBI中有一个专门的自定义视觉对象可以生成词云图:Word Cloud,这里就简单介绍一下该图的做法. 首先需要导入该自定义 ...
- 网站日志分析(二)——利用Quick BI制作企业化报表分析
使用QuickBI展示分析数据 本文接上篇网站日志分析(一) • 在上一篇数据已经处理完毕,各种需要的表页已经生成 • 对于处理完的数据,下面将使用Quick BI进行编辑并以图表的形式进行展示. • ...
最新文章
- Thrust快速入门教程(二)——Vector的使用
- EOJ Monthly 2019.2 (based on February Selection) D 进制转换 【数学 进制转换】
- 计算机等级考试试题4,计算机等级考试二级模拟试题4
- 李开复:AI能在15年内取代40%~50%岗位
- cstring移除指定字符串_从String中移除空白字符的多种方式!?差别竟然这么大!...
- python的内存泄露_Python 程序的内存泄露,教你一招来解决?
- 已完成私有化交易 “网红第一股”如涵退市
- I00007 打印菱形字符图案
- mobile兼容性调整,根据rem,字体大小,视窗宽度
- 使用Nginx实现负载均衡
- 计算机网络工程师中级软考试题及答案,软考中级历年真题+章节题库
- linux设计论文题目,计算机linux本科毕业论文题目
- JavaScript入门教程
- 高通9008端口刷linux,高通命令进入9008端口方式汇总
- VFP命令,DBF数据内部函数
- java clh_CLH lock 原理及JAVA实现
- 如何有效开展小组教学_如何有效开展小组合作学习
- pycharm关联git
- 【FXCG】波段操作的四个步骤
- c++整人代码,超级加倍,让人承认我是大傻猪