You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python实现:线段最近气象站匹配及网络图扩缩调整

Python实现空间线段邻域站点匹配与网络调整方案

一、高性能线段-最近气象站点匹配实现

原有嵌套循环方案时间复杂度为O(n*m),数据量上涨后性能会快速下降,替换为空间索引+粗筛+精算的方案可将复杂度降至O((n+m)logm),万级边+千级站点场景下性能可提升数十至上百倍。
核心依赖为geopandas、shapely、scipy、pandas、numpy,实现逻辑如下:

  • 先将所有数据从WGS84经纬度坐标系转换为研究区对应的等距投影坐标系(单位为米,直接支持米级距离计算,避免球面距离计算的性能损耗)
  • 基于站点坐标构建KDTree空间索引,对每条线段先用外接矩形做范围粗筛,仅对落在候选范围内的少量站点计算精确的点到线段距离,取1km邻域内距离最近的站点作为匹配结果
import geopandas as gpd
import pandas as pd
from shapely.geometry import Point, LineString
from scipy.spatial import KDTree
import numpy as np

# ---------------------- 1. 数据加载与投影转换 ----------------------
# 加载气象站点数据,替换为你的本地数据路径
stations_df = pd.read_csv("met_stations.csv")
stations_gdf = gpd.GeoDataFrame(
    stations_df,
    geometry=gpd.points_from_xy(stations_df.StationLongitude, stations_df.StationLatitude),
    crs="EPSG:4326" # WGS84经纬度坐标系
)
# 替换为研究区对应的UTM投影EPSG编码,例如中国中东部常用EPSG:32650,投影后坐标单位为米
proj_crs = "EPSG:32650"
stations_proj = stations_gdf.to_crs(proj_crs)

# 加载网络边数据,替换为你的本地数据路径
edges_df = pd.read_csv("network_edges.csv")
edges_gdf = gpd.GeoDataFrame(
    edges_df,
    geometry=edges_df.apply(lambda row: LineString([
        (row.StartLongitude, row.StartLatitude),
        (row.EndLongitude, row.EndLatitude)
    ]), axis=1),
    crs="EPSG:4326"
)
edges_proj = edges_gdf.to_crs(proj_crs)

# ---------------------- 2. KDTree构建与快速匹配 ----------------------
# 提取站点投影坐标构建空间索引
station_coords = np.array([(geom.x, geom.y) for geom in stations_proj.geometry])
station_tree = KDTree(station_coords)
SEARCH_RADIUS = 1000 # 1km邻域阈值,单位米

match_records = []
for edge_idx, edge_row in edges_proj.iterrows():
    line = edge_row.geometry
    # 用线段外接矩形做粗筛,快速圈定候选站点范围
    minx, miny, maxx, maxy = line.bounds
    search_centroid = [(minx+maxx)/2, (miny+maxy)/2]
    search_radius = SEARCH_RADIUS + max(maxx-minx, maxy-miny)/2
    candidate_idx = station_tree.query_ball_point(search_centroid, r=search_radius)
    
    nearest_station = None
    min_dist = float("inf")
    # 仅对候选站点计算精确点到线距离
    for s_idx in candidate_idx:
        station_geom = stations_proj.iloc[s_idx].geometry
        dist = line.distance(station_geom)
        if dist <= SEARCH_RADIUS and dist < min_dist:
            min_dist = dist
            nearest_station = stations_proj.iloc[s_idx]
    
    match_records.append({
        "edge_idx": edge_idx,
        "StartCity": edge_row.StartCity,
        "EndCity": edge_row.EndCity,
        "nearest_station_id": nearest_station.StationID if nearest_station is not None else None,
        "nearest_station_name": nearest_station.StationName if nearest_station is not None else None,
        "dist_to_station": min_dist if nearest_station is not None else None
    })
edge_station_match = pd.DataFrame(match_records)
initial_match_rate = edge_station_match["nearest_station_id"].notna().sum() / len(edge_station_match)

注意:禁止直接在WGS84经纬度坐标系下做米级距离计算,经纬度单位为角度,计算结果和实际距离偏差可达数倍到数十倍。如果研究区跨多个UTM带,可临时用EPSG:3857(Web墨卡托)做近似计算,精度足以满足1km邻域判断需求。


二、拓扑保持的线段调整方案(满足70%匹配率要求)

调整核心原则:不改变边连接的起止城市对应关系(即网络图拓扑结构完全不变),仅沿线段原有走向等比例延长线段,最小化几何调整幅度。
逻辑说明:只要气象站点到线段所在直线的垂直距离≤1km,将线段沿走向延长至覆盖站点到直线的垂足位置,站点就会落入线段1km邻域范围内,且线段始终在原两城市连线上,不会改变网络拓扑。
实现时优先调整仅需最小延长幅度即可匹配的线段,避免无意义的大尺度改动:

# ---------------------- 3. 线段调整满足70%匹配率 ----------------------
TARGET_MATCH_RATE = 0.7
required_match_count = int(np.ceil(len(edges_proj) * TARGET_MATCH_RATE))
current_match_count = edge_station_match["nearest_station_id"].notna().sum()
final_edges = edges_proj.copy()

if current_match_count < required_match_count:
    unmatched_edges = edge_station_match[edge_station_match["nearest_station_id"].isna()]
    adjust_plan = []
    for _, match_row in unmatched_edges.iterrows():
        line = edges_proj.loc[match_row["edge_idx"], "geometry"]
        start_point = Point(line.coords[0])
        end_point = Point(line.coords[-1])
        original_len = line.length
        
        # 找离线段所在直线最近的站点
        min_perp_dist = float("inf")
        nearest_foot = None
        for _, s_row in stations_proj.iterrows():
            s_geom = s_row.geometry
            foot = line.line_interpolate_point(line.project(s_geom))
            perp_dist = s_geom.distance(foot)
            if perp_dist < min_perp_dist:
                min_perp_dist = perp_dist
                nearest_foot = foot
        
        # 仅保留垂直距离在1km内、可通过延长匹配的边
        if min_perp_dist <= SEARCH_RADIUS:
            dist_foot_start = nearest_foot.distance(start_point)
            dist_foot_end = nearest_foot.distance(end_point)
            extend_start = max(0, dist_foot_start - original_len/2)
            extend_end = max(0, dist_foot_end - original_len/2)
            # 计算最小延长比例,留1%余量避免边界误差,设置最多延长2倍的阈值
            extend_ratio = min(max(extend_start, extend_end)/(original_len/2) + 0.01, 2)
            adjust_plan.append({
                "edge_idx": match_row["edge_idx"],
                "extend_ratio": extend_ratio
            })
    
    # 按延长幅度从小到大排序,优先调整改动最小的边
    adjust_plan = sorted(adjust_plan, key=lambda x:x["extend_ratio"])
    for plan in adjust_plan:
        if current_match_count >= required_match_count:
            break
        idx = plan["edge_idx"]
        original_line = edges_proj.loc[idx, "geometry"]
        start = np.array(original_line.coords[0])
        end = np.array(original_line.coords[-1])
        direction = end - start
        dir_norm = direction / np.linalg.norm(direction)
        new_half_len = original_line.length/2 * (1 + plan["extend_ratio"])
        mid_point = (start + end)/2
        # 生成沿原方向延长的新线段
        new_start = mid_point - dir_norm * new_half_len
        new_end = mid_point + dir_norm * new_half_len
        final_edges.loc[idx, "geometry"] = LineString([new_start, new_end])
        current_match_count += 1

# 转回WGS84经纬度坐标系输出结果
final_edges_wgs84 = final_edges.to_crs("EPSG:4326")
edge_station_match.to_csv("edge_station_relation.csv", index=False, encoding="utf-8-sig")
final_edges_wgs84.to_file("adjusted_network.gpkg", driver="GPKG", encoding="utf-8")

如果数据量超过10万条边,可将KDTree替换为BallTree、或用dask_geopandas做并行空间计算进一步压缩运行时间。

内容的提问来源于stack exchange,提问作者bsha

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 05:06:05