数据集笔记:剔除不在中环上的点

Overpass API 爬取上海中环geometry-CSDN博客 中的代码是有问题的:不在中环上的点,因为名字里也有中环,也被囊括进来了

0 导入库

python 复制代码
from shapely.geometry import LineString, MultiLineString, Point
from shapely.ops import unary_union
from pyproj import Transformer

1 创建MultiLineString

python 复制代码
mls = MultiLineString([LineString([(lon, lat) for lon, lat in seg]) for seg in lines])
mls

先把每条线段做成 LineString,再打包成 MultiLineString

2 零散线段融合

python 复制代码
ring_lines = unary_union(mls) 
ring_lines

把零散线段融合(会得到一个或多个连通折线)

  • unary_union 会把端点相接的线段合成更长的折线,去掉重复重叠部分;结果可能是:
    • 单条 LineString(如果全部连成一条)
    • 多条 MultiLineString(如果有多段互不相连)

3 经纬度--米制坐标 互转

python 复制代码
to_m = Transformer.from_crs(4326, 32651, always_xy=True)
to_deg = Transformer.from_crs(32651, 4326, always_xy=True)
  • EPSG:4326 = WGS84(经纬度,单位是度)
  • EPSG:32651 = UTM 51N(单位是米,上海附近用这个区带很合适)
  • always_xy=True 强制按照 (x=lon, y=lat) 的次序转换,避免某些坐标系默认把纬度放前面导致的轴顺序混乱。
python 复制代码
def geom_to_m(geom):
    if geom.geom_type in ("LineString", "LinearRing"):
        xs, ys = zip(*[to_m.transform(x, y) for x, y in geom.coords])
        return LineString(list(zip(xs, ys)))
    elif geom.geom_type == "MultiLineString":
        return MultiLineString([geom_to_m(g) for g in geom.geoms])
    else:
        raise ValueError("unsupported geometry: " + geom.geom_type)


ring_m = geom_to_m(ring_lines)

在"度"单位下做缓冲会得到"度"为单位的距离,没有实际长度意义;所以先投影到"米"。

这里只处理了线类型(LineString / MultiLineString)

4 建立缓冲区

python 复制代码
corridor_m = ring_m.buffer(400)
corridor_m

4.1 仅保留最大连通分量走廊

python 复制代码
corridor_m = max(corridor_m.geoms, key=lambda g: g.area) if corridor_m.geom_type == "MultiPolygon" else corridor_m
corridor_m
  • 这是以主线为中心线,双侧各 400m 的"带状区域",整体"带宽"约 800m。
    • 如果主线是闭合环,缓冲后通常得到"甜甜圈"样式的多边形(Polygon,带内孔);如果主线不连通,会得到 MultiPolygon。
    • 第二行只保留面积最大的连通块,避免主线之外某些零碎线段也生成小缓冲面,误把附近点识别成"好点"。

5 过滤点

python 复制代码
good_points, bad_points = [], []
for (lat, lon) in points:
    x, y = to_m.transform(lon, lat)
    if corridor_m.contains(Point(x, y)):
        good_points.append((lat, lon))
    else:
        bad_points.append((lat, lon))
  • points 存的是 (lat, lon) 顺序,这里转换时写成 to_m.transform(lon, lat)
  • 用 contains 判断点在不在多边形内部
    • contains 不包括边界,落在边界上的点会被判为 False
    • 如果想边界也算内,可以改成 corridor_m.covers(Point(x, y))

6 输出结果

python 复制代码
print(f"原始点数: {len(points)}")
print(f"保留(走廊内): {len(good_points)}")
print(f"异常(走廊外): {len(bad_points)}  ← 这些就是松江/杨浦等误判点")
'''
原始点数: 1369
保留(走廊内): 1327
异常(走廊外): 42  ← 这些就是松江/杨浦等误判点
'''

7 保存结果

python 复制代码
import csv, json
from shapely.geometry import LineString, mapping

name = "sh_middle_ring"

with open(f"{name}.csv", "w", newline="", encoding="utf-8") as f:
    w = csv.writer(f)
    w.writerow(["lat", "lon"])
    w.writerows(good_points)

8 求中环以内区域的geometry

python 复制代码
from shapely.geometry import MultiPoint, mapping
import numpy as np, json


pts = [(lon, lat) for lat, lon in good_points]
'''
good_points 里存的是 (lat, lon)(纬度在前,经度在后)。

但是 Shapely 一律用 (x, y) = (lon, lat),也就是 (经度, 纬度)。
'''

poly = MultiPoint(pts).convex_hull
'''
MultiPoint(pts):把点列表变成一个 Shapely 的多点对象。
.convex_hull:求点集的凸包,即所有点都在里面或边界上的最小凸多边形。
'''

poly

8.1 保存poly

python 复制代码
with open("sh_middle_ring_poly.wkt", "w", encoding="utf-8") as f:
    f.write(poly.wkt)

9 polygon用folium可视化

python 复制代码
import folium

# 建立底图(以上海市中心为例)
m = folium.Map(location=[31.23, 121.47], zoom_start=11)

# 提取 poly 的外边界坐标 (lon, lat) → (lat, lon)
coords = [(lat, lon) for lon, lat in poly.exterior.coords]
#folium 需要的是经纬度坐标 (lat, lon) 顺序,所以要从 poly.exterior.coords 里取 (lon, lat) 再转换回来

# 添加多边形
folium.Polygon(
    locations=coords,
    color="blue",       # 边框颜色
    weight=2,           # 边框粗细
    fill=True,          # 是否填充
    fill_color="blue",  # 填充颜色
    fill_opacity=0.3    # 填充透明度
).add_to(m)

# 保存为 html 文件
m