简介中国水系SHP 1级到5级数据集属于矢量化GIS文件集合涵盖全国1级至5级分级河流面向GIS使用者、城乡规划者、环境科研与水文管理人员用于河流分布展示、空间分析与流域研究。压缩包约6.8MB共27个文件以shp、dbf、prj、shx、sbn、sbx及xml等GIS标准格式组成其中shp记录河流几何、dbf承载属性、prj定义投影坐标可在ArcGIS、QGIS等平台直接加载。已有1395人学习与下载适合需要快速获取全国水系数据的从业者。数据按河流长度、流域面积与影响程度分为5级既能呈现长江、黄河等干流脉络也能展开地方性支流细节支持距离量算、流域划分、流向追踪并可叠加人口、土地利用等图层为水资源配置、生态保护及灾害应对提供基础依据。1. 中国水系SHP 1级到5级这套分级数据到底能拿来做什么做GIS的同行拿到中国水系SHP 1级到5级这套数据第一反应基本是按GRADE字段上色1级大河用粗线5级细支流用细线一张全国水网图几分钟就能出来。但这套数据真正值钱的地方不在出图而在于它自带汇流层次——每一条5级河最终归属到某条4级、3级再逐级汇进2级和1级。能不能把这套层次用起来决定你是拿SHP当底图还是拿它做流域分析、洪水推演和河网拓扑建模。下面的流程按我平时处理这类数据的顺序展开从分级逻辑、字段体检讲起到投影、简化、拓扑修复最后给一条从SHP直接建河网拓扑的路径。2. 分级体系和字段逻辑拿到SHP后先别急着画图2.1 从1级到5级河流等级到底在表达什么河流分级的核心目的是告诉使用者这条河在整个河网里处于哪一层汇流位置。我接触过的公开水系SHP里1级通常对应流域面积最大、流程最长的干流河段2级是直接汇入干流的较大支流3级开始下探到次级支流4级、5级则接近源头细流。要注意这不是“河越宽等级越大”的绝对排序而是数据生产方在制图综合时保留的层级。同一套数据里5级河段可能长达几十公里也可能只是几公里的溯源段不同来源的数据放在一起比对才会发现“5级河”的门槛完全不一样。字段逻辑上GRADE字段数值越小河段层级越高1级是干流5级是末端支流。水流方向是从5级汇向1级而不是反过来。但有些数据源把字段叫HYDLK_GRADE有些叫LEVEL甚至有把0定义为“运河/渠”的情况。所以在任何统计之前都应该先看字段分布不要默认1到5就是全部。我有个习惯拿到SHP先拉开属性表统计GRADE的value_counts看有没有0、6、99之类的异常值。0通常代表类型而非等级处理时要先筛掉或者单独归类否则后续拓扑分析会把运河、灌渠当成水系参与汇流结果会偏离实际。这里有非常容易被忽略的一点GRADE1并不等于“物理上的大河”。在有些数据里一条中等长度的河流虽然没有汇入大江却是本地最大河流依然可能被标为1级。所以1到5级是一种相对层级不是绝对流量或宽度分级。做跨区域汇总时不能拿两套数据的同等级数值直接相加。下面这张表只是最常见的约定不是所有数据源都严格如此等级通常代表典型特征1级流域尺度干流数量最少线宽最大2级直接汇入干流的较大支流数量明显增多3级次级支流进入区域河网尺度4级中小河流数量多部分河段较短5级末端细流和冲沟数量最多最易出现断头等级之间的连接关系是隐含在几何和字段里的。一组正确的1到5级数据几何上满足“低等级河段的端点落在高等级河段上”的拓扑关系。利用这点可以做很多事找孤立河段、推断汇流顺序、生成树状河网。但前提是数据本身没有断线、没有悬挂点。这也是为什么我建议先做体检再画图而不是打开就按颜色出图。颜色只能看个大概拓扑结构才决定能不能做分析。2.2 字段命名和坐标系先花十分钟做数据体检拿到SHP的第一件事我会建一个最小体检脚本。用Python的geopandas读一下几何类型、字段名、坐标范围顺带看有没有prj文件。下面是我常用的开头。import geopandas as gpd gdf gpd.read_file(rivers_1_5.shp, encodingutf-8) print(几何类型:, gdf.geom_type.unique()) print(字段列表:, gdf.columns.tolist()) print(坐标范围:, gdf.total_bounds) print(等级分布:) print(gdf[GRADE].value_counts().sort_index())这段脚本把最影响后续方案的几个信息一次打出来几何类型决定你是用线分析还是面分析字段列表告诉你等级字段到底叫GRADE还是HYDLK_GRADE坐标范围配合prj文件判断坐标系有没有问题等级分布直接暴露0、99等脏数据。我见过不止一次文件名写着“1级到5级”打开后实际只有4、5两级1到3级全被生产方剔除如果直接按等级统计结论必然偏。如果GRADE字段名不确定可以用一个兼容代码自动找grade_field None for col in gdf.columns: if col.upper().replace(HYDLK_, ).startswith(GRADE) or col.upper() LEVEL: grade_field col break if grade_field is None: raise ValueError(未找到等级字段请人工核对属性表) gdf[grade] gdf[grade_field].astype(int) print(gdf[grade].value_counts().sort_index())这段代码把各种命名的等级字段统一成小写grade列后续所有符号化、统计分析都基于这一列。参数上唯一要注意的是astype(int)如果字段里有空值或字符串会直接报错建议先dropna再转换。坐标系的体检同样重要。很多水系SHP携带prj但prj写的坐标系和你实际使用的坐标系未必一致。如果配套说明里写着CGCS2000经纬度但计算几何长度时忘记投影几何长度就是度而不是米。我一般先确认数据的原始坐标系再决定是转Web墨卡托做底图还是转等距投影做长度统计。SHP由shp、shx、dbf、prj、cpg等多个文件组成任何一次复制、压缩、传输都可能导致配套文件缺失。很多数据出问题不是坐标或属性错了而是prj丢了。遇到没有prj却又不能确定坐标系的情况我一般先用坐标范围判断全国范围大致落在73到135、18到53如果total_bounds是类似数值就按经纬度处理如果范围是几百到几千多半已经是投影坐标要结合元数据确认。坐标系是个容易栽跟头的环节多花十分钟后面能省半天。3. 从原始SHP到可用图层分级统计、投影转换与符号化3.1 用Python读SHP并统计各级政府河流数量数据体检通过后第一项实用操作是统计各级河流的长度和条数。比例尺跨度大的数据5级河段数量往往是1级的几十倍这个数字能帮你判断数据是否合理也能在出图时决定要不要抽稀。import geopandas as gpd gdf gpd.read_file(rivers_1_5.shp) gdf[grade] gdf[GRADE].astype(int) # 先转换到 Web 墨卡托便于后续出图 gdf_m gdf.to_crs(EPSG:3857) gdf_m[len_m] gdf_m.geometry.length gdf_m[len_km] gdf_m[len_m] / 1000 summary gdf_m.groupby(grade).agg( segments(geometry, count), total_km(len_km, sum) ).round(1) print(summary)这段代码里EPSG:3857是Web墨卡托投影几何对象的length属性单位跟随当前坐标系所以是米。段长统计能立刻看出等级分布是否健康正常情况下等级数值越大段数越多总长度也越大。如果发现2级段数比3级还多说明两个等级的取舍尺度可能有问题回到原始说明核对。EPSG:3857在高纬度地区长度形变很大。如果只要相对长度比如比较1级河段与5级河段的长度占比影响不大但如果要计算实际渠道长度建议替换成目标区域所在经度带的UTM投影。替换方式很简单gdf.to_crs(EPSG:326XX)XX是UTM带号具体带号按河流所在经度范围选择。3.2 投影、长度计算与几何简化的参数经验投影选择是处理水系SHP里最容易翻车的环节。常见做法有两种一是保持CGCS2000或WGS84经纬度适合做空间分析和制图底图二是转成Web墨卡托EPSG:3857适合切片出图。做长度统计、流域面积计算时我会额外转一次等距或等积投影。全国范围没有统一的等距投影能覆盖全境且误差小我一般按流域或行政区拆开后再选择所在经度带的UTM投影计算。如果应用只做画图和拓扑不必过度纠结投影保持经纬度即可。几何简化是常用的瘦身手段。水系数据往往顶点过多一条5级河可能存了几百个点整份SHP体积很大。简化参数中最容易踩的是单位坐标系还是经纬度时tolerance单位是度写0.01意味着约1公里河网会变得面目全非。必须先把数据投影成以米为单位再填简化阈值。我常用的阈值范围是20到100米视出图比例尺而定。gdf_simplified gdf.copy() gdf_simplified gdf_simplified.to_crs(EPSG:3857) gdf_simplified[geometry] gdf_simplified.geometry.simplify( tolerance30, preserve_topologyTrue ) gdf_simplified.to_file(rivers_1_5_sim.shp, encodingutf-8)simplify的preserve_topologyTrue能避免简化后产生自相交和断点但不保证不产生小悬挂。tolerance30表示30米适合1比100万左右出图如果只是建拓扑入库我建议先简化到100米减少节点数量能明显降低后续ST_Node和网络构建的时间。注意简化会移动线的位置不能用于需要精确对齐流向、断面分析的场景。还有一个参数经验不要对流经水库、闸坝的河段做单独简化那里的几何往往已经被人为切分简化阈值过大会把过闸前后的两段拉成一条直线丢失关键落差信息。我通常的做法是先剔除或标记水利要素等分析完成后再并入。3.3 按GRADE字段做分级符号化的一组可复用配置分级符号化的目标是让1级河明显、5级河隐去而不是等权全部画出来。绝大多数GIS软件都提供按字段分类符号化这里给出QGIS Python控制台里可跑的一组配置方便批量处理多个图层。from qgis.core import ( QgsCategorizedSymbolRenderer, QgsRendererCategory, QgsSymbol, ) from qgis.PyQt.QtGui import QColor layer iface.activeLayer() categories [] config { 1: (#c0392b, 2.0), 2: (#e67e22, 1.4), 3: (#f1c40f, 0.9), 4: (#3498db, 0.6), 5: (#bdc3c7, 0.4), } for grade, (color, width) in config.items(): symbol QgsSymbol.defaultSymbol(layer.geometryType()) symbol.setColor(QColor(color)) symbol.setWidth(width) categories.append( QgsRendererCategory(grade, symbol, f{grade} 级河流) ) renderer QgsCategorizedSymbolRenderer(grade, categories) layer.setRenderer(renderer) layer.triggerRepaint()这段配置将grade字段作为分类依据1级用深红线宽2.05级用浅灰线宽0.4。实际项目中线宽和颜色要根据底图背景调整但颜色层次建议保持“暖色主干、冷色支流、浅色末端”的思路这样图面阅读负担最小。QgsSymbol.defaultSymbol按图层几何类型生成默认符号运行时不需手动构造线符号。如果数据里grade有空值最好在读取时先补齐否则渲染器会把它划到“NULL”类导致统计数量对不上。出图时还有一个建议关闭5级河在缩放到全国尺度时的显示或者用规则“仅当图层比例尺大于某个阈值时显示5级”。可以用QGIS的比例尺范围或符号规则过滤否则PDF导出会非常慢打印出来也是一片灰线。4. 水系SHP最常见的5个坑现象、原因与修复方法4.1 拓扑错误河段在交叉点没有打断现象把SHP导入PostGIS运行ST_IsSimple或ST_IsValid报出大量自相交或“线只有一个点”错误更明显的表现是QGIS里两条河段视觉上交汇但查询交点时却找不到公共端点。原因原始水系SHP是按制图综合需要分段存储的两条河在空间上相交但几何上没有共享端点交点只是两条线段的坐标偶然重合。这种伪节点和未打断交叉几乎是所有公开河流线数据的通病因为生产时并没有强制做节点处理。解决我用PostGIS统一打断处理把整张线表套一遍ST_Node生成新的几何。CREATE TABLE rivers_node AS SELECT id, grade, ST_Node(geom) AS geom FROM rivers; ALTER TABLE rivers_node ADD PRIMARY KEY (id); CREATE INDEX ON rivers_node USING GIST (geom);ST_Node把所有线段在相交位置切分输出MultiLineString。处理后再查一次ST_IsSimple如果仍报错多半是存在重叠线段需要进一步清理。打断后的数据才能做路径搜索和网络分析。注意ST_Node保留原始记录属性但连接关系要重新通过节点构建。4.2 属性乱码DBF编码识别错现象用geopandas默认读取rivers_1_5.shp中文名称字段显示成菱形问号或乱码用QGIS打开却正常或者反过来。原因SHP的属性存储在dbf文件里dbf的编码信息不可靠。很多公开数据是按GBK或GB2312导出的少数是UTF-8。geopandas默认按UTF-8尝试读取遇到GBK就直接乱码。而QGIS会尝试读cpg文件或自动探测所以两端表现不一致。解决读取时指定encoding先试gbk再试utf-8。import geopandas as gpd for enc in [utf-8, gbk]: try: gdf gpd.read_file(rivers_1_5.shp, encodingenc) sample gdf[NAME].dropna().iloc[0] if not sample.startswith(): print(可用编码:, enc) break except Exception: continue这个办法虽然朴素但高效。判断乱码不能只看个别字符最稳的是取一个已知中文河名检查。修复只读问题不够如果后续要写出干净数据建议统一转成UTF-8并写入同名cpg文件避免其他同事用别的软件打开又乱。还有字段名截断问题SHP的dbf限制字段名10个字符像HYDLK_GRADE这种超长名称写入时可能被截成HYDLK_GRAD这不属于乱码解决思路是建立规范字段映射表不改源文件。4.3 等级口径不一致两个数据源拼在一起就乱现象把A套数据的1到5级和B套数据的同级数据合并后同一片区域出现两条“1级河”一条很粗一条很细按等级统计各流域长度时数值明显不合理。原因不同数据源对“1级”的判断标准不同。有些按流域面积有些按河道长度有些按制图比例尺综合取舍。等级只是相对层级不是通用编号。拼合前没有建立映射关系等于拿两套互不兼容的尺子量同一个东西。解决合并前先画一张等级口径对照表用交汇处的连接关系校验。常见做法是选取一个重点区域统计双方每个等级的段数和总长度再看交汇处低等级河段是否都正确汇入高等级河段。如果两套在等级定义上差异过大我通常保留一套主线数据另一套只作为参考不做硬拼。import geopandas as gpd gdf_a gpd.read_file(a_rivers.shp) gdf_b gpd.read_file(b_rivers.shp) gdf_a[grade] gdf_a[GRADE].astype(int) gdf_b[grade] gdf_b[LEVEL].astype(int) # 只做字段统一不做硬编码等级换算 print(gdf_a.groupby(grade).size()) print(gdf_b.groupby(grade).size())如果两套段数的分布形态接近可以进一步用空间连接判断同一河段两端的等级。空间连接代码容易写实际问题往往出在“同一河段”用什么容差判定。我建议用半径50米、基于端点捕捉而不是整线捕捉否则会把并行的两条河误判成同一条。4.4 简化把河网弄断tolerance的单位坑现象预处理时调用了simplify出图时发现不少5级河少了一截或者在河流交汇处出现了断头重新检查几何发现原本连通的小河网被简化成了孤立线段。原因simplify的容差单位跟随当前坐标系。SHP还是WGS84经纬度时如果tolerance填了0.01相当于约1公里一条几十米的小支流会被整体吞掉。preserve_topologyTrue能避免自相交但无法保证短小支流不被移除。解决把简化放在投影转换之后单位明确为米再执行。同时要检查简化前后的段数和端点变化。gdf gdf.to_crs(EPSG:3857) before gdf.geometry.length.sum() gdf[geometry] gdf.geometry.simplify(30, preserve_topologyTrue) after gdf.geometry.length.sum() print(f简化前总长度: {before:.0f} m) print(f简化后总长度: {after:.0f} m)如果after比before少了超过百分之几说明阈值吞掉太多细节。经验上1比100万出图用30到50米比较稳1比25万可以用10米。简化后还要跑一次端点捕捉把由于顶点位置变化而错开的汇流点重新吸附否则后续建拓扑仍会出现伪节点。这个“简化后重新捕捉端点”的步骤经常被跳过也是血泪经验。4.5 河流方向不统一做流向分析必须解决的源头问题现象想做从上游到下游的路径分析结果发现同一河流不同河段的几何方向是乱的这条河从南往北下一条从北往南按GRADE字段推断方向发现低等级河段和高等级河段的端点连接关系不对。原因SHP线要素的顶点顺序只表示“这条线怎么画”不代表水流方向。公开水系数据往往是多源拼接有的段按上游到下游录入有的段恰好相反。如果不先统一方向任何依赖方向的网络分析都会得到反常识结果。解决用“低等级汇向高等级”的规则估计方向再统一翻转。一个可用方案是先构建无向拓扑然后对每条边判断端点附近是否与更高等级河段相连把连接到的更高等级端点作为下游。实际全自动翻转很容易误判我一般先用几何距离算出每个端点到下一等级线段的最短距离再决定要不要翻转。方向修正后要做一个抽查随机挑20条河段目视检查上下游等级是否合理。方向问题属于玄学类坑数据看起来没问题但一分析就出问题最好在流程里固定一步“方向一致性检查”而不是等结果异常后再回头查。5. 进阶从分级SHP建河网拓扑把“画图数据”变成“分析数据”处理完拓扑和方向之后1到5级水系SHP就不再只是画图用的线图层了。最后一步我一般把它导入PostGIS用pgRouting建立河网拓扑。这样就能回答“某条5级河最终汇入哪条1级河”“指定河段的集水范围能不能连通”这类问题。导入后如果存在MultiLineString先执行一次ST_LineMerge拆成LineString再调用pgr_nodeNetwork。CREATE TABLE rivers ( id SERIAL PRIMARY KEY, grade INTEGER, geom GEOMETRY(LineString, 4326) ); SELECT pgr_nodeNetwork(rivers, 0.0001, id, geom);pgr_nodeNetwork会生成一张rivers_noded表参数0.0001是节点捕捉容差单位是度。容差不能太大否则会把平行河段粘连也不能太小否则打断不干净。对经纬度数据0.0001度大概10米适合绝大多数公开发布的水系SHP。然后创建拓扑属性并检查连通组件ALTER TABLE rivers_noded ADD COLUMN source INTEGER; ALTER TABLE rivers_noded ADD COLUMN target INTEGER; SELECT pgr_createTopology(rivers_noded, 0.0001, geom, id, source, target); SELECT * FROM pgr_connectedComponents( SELECT id, source, target, 1::float AS cost FROM rivers_noded );pgr_createTopology根据共点关系填充source和targetpgr_connectedComponents给出每个连通组件编号。我现在的习惯是拿到任何一套1到5级水系SHP先做这个组件分析组件里如果只有5级河而没有1级河就说明这套数据在源头区域存在断头不能直接用于流域统计。组件分析也能辅助等级口径校验正常情况下一个完整流域会形成一个闭合连通组件等级从小到大连贯存在。做到这一步你已经把“能看的水系图”变成了“能查的河网图”。回想我最初处理这类数据时的教训也是从只按颜色出图到被拓扑错误坑过几轮之后才把拓扑检查和组件分析固定为必经步骤。这套流程不算复杂但每一步都有明确的验证目标。希望帮到你。本文还有配套的精品资源点击获取