基于指定半径统计酒店周边POI数量及ID列表的技术问题
问题:酒店指定半径内POI统计偏差(半径高估1.5倍、数量统计错误)
我们需要统计指定半径(200米)内,每个酒店周边各类型POI的数量及对应ID,但现有方案经Tableau验证不符合预期:
- POI数量被高估
- 实际搜索半径约为设定值的1.5倍
现有数据集:
- 酒店数据集(
df_hotels):7万行50列,包含唯一酒店ID、经纬度、名称及其他属性 - POI数据集(
df_poi):2.5万行9列,包含唯一场所ID、经纬度、名称、设施类型(如restaurant、bar)等属性
由于数据量较大,无法用嵌套循环计算距离,计划用六边形网格优化效率,但当前用圆形搜索的代码存在偏差,需排查投影或取整问题。
现有代码的核心问题分析
半径转换逻辑错误
meters_to_degrees函数混用两种转换方式,最终仅使用公式计算结果,且创建buffer时错误只传入lon_deg作为半径,导致buffer是基于经度度数的圆形,而非对应200米的实际地理半径。- EPSG:4326是地理坐标系(经纬度),直接在此坐标系下用度数做buffer完全错误——经纬度的度数距离随纬度变化,且不是等距的,这是半径偏差的核心原因。
候选过滤缺失实际距离校验
- R-tree的
intersection仅返回buffer外接矩形内的POI,后续未校验这些POI到酒店的实际地理距离,导致矩形内超出圆形半径的POI也被统计,造成数量高估。
- R-tree的
其他笔误
- 代码最后修改的是
airbnb_test而非目标数据集df_hotels。
- 代码最后修改的是
修正后的解决方案(正确投影+距离校验)
核心思路:将地理坐标系(EPSG:4326)转换为局部UTM投影(以米为单位,保证距离计算准确),在投影坐标系下做buffer和距离计算,彻底避免经纬度的距离偏差。
import geopandas as gpd import pyproj # 输入参数 radius_meters = 200 # 1. 转换为GeoDataFrame并设置地理坐标系 hotels_geo = gpd.GeoDataFrame( df_hotels, geometry=gpd.points_from_xy(df_hotels["longitude"], df_hotels["latitude"]), crs="EPSG:4326" ) poi_geo = gpd.GeoDataFrame( df_poi, geometry=gpd.points_from_xy(df_poi["longitude"], df_poi["latitude"]), crs="EPSG:4326" ) # 2. 自动匹配区域UTM投影(以米为单位,保证距离计算准确) def get_utm_crs(gdf): lon_center = gdf.geometry.x.mean() utm_zone = int((lon_center + 180) / 6) + 1 is_northern = gdf.geometry.y.mean() > 0 epsg_code = 32600 + utm_zone if is_northern else 32700 + utm_zone return pyproj.CRS(f"EPSG:{epsg_code}") utm_crs = get_utm_crs(hotels_geo) hotels_utm = hotels_geo.to_crs(utm_crs) poi_utm = poi_geo.to_crs(utm_crs) # 3. 创建POI的R-tree空间索引 poi_sindex = poi_utm.sindex # 4. 提前初始化所有设施类型列,避免赋值报错 amenity_types = poi_utm["amenity"].unique() for amenity in amenity_types: df_hotels[amenity] = 0 # 5. 遍历酒店统计周边POI for idx, hotel_row in hotels_utm.iterrows(): # 在UTM投影下创建200米半径的buffer hotel_buffer = hotel_row.geometry.buffer(radius_meters) # 用R-tree获取buffer外接矩形内的候选POI candidate_ids = list(poi_sindex.intersection(hotel_buffer.bounds)) candidates = poi_utm.iloc[candidate_ids] # 精确筛选在圆形buffer内的POI(二次校验,排除矩形内超出圆形的点) valid_pois = candidates[candidates.geometry.within(hotel_buffer)] # 更新统计结果到原酒店数据集 if not valid_pois.empty: counts = valid_pois.groupby("amenity").size() for amenity, count in counts.items(): df_hotels.at[idx, amenity] = count # 进度打印 if idx % 10000 == 0: print(f"已处理 {idx} 条酒店数据") # 填充遗漏的NaN值为0 df_hotels.fillna(0, inplace=True)
六边形网格优化方案(可选,提升大数量级数据效率)
若数据量进一步增大,可通过六边形网格预分块减少查询范围:
- 用
geopandas.GeoDataFrame.hexbin生成覆盖所有POI的六边形网格(网格边长对应200米半径的等效覆盖范围) - 为每个POI标记所属的六边形ID
- 为每个酒店找到其所在及相邻的六边形,仅在这些六边形内搜索POI,避免全量索引查询
内容的提问来源于stack exchange,提问作者Chef Koch
相关产品推荐
相关产品推荐

