简介:这份矢量数据面向地理信息、城市规划、水文研究及GIS教学人员,提供北京市域内五大水系流域分布的面图层数据,可用于流域分析、地图制图、空间统计与课程实验。资源包共29个文件,以shp、shx、dbf、prj等Shapefile核心格式为主,辅以sbn、sbx空间索引文件及mxd配图工程,压缩包约6.21MB,目录按一级流域、二级水系、三级支流分层组织,便于直接加载与二次编辑。数据覆盖蓟运河、潮白河、北运河、永定河、大清河五大干流,流域面积占北京市总面积89.8%,并细分至多条重要支流,如泃河、拒马河、温榆河、通惠河等,其中北运河水系为唯一发源于北京境内的水系,通州以上称温榆河、以下称北运河,是京杭大运河起点。已有125人学习下载,适合需要快速获取北京水系空间边界、开展流域分区与制图分析的读者参考使用。
1. 北京市流域五大水系分布图:面图层矢量数据到底能拿来做什么
做北京水环境、洪涝风险、市政排水或者生态规划的人,大概率都遇到过同一个尴尬:手头有监测站点的点位数据,有降雨格点数据,但一到"这条河汇水范围到底覆盖哪几个区、哪几个排水分区"这种问题,就只能靠一张模糊的扫描图去描。北京市流域五大水系分布图(面图层)矢量数据,解决的正是这件事——它把永定河、潮白河、北运河、大清河、蓟运河这五大水系的流域范围,从"图上一片色块"变成"可以参与空间计算的 polygon"。它适合做流域统计、子流域裁剪、汇水区叠加分析、专题制图的人;不适合只想找一张挂墙图的人。下面按"数据长什么样 → 怎么用起来 → 坑在哪"的顺序讲透。
2. 五大水系面图层的数据结构与字段设计:先看懂再动手
2.1 面图层和线图层、点图层到底差在哪
很多人第一次拿到"流域分布图"会默认它是线状的河网,其实不是。河网是线图层(polyline),监测断面是点图层(point),而流域分布图是面图层(polygon)——它描述的是"这块地表的降水最终汇入哪条水系"的空间范围。这个区别决定了它能干什么:线图层只能算长度、做缓冲区,面图层才能做面积统计、区域裁剪、叠加求交。
北京五大水系的面图层,本质上是把全市域切成若干互不重叠(或按主水系归并后互不重叠)的汇水单元。永定河系主要覆盖西部和西南部山区及部分平原,潮白河系覆盖东北部和东部,北运河系横穿城区北部和东部平原,大清河系在西南角,蓟运河系在东部平谷一带。做叠加分析前,先确认你的面图层是"按主水系归并"还是"按子流域细分",这两者的属性表结构完全不同。
常见做法是:主水系归并图层字段少(一般就水系名称、编码、面积),子流域图层字段多(子流域名、所属水系、面积、可能还有编码层级)。如果你要做的是"每个区县各水系占比"这种统计,用归并图层就够;如果要做"某个断面以上汇水区提取",必须用子流域图层,否则精度不够。
2.2 属性表里哪些字段是必须核对的
拿到数据别急着画图,先打开属性表看这几列。下面是一个典型的字段核对清单,字段名可能因数据来源不同而有差异,但语义基本对应:
| 字段语义 | 常见字段名 | 核对要点 |
|---|---|---|
| 水系名称 | NAME / XSMC | 是否统一为"永定河""潮白河"等标准名,有无别名混用 |
| 水系编码 | CODE / XSBM | 是否唯一,是否与你的其他数据能对上 |
| 面积 | AREA / MJ | 单位是平方米还是平方公里,投影坐标系下才有意义 |
| 子流域名 | ZLYNAME | 只有细分图层才有,归并图层没有 |
| 上级水系 | PARENT | 用于从子流域回溯到主水系 |
核对时重点看两件事:一是面积字段的合计是否接近北京市总面积(约 1.64 万平方公里),差太多说明有重叠或缺失;二是水系名称有没有"永定河系""永定河流域""永定河"三种写法混在一起,这会让后续分组统计直接出错。
2.3 坐标系:面图层最容易翻车的地方
面图层的面积、长度计算全部依赖坐标系。如果数据是地理坐标系(比如 CGCS2000 经纬度),直接算面积得到的是"平方度",毫无意义。必须投影到合适的投影坐标系再算。
北京地区常用 CGCS2000 3 度带,中央经线 117°E,EPSG 代码常见为 4547(CGCS2000 / 3-degree Gauss-Kruger CM 117E)。用 QGIS 或 ArcGIS 转换时,确认目标 CRS 是这个而不是 Web Mercator(3857)——3857 在高纬度地区面积变形明显,做面积统计会偏。
# 用 ogr2ogr 把经纬度面图层投影到 CGCS2000 3度带 117E ogr2ogr -f "ESRI Shapefile" \ -t_srs "EPSG:4547" \ bj_watershed_projected.shp \ bj_watershed_wgs84.shp # 查看投影后图层的坐标系和范围,确认转换成功 ogrinfo -al -so bj_watershed_projected.shp这段命令做两件事:-t_srs指定目标投影,ogr2ogr自动完成坐标变换;ogrinfo -al -so只输出摘要信息(图层范围、字段、CRS),不打印每个要素,适合快速验证。参数上要注意,如果源数据本身已经是投影坐标,就别再加-t_srs,否则会二次投影导致位置偏移。转换后务必用ogrinfo看一眼 Extent,正常应该在几十万米量级(北京投影坐标大致在 X: 400000–600000,Y: 4300000–4500000),如果还是 115、40 这种小数,说明没转成功。
3. 用 QGIS 和 Python 把面图层跑通:从加载到分区统计
3.1 QGIS 里加载并检查拓扑
第一步永远是加载后目视检查。把面图层拖进 QGIS,右键图层 → 属性 → 信息,看要素数量和 CRS。然后做两件检查:一是"是否有缝隙或重叠",用矢量 → 几何工具 → 检查有效性;二是"是否覆盖全市",叠加一个北京市行政边界底图看边缘。
如果发现面之间有细缝,常见原因是数据从 CAD 或栅格转过来时边界没对齐。处理办法是用"融合"(Dissolve)按水系名称合并,再和行政边界做一次相交,把超出市界的部分裁掉。注意 Dissolve 会丢失子流域字段,所以先备份原始图层。
3.2 用 Python + GeoPandas 做区县水系面积统计
真正批量干活还是靠代码。下面这段用 GeoPandas 读面图层和区县边界,做叠加求交后统计每个区县各水系面积占比:
import geopandas as gpd import pandas as pd # 读取流域面图层和区县边界,两者必须同一投影坐标系 watershed = gpd.read_file("bj_watershed_projected.shp") districts = gpd.read_file("bj_districts_projected.shp") # 统一 CRS,防止叠加时报错或结果错位 if watershed.crs != districts.crs: districts = districts.to_crs(watershed.crs) # 叠加求交:每个区县与每个水系面相交,得到碎片面 inter = gpd.overlay(districts, watershed, how="intersection") # 计算相交面积(投影坐标系下单位为平方米),转平方公里 inter["area_km2"] = inter.geometry.area / 1e6 # 按区县和水系分组汇总 result = inter.groupby(["district_name", "watershed_name"])["area_km2"].sum().reset_index() # 计算每个区县内各水系占比 result["ratio"] = result.groupby("district_name")["area_km2"].transform(lambda x: x / x.sum()) print(result.head(20)) result.to_csv("district_watershed_stats.csv", index=False, encoding="utf-8-sig")逻辑上分四步:统一 CRS → overlay 求交 → 算面积 → 分组汇总。参数上最关键的是how="intersection",它只保留两者重叠部分;如果你要的是"每个区县完整范围",用how="union"但会产生大量空值。area / 1e6是把平方米转平方公里,别写成 1e3。输出 CSV 时加encoding="utf-8-sig",否则 Excel 打开中文会乱码。
跑完先别信结果,做一次校验:把 result 按 district_name 分组求 area_km2 之和,和区县边界自身面积对比,误差应在 1% 以内。差太多说明面图层有重叠或缝隙。
3.3 提取某个断面以上的汇水区
做水文分析的人经常要"某个监测断面以上的汇水范围"。如果面图层是子流域级别,可以用空间选择:先定位断面点,再做"按位置选择"选出包含该点的子流域,然后按水系拓扑向上游追溯。没有拓扑关系时,退而求其次用"包含该点的子流域 + 相邻同水系子流域"近似。
from shapely.geometry import Point # 断面坐标(投影坐标系下) station = gpd.GeoDataFrame( {"name": ["station_A"]}, geometry=[Point(520000, 4420000)], crs=watershed.crs ) # 找出包含断面的子流域 sub = watershed[watershed.contains(station.geometry.iloc[0])] print(sub[["sub_name", "watershed_name"]]) # 近似:取同水系的所有子流域作为上游汇水范围(需人工核对) upstream = watershed[watershed["watershed_name"].isin(sub["watershed_name"])] upstream.to_file("upstream_approx.shp", encoding="utf-8")contains判断点是否在面内,返回布尔序列。isin做属性筛选。这里要提醒:这种"同水系全取"只是近似,真实上游范围需要 DEM 水文分析或已有拓扑关系,别直接拿去出正式报告。参数上Point(x, y)的顺序是 X 在前 Y 在后,和经纬度习惯相反,容易写反。
4. 避坑与排查:面图层处理里最容易踩的五个坑
4.1 面积算出来是"平方度"
现象:用 GeoPandas 的.area算面积,结果是一堆 0.00x 的小数。原因:图层还是经纬度坐标系,.area返回的是平方度。解决:先to_crs("EPSG:4547")再算面积,或者用gpd.GeoSeries.area前确认.crs.is_geographic为 False。
4.2 叠加求交后面积对不上
现象:overlay 之后各区县面积之和比原边界小了一截。原因:面图层之间有缝隙,或者 overlay 时 CRS 不一致导致部分要素被丢弃。解决:先检查两个图层 CRS 是否一致,再用"检查有效性"修复几何,必要时对流域面做一次 buffer(0) 消除自相交。
4.3 中文属性乱码
现象:属性表里水系名称显示成问号或方块。原因:Shapefile 默认编码不是 UTF-8,而数据实际是 GBK。解决:读取时指定encoding="gbk",或转成 GeoPackage(.gpkg)格式,它原生支持 UTF-8,比 Shapefile 省心。
4.4 五大水系名称不统一
现象:分组统计时出现"永定河""永定河系""永定河流域"三组。原因:数据来源拼接时命名没规范。解决:建一个映射字典统一替换,再分组。别手动改,要素多了必漏。
name_map = {"永定河系": "永定河", "永定河流域": "永定河"} watershed["watershed_name"] = watershed["watershed_name"].replace(name_map)4.5 用 Web Mercator 做面积统计
现象:面积统计结果比实际偏大,越往北偏得越多。原因:EPSG:3857 在高纬度面积变形严重。解决:面积统计一律用等积投影或高斯克吕格投影,北京用 EPSG:4547。制图展示可以用 3857,但计算不能用。
5. 进阶:把面图层和栅格、点位数据联动起来
面图层真正的价值在于当"容器"。我一般会用它做三件事:一是把降雨栅格按流域分区统计均值,二是把监测点按流域归类做水质汇总,三是给每个子流域算地形特征(平均高程、坡度)。
以分区统计降雨为例,用 rasterstats 一行搞定:
from rasterstats import zonal_stats # 对每个流域面统计降雨栅格的平均值、最大值 stats = zonal_stats( "bj_watershed_projected.shp", "rainfall_2023.tif", stats=["mean", "max", "min"], geojson_out=True ) for feat in stats: print(feat["properties"]["watershed_name"], feat["properties"]["mean"])zonal_stats要求矢量和栅格 CRS 一致,不一致会报错或结果错位。stats参数按需选,mean最常用。如果栅格是经纬度而矢量是投影坐标,先用gdalwarp把栅格重投影,别指望它自动对齐。
验证方法上,我习惯抽一个流域,用 QGIS 的"分区统计"插件跑一遍,和代码结果对比,误差在 0.1 以内才放心。这一步看着多余,但能挡住 90% 的坐标系和 NoData 问题。
最后说个习惯:每次处理完面图层,我都会把中间结果存成 GeoPackage 而不是 Shapefile,字段名不受 10 字符限制,中文不乱码,一个文件装多层。这个习惯帮我省了无数次返工。希望帮到你。
本文还有配套的精品资源,点击获取