简介面向GIS学习者与水利信息化从业者的河流及流域矢量数据包聚焦水文地理中的河流分级与流域边界表达以矢量点线面结构呈现水系拓扑关系适合用于水文学基础教学、流域制图及空间查询分析。压缩包内共有46个文件核心数据是7个SHP要素文件分别对应四级河流、三级以上河流、一级河流、五级河流和三级流域等图层同时配套shx几何索引、dbf属性表、prj投影信息、sbn/sbx空间索引以及xml元数据等辅助文件整体体积约9.7MB。当前已有138人学习浏览可直接在ArcGIS、QGIS等常见GIS软件中加载使用。内容可支撑流域水资源分布评价、洪水风险评估、河流生态保护规划、河网密度提取与子流域面积统计等场景也能帮助初学者理解不同类型河流分级标准与流域划分逻辑是一份兼顾实践操作与理论学习的水文GIS数据素材。1. 拿到“河流及流域.zip”之后多数人第一件事就做错了很多新手解压完“河流及流域.zip”后的第一件事是双击里面的 shapefile 想在地图上看到河流。结果要么图层灰掉要么属性表全是乱码要么河流断成一截一截更常见的是流域面积算出来大得离谱。这个包的名字听起来像个档案馆号但它内部通常是一整套 GIS 数据河网线、流域边界、湖泊水库面、属性字段有时还带 DEM 栅格。解开它只是起点真正要做的是把坐标系、拓扑关系、字段编码理顺才能拿去制图、做水动力模型前处理或者算水网密度。适合水利规划、GIS 数据处理、智慧水务和环评项目的从业者。这一篇我会按自己处理这种提资包的习惯从拆包、体检坐标、修拓扑到自动出图把每一步的取舍和踩坑写清楚。2. 解开压缩包先别急画图识别数据格式与文件族谱“河流及流域.zip”这个命名方式在工程提资里非常常见但压缩包内部的格式组合几乎没有标准答案。我一般不会直接拖进 GIS 平台而是先把它当成一个“数据交付物”做体检哪些是矢量、哪些是栅格、哪些是说明文档坐标系有没有写清楚。只有把文件族谱摸清后面才不会被“打开失败”和“图层错位”反复折腾。2.1 一个河流及流域包里通常躺着什么从 shp 到栅格常见的情况是包里既有矢量也有栅格散落在多个文件夹里。下面这张表是我拆这类包时最常遇到的组合文件类型常见后缀在包里的角色shapefile 文件组.shp / .shx / .dbf / .prj河网线、流域边界、湖泊水库面地理数据库.gdb / .gpkg / .sqlite多个要素类打包在一个库里带拓扑规则栅格数据.tif / .img / .ascDEM、坡度、流向或土地利用底图轻量交换格式.geojson / .kml给 Web 地图或外业采集 App 用元数据.xml / .txt / .pdf数据来源、坐标系、采集时间、精度说明为什么要把文件族谱先拆出来因为 shapefile 不是单文件一个“河流_shp”实际上由 shp、shx、dbf、prj 四个缺一不可的文件组成。缺了 prj坐标系信息丢失缺了 dbf属性表全空缺了 shx几何索引读取缓慢甚至打不开。如果包里混合了原先位于某个 ArcGIS 工程里的 FileGDB那就不只是拷贝文件的问题还得确认要素集名称和坐标参考。2.2 用文件扩展名与目录判断数据来源避免瞎打开判断数据新旧不能只看文件名。旧项目下来的包经常是缺 prj 的 shapefile新项目则倾向于用 GeoPackage 或 FileGDB 整体交付。我拆包后的第一个动作是打开每个看似数据的顶层目录把根目录结构打印出来再决定用哪个工具链去读。根目录下如果是一堆散落 shp优先怀疑是某次 AutoCAD 转 ArcGIS 的成果几何精度和拓扑都要重点检查。如果看到 _shp 或 _shp.mxd 这类命名说明是直接从地图文档导出的属性字段可能与原工程完全一致但要素范围可能被裁剪过。如果只有 .tif 和 .asc没有矢量那这个“河流及流域.zip”大概率是用来做水文分析的原始 DEM 包后续要靠流向和汇流累积自己提河网。还有一种容易被漏掉的线索是属性字段名。不少省市河湖名录相关的包里经常出现 mn 这个字段它多半不是“姓名”而是“最小河段编号”或“统计最小单元编码”。做字段映射前先弄清它取值规则比上来就修几何更重要我的习惯是先把每个图层的字段名列表导出来存成 CSV用文本编辑器翻一遍再动手。2.3 批处理解压与目录重组的实用命令拿到 zip 后我从来不右键“全部解压”而是用命令行先建目录骨架避免中文路径和乱码目录把 GIS 工具搞崩。下面这套命令在 Linux 和 macOS 下可以直接跑Windows 下用 Git Bash 也一样适用mkdir -p 01_vector 02_raster 03_meta unzip -O GBK -q 河流及流域.zip -d raw_zip mv raw_zip/*.shp raw_zip/*.shx raw_zip/*.dbf raw_zip/*.prj 01_vector/ 2/dev/null find raw_zip/ -maxdepth 1 \( -iname *.tif -o -iname *.img -o -iname *dem* \) -exec mv {} 02_raster/ \; mv raw_zip/*.xml raw_zip/*.pdf raw_zip/*.txt 03_meta/ 2/dev/null ls -R raw_zip这段命令的逻辑是先把矢量、栅格、元数据分到三个目录里避免后续一次次翻找文件。-O GBK用于处理 Windows 中文压缩包常见的文件名编码问题如果省略这个参数解压出来的很多中文文件名会直接变成乱码。2/dev/null是压掉“文件不存在”的报错因为不同 zip 包内部目录差异很大mv 不存在的文件时终端干净一些。最后ls -R是快速回显结构确认有没有遗漏的非标准文件。参数说明目录名建议用英文加下划线后面所有 Python 和 QGIS 工程都用这个路径不要在中途再改。遇到 tar 包则不需要-O GBKtar 内部文件名一般走 UTF-8。如果 zip 本身是分卷压缩先把分卷合回单文件再执行上面命令否则解压结果容易半途报错。3. 坐标系是把流域放上地图的第一道坎投影与地理坐标的取舍河流及流域数据最容易让新手翻车的地方不是画图而是坐标系。很多人直接把经纬度坐标的数据丢进 QGIS再叠加在线影像底图发现河流整体漂到海里还有人用投影坐标的数据直接算面积得到的结果比实际流域大几十倍。这两个问题都属于坐标基准没有对齐。3.1 地理坐标和投影坐标为什么会让河网错位地理坐标用经纬度表达位置单位是度投影坐标把球面展开到平面单位是米。河流及流域的矢量数据如果是 CGCS2000 地理坐标系叠加在 Web 墨卡托底图上时只要椭球体或基准不一致几米到几十米的偏移就会出现。更隐蔽的是很多工程包里的 shapefile 根本没有 .prj 文件软件默认按 WGS84 猜猜错就让整个流域边界错位。处理前先看数据范围。如果河流线、流域边界的经纬度数值在小数点后 5 位左右通常还是地理坐标如果坐标数值动辄几十万、上百万那多半已经做过高斯-克吕格投影。我给这类包定坐标系的基本顺序是先看 prj 文件没有 prj 就看字段元数据再没有就按范围反推投影带绝不直接默认 WGS84。3.2 用 Python 统一河流及流域 zip 的坐标基准用 geopandas 做坐标体检比在 ArcGIS 里逐个打开快得多。以下代码会读取矢量文件、检查坐标系并在缺失 prj 时自动尝试从同名 .prj 文件补回坐标定义import os import geopandas as gpd from pyproj import CRS shp_path 01_vector/river.shp riv gpd.read_file(shp_path, encodingutf-8) print(要素数量:, len(riv)) print(原始坐标系:, riv.crs) if riv.crs is None: prj_path shp_path.replace(.shp, .prj) if os.path.exists(prj_path): with open(prj_path, r, encodingutf-8) as fp: riv.crs CRS.from_wkt(fp.read()) print(从 prj 读回坐标系:, riv.crs.name) # 统一投影到 CGCS2000 高斯-克吕格 3 度分带中央经线 117E riv_proj riv.to_crs(EPSG:4545) riv_proj.to_file(01_vector/river_4545.shp, encodingutf-8)这段代码里encodingutf-8控制 dbf 属性表的读取编码如果属性表中文乱码改成encodinggbk再试。riv.crs是 geopandas 判断坐标系的入口打印出来是 None 时说明 shapefile 缺 prj。CRS.from_wkt会读取 prj 文件里的 WKT 文本把坐标定义补到内存里。to_crs(EPSG:4545)表示 CGCS2000 高斯-克吕格 3 度分带第 39 带中央经线为东经 117 度适合流域中心约在 117 度附近的项目。如果你手里的包落在其他经度按本地经度重新选带。注意我在示例里用 EPSG:4545 是因为它很适合我国中东部很多中小流域但不代表所有包都应该转到这个编号。坐标转换最怕“一个编号走天下”先确认流域中心经度再按 3 度带或 UTM 分带去选。3.3 长度和面积统计前必须完成的投影设置坐标统一之后才能做长度和面积计算。下面这段代码把河流线转投影坐标后算长度把流域面转投影坐标后算面积并写进一个轻量的 SQLite 数据库里备用import geopandas as gpd riv gpd.read_file(01_vector/river_4545.shp, encodingutf-8) bas gpd.read_file(01_vector/basin_4545.shp, encodingutf-8) riv[len_m] riv.geometry.length riv[len_km] riv[len_m] / 1000.0 bas[area_km2] bas.geometry.area / 1e6 riv.to_file(02_raster/../01_vector/river_length.sqlite, layerriver_len, driverSQLite) bas.to_file(01_vector/basin_area.sqlite, layerbasin_area, driverSQLite)这段代码的核心是理解.length和.area的返回值依赖当前坐标系。数据还在地理坐标时河流长度单位是“度”流域面积单位是“平方度”这种数值拿去写报告没有任何工程意义。转成投影坐标后单位才是米和平方米。/ 1e6是把平方米换算成平方公里流域报告里习惯用 km²。导出 SQLite 而不是继续用 shapefile是因为在数据量变大时SQLite 单文件管理字段更省心也避免又出现 shp 缺伴侣文件的问题。投影设置上还有一条经验出图底图可以用 Web 墨卡托但面积统计千万不要用 EPSG:3857墨卡托在高纬度地区面积变形很严重。流域面积计算优先用等积投影或当地高斯投影。坐标系的坑往往不是不会转而是每转一次都要确认单位。4. 河网拓扑与流域边界清理从线到面的实用操刀流程坐标理顺之后第二个大问题就是拓扑。河流及流域数据的采集周期、比例尺和来源都不同河网图层经常出现断头、悬挂短支、边界缝隙重叠。这些细节不看放大图很难发现等导入 HEC-RAS 或 SWAT 模型时才会集中爆发到时候再回头修数据就晚了。4.1 读懂河网分级与 mn 字段先归类再动手动手修几何之前我一般先把属性表字段分类看一遍。一个典型河网线表会有河段编号、河名、长度、河流级别以及行政区划或流域编码。像 mn 这种缩写字段在很多工程建设包中属于“最小河段编号”它决定河网在模型里被识别的粒度。不要因为看不懂就删掉很多后续统计要靠它去标记上下级关系。用 pandas 把字段结构和取值打印出来是快速建立认知的办法import geopandas as gpd riv gpd.read_file(01_vector/river_4545.shp, encodingutf-8) print(全部字段:, riv.columns.tolist()) print(河流分级字段分布:) if grade in riv.columns: print(riv[grade].value_counts()) elif level in riv.columns: print(riv[level].value_counts()) elif mn in riv.columns: print(riv[mn].astype(str).str[:2].value_counts())这段脚本的用途不是出图而是提前知道河网层级怎么分布。value_counts()返回每个分级值的数量能立刻看出“干货流”和“小支流”的比例。如果 mn 字段前两位能稳定分出同一流域的大类说明这个包的数据组织比较规整如果取值非常零散后续合线时就要对“同编码才算同一条河”持谨慎态度。参数说明字段名在不同包里都不一样可能是grade、level、order、kind我见过用cl表示河流类别的。脚本里连续用 elif 做兼容就是为了应对这种“字段名随提资单位变化”的情况。用.astype(str).str[:2]是把 mn 值先转字符串再取前两位适合编码规则为“流域代码河段序号”的数据格式如果编码是纯数字累加这个动作可能会把真实河段编号切错要结合元数据确认。4.2 河流断线、短支与边界接缝的修复思路最常见的河网问题是上游支流和干流之间差了一小段线导致整条河在交点处断掉。我处理这种断头时不会手工去拖节点而是用线融合加拓扑打断。底层逻辑是先让所有线段在交点处断开再把相邻的断线按方向重新连接from shapely.ops import unary_union, linemerge, polygonize lines riv_proj.geometry.to_list() merged unary_union(lines) merged_line linemerge(merged) polys list(polygonize(merged)) print(融合后几何类型:, merged_line.geom_type) print(闭合面数量:, len(polys))unary_union会把所有线叠在一起消除重复线并把交点处打断linemerge再把端点能衔接的短线拼成连续长线这一步能解决大部分“看起来断头”的问题。polygonize会利用闭合的线段围成面输出的闭合面数量可以作为河网是否成环的参考如果线层是河网闭合面通常是江心洲或环状水系如果数量为 0说明河网没有形成闭合回路拓扑结构是树状。要注意的是polygonize对悬挂线无效一条线只连了一端它不会变成面这也是为什么还要配合专门的拓扑工具清理短线。如果要在 QGIS 里落地我一般用 GRASS 的v.clean工具参数选break先打断所有线段再用rmline删除短于阈值的悬挂线最后用snap把端点吸附到邻线。步骤是打开处理工具箱搜索 v.clean输入线图层选break容差按投影坐标下的米为单位比如 1 米再跑一次选rmline阈值填 5 米或 10 米取决于数据比例尺。处理完对比前后要素数量如果线数量急剧减少说明原图里有大量重复线被清理掉了。4.3 按流域汇总河流长度的最小计算脚本河网修通之后最常见的统计需求是按流域汇总河流长度、计算水网密度。很多人直接对整张河流表做 groupby 统计但干流往往跨多个流域这样会重复计算。正确做法是先做空间连接确认每条河段归属于哪个流域面再汇总import geopandas as gpd riv gpd.read_file(01_vector/river_4545.shp, encodingutf-8) bas gpd.read_file(01_vector/basin_4545.shp, encodingutf-8) riv_basin gpd.sjoin(riv, bas[[geometry, basin_id]], howleft, predicatewithin) stats riv_basin.groupby(basin_id)[len_m].sum().reset_index() stats.rename(columns{len_m: 河长_m}, inplaceTrue) bas_stats bas.merge(stats, onbasin_id, howleft) bas_stats[水网密度] bas_stats[河长_m] / 1000 / bas_stats[area_km2] bas_stats.to_file(01_vector/basin_stats.sqlite, layerbasin_stats, driverSQLite)这段代码里predicatewithin表示只统计完整落在流域面内部的河段干流如果从流域边缘穿过可能被划到相邻流域或者因为没有完全 within 而落到空值这种情况下建议改用intersects再做长度切割。groupby(basin_id)[len_m].sum()把同一流域内的河段长度相加。最后除流域面积得到的水网密度单位是 km/km²是河网发育程度的一个常用指标。我自己的经验是先跑这个脚本再把结果和原始资料里人工量算的干流长度对一遍。如果单条干流长度出现半截甚至缺失多半是矢量河网本身就有断头不是统计代码的问题需要退回到 4.2 节做拓扑修复。5. 避坑河流及流域包打开后的 5 个高频翻车现场这一章是血泪经验集中区。下面五条是我在项目中反复遇到、也看着别人反复踩的坑按“现象-原因-解决”的方式拆开讲。5.1 只拷贝了 .shp 文件图层打不开或属性全空现象拿到包后图省事只拷贝了河流.shp一个文件发给同事或拷到另一台电脑结果 GIS 平台报“打开要素类失败”或者能画线但没有任何属性字段。原因shapefile 本身就是多文件存储格式几何、索引和属性分别放在不同后缀里缺一个都会出问题。很多人把它当成单一文件是翻车的最常见起点。解决完整拷贝整个文件族至少包括 .shp、.shx、.dbf、.prj。如果要给下游更推荐直接转成 GeoPackage 或 SQLite 单文件再分发既不会丢部件也便于版本管理。5.2 dbf 属性中文乱码河名变成“锟斤拷”现象在 QGIS 里打开河网属性表河名、流域名称全部显示成乱码或者导出 CSV 后中文错位。原因shapefile 的 dbf 属性表常见编码是 GBK 或 UTF-8 两种软件默认用 UTF-8 读旧项目数据时就会乱码反之亦然。解决读取时显式指定编码。geopandas.read_file里传encodinggbkQGIS 里也可以在图层属性中的数据源设置里手动改编码。更彻底的方案是直接在 QGIS 里另存为 GeoPackage转换时把编码固定为 UTF-8后续所有工具都不用再猜。5.3 流域面积大得离谱或小得不合理现象一个山区小流域算出来面积 800 万平方公里或者只有 0.002 平方公里明显偏离实际情况。原因直接用地理坐标下的面要素去算面积得到的是平方度或者数据误用了 Web 墨卡托投影在高纬度区间面积被严重放大。坐标基准和投影单位没有对齐时几何计算就是黑匣子结果不可信。解决先确认 .prj 内容再按项目所在经度选择合适的高斯投影或 UTM 分带最后用投影坐标下的geometry.area计算。计算完后把结果和已知水文站控制面积交叉验证而不是直接写进报告。5.4 河网断头放大看每条河都差几米现象属性表里河流数量很多但顺着河流走向看源头和中游之间存在明显断开linemerge也拼不回去。原因不同比例尺的图幅拼接时接边处没有做节点捕捉或者上游小支流被简化后和干流失去交点。解决先跑unary_union再linemerge对线层做打断和合并。如果断点集中在图幅接边附近用 v.clean 的 snap 或者 PostGIS 的ST_Snap设置容差修正。修完后要检查是否引入重复线拓扑工具不是万能后悔药跑完一定要比较数量。5.5 流域边界与行政边界叠不上现象河流线和流域面叠加到影像底图后流域边界和地形分水岭明显不重合或者与相邻行政区域边界交叉重叠。原因流域边界如果是人工勾绘或由低分辨率 DEM 提取的精度和现势性都可能不足行政边界与自然流域边界本身就不一致两者叠加出现缝隙是正常的。解决面向不同用途采用不同处理策略。做水动力模型时以 DEM 提取的分水岭为准做规划管理时以行政工作区划为准不要强行把两套边界统一成一套。如果只是地图表达用拓扑检查工具找出 gap 和 overlap 区域再做融合或切割。6. 把河流及流域 zip 里的数据用活字段映射、拓扑验证与自动出图数据修到能画、能算、能进模型剩下的一公里就是把成果稳定地呈现在图面和接口里。这些活不值得每次手工做我习惯把字段映射和出图步骤沉淀成脚本与模板。6.1 字段映射把包内缩写名统一成自己的标准不同包的字段命名差异很大上一包叫mn下一包叫bh如果不统一后面二次开发接口会非常难受。我一般用一个小字典做统一映射import geopandas as gpd riv gpd.read_file(01_vector/river_4545.shp, encodingutf-8) field_map { river_id: mn, river_name: name, length_m: len_m, basin_id: ws_code, } for new_name, old_name in field_map.items(): if old_name in riv.columns: riv.rename(columns{old_name: new_name}, inplaceTrue) riv.to_file(03_meta/river_std.gpkg, layerriver, driverGPKG)这里用rename做字段名替换而不是直接删列重建是为了保留原字段里的合法取值。输出到 GeoPackage 后字段类型、编码和几何都更稳定后续无论是接 PostGIS 还是写 Web 服务都不容易再出现乱码问题。6.2 用流域面驱动自动出图批量打印河网图当项目要一整个县或一个流域的河网图时一张张手动框范围出图非常浪费时间。在 QGIS 里我会新建一个打印布局地图范围绑定到流域面图层然后在布局属性里设置按“流域属性”迭代利用 Atlas 功能批量导出 PDF。关键是先保证流域面要素的命名规则干净basin_id唯一导出文件名才能对得上。这种做法在几十个甚至上百个子流域的项目里能把出图时间从一天压到半小时。自己动手处理这种河流及流域压缩包时我习惯把所有转换动作写成一个 step 文件从解压、转坐标到拓扑修复都留痕。这样下次拿到命名相似、结构略不同的包可以直接复用整个流程而不是在菜单里重新点一遍。过程中踩过的坐标系和断头问题最终都会沉淀成这套流水线上的一段注释。希望帮到你。本文还有配套的精品资源点击获取