简介一套来自北京大学地理数据平台的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–1353–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现在的分析项目多数用 CGCS20002000 国家大地坐标系。如果初检确认数据是 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 ENCODINGUTF-8EPSG:4326 是 WGS84 的 EPSG 代码EPSG:4490 是 CGCS2000 地理坐标。这个转换在大多数分析和制图场景下够用如果原始数据其实是北京 54 或西安 80不能这样直转——基准面不同结果会有几十到上百米的偏移。-overwrite覆盖已存在的输出-lco ENCODINGUTF-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 或 GB2312QGIS 默认用 UTF-8 读取两者不匹配就乱码。解决在 QGIS 中重新打开 SHP 时编码选择 GBK或 GB2312。如果还是乱码查看同目录.cpg文件里声明的代码页以它为准。批量处理用 GDAL 时在-lco里指定编码Python 读 geopandas 时加encodinggbk。切记这个编码信息要写进处理记录传给下游使用者否则别人接手时又会踩一遍。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, encodinggbk) 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 章的流程修完再回跑一遍直到报告里没有警告。这个「先体检后干活」的习惯帮我省掉了大量返工。现在我拿到陌生路网数据的第一件事就是跑这个脚本看完报告再决定用不用、怎么用——坐标系不明的直接弃用拓扑不行的修完再看属性字段太少的按简化版处理。希望帮到你。本文还有配套的精品资源点击获取