Python应用指南:利用高德地图采集AOI数据

在数字化转型与智慧城市建设加速推进的背景下,地理空间数据已成为支撑城市治理、商业决策、社会服务和学术研究的重要基础资源。其中,兴趣面(Area of Interest, AOI) 作为对现实世界中具有明确边界和功能属性的空间单元(如住宅小区、学校、产业园区、商场等)的数字化表达,因其兼具几何形态与语义信息,被广泛应用于人口估算、设施可达性分析、用地分类、热力模拟、应急响应等多种场景。

国内主流地图服务商中,高德地图在其部分 POI(Point of Interest)详情页中,通过内部接口返回了包含 AOI 边界坐标(mining_shape) 的结构化 JSON 数据。这些边界通常由实地测绘或权威数据源生成,精度较高,且覆盖了大量具备实际管理意义的封闭区域(如"浦秀苑""上海师范大学附属闵行第三小学南校"等)。然而,这些数据并未以开放格式直接提供,而是嵌入在网页请求响应中,且格式为非标准的多对象拼接 JSON,无法被 GIS 软件直接读取或批量处理。

针对这一现状,本文提出并实现了一套轻量级、低门槛、可复现的 AOI 数据提取与转换流程:用户仅需通过浏览器开发者工具手动复制目标 POI 的原始响应内容,保存为文本文件后,运行一段基于 Python 的处理脚本,即可自动完成 JSON 对象分割、边界坐标解析、有效多边形构建、属性字段提取、基于 poiid 的智能去重,并最终输出符合 OGC 标准的 Shapefile 文件(采用 EPSG:4326 坐标系),完全兼容 QGIS、ArcGIS 等主流地理信息系统。该方案无需调用高德官方 API,亦不依赖复杂爬虫,有效规避了配额限制与反爬机制,特别适用于小规模、高精度 AOI 数据的定向采集,如社区微更新调研、教育资源分布评估或房地产地块分析等场景。需要强调的是,所获取的数据仍受高德地图相关使用条款约束,仅建议用于个人学习、科研或非商业用途;同时,用户应留意高德所采用的 GCJ-02 坐标系与国际通用 WGS84 之间的系统性偏移,在可视化的过程中需要进行坐标坐标转换。

第一步: 我们先登录高德地图的官方:高德地图,在地图点击某个地点名称,我们就阔以看到对应的轮廓;

**第二步:**我们打开开发者工具(F12 或 Ctrl + Shift + I),找到响应,再找到detail?id这个响应请求,里面的"shape"就是点坐标集;

**第三步:**再往下划,就是具体标签信息;

**第四步:**我们Ctrl+A进行全选,这里新建一个文本文档,把内容粘贴进去即可,然后继续重复此操作;

**第五步:**手动复制完成这些aoi面之后,就是保存一下txt,命名的话可以根据喜好来,然后修改一下txt文件路径,运行一下下面的python脚本即可;

方法思路

  • 1.打开 AOI 详情页,通过开发者模式找到对应完整 JSON 响应;
  • 2.新建文本文件,命名为:aoi原始数据.txt,复制整个 JSON 内容贴进去(多个重复此操作);
  • 3.通过python里面对应gis包功能实现点集成面,并把每个aoi合并成一个shp;
  • 4.这里增加了一个坐标转换功能,由高德的GCJ-02坐标系 转 通用的WGS84坐标系;
  • 5.结果另存为"aoi数据集"文件夹下的all_aois.shp图层;

完整代码#运行环境 Python 3.11

python 复制代码
# -*- coding: utf-8 -*-
import json
import geopandas as gpd
from shapely.geometry import Polygon
import os
import re
import math

# ========== GCJ-02 转 WGS84 坐标转换函数 ==========
x_pi = 3.14159265358979324 * 3000.0 / 180.0
pi = 3.1415926535897932384626  # π
a = 6378245.0  # 长半轴
ee = 0.00669342162296594323  # 偏心率平方

def gcj02_to_wgs84(lng, lat):
    """
    将GCJ02坐标系(火星坐标系)转换为WGS84坐标系
    :param lng: 火星坐标系的经度
    :param lat: 火星坐标系的纬度
    :return: WGS84坐标系的经纬度列表 [lng, lat]
    """
    if out_of_china(lng, lat):
        return [lng, lat]
    dlat = _transformlat(lng - 105.0, lat - 35.0)
    dlng = _transformlng(lng - 105.0, lat - 35.0)
    radlat = lat / 180.0 * pi
    magic = math.sin(radlat)
    magic = 1 - ee * magic * magic
    sqrtmagic = math.sqrt(magic)
    dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi)
    dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi)
    mglat = lat + dlat
    mglng = lng + dlng
    return [lng * 2 - mglng, lat * 2 - mglat]

def _transformlat(lng, lat):
    ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + \
          0.1 * lng * lat + 0.2 * math.sqrt(math.fabs(lng))
    ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 *
            math.sin(2.0 * lng * pi)) * 2.0 / 3.0
    ret += (20.0 * math.sin(lat * pi) + 40.0 *
            math.sin(lat / 3.0 * pi)) * 2.0 / 3.0
    ret += (160.0 * math.sin(lat / 12.0 * pi) + 320 *
            math.sin(lat * pi / 30.0)) * 2.0 / 3.0
    return ret

def _transformlng(lng, lat):
    ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + \
          0.1 * lng * lat + 0.1 * math.sqrt(math.fabs(lng))
    ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 *
            math.sin(2.0 * lng * pi)) * 2.0 / 3.0
    ret += (20.0 * math.sin(lng * pi) + 40.0 *
            math.sin(lng / 3.0 * pi)) * 2.0 / 3.0
    ret += (150.0 * math.sin(lng / 12.0 * pi) + 300.0 *
            math.sin(lng / 30.0 * pi)) * 2.0 / 3.0
    return ret

def out_of_china(lng, lat):
    return not (lng > 73.66 and lng < 135.05 and lat > 3.86 and lat < 53.55)

# ========== 主程序部分==========
INPUT_FILE = "aoi原始数据集.txt"
OUTPUT_FOLDER = "aoi数据集"
MERGED_SHP = "all_aois.shp"

def extract_json_objects(text):
    """从拼接的多个 JSON 对象中提取每个完整对象"""
    objects = []
    depth = 0
    start = None
    for i, char in enumerate(text):
        if char == '{':
            if depth == 0:
                start = i
            depth += 1
        elif char == '}':
            depth -= 1
            if depth == 0 and start is not None:
                objects.append(text[start:i+1])
    return objects

def get_unique_id(base):
    """以 poiid 为主键去重,缺失时用 name+address 哈希"""
    poiid = base.get("poiid")
    if poiid:
        return f"ID_{poiid}"
    name = base.get("name", "").strip()
    addr = base.get("address", "").strip()
    if name or addr:
        return f"NAMEADDR_{hash(name + '|' + addr)}"
    return None

def parse_poi(data_str):
    try:
        data = json.loads(data_str)
        if data.get("status") != "1":
            return None
        base = data["data"]["base"]
        spec = data["data"]["spec"]

        # 提取属性
        attrs = {
            "name": base.get("name", ""),
            "tag": base.get("tag", ""),
            "address": base.get("address", ""),
            "area": float(base.get("geodata", {}).get("aoi", [{}])[0].get("area", 0)),
            "poiid": base.get("poiid", ""),
            "city_adcode": base.get("city_adcode", ""),
            "code": base.get("code", ""),
            "classify": base.get("classify", ""),
        }

        # 解析原始 GCJ-02 坐标字符串
        shape_str = spec["mining_shape"]["shape"]
        coords_gcj02 = [
            p.strip() for p in shape_str.split(";") if p.strip()
        ]

        if len(coords_gcj02) < 3:
            return None

        # 转换为 WGS84 坐标
        wgs84_coords = []
        for coord in coords_gcj02:
            lng, lat = map(float, coord.split(","))
            wgs_lng, wgs_lat = gcj02_to_wgs84(lng, lat)
            wgs84_coords.append((wgs_lng, wgs_lat))

        polygon = Polygon(wgs84_coords)
        if not polygon.is_valid:
            polygon = polygon.buffer(0)
            if not polygon.is_valid:
                return None

        return attrs, polygon, base  # 返回 base 用于去重
    except Exception:
        return None

def main():
    os.makedirs(OUTPUT_FOLDER, exist_ok=True)

    # 读取文件
    try:
        with open(INPUT_FILE, "r", encoding="utf-8") as f:
            content = f.read()
    except Exception as e:
        print(f"读取文件失败: {e}")
        return

    # 分割 JSON 对象
    json_objects = extract_json_objects(content)
    if not json_objects:
        print("未找到任何有效的 JSON 对象")
        return

    seen_ids = set()
    features = []  # 存储所有有效 (attrs, geometry)
    total = len(json_objects)
    kept = 0

    print(f"共检测到 {total} 个 POI,开始去重与解析...")

    for i, obj_str in enumerate(json_objects, 1):
        result = parse_poi(obj_str)
        if not result:
            continue

        attrs, polygon, base = result
        unique_id = get_unique_id(base)

        if not unique_id or unique_id in seen_ids:
            continue

        seen_ids.add(unique_id)
        features.append((attrs, polygon))
        kept += 1

    # 合并导出
    if features:
        # 构建 GeoDataFrame 列表
        gdfs = [
            gpd.GeoDataFrame([attrs], geometry=[geom], crs="EPSG:4326")
            for attrs, geom in features
        ]
        merged = gpd.pd.concat(gdfs, ignore_index=True)
        output_path = os.path.join(OUTPUT_FOLDER, MERGED_SHP)
        merged.to_file(output_path, encoding="utf-8")
        print(f"\n成功导出合并文件: {output_path}")
        print(f"共去重后保留 {kept} 个 AOI")
    else:
        print("\n未生成任何有效 AOI")

if __name__ == "__main__":
    main()

这里选取了上海陆家嘴金融中心附近的一些aoi建筑数据做演示,因为脚本运行结束,我们直接得到是就是shp图层,所以我们直接用arcgis进行展示即可;

我们也可以试着把建筑进行拉伸,从立体的视角进行观测,先新建一个局部场景,把shp图层导进去,然后点击要素图层,找到类型选择基本高度,然后选择拉伸字段,这里也可以选择透明度,光照效果一些参数,可以自行调整;

然后就能得到一个3D立体的建筑模型;

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

相关推荐
梁正雄1 小时前
5、python 模块与包
linux·服务器·python
I_ltt_Itw,1 小时前
Python协程学习笔记
开发语言·网络·python
爱笑的眼睛111 小时前
Flask应用API深度开发:从单体架构到微服务设计模式
java·人工智能·python·ai
AI小云1 小时前
【数据操作与可视化】Matplotlib绘图-常用操作
python·数据可视化
木婉清fresh1 小时前
测开python高频面试精选100题
开发语言·python·面试
彼岸花开了吗1 小时前
构建AI智能体:四十、K-Means++与RAG的融合创新:智能聚类与检索增强生成的深度应用
人工智能·python
JHC0000001 小时前
47. 全排列 II
开发语言·python·面试
棒棒的皮皮1 小时前
【OpenCV】Python图像处理之特征提取
图像处理·python·opencv
用户576905308011 小时前
微调入门尝试:沐雪角色扮演
python·llm