简介这份四川省道路数据包以矢量格式覆盖全省道路体系面向 GIS 分析、城乡规划、交通制图等场景供需要分级路网数据进行空间分析与可视化的人员使用。数据包含城市一级至四级道路、高速、国道、省道、县道、乡道以及 OSM 来源的铁路、轨道交通与各级道路类型共16种道路矢量数据不同来源在分类口径与覆盖范围上既相互补充也有一定重复可用于交叉校验与深入考究。压缩包共97个文件以 shp 主文件为核心同时提供 dbf 属性表、prj 投影、shx 索引、cpg 编码和 xml 元数据覆盖道路分级、制图出图与空间分析所需的基本结构便于在 ArcGIS、QGIS 等平台直接加载整体大小146.64MB。目前已有931人学习下载适合需要对道路等级、路网密度和空间分布进行制图或分析的研究者参考。1. 四川省道路分级矢量数据一份乡道级底图意味着什么做区域规划、物流网络分析或者应急调度的人大概率都遇到过同一个窘境手里握着全省的公路概览图放大到乡镇一级路网断的断、缺的缺乡道更是只剩几条主干。这个时候你才会意识到一份真正“精确到乡道”的省级道路矢量数据有多稀缺。标题里这份四川省道路数据核心卖点就一个基于最新路网现状做了分级并且粒度沉到了乡道这一层不再只是国省道几张皮。它面向的典型场景很具体做县域路网可达性分析、规划客货邮融合线路、或者给乡镇做交通现状摸底。对这些活儿来说高速和国道只是骨架真正决定最后几公里能不能打通、站点怎么布、路线怎么绕的恰恰是县道和乡道这两级。这份数据把分级做全意味着你不必再东拼西凑去找乡镇级路网做融合一份数据包就能把基座搭起来。这篇笔记就沿着“怎么打开、怎么用、怎么避坑”的顺序把实际会碰到的操作和问题逐个说透。2. 数据包结构解析分级逻辑、文件内容与坐标系选型2.1 解压之后先看什么shapefile与文件组件的对应关系拿到 .rar 压缩包第一反应别急着拖进 GIS 软件先解压看目录结构。常见的省级路网数据会按 shp 格式组织一个完整的要素类至少包含 .shp几何、.shx索引、.dbf属性表、.prj坐标系、.cpg字符集这几个文件。如果解压后只看到一个孤零零的 .shp那大概率是发布方漏了配套文件后边加载时会有很大的麻烦。在 Windows 下建议用 7-Zip 打开 rar解压到纯英文路径因为 ArcGIS 或 QGIS 对中文路径以及带空格路径的容忍度不一致尤其老版本容易报 “Unable to open feature class” 或找不到数据源。打开后优先看 .prj 文件里的坐标系定义。四川全省范围的矢量数据如果做省级宏观分析通常用地理坐标系加 Albers 等积投影或 UTM 分带投影如果做县域尺度可能用 CGCS2000 / 3-degree Gauss-Kruger zone。用文本编辑器打开 .prj如果看到 “GCS_China_Geographic_Coordinate_System_2000” 或 “China_2000_3_Degree_GK_CM_105E” 这类描述说明数据已经是 CGCS2000 框架下的处理起来不会在投影转换上吃太多亏如果出现 “D_WGS_1984”就要注意它可能是早期采集成果和现在常见的 2000 国家大地坐标系底图叠加时会有几十米到百余米的偏移。2.2 图层分级字段道路等级的编码规则与符号化打开属性表dbf之后重点找等级字段常见命名有 “kind”“type”“class”“grad”“road_class” 等。由于各地数据加工习惯不同字段的枚举值也会差异很大。正常的一套分级编码大概分五种高速公路、国道、省道、县道、乡道有的还会分出一级公路、二级公路等公路技术等级。但如果属性表里只有行政等级字段没有技术等级字段做路由分析时可以先用行政等级替代只是要注意它不反映路面宽度和实际通行能力。四川省内乡道编码一般以 “Y” 或 “V” 开头比如 Y001、Y014 这类路线编号。这里有个常见坑同一份 shp 的 dbf 里乡道的 route_number 字段有时候会有前缀有时候没有。做关联时先做字段去空格与统一大小写处理再匹配路线编码避免漏提或错提。符号化方面QGIS 里按 “kind” 字段做分类渲染是最快的观察方式。给高速公路配红色、国道配橙色、省道配浅绿、县道配蓝色、乡道配灰色一眼就能看出路网的结构和密度分布。ArcGIS Pro 里同样在 Symbology 面板选择 Unique Values选中等级字段后应用。渲染完之后不要急着导出图片先按等级统计一下各层要素数量。正常来讲乡镇级路网数据中乡道要素的数量应该占到总数的 50% 到 70%。如果乡道占比过低说明这份数据的挂接情况需要进一步确认。2.3 数据包之外为什么“最新”不等于“现势性最强”标题里的“最新”指的是某个版本时点的数据更新状态但是对GIS数据来说“最新”不代表“今天就是对的”。一份2023年发布的数据其采集时点在2022年甚至某些偏远乡道修通时间在发布之后数据里就不会有这条路。这就意味着凡是做开工类、验收类、报审类的工作必须外业核查。内业做规划分析时则可以把这份数据当作基准底图结合遥感影像或在线地图做局部的补路操作。提示拿到数据包之后第一件事是检查 .prj 和 .cpg 文件是否存在。.cpg 缺失会导致 dbf 中的中文路名在 QGIS 中变成乱码。解决方法是在 QGIS 的数据源编码设置里强制指定 UTF-8 或 GBK具体取决于原数据的字符集。这属于老生常谈但真遇到了还是能卡住一下午。3. 在 QGIS 与 ArcGIS 中加载这套省域道路数据坐标系、图层分组与基础检查3.1 坐标系转换把数据切到 CGCS2000 统一底图上如果你手里的底图是天地图或者影像服务坐标系往往是 CGCS2000 经纬度而这份道路数据可能是投影坐标系。两步操作比较稳妥第一步用 QGIS 的 “EPSG:4490” 去叠加遥感底图先看空间位置对不对。第二步如果项目要求以 CGCS2000 3度分带 Gauss-Kruger 作为工作坐标系右键图层选择 Export → Save Features As在 CRS 里选择对应分带。注意四川跨了高斯投影多个分带东西向横跨较大选择分带时通常以省会或项目区中心经度确定比如成都一带用 CM 105E达州一带用 CM 108E。假定你在 QGIS 里要统一坐标系最小操作如下# 在 QGIS 的 Python 控制台或 Processing 工具箱中用 ogr2ogr 做一次坐标系转换 # 假设源数据是 WGS84 经纬度目标为 CGCS2000 / 3-degree Gauss-Kruger CM 105E ogr2ogr -f ESRI Shapefile \ -t_srs EPSG:4527 \ -lco ENCODINGUTF-8 \ D:/output/roads_gk.shp \ D:/source/rds.shp这段命令的意思是从源矢量文件读取几何加上投影定义输出到新的 shapefile。-t_srs EPSG:4527指定目标坐标系EPSG:4527 对应 CGCS2000 三度分带中央经线 105 度投影。-lco ENCODINGUTF-8强制输出属性表字符集避免中文路名变成乱码。跑完之后在 QGIS 里重新加载输出文件叠加一份在线卫星影像检查道路和实际路网是否对位。如果对位偏差小于 20 米说明原数据精度尚可如果偏差在 100 米以上可能是坐标系选错需要回到 .prj 重新确认原数据基准。3.2 按乡道级别做条件查询快速定位某个乡镇的道路归属精确到乡道的最大价值在于能够按乡镇编码单独抽出路网。假如你要研究四川省某县域的乡道覆盖情况先用行政区划数据叠加再按“乡镇代码”字段做空间连接。但前提是属性表里含有乡镇行政区划代码。有的数据包里这个字段叫“TOWN_CODE”有的叫“XZQDM”。如果缺失也不要慌用空间关联来补把乡道图层和乡镇界图层做 Join Attributes by Location让每条乡道继承所在乡镇的行政区划代码。常见做法是选择 “intersects” 规则并勾选 “one-to-one”这样不会因为一条路横跨两个乡镇而重复统计。QGIS 里做这个操作不需要写代码但底下的逻辑值得说清楚。空间连接的规则本身是黑匣子很多新手习惯默认选 intersect却不知道一条跨乡镇的乡道会被同时关联到多个乡镇导致按乡镇统计里程时出现双重计数。稳妥的替代方法是用 “within” 规则即只把质心落在乡镇界内的路分配给该乡镇。对于长线道路经过多个乡镇的情况则应先做线要素切割。用 v.split 工具GRASS或 QGIS 的 Split Lines by Length 工具把长线打断成短段再做空间关联这样统计结果才符合行业惯例。# 如果你更习惯用 GeoPandas 做空间关联这段代码可以控制关联规则 import geopandas as gpd road gpd.read_file(D:/output/roads_gk.shp, encodingutf-8) town gpd.read_file(D:/source/town.shp, encodingutf-8) # 关键参数predicatewithin 避免跨乡镇重复挂接 joined gpd.sjoin(road, town[[NAME, XZQDM, geometry]], howleft, predicatewithin) # 按乡镇统计乡道里程单位公里 joined[len_km] joined.geometry.length / 1000 summary joined.groupby(XZQDM)[len_km].sum().reset_index() summary.columns [乡镇代码, 乡道总里程] summary.to_excel(D:/output/town_road_stat.xlsx, indexFalse)这段代码先读取道路和乡镇两个矢量然后在空间连接时采用 within 谓词即只有当道路几何完全落在乡镇面内才发生关联。对于乡道这类短距离道路within 是安全的但对于跨乡镇的国道省道千万不要用 within 替代 intersect否则这些长线段会全部丢失关联。对于国道省道的里程统计正确做法是先按乡镇界做要素切割再分别对每一段做空间关联。groupby(XZQDM)之后汇总的就是每个乡镇的乡道总里程结果直接写入 Excel方便后续和统计年鉴核对。3.3 构建拓扑检查断头路和伪节点的快速发现在加载完数据之后、动手分析之前建议先给道路数据做一次拓扑检查。QGIS 的拓扑检查器或者 GRASS 的 v.clean 都可以用。调整好 snapping tolerance 后逐一检查 dangle 节点悬空点和 duplicate重复线。由于整个四川省数据量大直接对全要素跑 v.clean 会耗时较久建议按照市州或区县范围分块处理。比如先用行政区划筛选出成都范围内的道路再对该子集做拓扑检查。这样既能把检查时间控制在可接受范围还能避免全省数据一条线断裂导致后续 routing 网络不连通。拓扑检查不是可选项尤其是要做路网分析时断头路会直接导致路径规划失败这背后往往是原始矢量化的遗漏或者属性断裂。4. 乡道数据的属性清洗与里程统计字段拼接、里程计算与坐标偏移校正4.1 字段清洗中文路名乱码、空格、重复记录的处理顺序属性表里的脏数据主要分三类乱码、空格、重复。乱码的根因多半是字符集声明不一致.cpg 文件写的是 UTF-8实际 dbf 里存的是 GBK。在 QGIS 里打开属性表看到 “鏄亾” 这种字样基本可以判定是编码错位。处理时不要一个个字段手动改直接用 ogr2ogr 重新导出并指定编码更高效。# 统一转出为 UTF-8 编码的 GeoPackage避免 dbf 的编码兼容性问题 ogr2ogr -f GPKG \ -lco ENCODINGUTF-8 \ -nln sd_road_utf8 \ D:/output/sd_road.gpkg \ D:/source/乡道_shp/sd_road.shpGeoPackage 在编码上比 dbf 干净至少不需要再操心字段名是否被截断。转完之后检查 “ROUTE_NUM” 这类字段看一下是否含有前导空格或全角字符。常见做法是在 QGIS 属性表里用 Field Calculator 建一个新字段表达式写trim(replace(ROUTE_NUM, ,))把全角空格和半角空格一起清掉。重复记录检查则利用 route_num 加 begin_station、end_station 组合字段来判重。统计里程要特别留意坐标系。如果你的数据还是经纬度地理坐标直接用$length计算的结果是度不是米。必须先投影到适合的单位再计算长度。下面是 GeoPandas 中正确计算里程的方式import geopandas as gpd road gpd.read_file(D:/output/roads_gk.shp, encodingutf-8) # 检查坐标系必须是有单位的投影坐标系 print(road.crs) # 如果显示 EPSG:4490 这类地理坐标先投影到 CGCS2000 3度带 if road.crs.is_geographic: road road.to_crs(epsg4527) # CM 105E四川省中部适用 # 计算单位米 road[len_m] road.geometry.length road[len_km] road[len_m] / 1000 print(road.groupby(kind)[len_km].sum())这段代码的关键在于if road.crs.is_geographic判断它避免了拿经纬度去算长度的低级错误。EPSG:4527 是 CGCS2000 三度带投影长度单位是米计算结果可以直接用。如果项目区偏东比如在达州一带就改成 EPSG:4529CM 108E否则横向距离会产生几十米到几百米的误差。分组统计时按 kind 字段汇总可以得到各等级道路的总里程。这个结果和统计年鉴做交叉验证时通常偏差在 5% 以内都是正常范围超过 10% 就要回去查坐标转换和要素遗漏问题。4.2 坐标偏移校正和影像底图对不上怎么办打开数据叠加卫星影像时如果乡道和影像中的道路走向一致但整体挪了一个固定距离比如整条路向东偏了 80 米这就不是采集草率而是坐标系基准漂移常见于 WGS84 和 CGCS2000 混用。解决思路分两种已知偏移量做平移或采集控制点做仿射变换。平移操作在 QGIS 里可以用 Affine Transform 工具在 Processing Toolbox 里找到它填 X 偏移量和 Y 偏移量。但偏移量怎么确定一种方法是找一个明显的道路交叉口在影像上定位一个点再在道路图层上定位对应点两套坐标的差值即为偏移量。至少取三个均匀分布的点算平均值。如果偏移在局部区域不一致也就是有的地方偏 30 米有的地方偏 120 米那就不是平移能解决的问题了需要做空间校正。ArcGIS 里的 Spatial Adjustment 工具支持橡皮页变换QGIS 里则用 Vector Bender 插件。操作路径是先加载影像作为参考底图在道路图层上选取控制点对至少四个点最好 8 个以上分布到县城两端以及中间过渡地带然后执行仿射或样条变换。空间校正属于后处理手段在数据质量报告中要如实记录。否则下一个接手的同事拿这份校正后的数据做精度评估时会一头雾水。# 用 pyproj 做七参数转换适用于同椭球或不同基准间的精确转换 from pyproj import Transformer from pyproj import CRS crs_wgs84 CRS.from_epsg(4326) crs_cgcs2000 CRS.from_epsg(4490) # 注意这里仅作数据基准重定义不包含椭圆差偏移的七参数 transformer Transformer.from_crs(crs_wgs84, crs_cgcs2000, always_xyTrue) x_new, y_new transformer.transform(104.5, 30.5) print(x_new, y_new)这段代码演示的是 WGS84 经纬度直接转 CGCS2000 经纬度。需要注意EPSG:4326 和 EPSG:4490 都是经纬度坐标系但基准不同WGS84 与 CGCS2000 在理论上存在厘米级差异实际应用中大多数情况可以忽略。真正需要七参数的是老北京54或西安80数据转到 CGCS2000那种转换不能靠 EPSG 代码直接搞定必须有控制点计算七参数再套用。所以拿到一份道路数据发现和影像对不齐时先判断是单纯投影定义错误还是基准转换缺失后者需要更多控制点不能指望一段代码就万事大吉。4.3 乡道编号连续性与路线完整性核查乡道数据最扎心的问题还不是坐标而是路线编号对不上。比如某乡镇 2020 年修了一条新路路线编号规划成 Y020但数据里只有 Y019 和 Y021Y020 这一条要么缺失要么编号被别的老路占用了。做交通规划时这种断号情况会导致按编号统计路线里程时出现偏差。核查方法很简单把属性表里的 route_num 提出来去重后按序号排序观察跳号位置。再用道路图层按编号做 Dissolve把同一条路的多段线合并成一条要素检查合并后的要素条数和编号列表是否一一对应。这样一个流程走下来基本能把这套矢量数据的完整度摸到八九成。注意Dissolve 按路线编号合并后会出现一条乡道横跨多个乡镇面的情况。这是正常现象不要因此判定数据错误。需要按行政区统计时先沿乡镇界切割再关联统计才能得到正确里程。5. 避坑与常见问题排查处理省域乡道级矢量数据时的 5 个典型翻车现场5.1 现象shp 加载进去什么都有但图层是空的原因通常有三个一是几何字段损坏二是空间过滤条件太苛刻三是 dbf 的记录数超过 shapefile 2GB 上限被截断了。省域乡道数据要素数量动辄几十万条shapefile 很容易逼近 2GB 文件大小限制超过之后数据只显示一部分。解决方法是直接转 GeoPackageGeoPackage 没有 2GB 限制且支持空间索引对几十万条线要素的查询速度快得多。反过来如果在 QGIS 里能看到属性表记录数但地图画布上没有要素检查一下当前视图的缩放级别是否是要素太小或者图层透明度被调低。5.2 现象两条路在十字路口看起来相交但实际没有结点原因矢量化和拓扑检查环节缺失两条路在交点处各有自己的终点和起点彼此之间没有捕捉。如果要做路径分析这种假相交会导致路径规划时路线无法从一条路转到另一条路。解决方法是先跑一遍 v.clean 的 break 操作把相交位置打断形成节点再用 v.build.polylines 重建连接。这个处理对城市道路和乡道交叉口尤其重要越是密集的乡村路网假相交问题越普遍。# 用 GRASS 命令清理断点和创建真节点前提是在 QGIS Processing 中配置 GRASS 环境 # 输入已转换到投影坐标系的道路数据 v.clean inputroad_projected outputroad_clean \ toolbreak,rmdupl typeline \ threshold1.0 --overwrite参数里toolbreak负责在线的交叉点处打断rmdupl负责删除完全重复的线threshold1.0表示捕捉容差为 1 米即两条线的端点距离小于 1 米时自动融合。这个容差值不能拍脑袋设需要根据原始数据的采集中误差来定。如果原始数据坐标精度是 0.5 米容差设 1 米是合理的如果设成 10 米可能会把两条平行乡道错误合并成一条。清洗后重新加载道路数据进行路径分析时你会发现路口转向节点自动生成了路径规划也能正常计算。5.3 现象按乡镇名称统计时里程数对不上总值和年鉴差距巨大原因大多出在空间关联规则上。很多初学者习惯用 “intersects”但一条跨乡镇的道路会被同时算进两个乡镇导致总量虚高。而县道乡道中长路线又很常见一条 10 公里的乡道穿过两个镇如果按相交规则统计两个镇都会计入 10 公里但实际应该按切割后的长度各自统计。这就是前文提过的问题。解决方案就是先按乡镇边界切割路网再统计。QGIS 中可以用 “Intersect” 工具直接对道路和乡镇求交输出结果中自动带乡镇属性再按乡镇汇总长度不会出现重复计算。这里要提醒一句切割之后千万要重新计算几何长度不要沿用原线段的 length 字段。5.4 现象新增路段是断开的无法和老路网连通原因是增量更新时只改了属性字段没做几何匹配。这种情况往往在数据包更新版本中出现2022 版和 2024 版之间新增了部分乡道但新增的线端点没有落在旧路网的节点上出现几米到几十米的空隙。处理办法有两个一是用 v.net 的 connect 工具设置一个最大连接距离自动把断点连接到最近路网二是手动编辑把断头线的端点捕捉到已有道路端点。第一种适合批量处理第二种适合少量零星问题。批量处理时连接距离不宜过大一般 20 米以内否则会把本不应该连接的道路错误连上。5.5 现象属性表里中文路名变成了一串问号或乱码原因很直接dbf 文件的编码与读取工具默认编码不一致。ArcGIS 默认读系统 ANSI 编码即 GBKQGIS 默认按 UTF-8 尝试。如果你的数据是 GBK 编码在 QGIS 里就会乱码。解决方式是在 QGIS 加载界面手动设定数据源编码或者在数据源管理器中选择 GBK。如果想彻底解决推荐用 ogr2ogr 将 shp 转成 GeoPackage并指定-lco ENCODINGUTF-8一次转换永久无忧。转换后注意检查路名等字段是否仍然显示正常如果转换前已经乱码需要先找原始数据文件重新转不要在乱码状态上继续修复。6. 数据验证与进阶应用空间索引、网络分析与动态分段6.1 建立空间索引让几十万条乡道不再拖慢渲染和查询省域乡道数据加载进 QGIS 或 ArcGIS 之后最直观的感受是缩放地图时卡顿。这不一定是电脑性能问题而是 shapefile 缺省情况下没有空间索引。QGIS 里右键图层选择 Properties → Source → 勾选 “Create spatial index”。ArcGIS 则会在构建金字塔和统计时自动建立空间索引。GeoPackage 格式则天然支持空间索引这也是我推荐转换格式的原因之一。加了空间索引之后你会发现框选一个县城的几百条乡道时响应时间从几秒降到几十毫秒。空间索引不是玄学它就是底层构建的 R-tree让范围查询不再全表扫面。6.2 构建路网拓扑并做最短路径分析以乡道连通性为约束有了节点连续的路网之后就能做真正的网络分析了。QGIS 的 Road Graph plugin 或者 PostgreSQL pgRouting 都能算。pgRouting 的好处是能处理大规模数据SQL 方式灵活适合批量化计算。下面这段 SQL 展示如何从道路表构建路网拓扑并查询两个村庄之间的最短路。前提是道路数据已经导入到 PostGIS 中并且字段里有权重比如按时间和距离分别计算。-- 从清理后的道路表创建网络拓扑 SELECT pgr_createTopology(sd_road, 0.0001, geom, gid, source, target, oneway); -- 查询两个村之间的最短路径按几何长度 SELECT seq, node, edge, cost, geom FROM pgr_dijkstra( SELECT gid AS id, source, target, ST_Length(geom) AS cost FROM sd_road, 100, 254, directed : false ) AS di JOIN sd_road rd ON rd.gid di.edge;拓扑函数中的0.0001是容差单位是度适用于经纬度数据如果数据已投影成米则需要改成 0.5 或 1.0表示半米到一米内视为同一节点。source和target字段在调用拓扑函数后自动填充它们记录每条线的首尾节点编号。pgr_dijkstra的参数分别是 SQL 边表、起点节点 ID、终点节点 ID以及有向/无向设置。乡道一般取 directed : false即双向可通行。这样查出来的路径是沿线最短路径不代表实际最快如果需要按时间成本就把 cost 换算成ST_Length(geom) / 设计速度。6.3 结合动态分段做路网现状评估把病害、宽度、护栏数据挂到路线上乡道数据不只是用来画图和算里程。规划养护项目时需要知道某条乡道上哪些路段有护栏、哪些路段是砂石路面这类信息在基础路网矢量中通常没有需要外业采集后通过动态分段挂接。动态分段的原理是把桩号里程桩作为参照系统将属性数据挂到线要素的某个起点到终点区间上。PostGIS 同样支持分段查询。先给每条乡道建立里程参照再根据外业记录的起点桩号和终点桩号在线要素上定位对应区间。在实际项目中我一般会先在 QGIS 里给道路要素添加一个 route_id 字段再把外业采集的病害调查表导入按 route_id 和起终点桩号做关联。后续就能画出每 500 米一段的道路状况图。这种用法才是乡道级矢量数据的深度价值所在它让规划部门的同事不必再翻纸质图表可以直观地在图上看到哪里需要大修、哪里需要预防性养护。这套流程走下来后我对数据质量的判断标准也变得很朴素拓扑干净、编码统一、坐标精确到能直接叠加影像。只要这三条过关后续分析基本不会翻车。做数据处理这么多年我最深的体会是一份数据的价值不取决于标题多响而在于你能不能在几个小时内把它变成能支撑决策的东西。拿到这份四川省道路数据按上面顺序走一遍解压看结构、确认坐标系、检查拓扑、清洗属性、验证里程跑通一遍你的底气就不一样了。希望这些步骤对你也有用。本文还有配套的精品资源点击获取