爬取中国行政区划边界数据(GeoJSON格式)
前言
行政区划相关信息在GIS中算是不可或缺的基础数据,然而由于行政区划经常会进行调整,所以许多网上搜集的已经存在了不同程度的过时。而如果用国外的数据,又会有领土的问题,让人很膈应。偶然一次,在GitHub上看到了分享来自阿里云的数据,那么如果能够自动从网上抓取的话,岂不是可以获得较为准确的资料,并可以及时更新?
准备
数据的来源就是阿里云的“DATAV.GeoAtlas”(其实我并不知道这是个什么东西),具体如下图所示。根据其提供的json API可以发现,其返回值为GeoJSON,那么我们可以考虑用python的geopandas
库直接利用url进行读取并保存到本地,跳过requests的请求环节。
1 | import requests |
网站上提供的数据分为两种:不包含子区域的(称之为“simple”)和包含子区域的(称之为“full”),依据行政区划的精细程度分为国家、省、地级市和县区三级,所以先准备后存储数据的文件路径:
1 | type_list = ['simple', 'full'] |
递归获取GeoJSON
本来我是可以从网上找一张行政区划的编码表,然后遍历访问就可以。但是,由于我们的行政区划数据存在很多版本,而我并不知道这个网站上提供的是哪个版本,所以可能会存在不匹配的问题。在“full”版本的json中,包含了子区域的信息,假设这个网站上的数据是“自洽”的,那么我们可以从“中华人民共和国”出发,递归地找下去。
1 | def recur_fetch(adcode, threads): |
递归多线程
每次提到递归,都会感觉头大。在这个问题里,其实递归并不是理想的方案,但是由于不同递归深度之间是独立的,所以可以通过多线程的方式,并发从网站上获取数据。对比发现,多线程的方式比单线程的效率大约提高在5倍以上,效果还是比较明显的。
但是,我这个写法有一个问题就是:不知道什么时候所有数据下载完毕,也就是递归产生的线程什么时候运行结束。一开始,我考虑轮询threading.activeCount()
,当数字不再变化就认为已经结束,但是这个的可靠性存在问题;后来从网上找到了用join*()
阻塞主线程的方法,由于我们用的是递归的,所以通过传递参数的方式维护线程的列表。
1 | adcode = '100000' |
Elapsed time:301.6022205352783
最后上一张带九段线的中国地图:
1 | import matplotlib as mpl |
残留问题
这个小程序看上去非常简单,但是实际上还存在两个问题,只不过运行中抛出的异常并没有处理:
- 数据集并不是“自洽”的,具体而言,在“simple”中的区划信息与“full”中的信息并不一致,例如在天津的“full json”中,有16年撤县设区的“蓟州区”,但是却没有响应的“simple json”,而是原先的“蓟县”,如此的情况也出现在西安的户县等区域,对于这个问题,要解决的话,可以找出来不一致的,然后一个一个修正;
- 有些"simple json"有对应的"full json",但是“full json”其实是空的,会造成写文件会报错,最简单的方法就是忽略。
考虑到这两个问题的存在,我建议用“国家、省、市”三级的“full json”数据。
小结
用一个很简单的程序,我们可以不定期从网上更新中国行政区划的GeoJSON数据,为了进一步提高数据的使用效率,接下来考虑用数据库如PostgreSQL进行管理。