递归四叉树获取地图上所有的poi

  地图数据的来源,不同分类的数据,其来源,采集方法都是有大不同的。地图的数据分类,必须先理解一个概念,就是地图图层的概念:
递归四叉树获取地图上所有的poi_第1张图片
地图图层

  如上图,电子地图对我们实际空间的表达,事实上是通过不同的图层去描述,然后通过图层叠加显示来进行表达的过程。对于我们地图应用目标的不同,叠加的图层也是不同的,用以展示我们针对目标所需要信息内容。其次呢,我引入一下矢量模型和栅格模型的概念,GIS(电子地图)采用两种不同的数学模型来对现实世界进行模拟:
  矢量模型:同多X,Y(或者X,Y,Z)坐标,把自然界的地物通过点,线,面的方式进行表达


递归四叉树获取地图上所有的poi_第2张图片
矢量模型

  栅格模型(瓦片模型):用方格来模拟实体


递归四叉树获取地图上所有的poi_第3张图片
栅格模型

  我们目前在互联网公开服务中,或者绝大多数手机APP里看到的,都是基于栅格(瓦片)模型的地图服务,比如大家看到的百度地图或者谷歌地图,其实对于某一块地方的描述,都是通过10多层乃是20多层不同分辨率的图片所组成,当用户进行缩放时,根据缩放的级数,选择不同分辨率的瓦片图拼接成一幅完整的地图(由于一般公开服务,瓦片图都是从服务器上下载的,当网速慢的时候,用户其实能够亲眼看到这种不同分辨率图片的切换和拼接的过程)

  对于矢量模型的电子地图来说,由于所有的数据以矢量的方式存放管理,事实上图层是一个比较淡薄的概念,因为任何地图元素和数据都可以根据需要自由分类组成,或者划分成不同的图层。各种图层之间关系可以很复杂,例如可以将所有的道路数据做成一个图层,也可以将主干道做成一个图层,支路做成另外一个图层。图层中数据归类和组合比较自由。

  而对于栅格模型(瓦片图)来看,图层的概念就很重要的,由于图层是生成制作出来,每个图层内包含的元素相对是固化的,因此要引入一个底图的概念。也就是说,这是一个包含了最基本,最常用的地图数据元素的图层,例如:道路,河流,桥梁,绿地,甚至有些底图会包含建筑物或者其他地物的轮廓。在底图的基础上,可以叠加各种我们需要的图层,以满足应用的需要,例如:道路堵车状况的图层,卫星图,POI图层等等。

  POI数据:严格来说属于矢量数据,不过是最简单的矢量数据,换句话来说就是坐标点标注数据。也是电子地图上最常用的数据图层。

需求:获取全国所有汽车维修POI

  有个需求是获取全国所有的汽车维修POI,所以想通过矩形区域的地图poi接口获取汽车维修POI,但是每个矩形区域最多只能获得几百个poi,想利用这个接口来搞定全国汽车维修POI,需要先把全国分为若干矩形,保证每个矩形之内高德地图POI数量不超过地图返回POI上限个数(由于地图返回的上限不确定,所以保守取上限500)。

解决思路:

  将全国划分成x * x个栅格(x经纬度步长),每个栅格作为根节点获取POI,当POI不超过POI返回上限分页保存POI,超过500时,分成4个相等小栅格,这样递归下去创建出4叉树,直到把该栅格内的POI都找到,切换下一个栅格继续递归四叉树……

递归四叉树获取地图上所有的poi_第4张图片
栅格图
算法实现
# Author:Sunshine丶天
import requests, random, traceback, math
from poi_get import settings
import numpy as np
import time, traceback

DECIMAL_POINT= 8 # 保留小数点后几位
LNG_START = 73   # 国内经度最小值
LNG_END = 136    # 国内经度最大值
LAT_START = 18   # 国内纬度最小值
LAT_END = 54     # 国内纬度最大值
REC_STEP = 1     # 步长

class GET_POI(object):
    def __init__(self,type, offset):
        self.offset = offset
        self.pois_all = []
        self.type = type # gd: 高德地图  bd: 百度地图

    def run(self, lng, lat, area_count):

        self.getPolygonPoi(lng, lat, REC_STEP, area_count)

        if len(self.pois_all):
            out_path  = r'/Users/Sunshine/Desktop/gd_poi_lng%s-lat%s.txt'%(int(lng),int(lat)) if self.type == 'gd' else r'/Users/Sunshine/Desktop/bd_poi_%s-%s.txt'%(int(lng),int(lat))
            f_in = open(out_path, 'a+', encoding='utf-8')
            sets = set()
            for poi in self.pois_all:
                sets.add('%s\n' % poi)
            f_in.writelines(sets)
            f_in.close()

    def get_pois_gd(self, lng, lat, step, page):
        if self.type == 'gd':
            # 获取poi信息
            base_url = 'https://restapi.amap.com/v3/place/polygon'
            # 经纬度左上右下
            parameters = {'polygon': '%s,%s|%s,%s' % (
            round(lng + step / 2, DECIMAL_POINT), round((lat - step / 2), DECIMAL_POINT), lng, lat),
                          'key': random.choice(settings.MAPKEY_GD),
                          'types': settings.BIG_POI_CODE_GD,
                          'extensions': 'base',
                          'offset': self.offset,
                          'page': page}

            try:
                response = requests.get(base_url, parameters, timeout=5)
                contentDic = eval(response.content.decode().strip(''))
                count_poi = int(contentDic['count'])  # 该区域内的所有poi总数
                pois = contentDic['pois']  # poi数据数组
                return count_poi, pois
            except:
                traceback.print_exc()
        elif self.type == 'bd':
            # 获取poi信息
            base_url = 'http://api.map.baidu.com/place/v2/search?'

            # 经纬度左下右上
            parameters = {'query': settings.KEY_QUERY_BD,
                          'tag': settings.KEY_WORD_BD,
                          'bounds': '%s,%s,%s,%s' % (
                              lat, round((lng - step / 2), DECIMAL_POINT), round(lat + step / 2, DECIMAL_POINT), lng),
                          'output': 'json',
                          'coord_type': '2',
                          'page_size': 20,
                          'page_num': page,
                          'ak': random.choice(settings.MAPKEY_BD)}
            try:
                response = requests.get(base_url, parameters, timeout=5)
                contentDic = eval(response.content.decode().strip(''))
                print(contentDic)
                count_poi = contentDic['total']
                pois = contentDic['results']

                return count_poi, pois
            except:
                traceback.print_exc()

    def getPolygonPoi(self, lng, lat, step, area_count):
        # 从第一页开始获取
        count_poi, pois = self.get_pois_gd(lng, lat, 2 * step, 0)
        print('第%s个栅格开始获取poi数据,--->步长:%s---%s' % (area_count, step, count_poi))
        if len(pois):  # 有poi数据
            if count_poi >= 500:  # 该区域内poi数量超过500个 变换四叉树递归分裂下去
                # 左上
                lat_l_t = lat
                lng_l_t = lng
                self.getPolygonPoi(lng_l_t, lat_l_t, step / 2, area_count)
                # 右上
                lat_r_t = lat
                lng_r_t = lng + step / 2
                self.getPolygonPoi(lng_r_t, lat_r_t, step / 2, area_count)
                # 左下
                lat_l_d = lat - step / 2
                lng_l_d = lng
                self.getPolygonPoi(lng_l_d, lat_l_d, step / 2, area_count)
                # 右下
                lat_r_d = lat - step / 2
                lng_r_d = lng - step / 2
                self.getPolygonPoi(lng_r_d, lat_r_d, step / 2, area_count)
            else:
                self.pois_all += pois
                page_num = int(math.ceil(count_poi / self.offset))
                for index, page in enumerate(range(2, page_num + 1)):  # 获取每页中的poi数据
                    count_poi, pois = self.get_pois_gd(lng, lat, step * 2, page)
                    print(lng, lat, step, page,len(pois))
                    self.pois_all += pois
                return self.pois_all
        else:
            return self.pois_all

def get_pois(lng, lat, step, page):
    # 获取poi信息
    base_url = 'http://api.map.baidu.com/place/v2/search?'

    # 经纬度左下右上
    parameters = {'query': settings.KEY_QUERY_BD,
                  'tag': settings.KEY_WORD_BD,
                  'bounds': '%s,%s,%s,%s' % (
                  lat, round((lng - step / 2), DECIMAL_POINT), round(lat + step / 2, DECIMAL_POINT), lng),
                  'output': 'json',
                  'coord_type': '2',
                  'page_size': 20,
                  'page_num': page,
                  'ak': random.choice(settings.MAPKEY_BD)}

    try:
        response = requests.get(base_url, parameters, timeout=5)
        contentDic = eval(response.content.decode().strip(''))
        count_poi = contentDic['total']
        pois = contentDic['results']

        return count_poi, pois
    except:
        traceback.print_exc()


if __name__ == '__main__':
    count = 0

    def get_polygon(lng_l, lat_l):
        return round(lng_l, DECIMAL_POINT), round(lat_l, DECIMAL_POINT)

    lng_list = np.arange(LNG_START, LNG_END, REC_STEP)  # 生成lng范围
    lat_list = np.arange(LAT_START, LAT_END, REC_STEP)  # 生成lat范围
    rec_list = list()  # 全国矩形区域划分
    for lng in lng_list:
        for lat in lat_list:
            count += 1   # 第几个大栅格
            point =  get_polygon(lng, lat)
            # 高德每页最多 25条数据  百度每页 最多20条数据
            get_poi = GET_POI('gd', 25)
            get_poi.run(point[0], point[1], count)

补充:

多边形搜索api详情请参考百度开发支持api、高德开发支持api
参考文章:
https://www.cnblogs.com/sparrowjack/p/5151477.html
https://www.zhihu.com/question/21530085/answer/18728706

你可能感兴趣的:(递归四叉树获取地图上所有的poi)