高德poi获取之矩形搜索法(冲出900条限制)

文章目录

  • 前言
  • 查看API接口
  • 基本思想
  • 实现过程
  • 构建一次举手函数
  • 构建多次举手函数
  • 构建保存函数
  • 区域切分代码
  • 代码详解
  • 经纬度小数点问题
  • 列表的知识
  • 完整代码:
  • 运行截图
  • 功能更新
  • 前言

    据说高德POI的尽头是多边形搜索法,今天下午也是简单试了一下。

    高德poi的获取有四种基本方法,其中关键词搜索法已经在博客Python获取高德POI(关键词搜索法),许多基本的知识也在上文中详细赘述了,如果是没有基础的朋友建议先看完关键词搜索法,这有助于您看懂这篇文章。


    查看API接口

    高德地图的开放平台提供了四种poi获取的接口:

    1. 关键字搜索: 通过用POI的关键字进行条件搜索,例如:肯德基、朝阳公园等;同时支持设置POI类型搜索,例如:银行
    2. 周边搜索: 在用户传入经纬度坐标点附近,在设定的范围内,按照关键字或POI类型搜索;
    3. 多边形搜索: 在多边形区域内进行搜索
    4. ID查询: 通过POI ID,查询某个POI详情,建议可同输入提示API配合使用

    那么我们这次要使用的是多边形搜索法,而且用的是矩形来获取poi。通过查看多边形搜索法的使用说明,我们可以知道,如果要使用这个多边形搜索法,我们必须提注意以下几个地方:

    1. 提供多边形的坐标对。形如:112.953161,23.935966为一个坐标对,由经纬度构成
    2. 坐标对之间用“|”进行分割,坐标对按照经度在前、纬度在后的方式提供,且经纬度之间用逗号隔开。
    3. 当区域为矩形的时候,只需要提供左上右下两个顶点坐标。
    


    基本思想

    那么了解完这个API的使用,我们就可以开始动手构思如何对区域进行切分。
    在搜索完网上的一些多边形搜索法之后,不外乎以下两种:

    1. 通过用户提供的长度将多边形进行格网化,然后遍历每个格子内的poi。
    2. 通过四叉树的方法将区域不断切分成四个子矩形,当矩形内poi数量小于900的时候直接获取poi;当矩形内poi数量大于900的时候对该矩形继续切分,直至每个子矩形内的poi数量都小于900.

    优缺点:
    第一种格网化的方法是相对简单,只需要将区域的长和宽进行n等分,通过遍历来实现获取poi,但缺点也很明显,长度的大小由用户自己定义,当长度太小的时候,需要遍历的格网数量几何级增加,长度太大的时候不难保证poi的完整性。
    第二种方法是运用递归的方法,矩形切分的大小由区域内poi的数量来控制,既能保证poi的完整,又免去设置区域大小的麻烦。缺点在于由于是矩形搜索,因此获取的poi难免会有超过目标区域的地方,需要后期用GIS的手段进行裁剪

    实现过程

    引入我们需要的库

    #库的说明
    1.requests --爬虫常用库
    2.json  --该库能够帮我们处理高德返回的JSON格式的数据
    3.Coordin_transformlat -- 这个是自己写的一个坐标转换的库,其目的是为了解决开头说的坐标偏移的问题
    4.csv --这个是将poi点最终保存为csv文件所使用的库
    
    #引入所需的库
    import requests
    import json
    import csv
    from Coordin_transformlat import gcj02towgs84
    

    构建一次举手函数

    通过刚刚查看的多边形的API可以知道,如果我们要请求数据,那么需要填写的关键参数有以下几个:

    key:高德地图的密钥
    polygon:经纬度坐标对
    keywords或types:关键词或者poi分类
    page:表示当前页数,用于控制举手的次数
    

    所以我们一共有四个参数需要设置。
    Tips: 我们还是可以运用关键词搜索法里的思想,将这个过程理解成举手向老师索取数据,举一次手老师给你20条数据,跟关键词搜索法不同的地方在于,这个举手函数里用的API是多边形搜索法。

    def Get_poi_polygon(key,polygon,keywords,page):
        '''
        这是一个能够从高德地图获取poi数据的函数
        key:为用户申请的高德密钥
        polygon:目标区域的坐标对,默认是list格式,按照[左上经度,左上纬度,右下经度,右下纬度]的顺序输入
        keywords:POI数据的类型
        page:当前页数
        '''
        #设置header
        header = {'User-Agent': "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50"}
    
        #将输进来的矩形进行格式化
        Polygonstr = str(polygon[0]) + ',' + str(polygon[1]) + '|' + str(polygon[2]) + ',' + str(polygon[3])
    
        #构建url
        url = 'https://restapi.amap.com/v3/place/polygon?polygon={}&key={}&keywords={}&page={}'.format(Polygonstr, key, keywords, page)
        
    
        #用get函数请求数据
        r = requests.get(url, headers=header)
    
        #设置数据的编码为'utf-8'
        r.encoding = 'utf-8'
    
        # 将请求得到的数据按照'utf-8'编码成字符串
        data = r.text
        return data
    
    

    在上面的代码里,唯一需要注意的地方是需要对用户输入进来的坐标对进行格式化,因为用户输入的坐标对通常是浮点型的,但这个坐标对我们需要拼接到url里,因此我们需要将其转换成字符型,因此在url之前需要进行这个格式化。


    构建多次举手函数

    上面的函数是一次举手的函数,那么下面我们需要通过次数来实现爬取一个区域内的所有poi。
    刚刚上文说到可以通过page来控制举手的次数,那么利用高德地图每次返回的页数最多包含20条记录的特点,通过判断当前页数page下的count是否为0来实现控制次数。

    这里面代码的书写与上一篇大有不同,原因在于以下地方:
    需要通过传入的矩形,来判断当前区域内poi的数量是否大于800,如果大于800,需要返回一个值,方便我们继续对区域进行切分。如果小于800,我们就直接将数据写入csv中,因此我也是在这个函数里直接调用了写入数据的函数。

    def Get_times_polygon(key,polygon,keywords):
        '''
        这是一个控制Get_poi_polygon申请次数的函数
        '''
        page = 1
        # 执行以下代码,直到count为0的时候跳出循环
        while True:
            # 调用第一个函数来获取数据
            result = Get_poi_polygon(key,polygon, keywords, page)
            # json.loads可以对获取回来JSON格式的数据进行解码
            content = json.loads(result)
            pois = content['pois']
            count = content['count']
            print(count)
            #如果区域内poi的数量大于800,则认为超过上限,返回False请求对区域进行切割
            if int(count) > 800:
                return False
            else:
                #开始提取poi的信息
                for i in range(len(pois)):
                    name = pois[i]['name']
                    location = pois[i]['location']
                    #通过多次运行,我发现有时候会检索不到poi的地址
                    #当address为空时,代码将会报错,因此这里设置一下地址为空的时候仍能继续爬取
                   if 'address' not in pois[i].keys():
                        address = str(-1)
                    else:
                        address = pois[i]['address']
                    adname = pois[i]['adname']
                    result = gcj02towgs84(location)
                    lng = result[0]
                    lat = result[1]
                    row = [name, address, adname, lng, lat]
                    #调用写入函数来保存数据
                    writecsv(row, keywords)
                if count == '0':
                    break
                    # 递增page
                page = page + 1
    

    构建保存函数

    我是将数据保存成了csv文件,由于在上述的函数里提取过poi的信息了,所以就无需在保存函数里再进行过滤。直接将poi的信息进行写入即可。当然,注意一下写入的模式是追加,否则每次循环都会覆盖已有的数据。

    def writecsv(poilist,keywords):
        """
        这是写入成csv文件的函数
        :param poilist:
        :param keywords:
        :return: 输出的结果存放在代码文件夹下
        """
        with open('{}.csv'.format(keywords),'a',newline='',encoding='utf-8') as csvfile:
            writer = csv.writer(csvfile)
            writer.writerow(poilist)
    

    区域切分代码

    区域的切分主要用到的是四叉树的方法,当然也是借鉴了几篇博客。有一幅图可以直观明了地表示出四叉树的思想。

    其次呢,我们在代码里运用的是经度跟纬度,如果不熟悉地理的朋友估计容易混乱,因此我画了一幅图:

    那么有这个图的思想之后,我们就用经纬度来表示出每个子矩形。
    我们输入的坐标对是左上右下的坐标对
    因此左上角的矩形可以用
    (最小经度,最大纬度)和(最大经度+最小经度)/2,(最大纬度+最小纬度)/2来表示
    其余三个子矩形均可以通过最大最小的经纬度来表示

    简单理解了过程之后,就可以准备敲代码了。

    def Quadrangle(key,polygon,keywords):
        """
        :param key:高德地图密钥
        :param polygon: 矩形左上跟右下坐标的列表
        :param keywords: poi关键词
        :return:
        """
        #准备一个空列表,存放切割后的子区域
        PolygonList = []
        for i in range(len(polygon)):
            currentMinlat = round(polygon[i][0],6)#当前区域的最小经度
            currentMaxlat = round(polygon[i][2],6)#当前区域的最大经度
            currentMaxlon = round(polygon[i][1],6)#当前区域的最大纬度
            currentMinlon = round(polygon[i][3],6)#当前区域的最小纬度
    
            cerrnt_list = [currentMinlat, currentMaxlon, currentMaxlat, currentMinlon]
            #将多边形输入获取函数中,判断区域内poi的数量
            status = gp.Get_times_polygon(key,cerrnt_list,keywords)
            #如果数量大于800,那么返回False,对区域进行切分,否则返回区域的坐标对
            if status != False:
                print('该区域poi数量小于800,正在写入数据')
            else:
                #左上矩形
                PolygonList.append([
                    currentMinlat, #左经
                    currentMaxlon, #上纬
                    (currentMaxlat+currentMinlat)/2, #右经
                    (currentMaxlon+currentMinlon)/2]) #下纬
                #右上矩形
                PolygonList.append([
                    (currentMaxlat+currentMinlat)/2,#左经
                    currentMaxlon, #上纬
                    currentMaxlat, #右经
                    (currentMaxlon+currentMinlon)/2#下纬
                ])
                #左下矩形
                PolygonList.append([
                    currentMinlat,#左经
                    (currentMaxlon+currentMinlon)/2,#上纬
                    (currentMaxlat+currentMinlat)/2,#右经
                    currentMinlon#下纬
                ])
                #右下矩形
                PolygonList.append([
                    (currentMaxlat+currentMinlat)/2,#左经
                    (currentMaxlon+currentMinlon)/2,#上纬
                    currentMaxlat,#右经
                    currentMinlon#下纬
                ])
                #当带切分的区域数量为0时,返回上一层矩形
                if len(PolygonList) == 0:
                    break
                else:
                #继续切分区域
                    Quadrangle(key,PolygonList,keywords)
    

    代码详解

    四叉树的代码并不算复杂,其实就是用一个列表来放置切分的子矩形,然后遍历这些子矩形,遍历的时候继续做判断,需要的时候继续切分,加入现有的列表中。

    经纬度小数点问题

    其中round()函数是用来保留小数位点的函数,因为在高德地图API的说明里提到了经纬度小数点不能超过6位,因此这里需要注意一下。

    列表的知识

    还有另外一个知识点,代码里 PolygonList = []是在定义一个空的列表,列表就像一格格排列整齐的车厢,向列表里面添加东西需要用append()函数来添加,所以如果你感到好奇的话,可以理解为向PolygonList这个列表里添加四个子矩形

    出于时间原因,还有一些基础的知识点没来得及详细解释,深感歉意。

    到这里我的代码也就大致写完了,当然一部分是借鉴前人的经验,另一部分是自己改良的。在我多次的尝试过程中,我发现有时候爬取的过程中会出现 “假死” 现象,估计是我没有限制爬取速度,所以才会出现这种卡住不继续运行也不报错的现象,但这个问题不大。可以通过加入time库设置每次爬取的休眠时长来提高稳定性。

    完整代码:

    完整的代码我分成了三个文件,下面是这三个文件的描述

    1. Coordin_transformlat.py -- 坐标转换文件,用于对poi进行坐标系纠偏
    2. GetPoi_keywords.py --  对高德地图发起数据请求并写入csv文件
    3. RectanSearch.py --  四叉树划分法
    

    注意的地方:
    在 RectanSearch.py里面需要调用GetPoi_keywords.py里的方法进行判断
    在GetPoi_keywords.py里面需要调用Coordin_transformlat.py进行坐标系变换

    所以三个文件缺一不可!!!

    import GetPoi_keywords as gp
    
    def Quadrangle(key,polygon,keywords):
        """
        :param key:高德地图密钥
        :param polygon: 矩形左上跟右下坐标的列表
        :param keywords: poi关键词
        :return:
        """
        #准备一个空列表,存放切割后的子区域
        PolygonList = []
        for i in range(len(polygon)):
            currentMinlat = round(polygon[i][0],6)#当前区域的最小经度
            currentMaxlat = round(polygon[i][2],6)#当前区域的最大经度
            currentMaxlon = round(polygon[i][1],6)#当前区域的最大纬度
            currentMinlon = round(polygon[i][3],6)#当前区域的最小纬度
    
            cerrnt_list = [currentMinlat, currentMaxlon, currentMaxlat, currentMinlon]
            #将多边形输入获取函数中,判断区域内poi的数量
            status = gp.Get_times_polygon(key,cerrnt_list,keywords)
            #如果数量大于800,那么返回False,对区域进行切分,否则返回区域的坐标对
            if status != False:
                print('该区域poi数量小于800,正在写入数据')
            else:
                #左上矩形
                PolygonList.append([
                    currentMinlat, #左经
                    currentMaxlon, #上纬
                    (currentMaxlat+currentMinlat)/2, #右经
                    (currentMaxlon+currentMinlon)/2]) #下纬
                #右上矩形
                PolygonList.append([
                    (currentMaxlat+currentMinlat)/2,#左经
                    currentMaxlon, #上纬
                    currentMaxlat, #右经
                    (currentMaxlon+currentMinlon)/2#下纬
                ])
                #左下矩形
                PolygonList.append([
                    currentMinlat,#左经
                    (currentMaxlon+currentMinlon)/2,#上纬
                    (currentMaxlat+currentMinlat)/2,#右经
                    currentMinlon#下纬
                ])
                #右下矩形
                PolygonList.append([
                    (currentMaxlat+currentMinlat)/2,#左经
                    (currentMaxlon+currentMinlon)/2,#上纬
                    currentMaxlat,#右经
                    currentMinlon#下纬
                ])
                print(len(PolygonList))
                if len(PolygonList) == 0:
                    break
                else:
                    Quadrangle(key,PolygonList,keywords)
    
    
    #这里修改为自己的高德密钥
    key ='***************'
    
    #这里修改自己的poi类型
    keywords = '公园'
    
    #这里输入想要查询的城市
    city = '广州'
    
    #调用高德查询行政区的API接口来返回矩形坐标对
    Retance = gp.get_city_scope(key,city)
    
    #存储区域矩形的列表
    input_polygon = []
    input_polygon.append(Retance)
    
    Quadrangle(key,input_polygon,keywords)
    

    运行截图


    如果想传入poi的类型(types)来检索,可以将url里的keywords={}替换成types = {},其他地方均不用再做修改!!!

    成功突破了900条的限制!大功告成!!!

    功能更新

    由于很多朋友不知道区域最小外接矩形的坐标对如何获取,因为我在源码中添加了自动获取目标城市外接矩形的坐标对的方法,该方法调用了高德地图查询行政区划信息接口,只需要我们输入城市的名称或者编号即可。(注意这个区域一定是区级及以上单位的,乡镇级别是返回不了边界的!!!)
    点此查看高德城市编码表

    PS:以前是编程小白,做个科研连数据都拿不到,到处求大佬帮忙爬的时候真的是太难过了,都不知道是在爬数据还是爬着求大佬,所以暗暗下定决心自己要自力更生。在百度上能够得到的资源固然不少,但灌水重复的文章也不在少数,所以特别理解那些正在淋雨的人,因为我也是一步步摸索着过来的。能做的不多,能够照亮一点黑暗即知足。
    完整代码链接:
    链接: 完整源码
    提取码:455x
    源代码于2020/4/17 23:31已更新最新版

    物联沃分享整理
    物联沃-IOTWORD物联网 » 高德poi获取之矩形搜索法(冲出900条限制)

    发表评论