尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

基于OpenStreetMap的AOI与POI数据批量获取工具:一键导出GeoJSON

发布时间:2026/9/21 1:50:48

资讯中心
01
ARTICLE

基于OpenStreetMap的AOI与POI数据批量获取工具:一键导出GeoJSON

基于OpenStreetMap的AOI与POI数据批量获取工具:一键导出GeoJSON
1. 这个工具到底解决了什么问题做地理数据分析的人都有一个共同的痛点想要一份干净的、带边界坐标的全国范围AOIArea of Interest兴趣面或POIPoint of Interest兴趣点数据要么花钱买要么手动一个个画。商业地图API虽然能查但导出受限、坐标系加密、批量获取成本高而且拿到的数据格式往往不通用。OpenStreetMapOSM是全球最大的开源地图数据库里面沉淀了海量的建筑轮廓、道路、公园、学校、医院等地理要素。理论上你可以从OSM里把全国任何一个城市的AOI边界和POI点位扒下来导出成GeoJSON直接丢进QGIS、ArcGIS或者做空间分析。但问题是OSM的数据结构跟商业地图完全不一样它不是按“POI”“AOI”这种业务概念组织的而是用node、way、relation三种基础元素加上tag标签来描述世界。你想查“上海市所有医院”得自己写Overpass QL查询语句还得处理坐标系、数据清洗、格式转换、边界裁剪等一系列问题。我做的这个开源工具核心目标就一个让你输入一个地名或者一个行政区划代码一键拉取该区域内所有AOI边界和POI点位直接输出标准GeoJSON文件。不需要你懂Overpass QL语法不需要你手动处理坐标系偏移不需要你写数据清洗脚本。工具内部封装了查询构建、分块请求、去重合并、坐标转换、格式标准化这一整套流程。适合谁用做城市规划的、做选址分析的、做物流路径优化的、做学术研究需要地理数据的、做数据可视化需要底图要素的甚至你只是想看看自己家周边有哪些便利店和公园都能用。对新手来说你不需要有GIS背景只要能跑Python脚本就行对老手来说工具提供了完整的参数配置接口可以精细控制查询范围、要素类型、输出精度。2. 为什么选择OpenStreetMap而不是商业地图2.1 商业地图API的硬伤先说说为什么不直接用某德、某度。第一坐标系问题。国内商业地图普遍使用GCJ-02或者BD-09坐标系跟国际通用的WGS-84之间存在非线性偏移。你从API拿到的坐标直接叠加到OSM底图或者卫星影像上会发现整体偏移几百米。要纠偏就得做坐标转换而转换算法虽然公开但批量处理时精度和效率都是问题。第二数据导出限制。商业地图API通常只提供查询接口不提供批量导出。你想拿一个区的所有POI得按关键词分页请求每个关键词还有返回数量上限。一个中等规模的区可能要发几百次请求才能覆盖全而且很多API对QPS有严格限制跑一遍下来半天没了。第三数据授权问题。商业地图的数据你不能随便分发、不能用于商业产品、不能二次加工后公开发布。做学术研究发论文审稿人可能会质疑数据来源的合规性。2.2 OSM的独特优势OSM的数据是ODbL协议你可以自由使用、分发、修改只要署名并以相同方式共享。坐标系是WGS-84跟GPS原始坐标一致不需要纠偏。数据结构虽然复杂但一旦理解了tag体系你会发现它的信息密度极高。一个building的way可能带着buildingyes、building:levels6、addr:streetxxx、addr:housenumberxxx等十几个标签这些信息在商业地图里往往是分散的、不完整的。更重要的是OSM有Overpass API。这是一个专门为OSM数据设计的查询接口支持按区域、按标签、按几何关系组合查询。你可以写一条语句把某个行政区内所有amenityschool的node和way全部拉出来返回格式可以是JSON、XML或者CSV。Overpass QL的语法虽然有点陡峭但一旦掌握查询效率极高。2.3 工具的核心设计思路我的工具本质上是一个Overpass QL的封装器加数据后处理器。整体流程分四步第一步地理编码。用户输入“上海市浦东新区”或者行政区划代码“310115”工具调用NominatimOSM的官方地理编码服务获取该区域的OSM关系ID和边界多边形。Nominatim返回的边界多边形是WGS-84坐标的GeoJSON直接可用。第二步查询构建。根据用户选择的要素类型AOI还是POI或者两者都要工具动态生成Overpass QL语句。AOI查询主要针对闭合的way和relation比如building、landuse、leisure、amenity等标签POI查询主要针对node和部分way比如shop、amenity、tourism、office等标签。第三步分块请求与合并。Overpass API对单次查询有面积和超时限制。全国范围一次查完基本不可能所以工具会把大区域切成网格逐块查询然后按OSM ID去重合并。切块的大小可以配置默认是0.1度×0.1度大约11公里×11公里。第四步数据清洗与导出。原始返回的JSON里每个元素都带着一堆元数据版本号、时间戳、变更集、用户等工具会剥离这些无关字段只保留geometry和核心tags然后统一转换成GeoJSON FeatureCollection。AOI输出Polygon或MultiPolygonPOI输出Point。3. 环境准备与依赖安装3.1 基础环境要求工具用Python 3.8开发主要依赖requests、shapely、geopandas、rtree这几个库。如果你只是跑基础功能不装geopandas也行但做空间裁剪和几何运算时会慢很多。建议用conda建一个独立环境避免跟系统Python冲突。conda create -n osm-tool python3.10 conda activate osm-tool pip install requests shapely geopandas rtree pyproj如果你不用conda用venv也可以python -m venv osm-env source osm-env/bin/activate # Linux/Mac osm-env\Scripts\activate # Windows pip install requests shapely geopandas rtree pyproj3.2 Overpass API端点选择Overpass API有多个公共端点不同端点的负载和响应速度差异很大。主端点overpass-api.de负载最高经常排队kumi.systems和overpass.openstreetmap.ru相对快一些但稳定性看运气。工具默认配置了三个端点自动轮询某个端点超时就切下一个。注意公共端点都有 fair use 政策不要短时间内发大量请求。工具内置了请求间隔控制默认每次请求间隔1秒批量查询时建议调到2-3秒。3.3 地理编码服务配置Nominatim是OSM官方的地理编码服务免费但有限制每秒最多1次请求每天不超过一定量。工具里做了缓存机制同一个地名第二次查询直接读本地缓存不重复请求。如果你需要高频地理编码可以自建Nominatim服务或者用其他开源地理编码方案。4. 核心查询逻辑与Overpass QL实战4.1 Overpass QL基础语法速成Overpass QL的语法结构可以类比SQL但它是专门为空间数据设计的。一条典型的查询语句长这样[out:json][timeout:60]; area[name浦东新区]-.searchArea; ( node[amenityschool](area.searchArea); way[amenityschool](area.searchArea); relation[amenityschool](area.searchArea); ); out body; ; out skel qt;逐行解释第一行设置输出格式为JSON超时60秒。第二行查找name为“浦东新区”的area存到变量searchArea。第三行到第五行在searchArea范围内分别查node、way、relation中amenityschool的元素。第六行输出元素的完整信息。第七行是递归向上查询把way和relation引用的node也拉出来。第八行输出骨架信息和四叉树排序。这个查询会返回浦东新区所有学校的点位和建筑轮廓。但实际用的时候你会发现几个问题一是area的name匹配可能不准确同名区域很多二是有些学校没有amenityschool标签而是用landuseeducation或者buildingschool三是relation类型的学校比如大学校园需要特殊处理。4.2 AOI查询的标签体系AOI的本质是一个闭合区域在OSM里对应way或relation。但不是所有闭合way都是AOI比如一个环形交叉路口也是闭合way但它不是兴趣面。所以需要按标签过滤。我整理了常用的AOI标签组合类别标签键典型值几何类型建筑轮廓buildingyes, residential, commercialPolygon土地利用landuseresidential, commercial, industrialPolygon休闲设施leisurepark, sports_centre, gardenPolygon教育科研amenityschool, university, hospitalPolygon交通设施aeroway, railwayterminal, stationPolygon水系naturalwater, wetlandPolygon查询时用正则表达式组合多个值比如[building~yes|residential|commercial]这样一条语句就能覆盖多种建筑类型。4.3 POI查询的标签体系POI在OSM里主要是node但也有少量way比如一个便利店可能画成小矩形。POI的标签体系比AOI更丰富因为POI的分类更细。常用的POI标签类别标签键典型值餐饮amenityrestaurant, cafe, fast_food购物shopsupermarket, convenience, mall医疗amenityhospital, clinic, pharmacy教育amenityschool, kindergarten, library交通highwaybus_stop, crossing金融amenitybank, atm旅游tourismhotel, attraction, museumPOI查询时要注意有些POI同时有多个标签比如一个加油站既有amenityfuel又有shopconvenience去重时按OSM ID去重即可不会重复。4.4 分块查询的实现细节全国范围直接查Overpass基本会超时必须分块。我的做法是先用Nominatim拿到目标区域的边界多边形然后用shapely的box函数生成网格跟边界多边形做相交运算得到每个网格块的实际查询范围。每个网格块单独发一次Overpass请求返回结果按OSM ID去重后合并。网格大小的选择是个权衡网格太小请求次数多总耗时长网格太大单次查询可能超时。实测下来0.1度×0.1度在大多数情况下能在30秒内返回是比较稳妥的选择。如果目标区域建筑密度特别高比如上海内环可以调到0.05度如果目标区域是郊区或农村可以放大到0.2度。from shapely.geometry import box import geopandas as gpd def generate_grid(boundary_gdf, grid_size0.1): minx, miny, maxx, maxy boundary_gdf.total_bounds grid_cells [] x minx while x maxx: y miny while y maxy: cell box(x, y, x grid_size, y grid_size) if cell.intersects(boundary_gdf.geometry.iloc[0]): grid_cells.append(cell) y grid_size x grid_size return gpd.GeoDataFrame(geometrygrid_cells, crsEPSG:4326)这段代码生成覆盖目标区域的网格只保留与边界相交的格子。实际查询时每个格子单独构造Overpass QL把格子的bbox传进去。5. 数据清洗与GeoJSON导出5.1 原始数据的结构问题Overpass返回的JSON里每个元素的结构是这样的{ type: way, id: 123456789, bounds: {minlat: 31.0, minlon: 121.0, maxlat: 31.1, maxlon: 121.1}, nodes: [123, 456, 789, 123], tags: {building: yes, addr:street: 某某路} }注意nodes字段只存了node的ID没有坐标。坐标在另外的node元素里。所以处理时需要先建立node ID到坐标的映射然后按nodes顺序重建几何。如果nodes的第一个和最后一个ID相同说明是闭合way可以构成Polygon否则是LineString对于AOI来说应该丢弃。relation更复杂它由多个way和node组成有outer和inner角色之分。outer构成外环inner构成内环比如一个小区中间有个湖。重建relation几何时需要把outer的way拼成外环inner的way拼成内环然后构造Polygon或MultiPolygon。5.2 几何有效性修复OSM数据是人工编辑的难免有几何错误自相交、重复点、环方向不对、外环和内环嵌套错误等。shapely提供了make_valid函数可以修复大部分问题但修复后可能变成MultiPolygon需要做类型统一。from shapely.validation import make_valid from shapely.geometry import Polygon, MultiPolygon def fix_geometry(geom): if not geom.is_valid: geom make_valid(geom) if isinstance(geom, Polygon): return MultiPolygon([geom]) return geom统一成MultiPolygon的好处是后续处理不用判断类型直接遍历所有polygon即可。5.3 坐标系与精度处理OSM原始坐标是WGS-84精度到小数点后7位大约1厘米。但实际数据中很多node的坐标只精确到小数点后5位大约1米。导出GeoJSON时建议保留6位小数既能保证精度又不会让文件过大。如果你需要做面积计算WGS-84的度单位不能直接用需要投影到等面积投影。中国区域常用EPSG:4527CGCS2000 3度带或者EPSG:3857Web Mercator但面积变形大。工具里提供了投影转换的选项默认不转换保持WGS-84输出。5.4 GeoJSON输出格式规范输出的GeoJSON遵循RFC 7946标准FeatureCollection里每个Feature包含geometry和properties。properties里保留原始tags但去掉元数据字段version、timestamp、changeset、user、uid。另外增加两个字段osm_id和osm_type方便溯源。{ type: FeatureCollection, features: [ { type: Feature, geometry: { type: MultiPolygon, coordinates: [[[[121.0, 31.0], [121.1, 31.0], [121.1, 31.1], [121.0, 31.1], [121.0, 31.0]]]] }, properties: { osm_id: 123456789, osm_type: way, building: yes, addr:street: 某某路 } } ] }提示GeoJSON文件超过100MB后很多软件打开会卡顿。如果目标区域很大建议按行政区拆分输出或者用GeoPackage格式替代。6. 实操全流程以上海市浦东新区为例6.1 第一步获取行政区边界输入“上海市浦东新区”工具调用Nominatim查询。Nominatim返回的结果里有一个osm_typerelation、osm_id某值的记录这就是浦东新区的行政边界。获取边界GeoJSON后用geopandas读入确认坐标系是EPSG:4326。import requests import geopandas as gpd from shapely.geometry import shape def get_boundary(place_name): url https://nominatim.openstreetmap.org/search params { q: place_name, format: geojson, polygon_geojson: 1, limit: 1 } headers {User-Agent: osm-aoi-tool/1.0} resp requests.get(url, paramsparams, headersheaders) data resp.json() if data[features]: return shape(data[features][0][geometry]) return None实测下来Nominatim对国内地名的匹配准确率还不错但“浦东新区”可能返回多个结果需要按osm_typerelation过滤。如果匹配不到可以改用行政区划代码查询或者手动指定OSM relation ID。6.2 第二步生成查询网格浦东新区面积约1210平方公里按0.1度网格切分大约产生60-80个网格块。每个网格块单独查询总请求数在80次左右。按每次请求间隔1.5秒算总耗时约2分钟。如果并发请求可以压缩到30秒内但要注意公共端点的限流。boundary get_boundary(上海市浦东新区) grid generate_grid(boundary, grid_size0.1) print(f生成 {len(grid)} 个网格块)6.3 第三步构建并发送Overpass查询对每个网格块构造Overpass QL。AOI查询和POI查询分开因为标签体系不同。AOI查询用way和relationPOI查询用node和way。def build_aoi_query(bbox): minx, miny, maxx, maxy bbox bbox_str f{miny},{minx},{maxy},{maxx} query f [out:json][timeout:60]; ( way[building]({bbox_str}); way[landuse]({bbox_str}); way[leisure]({bbox_str}); way[amenity]({bbox_str}); relation[building]({bbox_str}); relation[landuse]({bbox_str}); relation[leisure]({bbox_str}); relation[amenity]({bbox_str}); ); out body; ; out skel qt; return query发送请求时用requests的POST方法把query作为data传过去。Overpass API支持GET和POSTPOST更适合长查询。def query_overpass(query, endpointhttps://overpass-api.de/api/interpreter): resp requests.post(endpoint, data{data: query}, timeout120) if resp.status_code 200: return resp.json() return None6.4 第四步解析与合并结果每个网格块返回的JSON里elements数组包含node、way、relation。先建立node ID到坐标的映射然后遍历way和relation重建几何。所有网格块的结果按OSM ID去重合并成一个大的FeatureCollection。def parse_elements(elements): nodes {} ways [] relations [] for el in elements: if el[type] node: nodes[el[id]] (el[lon], el[lat]) elif el[type] way: ways.append(el) elif el[type] relation: relations.append(el) features [] for way in ways: coords [nodes[nid] for nid in way[nodes] if nid in nodes] if len(coords) 4 and coords[0] coords[-1]: geom Polygon(coords) features.append({ type: Feature, geometry: geom.__geo_interface__, properties: {osm_id: way[id], osm_type: way, **way.get(tags, {})} }) return featuresrelation的解析更复杂需要按role分组outer拼外环inner拼内环。这里不展开代码核心思路是用shapely的polygonize或者手动拼接。6.5 第五步导出GeoJSON合并去重后用geopandas的to_file导出。如果数据量大建议用GeoPackage格式读写更快支持空间索引。gdf gpd.GeoDataFrame.from_features(features, crsEPSG:4326) gdf.to_file(pudong_aoi.geojson, driverGeoJSON) gdf.to_file(pudong_aoi.gpkg, driverGPKG)实测浦东新区全量AOI大约15万条GeoJSON文件约200MBGeoPackage约80MB。用QGIS打开GeoPackage流畅GeoJSON会卡几秒。7. 常见问题与排查技巧实录7.1 查询超时怎么办Overpass API超时是最常见的问题。原因通常是查询范围太大、标签太宽泛、或者端点负载高。解决办法缩小网格尺寸、减少标签组合、换端点、增加超时时间。如果某个网格块反复超时可以单独把它再切小或者跳过该块最后用相邻块的数据补。实操心得把[timeout:60]改成[timeout:180]能解决大部分超时但公共端点可能直接拒绝。更好的做法是优化查询语句比如用nwr代替分开写node、way、relation减少语句长度。7.2 数据缺失与标签不全OSM的数据覆盖度在国内城市差异很大。一线城市核心区数据很全郊区就差很多农村地区可能只有道路没有建筑。如果你发现某个区域查不到数据先确认OSM上有没有数据。打开openstreetmap.org定位到该区域看看有没有建筑轮廓。如果没有那就是源头没有数据工具也变不出来。另一个常见问题是标签不规范。比如一个学校可能标了amenityschool也可能只标了buildingyes加name某某学校。工具默认只查标准标签如果你需要更全的结果可以放宽标签条件比如查所有带name标签的way。7.3 几何错误与修复自相交多边形、重复点、环方向错误是OSM数据的常见问题。shapely的make_valid能修复大部分但修复后可能产生空几何或碎片。建议在导出前做一次过滤丢弃面积为0或极小的多边形。gdf gdf[gdf.geometry.area 1e-8] gdf gdf[gdf.geometry.is_valid]如果修复后还是有问题可以用QGIS的“检查几何有效性”工具手动修或者用PostGIS的ST_MakeValid函数。7.4 坐标系偏移问题OSM是WGS-84如果你要跟国内商业地图数据叠加需要做坐标转换。GCJ-02的转换算法是公开的但批量转换时要注意精度损失。工具里提供了可选的坐标转换模块默认关闭。如果你只是做可视化用OSM底图不需要转换如果要跟某德数据叠加再开启转换。7.5 常见问题速查表问题现象可能原因解决方法查询返回空区域无数据或标签不匹配放宽标签条件确认OSM有数据查询超时范围太大或端点负载高缩小网格换端点增加超时几何无效自相交或环方向错误make_valid修复过滤空几何坐标偏移坐标系不一致开启坐标转换模块文件过大数据量太大按行政区拆分用GeoPackage去重不彻底OSM ID重复按osm_typeosm_id联合去重8. 进阶用法与扩展思路8.1 按自定义多边形查询除了按行政区查询工具还支持传入自定义多边形。比如你有一个园区的边界GeoJSON想查园区内的所有建筑和POI直接把GeoJSON传给工具它会用这个多边形做空间过滤。实现方式是用shapely的intersects判断每个要素是否在多边形内或者用Overpass的poly过滤器。way[building](poly:31.0 121.0 31.1 121.0 31.1 121.1 31.0 121.1);poly过滤器接受一串经纬度点构成查询多边形。注意点的顺序要闭合且不能太多Overpass有限制。8.2 定时增量更新OSM数据每天都在变如果你需要保持数据最新可以设置定时任务每天或每周跑一次增量更新。Overpass API支持按时间范围查询变更用[diff:2024-01-01T00:00:00Z]可以只返回指定时间之后的变更。增量更新比全量查询快很多适合长期维护的数据集。8.3 与其他数据源融合OSM数据可以和人口数据、经济数据、交通数据融合。比如把POI密度和人口密度做相关性分析或者把AOI边界和房价数据叠加。工具输出的GeoJSON可以直接导入PostGIS、DuckDB Spatial、或者GeoPandas做进一步分析。8.4 可视化展示导出的GeoJSON可以用QGIS、Kepler.gl、Mapbox GL JS、Leaflet等工具可视化。如果做网页展示推荐用Mapbox GL JS或者MapLibre GL JS矢量瓦片渲染性能好。如果只是快速看效果QGIS拖进去就行。提示GeoJSON文件在网页端加载时超过50MB建议转成矢量瓦片Vector Tiles用tippecanoe工具切片加载速度能提升一个数量级。9. 我在实际使用中踩过的坑第一个坑是Nominatim的地理编码结果不稳定。同一个地名不同时间查询可能返回不同的relation ID因为OSM的行政区划边界在调整。解决办法是把第一次查询到的relation ID缓存下来后续直接用ID查询不再走地理编码。第二个坑是Overpass API的公共端点限流。有次我并发发了20个请求直接被封了IP等了半小时才恢复。后来改成串行加间隔虽然慢但稳定。如果你需要高频查询建议自建Overpass实例或者用商业的OSM数据服务。第三个坑是relation几何重建的复杂性。有些relation的outer way不是首尾相连的需要手动排序和拼接。我写了一个基于端点距离的排序算法但遇到断开的环还是得特殊处理。后来发现用shapely的linemerge和polygonize组合能解决大部分情况。第四个坑是内存溢出。全国范围的AOI数据如果一次性加载到内存16GB内存的机器直接爆。后来改成流式处理每个网格块解析完就导出最后用ogr2ogr合并。这样内存占用稳定在2GB以内。第五个坑是标签编码问题。OSM的tags里可能有中文、日文、韩文等多语言字符导出GeoJSON时如果编码不对会变成乱码。确保Python脚本用UTF-8编码geopandas导出时指定encodingutf-8。10. 这个工具后续还能怎么扩展目前工具的核心功能是AOI和POI的批量获取与导出。后续可以扩展的方向有几个一是增加更多要素类型比如道路网络、水系、土地利用分类二是支持按属性过滤比如只查面积大于1000平米的建筑三是增加数据质量评估模块自动检测几何错误和标签缺失四是提供REST API让其他系统可以直接调用五是做QGIS插件在QGIS里一键拉取数据。如果你对这个工具感兴趣代码已经开源。核心逻辑不复杂主要是对Overpass QL的封装和几何处理。你可以根据自己的需求修改标签体系、调整网格大小、增加输出格式。遇到问题欢迎交流我踩过的坑你可以直接跳过。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。