背景

近期一个需求,有一些全球的IP地址以及关联的数据,需要根据这些IP找出对应的城市,之后根据城市信息以及和IP关联的数据生成一个热力地图,可以从地图上直观的看到各个区域的数值。

城市可以通过IP库找到,IP库有很多,可以直接找一个免费的,当然准确性可能有问题,但这个问题不大,大体准确就行。这里的关键是根据城市信息和数据生成热力图。

经过一番查找,打算使用pyecharts这个库来实现。pyecharts默认自带中国地图,但我这里的数据是全球的,且粒度需要细到城市级别,因此pyecharts自带的地图就不合适了。幸好pyecharts支持使用百度地图,这里总结下使用百度地图的一些问题。

基本的使用方法在官方的文档中已经有了,示例如下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
from pyecharts import options as opts
from pyecharts.charts import BMap
from pyecharts.faker import Faker

c = (
    BMap()
    .add_schema(baidu_ak="FAKE_AK", center=[120.13066322374, 30.240018034923])
    .add(
        "bmap",
        [list(z) for z in zip(Faker.provinces, Faker.values())],
        type_="heatmap",
        label_opts=opts.LabelOpts(formatter="{b}"),
    )
    .set_global_opts(
        title_opts=opts.TitleOpts(title="BMap-热力图"), visualmap_opts=opts.VisualMapOpts()
    )
    .render("bmap_heatmap.html")
)

其中[list(z) for z in zip(Faker.provinces, Faker.values())]这一部分生成的数据格式是这样的。

1
2
3
4
5
6
7
[['广东', 127],
 ['北京', 43],
 ['上海', 80],
 ['江西', 55],
 ['湖南', 57],
 ['浙江', 81],
 ['江苏', 110]]

所以我们的数据也按照这种格式填写就行了,但是在按照这个格式填了后发现pyecharts库报错,报错的内容大体是当前地点: ('xxx', 120) 坐标不存在, 错误原因: cannot unpack non-iterable NoneType object,查找文章使用pyecharts的百度地图BMap时无法显示自定义站点的解决办法得知是在因为这些城市的坐标不在pyecharts库的配置中,实际上pyecharts库生成地图要的是坐标信息,而不是城市名。所有的这些坐标信息都存在配置city_coordinates.json中。

所以现在的问题就变为了找到IP对应城市的坐标信息,并添加到city_coordinates.json配置中。IP库好找,但全球主要城市的坐标信息就不太好找了,找了一圈,终于在World Cities Database这里找到了一个免费的城市坐标库。

通过坐标库将城市名与坐标信息关联起来后,将坐标信息添加到配置city_coordinates.json中,之后再运行程序,就可以生成热力地图了。

但发现生成的热力图非常不对,城市的位置都错了,一直以为是自己的用法问题,后来仔细想了下,自己在将坐标库中的坐标添加到配置里时,没有注意经纬度的顺序问题,将顺序重新调整后,再生成热力图就没有问题了。

最终生成的热力地图如下,还是非常直观的。

参考文章

  1. Bmap - Bmap_heatmap
  2. BMap:百度地图
  3. Python地理数据可视化工具pyecharts
  4. 使用pyecharts的百度地图BMap时无法显示自定义站点的解决办法
  5. World Cities Database