0
点赞
收藏
分享

微信扫一扫

Python+百度地图API+半正矢公式=地图可视化

    最近笔者学了些“驾驶”相关的常识,发现司机的驾驶证如若到期,则必须提交体检信息才可申请换证,这就需要司机本人去附近的医院体检了。

    考虑到并非所有医院都支持驾驶人体检,对于司机而言,就需要提前知道哪些医院是“有效”的。

    本笔记以北京市为例,企图帮助司机在各个“有效”医院中寻找到“距离”最近的进行体检,并将各个医院与司机本人的相对距离进行可视化。


目录

Part1:数据预处理与分析

 Step1:查询经纬度

       Step2:计算相对距离

Part2:数据可视化与分析

写在最后


Part1:数据预处理与分析

    点击即可跳转至目标网址:2021年北京市驾驶人体检医院信息

    在上方的网页中,交管局提供了223个医院地址和相关的联系方式、工作时间等信息,非常详细。但是我们要想知道哪家医院离司机最近,就必须“量化分析”,也就是计算。

    要想计算距离,就必须要知道计算原理或公式,还必须要有计算所需的参量,我想,既然待计算量是地图上的两点间距离,那么势必与每个地点的经纬度相关,故而我们必须要查询每家医院的经纬度。

    当然,在以上的所有工作开始之前,我们首先要将这223家医院的基本信息存储在.csv格式的表格中,便于我们结构化地使用数据。

    这里我想到两种办法:一种是直接从网页上复制所有信息再粘贴到.csv表格中;另一种则是通过网络爬虫,解析网页后将目标数据存入.csv表格。

    个人感觉第二种方式更加普适,因为当信息量巨大(大数据)时,我们再复制粘贴的话,“工程量”太大了,反而事倍功半。

    不过,只要你尝试就会发现,此网站并不是那么容易爬虫的,交管局的网络安全措施做得还是不错的,因此爬虫过程将涉及逆向工程(解密和加密),这当然不是我们本次工作的重点,况且数据总量较小,我们可以直接复制粘贴!

    表格文件不妨命名为:北京驾驶人体检医院信息.csv

 Step1:查询经纬度

    由于经纬度涉及到地图上某一地点的位置信息,所以势必要借助“导航卫星”。

    当然我们肯定不能控制卫星,所以要求助于“百度地图”,通过申请免费的API来进行开发者操作

    百度地图开放平台可以帮助我们查询经纬(在“控制台”中创建访问应用,即AK,得到访问密钥)。

 

    “创建应用”界面有相关选项,根据自己的需求选择合适的选项。本笔记中涉及两个:服务端浏览器端

     “创建应用”下有一配置项“IP白名单”,通常情况下,如果只是学习用,那么服务端就设置为0.0.0.0/0,浏览器端就设置为*。

    创建成功后即可得到AK,在查询经纬度时我们需要用到。

# 导入相关库
import requests
import json

# 定义get_ll()函数获取目标地点的经度(longitude)和维度(latitude)
def get_ll(x):
    url = f"http://api.map.baidu.com/geocoding/v3/?address={x}&output=json&ak=这里填自己的AK&callback=showLocation"
    Post_data = {'address': x}
    se = requests.session()
    Text = se.post(url, data=Post_data).text.replace("'", '"').replace('/ ', '/')[27:-1]
    jsonValue = json.loads(Text)
    if('result' in jsonValue):
        return [jsonValue['result']['location']['lng'], jsonValue['result']['location']['lat']]
    else:
        return ' '

    我们来检验一下此函数的正确性。检验思路是这样的:我们给函数传入参数“北京邮电大学”,查看获取到的经纬度是否与地图上的经纬度大致相当(因为同一名称的地点在地图上可能不止一处)。 

    函数运行结果如下:

    可知 北京邮电大学 的经纬度(坐标)为:[116.36471727747563, 39.96538691453649]

    检验 北京邮电大学 经纬度的正确性:打开百度地图拾取坐标系统,开启坐标反查,输入所得的经纬度,发现的确是 北京邮电大学 。

   😀事实证明:坐标经纬度基本一致,我们的程序没有问题。

    在正式开始计算相对距离之前,我们要根据get_ll()函数分别获取223家医院的经纬度。这时你可能会问,难道我要一个个将医院名称“输入”到函数里吗?

    逻辑上来说是这样的,但考虑到医院数量较多,且某些医院名称实在太长,这么做会累死人的。

    其实这种机械重复的行为我们可以让计算机帮助我们完成。

    有些学过C语言的同学现在可能已经跃跃欲试了,因为解决这个问题很容易,一个循环语句就能搞定,不过从代码的优美性和算法的简便性的角度来看,这么做显得程序员很“呆”。

    这时就能体现出我们将数据整理成.csv表格的优势了。考虑到Python特有的“索引切片”功能,如果将这些数据做统一的结构化封装处理,那么问题就变得十分简单。

    这就很像从冰箱里取肥宅快乐水,你可以取一次汽水即关一次冰箱门,如此重复做223次,自然地,你也可以先想好要取的223瓶汽水,然后一次性取出它们再关冰箱门。有了“索引切片”,我们就可以“一次取多个”!

    实现对表格中结构化数据的统一处理,我们可以导入Pandas库,它是最常用于数据分析的第三方库之一。

    于是开始以下操作↓

# 导入pandas库
import pandas as pd
# 读取表格信息
df = pd.read_csv("北京驾驶人体检医院信息.csv")
# 查看字段信息
df.head()

     输出结果如下:

     “归一化”处理:

# 将“医院地址”字段的信息分别应用于get_ll()函数,得到新字段“经纬度”的信息写入df
# 初次运行耗时约 20 s
df["经纬度"] = df["医院地址"].apply(get_ll)  # 此处有一“大坑”:如果字段名称键入的是'经纬度'和'医院地址'(单引号),将引发JSONDecodeError!
df.head()

    代码段中的注释内容应当引起注意,否则将使你举步维艰。运行结果如下:

Step2:计算相对距离

    如何通过经纬度信息,计算两地距离呢?

    我们都知道,地球的赤道半径是 6378 公里,极半径是 6356 公里,平均半径为6371公里,所以它并不是一个完美的球体。

    但我们这里并不是要得到十分精确的结果,只是为了便于司机决策而已,所以只要将地球简化为一个球体,那么就可以使用数学公式计算两地的近似距离。

    问题来了,这个数学公式长啥样呢?

    通过搜索,我查到了一种非常古老但简便的方法——半正矢公式(Haversine公式)。

    具体的数学推导在此不加赘述。

    通过以上的操作,我们已经获取了223家医院对应的经纬度,下面就需要定义函数来计算相对距离了。同样地,我们先计算预设两点间的相对距离

    下方的代码中我们分别预设了两个地点的坐标:“北京邮电大学”和“故宫博物院”。

# 导入math库,便于进行数学计算
from math import radians, cos, sin, asin, sqrt

start, end = "北京邮电大学", "故宫博物院"
start_address, end_address = get_ll("北京邮电大学"), get_ll("故宫博物院")  # 获取“北京邮电大学”(出发点)和“故宫博物院”(目的地)的经纬度

def get_address_distance(ll):
    lon1, lat1, lon2, lat2 = map(radians, [start_address[0],start_address[1],ll[0],ll[1]])
    # 半正矢公式
    dlon = lon2 - lon1  # 经度差
    dlat = lat2 - lat1  # 纬度差
    # 定义:θ = d / r ,其中,记“距离-半径比θ”为“theta”,“两点间连线距离d”为“dis”,“地球平均半径r”为“rad”
    hav_theta = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2  # θ的半正矢
    theta = 2 * asin(sqrt(hav_theta))  # θ
    rad = 6371  # r
    dis = round(theta * rad, 2)  # d
    return dis

ver = get_address_distance(end_address)  # 定义“验证变量ver”(vertification)
print("“{}”和“{}”间的相对距离为:{}公里".format(start, end, ver))  # 格式化输出

    输出结果为:

“北京邮电大学”和“故宫博物院”间的相对距离为:5.65公里

    同样地,这里我们也有一种验证方式。

    检验“北京邮电大学”与“故宫博物院”间相对距离的合理性:打开百度地图网页版,使用“工具箱”内的“测距功能”即可测出两地距离。

 

    结果为:5.6公里,与我们的计算结果  5.65公里  仅相差50米。 

    😀事实证明,两点之间的相对距离基本一致,我们的程序没有问题。

     接着,就可以应用以上定义的函数去计算每一家医院与司机间的相对距离了!

    不妨假设我就是那个需要体检换证的“老司机”👴,现在住在北邮,于是便有了以下的操作↓

# 将“经纬度”字段的信息分别应用于get_address_distance()函数,得到新字段“距离(公里)”的信息写入df
df["距离(公里)"] = df["经纬度"].apply(get_address_distance)
df.head()

 

     最后,将df的数据按距离从小到大排序即可确定离我最近的医院及其相关信息。

df.sort_values(by = ["距离(公里)"]).head()

    由上可知,离我最近的驾驶人体检医院是“北下关社区卫生服务中心”,仅有1.04公里。当然“北京市海淀区北太平庄社区卫生服务中心”同样离我很近,仅有1.07公里

    对此我只能说:😋硬币一抛,答案知晓! 


Part2:数据可视化与分析

    作为一名“老司机”,我自然不愿意盯着表格里的这些数据,(有没有一种可能😏)因为上了年纪,我看这些数字很费劲,一不小心就看岔了😂。这时就需要借助图像更加直观地反映223家医院分别离我多远了。

    鉴于Pyecharts生成的可视化文件支持交互,鼠标移至每个途经点时,都会出现该医院的名称和相对距离,故而使用Pyecharts库进行数据可视化。

# 导入相关库,首先使用Geo可视化,该可视化方法的特点为“需给定坐标点,可视化结果清晰”
import pyecharts
from pyecharts import options as opts
from pyecharts.charts import Geo
from pyecharts.globals import ThemeType

# 获取坐标点数据,用于后续的Geo可视化
data_pair = [(df.iloc[i]["医院名称"], df.iloc[i]["距离(公里)"])
             for i in range(len(df))]
# 初始化配置Geo
geo = Geo(init_opts=opts.InitOpts(theme=ThemeType.DARK))
geo.add_schema(maptype="北京")  # 选定“北京”的地图

# 获取json格式的坐标点数据
for idx, row in df.iterrows():
    geo.add_coordinate(row["医院名称"], row["经纬度"][0], row["经纬度"][1])

# 为Geo图像加入绘图元素
geo.add('', data_pair, symbol_size=6,
        itemstyle_opts=opts.ItemStyleOpts(color="blue"))  # 字体设置
geo.set_series_opts(label_opts=opts.LabelOpts(is_show=False), type="effectScatter")  # 标签设置
geo.set_global_opts(visualmap_opts=opts.VisualMapOpts(),
                    title_opts=opts.TitleOpts(title="北京驾驶人体检医院信息"))  # 标题设置
geo.set_global_opts(
    visualmap_opts=opts.VisualMapOpts(max_=50,
                                      is_piecewise=True,
                                      pieces=[
                                          {"max": 200, "min": 31,
                                           "label": ">31", "color": "#54D7BA"},
                                          {"max": 30, "min": 11,
                                                      "label": "11-30", "color": "#BAAB4C"},
                                          {"max": 10, "min": 6, "label": "6-10",
                                                      "color": "#FF8605"},
                                          {"max": 5, "min": 0, "label": "0-5",
                                           "color": "#FF4E38"}
                                      ]
                                      ))  # 视觉映射按键设置

geo.render_notebook()  # 在JupyterNotebook中动态渲染图像,由于后续将采用BMap可视化,故而不render至.html文件中

    如果您使用的环境不是JupyterNotebook,那么可以将geo.render_notebook()替换为geo.render("你的文件名.html"),这样就可以通过浏览器访问一个网页图像了。

    在JupyterNotebook中的运行结果如下图:

    当然,如果觉得这种地图样式不够美观或上面的信息不够全面,那么可以调用百度地图API,使用Pyecharts下的BMap模块也是可以的。代码示例如下:

# 使用百度地图BMap库进行可视化,该可视化方法的特点为“地图信息全面,可交互性更佳”
from pyecharts.charts import BMap

data_pair = [(df.iloc[i]["医院名称"], df.iloc[i]["距离(公里)"])
             for i in range(len(df))]

# 初始化配置BMap图像
bmap = BMap(init_opts=opts.InitOpts(width="1400px", height="800px", page_title="2021年北京市驾驶员可体检医院分布图",))
bmap.add_schema(
        baidu_ak="npEaMRN8IPbvsWzjwaECRu2D0owHnex5",  # 自己的AK,注意类型为“浏览器”而非“服务端”
        center=get_ll("北京邮电大学"),
        zoom=15,  # 地图缩放大小,数值越小,视野越开阔,但可见坐标点越少
        is_roam=True,  # 开启鼠标缩放和平移漫游
)  # 调用百度地图API

# 添加图像元素
bmap.add(
        '',
        data_pair=data_pair,
        symbol_size=13,
        itemstyle_opts=opts.ItemStyleOpts(color="blue")
)  # 字体设置

# 图像系列设置
bmap.set_series_opts(label_opts=opts.LabelOpts(is_show=False),
                     type='effectScatter'
                    )  # 标签设置
bmap.set_global_opts(
        visualmap_opts=opts.VisualMapOpts(max_=50,
                                          is_piecewise=True,
                                          pos_bottom=60,
                                          pieces=[
                                              {"max": 200, "min": 31,
                                               "label": ">31", "color": "#4169E1"},
                                              {"max": 30, "min": 11,
                                                      "label": "11-30", "color": "#9400D3"},
                                              {"max": 10, "min": 6, "label": "6-10",
                                                      "color": "#FF1493"},
                                              {"max": 5, "min": 0, "label": "0-5",
                                               "color": "#FF4500"}
                                          ]
                                         ),  # 视觉映射按键设置
        title_opts=opts.TitleOpts(title="2021年北京市驾驶员可体检医院分布图",
                                  title_link="http://jtgl.beijing.gov.cn/jgj/qtym/1734494/index.html",pos_left="center",
                                  pos_top=20,
                                  title_textstyle_opts=opts.TextStyleOpts(color="#000000", font_size=30)
                                )  # 标题设置
                    )

# 将图像render至.html文件,在浏览器中查看动态图像
bmap.render("北京驾驶人体检医院分布图.html")

     程序运行后将生成一个HTML文件,通过浏览器访问即可查看动态图像。

     数据可视化的方法有很多,使用Python的第三方库Pyecharts只是其中之一,通常我们也使用Matplotlib和Seaborn等第三方库进行数据可视化。

    除此以外,一些非编程类工具也可以实现数据的可视化,例如Echarts等。


写在最后

    “可视化”作为数据和人之间交互的“窗口”已然成为大数据时代下人人必会的技能之一,相信在不远的将来,中国的“娃娃”们都能熟练地使用计算机进行数据可视化!😊

    行文至此,业近尾声。通过探究这次突发的“奇想”,我又学会了“老司机”的一种“偷懒”方式,下次换证体检就去最近的医院😛。

    虽说笔记不长,但确实助我回顾了Pyecharts地图可视化的语法(有一说一,Pyecharts官网的“使用手册”将语法讲得很全面)。

    当然,“语法”学习只是“可视化”学习的一个环节,最重要的还是要提升自己的“鉴赏”能力,力求可视化结果的“信、达、雅”,用美学思维指导程序设计。

举报

相关推荐

0 条评论