简介:全国湖泊矢量数据集覆盖1960、1970、1990、1995、2000、2005、2010、2015、2020共十个时相,以通用ShapeFile格式存储大于1平方公里的湖泊分布信息,GIS相关专业学生、科研人员及环境监测从业者可直接用于湖泊时空变化分析、专题制图与空间统计。数据基于Landsat影像与地形图,结合半自动水体提取和人工目视解译制作,文件命名中zone_check等标识便于区分质量检查版本;多时相结构为重建1960年以来湖泊演变序列提供了便利。压缩包共81个文件,各年份矢量主文件均配套shx几何索引、dbf属性表、prj投影定义、cpg字符编码及sbn/sbx空间索引,使用ArcGIS、QGIS等软件读取时需保持同目录完整文件集,整体体积约157.46MB。资源描述显示,中国湖泊总数量从1960年2127个增至2020年2621个,总面积从68537平方公里扩张至82302平方公里,这些统计值可直接支撑论文背景、图表绘制与区域对比;每份湖泊边界还包含几何属性,便于面积量算、条件筛选与制图标注。已有97人学习浏览,适合需要现成全国湖泊边界数据完成课程设计、科研绘图或GIS基础操作的读者按需获取。
1. 全国湖泊矢量数据集:一份能回答“湖面变了多少”的60年底图
做水环境评估的朋友第一次拿到全国湖泊矢量数据集时,第一反应往往是双击就打开看。湖的轮廓是出来了,可属性表一片乱码,面积排序也不对,地图和在线底图错开几百米——这些不是数据坏了,而是你还没搞清楚这份 ShapeFile 的“脾性”。这份覆盖 1960—2020 年的全国湖泊矢量数据,最大的价值不是画一张好看的水系图,而是按年份筛选、按湖名聚合、按面积统计,回答“某个湖这 60 年面积到底变了多少”这类问题。适合遥感解译、水环境评价、国土空间规划以及 GIS 教学的人拿来当基础底图。但要把这份数据真正用起来,先得过文件结构、坐标系、字段单位这三关,后面才好做统计和对比。
2. 读懂ShapeFile里的湖泊信息:先看清五个成员文件与字段单位
2.1 一个ShapeFile不是“一个文件”:五个必留成员与各自缺了会怎样
很多人把 ShapeFile 当成某个单一文件,其实它是一组文件的集合。拿到一份完整的湖泊数据,至少有 .shp、.shx、.dbf、.prj 这四样,常见还会有 .cpg。它们各管一段,缺一个都会出问题。
| 扩展名 | 作用 | 缺了会发生什么 |
|---|---|---|
| .shp | 存储面元素的几何坐标 | 图形无法显示,这是主文件,缺了基本等于没有 |
| .shx | 几何索引,加速图形定位 | 缺失时多数 GIS 软件还能打开,但速度明显变慢 |
| .dbf | 存属性表,湖名、面积、年份都在这 | 只剩几何没有属性,无法做任何按年份的筛选 |
| .prj | 记录坐标系文本 | 软件会“猜”成 WGS84,图层和底图大概率错位 |
| .cpg | 声明属性表字符编码 | 中文湖名变成乱码或问号,是高频翻车点 |
实际拿到手时,有的压缩包里 .prj 和 .cpg 根本没有,或者是从其他工程里拷来的同名文件。最好的习惯是先把整个目录的文件列一遍,确认这五个成员都在,再考虑加载。Windows 下如果在资源管理器里只看到 .shp 一个文件,多半是系统设了“隐藏已知文件类型的扩展名”,要把扩展名显示出来再核对。
2.2 属性表字段与单位判断:先从面积排序开始
用 QGIS 或 ArcGIS 打开属性表,湖泊数据的字段结构大同小异。常见的有 OBJECTID、NAME、AREA、PERIMETER、YEAR,也有中文拼音混排的情况,比如 HUMING(湖名)、MJ(面积)、NF(年份)。字段命名并不统一,但至少会有一个名称字段和一个面积字段,年份字段则要看这份数据是否做了分年存储。
面积单位是这份数据里最容易踩的坑。有的数据用平方公里,有的用平方米,还有的用公顷。判断方法很简单,不用看说明文档,直接按面积字段降序排列。全国湖泊前几名应该是青海湖、鄱阳湖、洞庭湖这个量级:如果排第一的数值是 4000 多,那单位大概率是平方公里;如果排第一的数值是 400 多亿,那多半是平方米;如果排第一是 40 多万,则很可能是公顷。这个判断在 2 分钟内就能做,比翻文档快得多。
年份字段也要留意。有的数据按单一时期存储,字段叫 YEAR 表示“该湖面反映的年份”;有的数据把 1960—2020 分成多个时期,用 STIME 和 ETIME 表示起止年份。你需要搞清楚字段的语义再做筛选,否则按 YEAR 过滤时很容易把“某个时间段平均湖面”当成“某一年份湖面”。
2.3 坐标系先验证再动手:别让图层和底图错开几百米
ShapeFile 的坐标系写在 .prj 文件里。用记事本打开 .prj,可以看到类似下面的文本:
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]如果看到 GEOGCS 且包含 WGS_1984,说明是经纬度坐标系;如果看到 PROJCS 和 Albers_Conic_Equal_Area 这样的字眼,则是投影坐标系。不过 .prj 文件是“声明”,并不保证内容正确。最可靠的方式是加载数据后叠加一张在线底图,找一处你熟悉的湖泊或河段,看边界是否贴合。如果整体平移几十米到几百米,或者湖岸线和底图上的水系走向不一致,就要怀疑坐标系标注有问题了。
一个容易被忽略的细节:QGIS 在缺失 .prj 时默认按 WGS84 处理,表面上能显示,但面积量算会出现偏差。因为经纬度坐标系下用软件直接算面积,得到的是“度平方”而不是平方米,这会给后面的面积统计埋下隐患。所以无论从哪个渠道拿到的湖泊数据,加载后的第一件事不是画图,而是确认坐标系声明和实际地理位置对得上。
3. 在QGIS里跑通最小流程:加载、按年份筛选与导出GeoPackage
3.1 拖入图层并做分级着色:为何选Jenks而非等距
把 .shp 文件直接拖进 QGIS 窗口,图层会以默认方式显示——通常是一个纯色面加细黑边。全国尺度下,上千个湖挤在一起,根本看不出分布规律。这时右键图层打开“属性”,切到“符号化”,把“单符号”改成“分级”。值选择面积字段,模式选“Natural Breaks (Jenks)”,类别数给 5 到 6 个,配色用蓝色系。
为什么要选 Jenks 而不是等距间隔?湖泊面积是典型的偏态分布,鄱阳湖、青海湖这样的巨型湖会把等距间隔的整个范围拉大,其余小湖全部落在第一个色阶里,图面基本全是一种颜色。Jenks 基于数据分布找断点,能照顾到大部分中小湖泊。类别数 5 到 6 个是视觉上的平衡点:太少区分度不够,太多读图费力。
![注意] QGIS 的分级模式里还有“分位数”和“标准差”。如果只需要看分布趋势,分位数更稳;如果要做面积分级对比,Jenks 更合适。
3.2 按年份构造查询表达式,把“无年份”要素踢出去
做时序分析前,先做一次按年份的要素选择。假设属性表里的年份字段叫 YEAR,双击鼠标选中某一年很简单,但要把条件写清楚,还是用“按表达式选择要素”更靠谱。打开菜单“选择”-“按表达式选择要素”,输入:
"YEAR" = 1990这会选中 1990 年的所有湖泊图斑。如果 YEAR 字段是文本类型,直接等值匹配可能查不到结果,要先做类型转换:
to_int("YEAR") = 1990 AND to_real("AREA_KM2") > 10to_int 和 to_real 是 QGIS 表达式里的转换函数,to_int 把字符串“1990”转成整数 1990,to_real 把面积字符串转成浮点数。需要注意,这个转换只是读取时的临时计算,不会修改原始字段。表达式是 QGIS 的语法,和 PostGIS 里的标准 SQL 不完全一样,在写具体函数前先确认对话框底部有没有报错提示。
实际数据里经常混着 YEAR 为 0、为空或为 NULL 的要素。这些要素往往代表“底图湖面”,也就是无法确定具体年份的基础湖泊信息。做时间序列分析时要把它们排除,否则统计结果会出现莫名其妙的“0 年湖泊”。我一般会先跑一次统计,看各个年份的要素数量分布,确认数据覆盖情况再开始正式分析。
3.3 导出GeoPackage,避免每次打开都卡半天
全国尺度的湖泊面数据动辄几百兆,直接拿 .shp 反复读写会很吃力,尤其是 .dbf 属性表大时每次全表扫描都明显卡顿。建议右键图层选择“导出”-“保存要素为”,格式选 GeoPackage,目标文件名给成 lakes_1960_2020.gpkg。GeoPackage 是单文件格式,自带空间索引,读写性能比成堆的 .shp 文件好得多。
导出时有两个选项要留意:一是“仅保存所选要素”,如果你已经按年份筛选过,勾上这个可以把当前选中的要素单独导成一份子集;二是 CRS 选择,默认是“本图层坐标系”,如果后续要和在线底图叠加分析,导出的同时勾选目标坐标系即可。直接在这里做重投影,比加载后再“另存为”省一步操作。
如果团队协作需要入库,也可以用导出对话框里的 PostGIS 连接,把数据直接写入数据库。大批量导入时,QGIS 的“DB Manager”导入工具在文件较大时偶尔超时,更稳的做法是先用 GDAL 命令行做一次转换,再导入。不过对于多数单机分析场景,导成 GeoPackage 已经足够。
4. 用Python做60年湖泊统计:GeoPandas读取、面积重算与聚合口径
4.1 read_file读取ShapeFile:先看行数、字段和坐标系
Python 处理 ShapeFile 最顺手的方式是 GeoPandas。安装好 geopandas、pandas 和 pyogrio 之后,读取一份文件只需两行:
import geopandas as gpd gdf = gpd.read_file("lakes_1960_2020.shp", encoding="UTF-8") print("要素总数:", len(gdf)) print("字段列表:", gdf.columns.tolist()) print("坐标系:", gdf.crs) print(gdf.head(3))编码参数是踩坑重灾区。如果属性表里汉字显示成乱码,把 encoding 改成 "GBK" 再读一次。有的数据虽然提供了 .cpg 文件,但实际 .dbf 内容还是 GBK 编码,README 和实际文件不一致的情况并不少见。所以第一次读取时别急着跑后续代码,先把字段和几条要素打印出来确认。
如果读出来后 gdf.crs 显示为 None,说明 .prj 缺失。这时候你可以用gdf.crs = "EPSG:4326"临时指定接坐标,但前提是你有把握这份数据确实是 WGS84 经纬度。乱指定坐标系比缺失更危险,因为所有几何计算和投影转换都会基于这个错误假设继续错下去。拿不准时宁可在代码注释里标明“待定坐标系”,也不要随手填一个。
4.2 按年份与湖名聚合:一次命中两个统计口径
拿到数据后的第一个统计需求通常很直接:每年有多少个湖,总面积多少。核心代码很简单:
# 年份字段若是文本先转数值 gdf["YEAR"] = gdf["YEAR"].astype(int) # 按年份聚合:统计每个年份的湖泊个数和总面积 year_stats = gdf.groupby("YEAR").agg( lake_count=("AREA_KM2", "count"), total_area=("AREA_KM2", "sum") ) print(year_stats)但这里有个隐蔽问题:一个湖在矢量数据里未必只有一个面。湖中岛、复杂岸线、多子湖面都可能导致同一湖泊被拆成多个图斑,同一个湖名在同一个年份里出现多次。如果直接按要素计数,会把几个子湖面当成几个湖。处理办法是二次聚合:
# 先筛出目标年份,再按湖名聚合 lake_1990 = gdf[gdf["YEAR"] == 1990].groupby("NAME")["AREA_KM2"].sum() print(lake_1990.sort_values(ascending=False).head(10))先按年份筛,再按湖名求和,才是“湖泊面积”的正确口径。如果你拿到的是含部分湖泊多个年份的完整 60 年序列,我建议额外统计每个湖在每个年份出现的图斑数,如果某些年份图斑数突然增多,很可能是数据在当年做了更细的拆分,而不是湖真的多了。
4.3 面积字段和几何面积闹矛盾:什么时候要投影重算
很多湖泊数据自带了 AREA_KM2 字段,但它的计算口径未必统一。有的数据用原始投影坐标直接量算,有的数据做了等积投影却忘了改单位,还有的只是某个时期的参考面积,年代跨越后湖面已经变了。稳妥做法是用几何重新算一遍面积。
# 转成适合全国范围的Albers等积投影 gdf_aea = gdf.to_crs("EPSG:102025") # 算出新的面积,单位是平方米 gdf_aea["calc_area_km2"] = gdf_aea.geometry.area / 1e6 # 和原始面积字段比较差异比例 gdf_aea["diff_pct"] = ( (gdf_aea["AREA_KM2"] - gdf_aea["calc_area_km2"]) / gdf_aea["AREA_KM2"] * 100 ) print(gdf_aea["diff_pct"].describe())EPSG:102025 是常见的选择,它基于 Krasovsky 椭球体的 Albers 等积投影,覆盖中国全境时面积变形比较小。如果数据本身是分省或分流域的,还可以按所在区域选对应的 UTM 分带(比如华东用 EPSG:32650,华南用 EPSG:32649),分带覆盖范围内误差更小。要注意的是,Web Mercator 投影(EPSG:3857)不能用来算面积,它在高纬度会把面积显著放大,结果会非常离谱。
diff_pct 的结果如果中位数在 1% 以内,说明原始面积字段和几何面积基本一致,可以放心用原字段;如果差异超过 5%,那就要统一用重算后的 calc_area_km2 做后续分析,并在交付时说明面积口径。数据处理里最怕的就是每个环节都看似正常,最后汇总时发现面积前后对不上。
5. 避坑:湖泊矢量数据最常遇到的4个问题与修复步骤
5.1 属性表乱码:满屏问号不是数据集损坏
现象:打开属性表,湖名一栏显示成“鍚磋”或者全部是“?”,完全没法看。
原因:ShapeFile 的 .dbf 属性表本身不保存编码信息,靠 .cpg 文件声明。很多历史数据是用 GBK 编码写入的,但 .cpg 丢失,或者 QGIS 默认按 UTF-8 读取,导致中文字符解析失败。
解决:在 QGIS 的“数据处理”工具箱里搜索“修复数据文件编码”,或者打开数据源时手动指定编码为 GBK。Python 端就是前面提到的,在 read_file 里传 encoding="GBK"。如果整份数据里只有个别字段乱码,可以用 QGIS 的“编辑字段”工具手动重建该字段并复制值,但更稳的做法是拿着原始压缩包里的说明文档确认编码,再统一处理。血泪经验:千万别在 QGIS 里直接手动逐个改名,几千个湖名改不完。
5.2 几何自相交:合并和缓冲区操作莫名报错
现象:加载数据不报错,但执行“合并”“缓冲”“修复几何”等操作时提示“几何无效”或“面自相交”。选中某个湖泊时,QGIS 状态栏提示存在无效几何。
原因:原始矢量化时岸线经过多次编辑,同一个面内出现自相交的环,或者顶点重复、顺序错乱。这类问题在人工勾绘的历史湖泊数据里很常见,最新遥感解译数据反而少一些。
解决:在 Python 里先用 buffer(0) 做一个零距离缓冲修复:
# 先备份原始面积字段,修复可能微调边界 gdf["AREA_SRC_KM2"] = gdf["AREA_KM2"] # buffer(0) 会消除自相交,但保持边界基本不变 gdf["geometry"] = gdf.geometry.buffer(0) # 检查修复后有效性 print(gdf.geometry.is_valid.value_counts())也可以直接在 QGIS 里用“修复几何”工具,输出一个新图层。需要注意,任何几何修复都可能让岸线发生几米到几十米的微调,面积统计结果会有一点点变化。所以修复前务必备份原始面积字段,修复后重新核算一遍,写到新字段里,不要覆盖原始数据。修复完如果还有零星无效要素,先单独导出再做人工检查,别一刀切删除。
5.3 坐标系错位:图层和底图对不上,先分清“指定”和“重投影”
现象:叠加在线底图上,湖泊整体跑到山体上,或者整体偏移一段固定距离。
原因:坐标系声明和实际数据不一致。常见有两种:一是数据实际采用 CGCS2000 或西安 80 坐标系,但 .prj 写成 WGS84;二是 .prj 缺失,QGIS 默认当成 WGS84 加载,而原始数据是投影坐标。
解决:首先要分清两个概念。“设置源 CRS”是告诉软件“这份数据原本是什么坐标系”,不会改变坐标数值;“重投影”是改变坐标数值来匹配另一个坐标系。在 QGIS 里用“图层属性-源-分配源 CRS”指定正确的原始坐标系,导出时再选择目标坐标系,这条链路才对。很多人一看到错位就点“重投影”,结果原本只偏一点的数据被二次投影,错得更远。判断依据是看偏移模式:如果整幅图朝同一方向平移固定距离,多半是椭球体基准差异;如果不同区域偏移方向不一致,就要考虑七参数转换,那已经不是点几个按钮能解决的事了。
5.4 年份不连续、缺失和无年份要素混在一起
现象:做趋势图时发现 1960、1975、1990、2020 几个年份数据完整,中间年份大面积缺失;年份字段里还有不少 0、NULL 或空字符串。
原因:这类长时序矢量数据集大多不是逐年观测,而是把“关键年份实测”和“长期平均范围”拼装在一起。年份为 0 或 NULL 的要素通常是用来表达历史最大湖面或底图水体范围的,不属于特定年份。
解决:动手统计前先做一次年份分布检查:
# 检查每个年份的要素数量 print(gdf["YEAR"].value_counts().sort_index().head(20))看到缺失年份不要用插值补齐,也不要拿 NULL 要素去填,那样做出来的曲线看起来平滑,实际上是把不同口径的数据强行拼到了一起。正确做法是:写分析报告时明确列出有数据的年份;无年份要素单独存成一个底图图层,只在需要“历史最大湖面”时参与叠加显示,不进时序统计。缺年份的另一个常见后果是,趋势图会从 1960 直接跳到 2020,让人误以为湖泊面积在长期线性变化,实际上中间数据根本不存在。对这种长时序数据,采样点之间做线性连接是可以的,但一定要在图表上把有实测值的年份标清楚。
6. 从“能打开”到“能下结论”:面积校验与口径固化
6.1 选一个湖做闭环验证
整套流程跑通后,先别急着出全图。挑一个你最熟悉的湖,比如鄱阳湖或者青海湖,查它某个年份的面积,和公开水文资料或遥感影像上的量测值对比。误差在 5% 以内,说明数据质量可以信任;超过 10%,就要检查是不是面积单位、坐标系或筛选条件出了问题。
6.2 做“数量-面积”双曲线
把每年湖泊总数和总面积放在同一张图里看。如果某年湖泊数量陡增但总面积没变,说明这一年把更多小湖纳入了采集范围;如果数量不变但面积突变,才是真实的湖面变化。这两条曲线能快速暴露字段口径不一致问题,比单看一条面积曲线可靠得多。
6.3 把处理口径写进元数据
我习惯在任何筛选、投影和面积重算后,把处理过程写成一个小文件放在输出目录里:
{ "input": "lakes_1960_2020.shp", "encoding": "GBK", "source_crs": "WGS84", "area_crs": "EPSG:102025", "missing_years": [1961, 1962], "area_field": "AREA_KM2", "notes": "几何已用buffer(0)修复,面积统一为重算值" }这个习惯来自一次翻车经历:我曾经对一份数据做了投影转换却没有记录原坐标系,两周后回头发现面积统计结果对不上,查了半天才想起来是坐标系不一致。从那以后,任何处理过的矢量数据都必须带一份可读的元数据,否则一个月后连自己都说不清“当时为什么这么算”。希望帮到你。
本文还有配套的精品资源,点击获取