Python应用指南:利用高德地图API获取POI数据

news2024/9/23 22:01:08

随着地理信息系统(GIS)技术的发展,地理位置数据在城市规划、商业分析、旅游推荐等多个领域发挥着越来越重要的作用。POI(Point of Interest,兴趣点)数据作为地理信息的重要组成部分,提供了丰富的地点信息,如餐厅、酒店、景点等,对于研究和应用具有重要意义,本篇文章旨在通过Python编程语言,结合高德地图API,实现对指定区域内的POI数据的高效获取和处理。

参考文章:Python使用高德API批量下载POI数据 (qq.com)

先讲一下方法思路,一共四个步骤;

方法思路

  1. 通过高德拾取坐标,生成矩形,打印出来坐标
  2. 获取POI数据——通过调用高德地图API
  3. GeoJSON转成csv
  4. 坐标转换——高德坐标系(GCJ-02) to WGS84

本篇文章就以上海比较著名的上海城隍庙广场为例,讲一讲,为什么选择一个比较小的范围内,还是因为高德的POI API 获取限额的原因,高德把这部分业务高度商业化了,所以给到个人开发者的额度就非常少,当然如果有企业开发者账号,或者通过购买额度的方式,就没什么限制~

第一步,通过高德拾取坐标:坐标拾取器 | 高德地图API (amap.com),先拾取要获取POI范围的坐标中心点,生成边长为500m的矩形,这里需要更改范围的话,自行调整即可,把这个side_length_meters = 500,调整成需要的范围即可,因为这里的地理坐标使用的是高德坐标系(GCJ-02),所以生成的范围也是高德坐标系下的结果,打印出来坐标;

完整代码#运行环境Python 3.11

import math


def get_rectangle_corners_gps(center_lng, center_lat, side_length_meters=500):
    # 地球平均半径,单位为米
    earth_radius = 6371000
    # 每度纬度对应的米数
    meters_per_degree_lat = math.pi * earth_radius / 180
    # 每度经度对应的米数(在给定纬度处)
    meters_per_degree_lng = meters_per_degree_lat * math.cos(math.radians(center_lat))

    # 计算纬度和经度的变化量
    delta_lat = side_length_meters / meters_per_degree_lat / 2
    delta_lng = side_length_meters / meters_per_degree_lng / 2

    # 计算四个角的坐标
    top_left = (center_lng - delta_lng, center_lat + delta_lat)
    top_right = (center_lng + delta_lng, center_lat + delta_lat)
    bottom_left = (center_lng - delta_lng, center_lat - delta_lat)
    bottom_right = (center_lng + delta_lng, center_lat - delta_lat)

    return [top_left, top_right, bottom_left, bottom_right]


# 使用示例
center_lng, center_lat = 121.508092, 31.098532
side_length_meters = 500
# 包含四个角的经纬度坐标的列表,顺序为左上角、右上角、左下角、右下角
corners = get_rectangle_corners_gps(center_lng, center_lat, side_length_meters)
print("矩形四个角的坐标为:", corners)

然后把得到的坐标放到下面的代码部分进行替换,另外,这里的key需要填一下,有多个的话用逗号隔开,关键词也需要选择一下,KEYWORD = "餐厅",这里生成网格的大小也可以根据实际进行调整,关键词可以改成"酒店"、"公司"、"公园"等等,可以参考高德地图 API POI 分类对照表:poi-type-list.pdf (xdc.at);

KEYS = ["你的key"]  # 改成自己的key
SAVE_DIR = r"D:\data\gaode"  # 数据保存目录
GRID_SIZE = 0.001  # 网格大小为0.001度(大约为100m)
KEYWORD = 餐厅"  # 查询关键字,可以修改为其他关键词
polygon_coords = [
    [121.49064782905262, 31.227263304014794],
    [121.49590617094739, 31.227263304014794],
    [121.49064782905262, 31.222766695985204],
    [121.49590617094739, 31.222766695985204]
]  # 查询的多边形区域坐标,顺序为左上角、右上角、左下角、右下角

完整代码#运行环境Python 3.11 

import requests
import json
import os
import math
from concurrent.futures import ThreadPoolExecutor

# 全局变量
KEYS = ["你的key"]  # 高德地图API的Key列表,可以添加多个以应对请求限制
SAVE_DIR = r"D:\data\gaode"  # 数据保存目录
GRID_SIZE = 0.001  # 网格大小为0.001度(大约为100m)
KEYWORD = "餐厅"  # 查询关键字,可以修改为其他关键词
polygon_coords = [
    [121.49064782905262, 31.227263304014794],
    [121.49590617094739, 31.227263304014794],
    [121.49064782905262, 31.222766695985204],
    [121.49590617094739, 31.222766695985204]
]  # 查询的多边形区域坐标
NUM_THREADS = 4  # 线程数量,可以根据需求调整

def file_exists(polygon, keyword, page):
    """检查文件是否已经存在"""
    lng_min, lat_min = polygon[0][0], polygon[2][1]
    filename = f"{SAVE_DIR}/poi_{lng_min}_{lat_min}_{keyword}_page{page}.geojson"
    return os.path.exists(filename)

def save_poi_data(polygon, keyword, page, data):
    """保存POI数据到GeoJSON文件"""
    if not os.path.exists(SAVE_DIR):
        os.makedirs(SAVE_DIR)  # 如果目录不存在则创建
    lng_min, lat_min = polygon[0][0], polygon[2][1]
    filename = f"{SAVE_DIR}/poi_{lng_min}_{lat_min}_{keyword}_page{page}.geojson"
    with open(filename, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=4)  # 保存数据为GeoJSON格式
    return True

def fetch_poi_data(polygon, key, keyword):
    """获取POI数据,支持分页"""
    lng_min, lat_min = polygon[0][0], polygon[2][1]
    page = 1
    while True:
        if file_exists(polygon, keyword, page):
            print(f"文件已存在,跳过:网格: {polygon},页数: {page}")
            page += 1
            continue

        polygon_str = f"{lng_min},{lat_min}|{polygon[1][0]},{polygon[1][1]}"
        api_url = f"https://restapi.amap.com/v3/place/polygon?polygon={polygon_str}&keywords={keyword}&key={key}&page={page}"

        try:
            response = requests.get(api_url, timeout=10)
            response.raise_for_status()  # 检查HTTP响应状态码
            data = response.json()

            infocode = data.get("infocode")
            if infocode == "10000" and data.get("pois"):
                save_poi_data(polygon, keyword, page, data)
                print(f"下载成功!网格: {polygon_str},页数: {page}")
                page += 1
                if len(data.get("pois")) < 20:  # 当POI数据少于20时,说明已经是最后一页
                    break
            elif infocode in ["10001", "10003", "10004"]:
                print(f"Key 出现问题,infocode: {infocode},切换到下一个Key进行重试...")
                return False  # 返回False以便切换Key进行重试
            else:
                print(f"请求失败或无数据,infocode: {infocode},信息: {data.get('info')}")
                break
        except Exception as e:
            print(f"请求异常,跳过此Key:{str(e)}")
            break
    return True  # 下载成功或完成所有页数时返回True

def generate_grids(polygon_coords):
    """生成网格"""
    min_lng = min([coord[0] for coord in polygon_coords])  # 获取多边形区域的最小经度
    max_lng = max([coord[0] for coord in polygon_coords])  # 获取多边形区域的最大经度
    min_lat = min([coord[1] for coord in polygon_coords])  # 获取多边形区域的最小纬度
    max_lat = max([coord[1] for coord in polygon_coords])  # 获取多边形区域的最大纬度

    grids = []
    lng_steps = math.ceil((max_lng - min_lng) / GRID_SIZE)  # 计算经度方向上的网格数量
    lat_steps = math.ceil((max_lat - min_lat) / GRID_SIZE)  # 计算纬度方向上的网格数量

    for i in range(lng_steps):
        for j in range(lat_steps):
            grid_min_lng = min_lng + i * GRID_SIZE  # 计算当前网格的最小经度
            grid_max_lng = min(grid_min_lng + GRID_SIZE, max_lng)  # 计算当前网格的最大经度
            grid_min_lat = min_lat + j * GRID_SIZE  # 计算当前网格的最小纬度
            grid_max_lat = min(grid_min_lat + GRID_SIZE, max_lat)  # 计算当前网格的最大纬度
            grid_polygon = [
                [grid_min_lng, grid_max_lat],
                [grid_max_lng, grid_max_lat],
                [grid_max_lng, grid_min_lat],
                [grid_min_lng, grid_min_lat]
            ]
            grids.append(grid_polygon)
    return grids

def download_poi_for_grid(polygon, key, keyword):
    """下载单个网格的POI数据"""
    success = fetch_poi_data(polygon, key, keyword)
    if not success:
        return False  # 返回False以便线程外层处理Key切换
    return True

def main():
    grids = generate_grids(polygon_coords)
    with ThreadPoolExecutor(max_workers=NUM_THREADS) as executor:
        for idx, grid in enumerate(grids):
            key_idx = 0
            while key_idx < len(KEYS):
                key = KEYS[key_idx]
                future = executor.submit(download_poi_for_grid, grid, key, KEYWORD)
                if future.result():
                    break  # 如果下载成功或完成,则跳出循环
                key_idx += 1
            if key_idx == len(KEYS):
                print(f"所有Key均不可用,跳过此网格: {grid}")

if __name__ == "__main__":
    main()

运行成功的话会打印出下面的结果;

接下来就是把得到的GeoJSON转成csv,这里会生成多个GeoJSON文件的原因是:多边形搜索接口中每个边界范围能够获取到的POI数量是有限制的,超过该限制的POI数据则不会返回,这里设置的是每个多边形内POI数量不超过20个以防止不返回结果,也就是20条POI数据会生成一个GeoJSON文件,我们需要把多个GeoJSON提取需要的标签,并统一合并成一个csv,我们先打开一个GeoJSON看一下POI标签内容;

标签包括name、type、address、等等这里就列举部分,具体标签解释可以参考官方文档:搜索POI 2.0-高级 API 文档-开发指南-Web服务 API | 高德地图API (amap.com)

接下来,我们获取一些关键的标签,这里仅获取了id、name、location、address、adname、cityname、type这些标签,有需要其他标签的,可以自行添加,获取这些标签的同时把这些GeoJSON合并成一个csv;

完整代码#运行环境Python 3.11 

import json
import csv
from glob import glob


def merge_pois_from_files(file_pattern, output_file):
    all_pois = []

    # 获取所有匹配给定模式的文件路径
    file_paths = sorted(glob(file_pattern))

    for input_file_path in file_paths:
        try:
            # 打开文件并读取内容,指定编码(根据你的文件实际编码,这里使用 utf-8 作为示例)
            with open(input_file_path, 'r', encoding='utf-8') as f:
                # 加载 JSON 数据
                data = json.load(f)

                # 检查是否存在 'pois' 键
                if 'pois' in data:
                    # 遍历 pois 列表
                    for poi in data['pois']:
                        # 提取所需字段
                        id = poi.get('id', '未找到')
                        name = poi.get('name', '未找到')
                        location = poi.get('location', '未找到')
                        address = poi.get('address', '未找到')
                        adname = poi.get('adname', '未找到')
                        cityname = poi.get('cityname', '未找到')
                        type = poi.get('type', '未找到')

                        # 将提取的信息添加到列表中
                        all_pois.append({
                            'id': id,
                            'name': name,
                            'location': location,
                            'address': address,
                            'adname': adname,
                            'cityname': cityname,
                            'type': type
                        })
                else:
                    # 如果文件中不包含 'pois' 键,打印错误信息
                    print(f"{input_file_path} does not contain 'pois' key.")
        except FileNotFoundError:
            # 捕获文件未找到的错误
            print(f"File not found: {input_file_path}")
        except json.JSONDecodeError:
            # 捕获 JSON 解析错误
            print(f"Invalid JSON format in {input_file_path}.")
        except Exception as e:
            # 捕获其他未预料的错误
            print(f"Unexpected error occurred while processing {input_file_path}: {e}")

    # 写入 CSV 文件
    write_to_csv(all_pois, output_file)


def write_to_csv(pois_list, output_file_path):
    # 定义 CSV 文件的字段名
    fieldnames = ['id', 'name', 'location', 'address', 'adname', 'cityname', 'type']

    # 写入 CSV 文件
    with open(output_file_path, 'w', newline='', encoding='utf-8') as csvfile:
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)

        # 写入表头
        writer.writeheader()

        # 写入数据
        for poi in pois_list:
            writer.writerow(poi)


if __name__ == '__main__':
    # 输入文件模式
    input_file_pattern = r'D:\data\gaode\poi_121.49064782905262,31.222766695985204_餐厅*page*.geojson'
    # 输出文件路径
    output_file_path = 'D:\data\gaode\pois.csv'

    # 合并所有文件中的 POIs 并写入 CSV 文件
    merge_pois_from_files(input_file_pattern, output_file_path)

我们把csv的坐标列手动分列一下,并把坐标从高德坐标系(GCJ-02)转到WGS84,批量转换工具:地图坐标系批量转换 - 免费在线工具 (latlongconverter.online);

并把csv导入arcgis/arcgispro进行展示,可以看到周边餐饮POI的分布情况;

先说两个高德对POI获取的限制,多边形搜索接口中每个边界范围能够获取到的POI数量是有限制的,超过该限制的POI数据则不会返回。所以有学者用四叉树索引的概念,将大的多边形不断四分,直到所有小多边形均满足数量限制为止,但是所谓道高一尺魔高一丈,现在对个人开发者的多边形搜索限额也降到了100条/天,意味着你是个人开发者且只使用免费额度的话,你一天可以获取的POI极其有限,而且一次仅可以获取一个类型的POI,并且会出现POI接口返回数据是不完全的情况,这个导致对额度的需求几何倍增加,可以参考这篇文章:2024年5月最新高德poi数据采集科普 (qq.com);

结论:利用Python和高德地图API批量获取POI数据路径上可以实现,现阶段基本上都是利用高德开放的api接口:多边形搜索,把需要获取的多边形切分为无数个满足返回上限的小多边形的方式来遍历POI数据,但是高德POI数据本身的商业程度很高,并且通过限制接口及配额两种方法来增加POI数据免费获取的难度,以至于获取城市量级的POI可行性基本上不高,需要购买额度,或者购买企业账号。

文章仅用于分享个人学习成果与个人存档之用,分享知识,如有侵权,请联系作者进行删除。所有信息均基于作者的个人理解和经验,不代表任何官方立场或权威解读。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2158571.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

线程对象的生命周期、线程等待和分离

线程对象的生命周期、线程等待和分离 #include <iostream> #include<thread> using namespace std;bool is_exit false;//用于判断主线程是否退出 void ThreadMain() {cout << "begin sub thread main ID: " << this_thread::get_id() &l…

数据结构(Day19)

一、学习内容 单链表 头删 int front_dele(Plink L) {if(LNULL||L->len0){printf("头删失败\n");return -1;}Plink Q L->next;//保留要删除的1号节点L->next L->next->next;L->len--;free(Q);//释放空间QNULL;return 0; }尾删 int rear_dele(Pli…

JavaWeb - 5 - 前端工程化 Element

一.前后端分离开发 前后端混合开发 缺点&#xff1a;沟通成本高&#xff0c;分工不明确&#xff0c;不便管理&#xff0c;不便维护拓展 前后端分离开发 当前最为主流的开发模式&#xff1a;前后端分离 前后端分离开发中很重要的是API接口文档&#xff08;如&#xff1a;YApi&…

AH2212-12V转4.2V充电芯片

AH2212——12V转4.2V充电芯片&#xff0c;峰值2A输出编程电流&#xff0c;实现精准同步开关降压锂电池充电 随着科技的不断发展&#xff0c;移动电源、智能穿戴、电动工具等设备的应用越来越广泛&#xff0c;对电池充电芯片的需求也日益增大。本文将为您介绍一款高性能的充电芯…

通过iFIX在ARMxy边缘计算网关上实现维护管理

在当今快速发展的工业环境中&#xff0c;维护管理的有效性直接影响到生产效率和设备可靠性。随着物联网和边缘计算的兴起&#xff0c;传统的维护方式正在被更智能和高效的解决方案所替代。ARMxy系列的BL340控制器&#xff0c;凭借其灵活的IO配置和强大的处理能力&#xff0c;成…

linux使用docker安装运行kibana报错“Kibana server is not ready yet“的解决办法

首先docker log <container-id>查看日志是什么问题(以下是我的最后一条日志报错): {"type":"log","timestamp":"2024-09-23T12:27:0700:00","tags":["error","elasticsearch-service"],"pi…

【中关村在线-注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

基于单片机的无线宠物自动喂食系统设计

本设计研究了一种无线宠物自动喂食器&#xff0c;其功能是先将宠物饲料放入其中&#xff0c;通过设定喂食时间点&#xff0c;当到达这一时间点后&#xff0c;系统开始播报语音同时控制步进电机转动&#xff0c;自动进行喂食。本设计主要研究怎么设定时间并进行投喂&#xff0c;…

(学习记录)使用 STM32CubeMX——GPIO引脚输出配置

学习总结&#xff1a;&#xff08;学习总结&#xff09;STM32CubeMX HAL库 学习笔记撰写心得https://blog.csdn.net/Wang2869902214/article/details/142435481 STM32F103C8T6的GPIO引脚输出配置 时钟配置 &#xff08;学习记录&#xff09;使用 STM32CubeMX——配置时钟&…

【软件工程】可行性研究

一、目的 二、任务 三、步骤 四、结果&#xff1a;可行性研究报告 例题 选择题

创新学生宿舍管理:Spring Boot框架实践

第2章 开发环境与技术 学生宿舍管理系统的编码实现需要搭建一定的环境和使用相应的技术&#xff0c;接下来的内容就是对学生宿舍管理系统用到的技术和工具进行介绍。 2.1 MYSQL数据库 本课题所开发的应用程序在数据操作方面是不可预知的&#xff0c;是经常变动的&#xff0c;没…

pdf怎么删除空白页?分享5个删除pdf页面的方法(批量删除法)

pdf文件因其跨平台、格式稳定的特性&#xff0c;已成为我们工作、学习中不可或缺的一部分。那么在编辑pdf格式文档中&#xff0c;总会遇到一些难题&#xff0c;比如说pdf怎么删除空白页 pdf与word一样&#xff0c;具备了多种编辑功能&#xff0c;只不过是word倾向于编辑&#x…

zabbix入门单机部署

zabbix官网 1进入官网后选择右上角Download 选择你要的版本以及需要的组件&#xff0c;网页下方会自动生成需要操作的步骤 &#xff0c;跟着步骤一步一步安装即可&#xff1a; 这里跟着官网步骤一步步走下去就可以了 但是需要注意的是安装 yum install centos-release-scl源…

活动报名| 探索存内计算的未来,共话AGI时代

活动日期&#xff1a;2024年09月28日 下午一点到6点 地点&#xff1a;杭州技术转移中心 三楼路演厅 议程亮点&#xff1a; 存内计算技术架构以及最新趋势AGI开源项目交流存内计算实操上板体验 存内计算 ——突破物理极限的下一代算力技术 直接消除“存”“算”界限&…

【C++】10道经典面试题带你玩转二叉树

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:C ⚙️操作环境:Leetcode/牛客网 目录 一.根据二叉树创建字符串 二.二叉树的层序遍历 三.二叉树的层序遍历 II 四.二叉树的最近公共祖先 五.二叉搜索树与双向链表 六.从前序与中序遍历序列构造二叉树 七.从中序与后序遍历…

【笔记】机器学习算法在异常网络流量监测中的应用

先从一些相对简单的综述类看起&#xff0c;顺便学学怎么写摘要相关工作的&#xff0c;边译边学 机器学习算法在异常网络流量监测中的应用 原文&#xff1a;Detecting Network Anomalies in NetFlow Traffic with Machine Learning Algorithms Authors: Quc Vo, Philippe Ea, Os…

【雅特力AT32】I2C 配置工具Artery_I2C_Timing_Configuration的使用

功能 可以实现对主机和从机的时钟、数字滤波、模拟滤波配置。 环境安装 软件环境 Artery_I2C_Timing_Configuration.exe &#xff08;附件压缩包含安装包和配置工具使用指南&#xff0c;需要免费自取&#xff09; /*** function name:* - void i2c_init(i2c_type *i2c_x…

马尔科夫蒙特卡洛_吉布斯抽样算法(Markov Chain Monte Carlo(MCMC)_Gibbs Sampling)

定义 输入:目标概率分布的密度函数 p ( x ) p(x) p(x),函数 f ( x ) f(x) f(x) 输出: p ( x ) p(x) p(x)的随机样本 x m 1 , x m 2 , ⋯ , x n x_{m1},x_{m2},\cdots,x_n xm1​,xm2​,⋯,xn​,函数样本均值 f m n f_{mn} fmn​; 参数:收敛步数 m m m,迭代步数 n n n。 (1)初…

【Linux】常用指令(下)(内含more、less、 head、tail、date、find、grep、zip、tar以及学习笔记)

文章目录 前言1. more指令2. less指令&#xff08;重要&#xff09;3. head指令4. tail指令5. 管道&#xff08;做到学会使用即可&#xff09;6. date指令6.1 时间戳 7. cal指令8. find指令9. grep指令10. zip/unzip指令11. tar指令 前言 Linux下的常用指令终于要在本文落下帷…

如给Excel表格设置保护,防止表格被移动或删除

在日常工作和学习中&#xff0c;Excel表格是我们经常使用的工具之一。然而&#xff0c;在某些情况下&#xff0c;我们可能需要保护Excel工作簿的结构&#xff0c;防止工作表被随意移动或删除&#xff0c;以确保数据的完整性和安全性。下面小编就来给大家详细介绍如何在Excel中设…