简介:一套来自北京大学地理数据平台的2000年全国道路交通网矢量图数据,以SHP格式存储,涵盖国道、铁路、高速公路三类线状路网要素,适合GIS学习者和城乡规划、交通研究者用于制图、空间分析与路网特征提取。压缩包共48个文件,以shp、shx、dbf、prj等格式为主,shp保存几何、dbf保存属性、prj定义坐标系,另含少量编辑锁文件,整体约7.08MB。数据以线要素polyline表达道路几何,并带Identity标识字段,便于区分不同路段。目前已有9333人浏览学习。使用者需具备ArcGIS或QGIS基础,在论文或公开成果中正确标注数据来源于北京大学城市与环境学院地理数据平台,既是学术规范,也能有效避免版权争议。
1. 2000年全国道路交通网SHP:先确认它是不是你要的那份路网底图
解压一份名为「2000年全国道路交通网矢量图SHP.zip」的压缩包,第一反应通常是一串疑问:2000年的路网图,放到今天还能干什么?先给结论:如果要做交通基建演变对比、历史路段可达性回溯,或者给规划课题找一份当年路网底图,这份数据是稀缺资源,比买新数据还难找。它把二十多年前全国路网的等级、走向、连通关系定格在了一个时间截面。
它的技术含量不在画得精不精,而在你会不会用。SHP 是 GIS 里最通用的矢量格式,但一套 SHP 由多个同名文件组成,坐标信息藏在 .prj 里,属性表编码可能是 GBK 也可能是 UTF-8,几何可能断线、重线、缺节点。这些坑你今天绕不过去,写进代码里也要处理。
适合读这篇文章的人:手里正好有这份 ZIP 但打不开属性的新手;拿历史路网做分析却发现里程对不上的熟手;以及想系统地把旧路网数据变成可用分析底图的从业者。下面按我处理这类数据的一贯顺序讲。
2. 打开SHP先看三个底账:坐标系、字段表与几何完整性
拿到 SHP 先别急着加载地图。打开同目录下的 .prj 文本文件(用记事本就能看),里面写的是坐标系描述。2000 年前后的全国路网数据,最常遇到三种情况:WGS84 经纬度、北京 54 或西安 80 坐标系、以及直接采用某种投影方式(如等积圆锥)。这三种情况的处理方式完全不同。
判断方法有三步:先看 .prj 内容里有没有 GEOGCS、PROJCS 关键字;再看要素坐标值的量级;最后加载后和已知底图叠一下。快速经验是:如果 X 坐标在 73 到 135 之间、Y 在 3 到 53 之间,说明是经纬度;如果 X 是六位数、Y 是七位数甚至更大,说明是投影坐标系,需要查投影参数再转换。
如果 .prj 缺失,QGIS 会默认当 WGS84 加载。这不是好消息:如果真实坐标是北京 54,默认 WGS84 会整体偏移。此时不要急着用「平移」工具,先把元数据搞清楚再动手。坐标系搞错,后面所有长度统计、叠加分析全作废,这是历史数据最容易翻车的地方。
2.1 从.prj读懂坐标系:经纬度还是投影坐标
先看 .prj 文件。一个典型的 WGS84 经纬度坐标系,文本里会出现GEOGCS["WGS 84",DATUM["WGS_1984"...这样的片段;如果是投影坐标系,开头一般是PROJCS[...,后面跟着投影方法名。2000 年那批数据里,PROJCS出现时往往带 Krasovsky 椭球或 IAG-75 椭球的字样,对应北京 54 和西安 80 两个历史基准。
再看坐标值量级。用 QGIS 打开图层后,在图层属性 → 信息面板里能看到范围(Extent)。如果 X 范围在 73–135 之间且带小数,Y 在 3–53 之间,说明是地理坐标,单位是度。如果 X 变成了 300000 到 500000 这样的六位数,Y 变成了 3000000 以上的七位数,说明是投影坐标,单位是米,而且大概率采用了某种中央经线投影带。
最麻烦的是没有 .prj 的情况。QGIS 加载时会弹「未知 CRS」或直接默认 WGS84,这时候只能靠坐标量级反推。我一般会在图层上放一个已知的 WGS84 世界底图做参考:如果路网整体跑到非洲海岸线上,说明数据本身是投影坐标却被当成经纬度;如果位置大致对但整体偏移几十米到几百米,说明基准面不对,是北京 54 或西安 80 典型特征。
2.2 属性表字段认名:等级、编号与名称
双击图层打开属性表,2000 年路网 SHP 的字段远没有现代数据那么多,但命名习惯五花八门。常见字段和含义如下表。我处理过的一份典型路网数据,字段就是 ID、要素等级、路线编号、名称、长度这五个,其中名称栏大约三成是空的。
| 字段名(常见变体) | 含义 | 使用注意 |
|---|---|---|
| FID / ID | 要素唯一编号 | 删除要素后会自动重排,别当永久主键 |
| CLASS / ROAD_TYPE / 等级 | 道路等级 | 数字或文字,需要按值域推断 |
| RN / NO / 路线编号 | 国省道编号 | 可能为空,国道一般较全 |
| NAME / 名称 | 道路名称 | 空值占比高,别用来做匹配主键 |
| LEN / LENGTH / 里程 | 原始长度字段 | 单位不明时不要直接 SUM,几何重算最保险 |
| LANES / WIDTH | 车道数 / 路宽 | 2000 年数据普遍缺失 |
等级字段是最有用的字段。如果值是数值,先看取值范围:1–4 往往对应国道到乡道四级,1–6 则可能是加了高速和服务道路。如果只有两类值,大概率是主要道路/次要道路的二分类。这些都要记到字段翻译表里,后面做速度假设和分析都靠它。一旦字段含义猜错,整份数据的分析结论都不可信。
2.3 几何初检:一张表给出数据健康度
加载到 QGIS 后,用图层属性 → 信息面板看几个关键指标。我常用下面这张表作为初检清单,每个指标都能指向一个明确的后续动作。
| 指标 | 正常表现 | 异常说明 |
|---|---|---|
| 几何类型 | LineString / MultiLineString | 出现 Point / Polygon 说明混入了非道路要素 |
| 要素数量 | 与全国范围对应,通常在数十万级 | 数量过少(几百条)说明是裁剪版 |
| 坐标范围 | 73–135,3–53(经纬度模式) | 六位数坐标说明是投影坐标系 |
| 字段数量 | 5–15 个 | 只有 1–2 个说明数据被简化过 |
| 是否有 Z/M 值 | 无,或有 Z 无 M | 有 Z 值时三维算法会干预长度计算 |
几何初检的结论直接决定第 3 章的预处理方案:要素量大说明后面打断操作耗时可能很长;字段少说明做不了太细的等级分析;坐标系不对说明必须先转换再算长度和密度。这一步不查清楚,后面所有操作都可能白做。别嫌麻烦,历史数据多花半小时体检,能省后面一整天的排错。
3. 预处理三关:解压、拓扑修复与坐标系转换
拿到 ZIP 先解压,不要直接在 GIS 里双击 ZIP 里的 SHP。部分软件会读取失败或丢失编码信息,尤其是有中文路径名时,容易报「无法打开文件」。解压命令很简单,但解压后的文件检查才是关键。
unzip -q "2000年全国道路交通网矢量图SHP.zip" -d ./road_2000 cd ./road_2000 ls -la | grep -E "\.(shp|shx|dbf|prj|cpg)$"这里的-q是安静模式,避免解压时刷屏,-d指定输出目录。最后一行 grep 列出 SHP 的五个核心伴侣文件:.shp存几何、.shx存索引、.dbf存属性、.prj存坐标系、.cpg存字符编码声明。如果缺了任意一个,先别急着删除原压缩包,重新解压比对一次。
如果发现 ZIP 里有多个文件夹,每个文件夹各有一套 SHP,注意看是否有说明文档:全国路网可能被按省拆分,也可能是总图加分省。我遇到过把全国做成一个大图层的,也遇到过做成几百个省区小图层的。前者适合直接分析,后者需要先合并:QGIS 处理工具箱 → 矢量通用 → 合并矢量图层,把所有分省要素合并成一张全国路网。
3.1 解压与文件伴侣检查:一个要素都不能少
SHP 一族里,.shp、.shx、.dbf三个文件缺一不可:没有.shx,软件建空间索引会失败;没有.dbf,属性表直接消失,要素只剩几何空壳;没有.prj,坐标系全靠猜。还有一个容易被忽略的.cpg,它声明 dbf 的代码页,有它之后打开属性表乱码的概率会小很多。
解压后建议做一次文件完整性检查,除了 ls 列出文件外,还可以用 GDAL 自带工具验证:
ogrinfo -so ./road_2000/road_2000.shp-so是 summary-only 模式,只输出图层概要,不遍历要素。正常返回会显示图层名、几何类型、要素数量、范围四行信息。如果这一步报错,大概率是文件缺失或损坏。如果返回Unable to open,先检查路径里有没有中文或空格,GDAL 对路径敏感,把目录名改成纯英文再试。
3.2 打断、去重、修复几何:让路网真正连通
这是预处理里最耗时的一步。2000 年这版路网的断线和重复问题,比现代导航路网严重得多:同一条国道可能被分成几十段,每个县界处都断开;采集员分段描线时,路口处两条线只是视觉相交,几何上根本没有公共节点。
我按顺序跑四个处理,缺一不可:
- 多部件转单部件(Multipart to singleparts):避免一条路被存成多种几何,为后续分析统一基础。
- 按线分割(Split with lines):用路网自身做分割线,确保每个路口处都有节点。
- 修复几何(Fix geometries):把自相交、无效环修掉。
- 删除重复几何和空几何:直接用对应算法,输入刚才的输出图层。
以 QGIS 为例,处理工具箱里搜索「Split with lines」,输入图层选路网,分割图层也选路网本身,容差默认 0 即可。这个操作的核心逻辑是:让每条线在与其他线相交的位置全部打断,生成真正的公共节点。如果原始数据是投影坐标系,容差单位是米,默认 0 代表严格相交,精度要求高但结果干净。
分割后要素数通常会明显增加,这是正常现象,不代表数据坏了。真正的路口节点建立后,网络分析才能识别转弯、过路口。这一步做完,还要顺手清理「分割出来的碎线」:用属性表选择长度小于 1 米的要素删除。注意,如果原始数据是经纬度坐标系,长度筛选不能直接用米;要先重投影到米制坐标系再筛,否则筛选结果毫无意义。
注意:分割后要素数暴增是正常现象,真正要警惕的是生成了大量长度小于 1 米的碎线——删除时按长度排序先人工看一眼,别把短桥和匝道误删。
3.3 坐标系转换实操:从WGS84到CGCS2000
现在的分析项目多数用 CGCS2000(2000 国家大地坐标系)。如果初检确认数据是 WGS84 经纬度,直接转,用 GDAL 一行搞定:
ogr2ogr -f "ESRI Shapefile" ./output/road_2000_cgcs2000.shp ./road_2000/road_2000.shp \ -s_srs EPSG:4326 -t_srs EPSG:4490 -overwrite -lco ENCODING=UTF-8EPSG:4326 是 WGS84 的 EPSG 代码,EPSG:4490 是 CGCS2000 地理坐标。这个转换在大多数分析和制图场景下够用;如果原始数据其实是北京 54 或西安 80,不能这样直转——基准面不同,结果会有几十到上百米的偏移。-overwrite覆盖已存在的输出,-lco ENCODING=UTF-8指定 dbf 编码为 UTF-8,既解决乱码,也统一后续编码。
转换完成后,用第 2 章的初检表再核一遍坐标范围。如果 X/Y 范围还是投影坐标量级,说明-s_srs写错了。如果转换后图层位置和已知底图叠不上,先回到 2.1 重新核对基准面,别急着做配准。坐标系转换这件事,参数错了不会报错,只会悄悄地把结果偏移,只能靠验证发现问题。
4. 让2000年路网跑起来:网络分析、密度对比与可达性回溯
预处理做完,这份 2000 年路网才真正进入可用状态。这章讲三个最常见的落地分析:网络分析、路网密度对比、可达性回溯。三者都依赖前两章的坐标系和拓扑正确性,跳过预处理直接跑,结果只会是「输出很漂亮,结论全不对」。
4.1 构建网络数据集:参数怎么设
做可达性、最短路径、服务区分析,前提是路网是一张可导航的网络。这也解释为什么第 3 章的打断工作跑不脱:如果路口处没有节点,路径在交叉口就无法转弯,算出来的结果会非常离谱。
常见做法是在 QGIS 里用网络分析工具箱,参数按下表设置:
| 参数 | 设置 | 说明 |
|---|---|---|
| 路网图层 | 打断后的单部件线图层 | 一定不能是原始多部件 |
| 成本类型 | 时间或距离 | 默认距离最简单,时间需要速度字段 |
| 速度字段 | 等级映射速度 | 无字段时用恒定速度 |
| 最大成本 | 60 分钟 | 都市区可缩到 30,县域可放大到 90 |
| 容差 | 0.001 | 在米制坐标下约 1 毫米,跨图层配准时用 |
跑完先检查服务区边界上有没有悬空道路。如果有,大概率是打断时碎线没清理干净,回到 3.2 重新筛选删除。网络分析的输出质量几乎完全取决于输入路网的连通性,这一步的投入产出比最高。
4.2 历史路网密度对比:统计口径与边界
另一种高频用途:把 2000 年路网和近年路网叠到同一套统计格网里,求出密度差值,直观展示路网成长。做法分四步:
- 建格网:矢量生成 → 格网,用投影坐标建 10km × 10km 格网。
- 相交:叠加分析 → 相交,输入为路网,叠加为格网。
- 统计:按格网 ID 汇总长度,用处理工具箱 → 按属性汇总,分组字段选格网 ID,数值字段选 Length,统计方法选总和。
- 计算密度:总长度除以格网面积,得到每平方公里路网里程。
长度必须以米制坐标系计算,经纬度坐标下的 Length 数值没有任何量纲意义。这一点是新手最常踩的坑:在 WGS84 下算出来的「长度」是度,不是米,除以面积后得到密度值毫无意义。
我的经验是统计完把零值格网和异常值格网单独导出来,看是数据缺失还是真没有路。2000 年大量西部县乡道路被漏采是常态,对比结论里要注明这个偏差,否则报告会被同行挑刺。
4.3 可达性回溯:2000年的速度假设
如果要问「2000 年时某地到最近国道/县城要多长时间」,就是可达性回溯。核心是速度假设。2000 年路网和今天差异很大:高速公路少、路窄、县道多断头路。给一个我常用的速度假设表:
| 等级 | 2000 年通行速度(km/h,经验值) |
|---|---|
| 高速公路 | 90 |
| 国道 | 65 |
| 省道 | 45 |
| 县道 | 30 |
| 乡道 | 20 |
这是历史可达性研究里常见的粗略折减做法。如果研究区内有地形数据(如坡度),按坡度再折减,结果更可信。比如坡度超过 5% 的路段,速度按原值打七折计算。速度假设会直接影响等时圈外扩范围,必须写进报告或论文的方法部分,不能静默处理。
跑可达性时我习惯把速度字段直接建到属性表里,用等级字段做一次字段计算器映射,而不是在算法参数里手工填恒定速度。这样后续调整速度假设时,只需改属性表再重跑,不需要重复建网络数据集。
5. 2000年路网SHP避坑指南:五个高频数据坑
历史数据的问题往往不报错,而是悄悄给你一个错误结果。这章列出我在路网分析项目里踩过的五个高频坑,每一条都是先给现象再说原因和解决。
5.1 属性表中文乱码,字段名全是问号
现象:打开属性表,字段名和值全是乱码或问号,完全不可读。
原因:dbf 文件的代码页不是 UTF-8。2000 年数据多用 GBK 或 GB2312,QGIS 默认用 UTF-8 读取,两者不匹配就乱码。
解决:在 QGIS 中重新打开 SHP 时,编码选择 GBK(或 GB2312)。如果还是乱码,查看同目录.cpg文件里声明的代码页,以它为准。批量处理用 GDAL 时,在-lco里指定编码;Python 读 geopandas 时加encoding='gbk'。切记这个编码信息要写进处理记录,传给下游使用者,否则别人接手时又会踩一遍。
5.2 路网在路口全部断开,网络分析建不了图
现象:数据加载和显示都正常,一建网络数据集就报不连通,或路径计算绕远路,明明有直路非要走回头路。
原因:原始数据是分段采集的,路口处只是几何交叉,没有节点。网络分析只能沿着线的节点走,没有节点的交叉口等于不存在。
解决:对路网跑一遍「按线分割」,把交点全切成节点;再删除 1 米以下碎线。删除碎线时先按长度排序人工看一眼,短桥、匝道、收费站引道的长度往往在 5 到 20 米之间,别一刀切。如果分割后仍有局部不连通,用 QGIS 的网络分析插件跑一次「连通性检查」,把未连通的子网单独导出,逐段排查。
5.3 图层整体偏移,叠不上现代底图
现象:路网和影像或现代路网错开几十米到几百米,整体朝一个方向一致平移,而不是局部扭曲。
原因:坐标系基准不同。千禧年前后的数据常用北京 54 或西安 80 底图,标注却写的 WGS84,或者 .prj 缺失被默认成 WGS84。
解决:先核对 .prj 和坐标量级,确认基准后做带参数的坐标系转换。若基准也查不到,选几个明显交叉点做仿射校正(QGIS 配准工具)。但配准是不得已的后悔药,精度依赖控制点质量,结果要注明「有漂移风险」。更重要的是,配准只能纠正平移和旋转,治不了投影变形;能用参数转换解决的问题,就不要用配准。
5.4 要素几百万,有效里程却严重缩水
现象:要素高达百万级,按道路名汇总出来的总里程却比统计年鉴短很多,甚至不到一半。
原因:属性表里的 LEN 字段可能单位不是千米、可能是分段采集时的原始长度而非实际路径长度、也可能是同一条路的多段重复存储。直接从业务字段 SUM 是错误做法,几何长度才是可信的。
解决:用几何长度重算,并以投影坐标系为准。Python 里用 geopandas 的gdf.geometry.length得到的是当前 CRS 下的长度,经纬度 CRS 下这是「度」而非米,必须先to_crs到米制投影再算。重算后和统计年鉴对一下数量级,差 20% 以内都算正常,超过就要怀疑数据缺段。关于这一点,历史路网数据里字段名带长度字样但不可信的比例相当高,养成重算几何的习惯。
5.5 坐标系标注与真实数据不符,跑到海里
现象:按 .prj 的标注做完转换,图层出现在海洋中央,或者坐标范围完全不对,和底图八竿子打不着。
原因:prj 标注的坐标系和实际坐标不是一回事,常见于历史数据的「标错」。这个比乱码还坑,因为软件不会报错,过程和结果看起来都很正常。
解决:别信标注,看数据本身。经纬度模式的坐标范围应该在 73–135、3–53(全国范围);出现六位数说明是投影坐标,需要反推投影参数。用第 6 章的检查脚本直接输出 CRS、范围和几何信息,先把真实身份查清楚再做后续处理。血泪经验:历史数据里 prj 的可信度比现代数据低一个量级,一切以坐标值量级为准。
6. 给2000年路网做自动化体检:一个质量检查脚本
6.1 用打分脚本代替肉眼排查
拿到任何一份历史 SHP,我现在的习惯是先跑一遍检查脚本,把关键质量指标一次性打印出来,再决定要不要做人工处理。脚本不复杂,但能把最容易出问题的五个点全覆盖:
import geopandas as gpd path = "road_2000.shp" gdf = gpd.read_file(path, encoding="gbk") print("CRS:", gdf.crs) print("要素数:", len(gdf)) print("几何类型:", gdf.geometry.geom_type.value_counts().to_dict()) null_count = int(gdf.geometry.isna().sum()) invalid_count = int((~gdf.geometry.is_valid).sum()) empty_count = int(gdf.geometry.is_empty.sum()) if gdf.geometry.is_empty.any() else 0 print("空几何:", null_count, "无效几何:", invalid_count, "空集几何:", empty_count) if gdf.crs and gdf.crs.is_geographic: print("警告: 当前为经纬度坐标系, 长度不是米, 请先投影再计算里程") else: print("总里程(km):", round(float(gdf.geometry.length.sum()) / 1000, 2))逻辑说明:read_file用encoding指定读取代码页,对应第 5.1 节的乱码问题;打印 CRS 和坐标范围,对应 5.5 的坐标系标注问题;geom_type.value_counts()检查是否混入点或面要素;is_valid检查几何有效性,对应 3.2 的自相交问题;最后在投影坐标系下直接求和总里程,和年鉴数据对数量级,对应 5.4 的里程缩水问题。
参数说明:encoding取值要看原数据 dbf 的实际代码页,GBK 和 UTF-8 二选一,猜错就换成另一个;is_valid在百万要素上校验会比较慢,初次检查时可以抽样 10 万条,确认无异常再全量跑;is_empty检查空集几何,部分版本 geopandas 有兼容性差异,所以我先用了any()判断再取数量。
如果脚本跑出异常,按第 5 章的流程修完再回跑一遍,直到报告里没有警告。这个「先体检后干活」的习惯,帮我省掉了大量返工。现在我拿到陌生路网数据的第一件事就是跑这个脚本,看完报告再决定用不用、怎么用——坐标系不明的直接弃用,拓扑不行的修完再看,属性字段太少的按简化版处理。希望帮到你。
本文还有配套的精品资源,点击获取