简介:这份北京城区道路矢量数据包面向交通规划、GIS制图与城市建模方向的从业者和学习者,提供主城区范围内精细化路网数据,可直接用于路径分析、地图叠加与空间可视化。压缩包共22个文件,约8.08MB,以Shapefile全套组件为主,包含shp、shx、dbf、prj、cpg、sbn、sbx及shp.xml等,另附png预览图、txt说明与Python脚本,覆盖主干道、次干道、支路及部分街巷,坐标系以prj文件为准,属性字段含道路名称、等级、方向、车道数等基础信息。文件命名统一、结构清晰、无冗余,可导入ArcGIS、QGIS、SuperMap等平台直接编辑与叠加分析。目前已有71人学习下载,适合需要快速获取北京路网底图、开展交通研究或制图练习的读者参考使用。
1. 北京路网 Shapefile 数据包:从拿到文件到跑通第一条空间查询
做城市分析的人多半遇到过这种局面:底图能看不能用,在线瓦片拉下来是一张张图片,想按道路等级筛选、想算某条主干道的长度、想把 POI 沿街挂点,全卡在“没有矢量”这一步。北京路网 Shapefile 数据包就是冲着这个痛点来的——它把城区道路以矢量要素的形式落成一套标准 Shapefile 文件,每条道路是一个带属性的几何对象,能直接进 GIS 软件、能进 Python 脚本、能进空间数据库。适合做交通可达性、选址分析、城市形态研究、地图可视化底图,以及需要把路网当“骨架”去挂接其他数据的场景。它不解决数据时效和精度问题,但解决“有没有、能不能算”的问题,这是绝大多数分析项目的第一步。
2. Shapefile 到底是什么:拆开这套北京路网数据包的内部结构
2.1 一套 Shapefile 不是单个文件
很多人第一次拿到 Shapefile 会懵:明明说是一个数据,怎么解压出来一堆同名不同后缀的文件。这不是打包失误,Shapefile 本身就是一种多文件协同的格式,ESRI 当年设计时把几何、属性、索引拆开存,缺一个都可能打不开或者属性丢失。北京路网数据包解压后,核心是这几个:
| 后缀 | 作用 | 缺失后果 |
|---|---|---|
| .shp | 存几何形状,点线面的坐标 | 没有它整个数据不存在 |
| .shx | 几何索引,加速定位 | 部分软件能重建,但读取变慢 |
| .dbf | 存属性表,道路名称、等级等 | 只剩空壳几何,没法筛选 |
| .prj | 存坐标系定义 | 坐标能读但不知道落在哪,叠加必错位 |
| .cpg | 存属性表字符编码 | 中文道路名可能变乱码 |
我一般拿到手先看这五个在不在,尤其是 .prj 和 .cpg。前者决定你能不能和别的数据叠上,后者决定中文名会不会变成问号。北京路网这种带中文属性的数据,.cpg 里通常是 UTF-8 或 GBK,打开前先确认,能省掉后面一堆乱码排查。
2.2 坐标系:北京路网最容易被忽略的坑
北京城区数据常见的坐标系有两类:一类是地理坐标系 WGS84 或 CGCS2000,单位是度;一类是投影坐标系,比如高斯克吕格或 UTM,单位是米。区别很实在——你要算道路长度、做缓冲区,必须用投影坐标系,因为按“度”算出来的长度没有物理意义。
判断方法很简单,看 .prj 文件内容,或者用 Python 读一下:
import geopandas as gpd # 读取北京路网数据,指定编码避免中文乱码 gdf = gpd.read_file("beijing_roads.shp", encoding="utf-8") # 查看坐标系定义 print(gdf.crs) # 输出示例:EPSG:4326 表示 WGS84 地理坐标 # 输出示例:EPSG:4547 表示 CGCS2000 高斯投影(北京带) # 查看数据范围和要素数量 print(gdf.total_bounds) # [minx, miny, maxx, maxy] print(len(gdf)) # 道路要素总数如果 crs 显示的是 4326 这类地理坐标,而你要算长度,先转投影:
# 转为适合北京的投影坐标系,EPSG:4547 为 CGCS2000 三度带 gdf_proj = gdf.to_crs(epsg=4547) # 此时长度单位是米,结果才有意义 gdf_proj["length_m"] = gdf_proj.geometry.length print(gdf_proj["length_m"].describe())参数说明:to_crs里的 epsg 代码要选对北京所在投影带,选错会导致长度有系统性偏差。4547 是常见的北京区域 CGCS2000 投影,具体以数据实际覆盖范围为准。geometry.length对线要素返回长度,对点返回 0,对多边形返回周长,用之前确认几何类型。
2.3 属性表:决定你能筛出什么
路网数据值不值钱,一半看几何,一半看属性。北京路网 Shapefile 的属性表通常包含道路名称、道路等级、类型等字段。字段名可能是中文也可能是英文缩写,打开先看一眼:
# 查看所有字段名和类型 print(gdf.dtypes) # 查看前几行属性,了解字段内容 print(gdf.drop(columns="geometry").head(10)) # 统计道路等级分布,判断数据完整度 if "道路等级" in gdf.columns: print(gdf["道路等级"].value_counts())这一步的目的是搞清楚:有没有等级字段、等级分几类、名称字段是否完整。如果等级字段缺失,你只能按几何长度或拓扑关系做粗筛;如果有等级,就能快速分离高速、主干道、次干道、支路。常见做法是先把等级字段的值列出来,看看有没有空值或异常编码,再决定筛选逻辑。
3. 把北京路网跑起来:从加载到空间查询的完整操作
3.1 环境准备与依赖安装
处理 Shapefile 最顺手的组合是 Python + GeoPandas,底层是 GDAL/OGR,能读能写能算。装的时候注意版本匹配,GDAL 和 Fiona 版本不对会直接报错。
# 建议用 conda 装,依赖关系处理得干净 conda install -c conda-forge geopandas fiona shapely pyproj # 或者 pip 装,但 GDAL 可能需要单独处理 pip install geopandas shapely pyproj fiona装完验证一下:
import geopandas as gpd import shapely print(gpd.__version__) print(shapely.__version__)如果 import 报错,多半是 GDAL 没装好。Windows 上常见做法是用 conda 而不是 pip,能避开编译问题。Linux 和 macOS 一般 pip 也能过,但系统里最好有 gdal 库。
3.2 加载数据并做第一次可视化
加载之后先画一眼,确认数据范围和形状对不对:
import matplotlib.pyplot as plt # 读取数据 gdf = gpd.read_file("beijing_roads.shp", encoding="utf-8") # 快速出图,看整体形态 fig, ax = plt.subplots(figsize=(12, 12)) gdf.plot(ax=ax, linewidth=0.3, color="gray") ax.set_title("Beijing Road Network") ax.set_axis_off() plt.show()如果图出来是一片空白或者形状明显不对,先查坐标系和范围。常见情况是数据本身没问题,但 matplotlib 的坐标轴把度当成了大数,或者投影坐标数值太大导致比例失调。这时候看gdf.total_bounds的数值量级就能判断:几百以内是度,几十万是米。
3.3 按道路等级筛选并导出子集
拿到全量路网后,通常只需要某一等级的路做分析。比如只保留主干道以上:
# 先看等级字段有哪些值 print(gdf["道路等级"].unique()) # 按等级筛选,假设字段值为中文 main_roads = gdf[gdf["道路等级"].isin(["高速", "主干道"])] # 导出为新的 Shapefile main_roads.to_file("beijing_main_roads.shp", encoding="utf-8") print(f"筛选后要素数:{len(main_roads)}")参数说明:isin里的值必须和属性表里的实际值完全一致,包括中英文和空格。导出时指定encoding="utf-8"能避免中文属性在新文件里乱码。如果导出后打开发现属性丢失,检查是不是 .dbf 字段名超长被截断——Shapefile 的字段名有 10 字符限制,中文名容易被截,必要时先重命名字段再导出。
3.4 空间查询:找出某点周围的道路
这是路网数据最常用的操作之一。比如给一个坐标,找出 500 米内的所有道路:
from shapely.geometry import Point # 定义中心点,这里用北京某地坐标示例(投影坐标下单位是米) center = Point(450000, 4420000) # 需替换为实际投影坐标 # 建立 500 米缓冲区 buffer = center.buffer(500) # 用缓冲区裁剪路网 nearby_roads = gdf_proj[gdf_proj.intersects(buffer)] print(f"500米范围内道路数:{len(nearby_roads)}") print(nearby_roads[["道路名称", "道路等级"]].head())逻辑说明:buffer生成一个圆形多边形,intersects判断每条道路几何是否与这个圆相交。注意这里用的是投影坐标,500 才是 500 米;如果数据还是地理坐标,buffer(500)会变成 500 度,结果完全错误。这是新手最容易翻车的地方,坐标没转就做距离运算,出来的结果自己都不敢信。
4. 避坑与排查:北京路网 Shapefile 的五个血泪经验
4.1 中文属性乱码
现象:打开数据后道路名称显示为乱码或问号。 原因:.dbf 文件的字符编码和读取时指定的编码不一致,常见是数据存的是 GBK 但用 UTF-8 读。 解决:先试encoding="gbk",再试encoding="utf-8"。如果都不行,用chardet检测 .dbf 编码,或者用 QGIS 打开看属性是否正常,QGIS 会自动识别编码。
4.2 坐标系缺失导致叠加错位
现象:把路网和行政区划、POI 叠在一起,位置整体偏移几百米甚至几公里。 原因:.prj 文件丢失,或者两个数据坐标系不一致但没做转换。 解决:先确认 .prj 是否存在,不存在就根据数据来源补一个。叠加前统一用to_crs转到同一坐标系,不要靠肉眼对齐。
4.3 几何无效导致空间运算报错
现象:做 buffer、intersect 时抛出TopologyException或返回空结果。 原因:部分道路几何自相交或坐标重复,属于无效几何。 解决:运算前做一次修复:
from shapely.validation import make_valid # 修复无效几何 gdf["geometry"] = gdf["geometry"].apply( lambda geom: make_valid(geom) if not geom.is_valid else geom )修复后再做空间运算,成功率会高很多。如果修复后仍有问题,检查是否有空几何或 None 值,先剔除再算。
4.4 字段名截断导致属性丢失
现象:导出新 Shapefile 后,某些属性列不见了或列名变了。 原因:Shapefile 格式限制字段名最多 10 个字符,中文或长英文名会被截断或丢弃。 解决:导出前把字段重命名为短英文名,或者改用 GeoPackage(.gpkg)格式,没有字段名长度限制,也是现在更推荐的存储格式。
4.5 大数据量下读取缓慢
现象:路网要素几十万条时,read_file要等很久,内存占用高。 解决:只读需要的列和范围:
# 只读必要字段,减少内存 gdf = gpd.read_file( "beijing_roads.shp", encoding="utf-8", columns=["道路名称", "道路等级", "geometry"] ) # 或用 bbox 限定读取范围,只读某个区域 from shapely.geometry import box area = box(minx, miny, maxx, maxy) gdf_sub = gpd.read_file("beijing_roads.shp", bbox=area)columns参数减少属性加载,bbox参数只读指定范围内的要素,两者结合能显著提速。
5. 进阶技巧:用北京路网做可达性分析的一个实用套路
路网数据最值钱的用法之一是做可达性——给定若干起点,算沿道路到其他点的最短路径距离。这里不展开完整算法,说一个我常用的轻量套路:把路网转成图结构,用 NetworkX 算最短路径。
import networkx as nx from shapely.geometry import LineString # 假设 gdf_proj 是投影后的路网 G = nx.Graph() # 把每条道路的起点和终点作为节点,道路长度作为边权重 for idx, row in gdf_proj.iterrows(): geom = row.geometry if isinstance(geom, LineString): coords = list(geom.coords) start = coords[0] end = coords[-1] length = geom.length G.add_edge(start, end, weight=length, name=row.get("道路名称", "")) print(f"图节点数:{G.number_of_nodes()},边数:{G.number_of_edges()}")逻辑说明:这里把每条道路的两个端点当节点,道路长度当权重,构建一个无向图。实际路网中道路是相交的,但 Shapefile 里每条道路是独立要素,端点不一定重合,所以这个图是“简化版”,适合快速估算,不适合精确导航。要精确做,得先做拓扑打断,让相交处生成节点,这一步用shapely.ops.unary_union或 PostGIS 的ST_Node更合适。
算最短路径:
# 选两个节点算最短路径 source = list(G.nodes())[0] target = list(G.nodes())[100] try: path = nx.shortest_path(G, source=source, target=target, weight="weight") total_length = nx.shortest_path_length(G, source=source, target=target, weight="weight") print(f"路径节点数:{len(path)},总长度:{total_length:.2f} 米") except nx.NetworkXNoPath: print("两点之间不连通,检查路网是否断开")参数说明:weight="weight"表示按边权重算最短,不指定就是按跳数。NetworkXNoPath说明两个节点不在同一连通分量里,常见于路网有断头路或数据不完整。这时候可以先用nx.connected_components看图的连通性,判断数据质量。
我一般会先跑一遍连通分量统计,如果最大连通分量占比不到 90%,说明路网打断有问题,得回去检查数据。这个习惯帮我省过好几次“算出来路径绕地球一圈”的尴尬。
从那以后我每次拿到新的路网 Shapefile,都强制走一遍:看 .prj、转投影、查几何有效性、跑连通性。四步走完再动分析,基本不会出大问题。希望帮到你。
本文还有配套的精品资源,点击获取