1. 为什么CLCD数据集值得你花时间折腾
做地理信息、生态评估、城市规划或者农业遥感的人,手里没几套趁手的土地覆盖数据,基本等于上战场没带枪。我入行这些年,经手的土地覆盖产品少说也有七八套,从早期的UMD、GLCF到后来的ESA CCI、GlobeLand30,每一套都有自己的脾气。但要说近两年圈子里讨论度最高、实际项目里出场率最猛的,CLCD(China Land Cover Dataset)绝对排得上前三。这套由武汉大学杨杰教授团队发布的年度土地覆盖数据集,覆盖了从1985年到2025年的长时间序列,空间分辨率30米,直接对标Landsat系列影像的原始粒度,而且逐年更新,这个更新频率在国产数据里算是相当能打的了。
很多人第一次听到CLCD,最关心的问题就两个:这东西准不准,以及我上哪儿弄。先说准不准。CLCD的整体精度在80%以上,部分年份和区域能到85%甚至更高,这个数字放在全球同类产品里不算天花板,但考虑到它用的是Landsat这种免费但噪声不小的数据源,再加上中国复杂的地表覆盖类型,能做到这个水平已经相当不容易了。更关键的是,它针对中国区域做了大量本地化优化,比如对农田撂荒、城市扩张、退耕还林这些典型变化做了专门处理,这一点是很多全球产品做不到的。你拿ESA CCI的数据去看中国某个县的耕地变化,经常会出现莫名其妙的跳变,但CLCD在这方面的表现就稳得多。
再说格式。CLCD提供的是GeoTIFF格式,每个年份一个文件,坐标系是WGS84,投影是经纬度,像素值对应的是土地覆盖类别编码。这个格式对搞GIS的人来说几乎是零门槛,ArcGIS、QGIS、ENVI、Python的rasterio、GDAL都能直接读,不需要额外的转换工具。文件大小方面,全国范围的单年份数据大概在几百MB到1GB左右,具体取决于压缩方式和年份,这个体量对现在的硬盘和内存来说完全不是问题,普通笔记本就能跑得动。
适合谁用?我大概列一下:做土地利用变化监测的、搞生态系统服务评估的、做城市扩张分析的、研究农业种植结构的、写论文需要长时间序列数据的、做国土空间规划的,甚至一些做碳汇核算的团队也在用。如果你只是偶尔需要看看某地的地表类型,那用在线地图或者Google Earth就够了,没必要折腾这套数据。但如果你要做时间序列分析、要算转移矩阵、要出图出表出报告,那CLCD基本是目前国内最省心的选择之一。
2. CLCD数据集的核心设计与技术拆解
2.1 数据生产的技术路线
CLCD的生产流程不是简单地把Landsat影像做监督分类就完事了。杨杰团队用的是一套“样本迁移+时空滤波”的组合拳。具体来说,他们先基于高分辨率影像和实地调查数据建立了一个高质量的样本库,然后用这些样本去训练分类器,对每一年的Landsat影像做初步分类。但初步分类的结果往往会有噪声,比如某一年某块地突然从耕地变成林地,下一年又变回来,这种跳变在现实中很少发生,大概率是分类错误。所以他们又加了一步时空滤波,利用时间序列上的连续性和空间上的邻域一致性来修正这些异常值。
这个思路听起来简单,但实现起来非常考验功力。样本库的质量直接决定了分类的上限,而时空滤波的参数设置又决定了修正的力度。力度太小,噪声去不干净;力度太大,真实的变化信号又被抹掉了。我实测下来,CLCD在大多数区域的表现是偏保守的,也就是说它宁可漏掉一些细微变化,也不愿意引入太多假变化。这个取舍对于做宏观分析的人来说是好事,但对于做精细地块监测的人来说可能就不太够用。
2.2 分类体系与编码规则
CLCD用的是9类分类体系,具体包括:耕地、林地、灌木、草地、水体、冰雪、裸地、不透水面、湿地。这个体系跟IPCC的土地利用分类基本对齐,做碳核算和生态评估的时候可以直接映射,省去了很多转换的麻烦。每个类别对应的像素值我列一下,方便你写代码的时候直接查:
| 类别名称 | 像素值 | 典型地物 |
|---|---|---|
| 耕地 | 1 | 水稻、小麦、玉米等农田 |
| 林地 | 2 | 乔木林、竹林、经济林 |
| 灌木 | 3 | 灌木丛、灌草丛 |
| 草地 | 4 | 天然草地、人工牧草地 |
| 水体 | 5 | 河流、湖泊、水库、坑塘 |
| 冰雪 | 6 | 冰川、永久积雪 |
| 裸地 | 7 | 沙地、裸岩、裸土 |
| 不透水面 | 8 | 城市、村庄、道路、工矿 |
| 湿地 | 9 | 沼泽、滩涂、红树林 |
这里有个坑要注意:不同年份的数据,编码规则理论上是一致的,但如果你从不同渠道下载,有可能遇到编码被重新映射的情况。我就遇到过某次从第三方镜像站拿到的数据,水体被编码成了5,但另一个年份的水体被编码成了255,差点把分析结果搞废。所以拿到数据的第一件事,永远是先用QGIS或者Python打开看一眼,确认像素值的分布范围,别上来就跑模型。
2.3 与同类产品的对比
市面上能跟CLCD掰手腕的产品不多,我挑几个常见的做个对比:
| 产品名称 | 分辨率 | 时间范围 | 中国区域精度 | 获取难度 |
|---|---|---|---|---|
| CLCD | 30m | 1985-2025 | 80%+ | 中等 |
| GlobeLand30 | 30m | 2000,2010,2020 | 75-85% | 较低 |
| ESA CCI | 300m | 1992-2020 | 70-80% | 低 |
| MCD12Q1 | 500m | 2001-2022 | 75-85% | 低 |
| FROM-GLC | 30m | 2010,2015,2017 | 70-80% | 中等 |
从表里能看出来,CLCD最大的优势是时间序列长且连续,1985到2025每年都有,这对做长期变化分析的人来说是刚需。GlobeLand30虽然也是30米,但只有三个年份,做趋势分析的时候中间得靠插值,误差会累积。ESA CCI和MCD12Q1分辨率太粗,做县级以下的分析基本没法看。FROM-GLC的年份又太少。所以如果你要做的是“某地过去30年土地利用怎么变的”这类问题,CLCD几乎是唯一的选择。
3. 从下载到出图:CLCD实操全流程
3.1 数据获取的几条路子
CLCD的官方发布渠道是武汉大学杨杰教授团队的GitHub页面和Zenodo仓库。GitHub上主要是代码和说明文档,Zenodo上存放的是实际的数据文件。Zenodo的好处是支持直接下载,不需要注册,而且有DOI,引用起来方便。但Zenodo的下载速度在国内有时候不太稳定,尤其是大文件,断流是常有的事。我的经验是,如果单个文件超过500MB,最好用下载工具挂着,别用浏览器直接下,不然下到一半断了会非常抓狂。
除了官方渠道,国内也有一些科研机构和企业做了镜像,比如一些高校的地理数据中心、国家地球系统科学数据中心等。这些镜像的好处是下载速度快,但缺点是更新可能不及时,而且有些镜像会对数据进行重投影或者重采样,用之前一定要确认清楚。我个人的习惯是,能用官方就用官方,实在下不动再考虑镜像,而且用镜像数据之前一定会拿官方数据做个交叉验证,确保像素值对得上。
还有一个渠道是学术社交平台,比如ResearchGate上经常有人分享自己整理好的CLCD数据包,有些还附带了预处理脚本。这种资源用起来要小心,因为你不确定对方有没有改过数据。我一般只拿这些包做参考,实际分析还是用官方原始数据。
3.2 数据预处理的关键步骤
拿到GeoTIFF文件之后,别急着往模型里塞。我一般会走这么几步:
第一步,检查数据的完整性和一致性。用Python的rasterio或者GDAL写个脚本,批量读取所有年份的文件,输出每个文件的尺寸、投影、像素值范围、NoData值。这一步能帮你快速发现有没有文件损坏或者投影不一致的问题。我遇到过某一年份的数据投影是Albers,其他年份是WGS84,如果不检查直接合并,结果会错得离谱。
第二步,统一NoData值。CLCD的NoData通常是0或者255,但不同年份可能不一样。我一般统一设成0,然后在后续分析中把0排除掉。这个操作在rasterio里很简单,读进来之后用numpy做个掩膜就行。
第三步,按需裁剪。全国范围的数据很大,如果你只研究某个省或者某个流域,没必要把全国的数据都读进来。用GDAL的Warp或者rasterio的mask功能,按你的研究区边界裁剪,能省不少内存和时间。裁剪的时候注意用矢量边界,别用手画的矩形,不然边缘会出现锯齿。
第四步,重投影(如果需要)。CLCD默认是WGS84经纬度坐标,如果你要做面积统计,最好重投影到等面积投影,比如Albers或者UTM。经纬度坐标下算面积会有变形,尤其是高纬度地区,误差能到百分之十几。重投影用GDAL的warp或者rasterio的reproject都行,重采样方法选最近邻,因为土地覆盖是分类数据,用双线性或者立方卷积会把类别值搞乱。
3.3 用Python做转移矩阵的完整代码
转移矩阵是土地利用分析里最常用的工具,能直观看出不同类别之间的转换关系。下面是我常用的一个脚本,基于rasterio和pandas,直接跑就行:
import rasterio import numpy as np import pandas as pd def transition_matrix(tif_early, tif_late, output_csv): with rasterio.open(tif_early) as src: early = src.read(1) with rasterio.open(tif_late) as src: late = src.read(1) mask = (early > 0) & (late > 0) early_valid = early[mask] late_valid = late[mask] classes = [1, 2, 3, 4, 5, 6, 7, 8, 9] matrix = np.zeros((len(classes), len(classes)), dtype=np.int64) for i, c1 in enumerate(classes): for j, c2 in enumerate(classes): matrix[i, j] = np.sum((early_valid == c1) & (late_valid == c2)) df = pd.DataFrame(matrix, index=classes, columns=classes) df.to_csv(output_csv) return df result = transition_matrix('CLCD_2000.tif', 'CLCD_2020.tif', 'transition_2000_2020.csv') print(result)这段代码的逻辑很直白:读两个年份的数据,取有效值,然后双重循环统计每个类别对的数量。跑全国数据的话,内存占用大概在2-4GB,普通笔记本能扛住。如果内存不够,可以分块处理,rasterio支持窗口读取,按行块循环就行。
跑完之后,你拿到的CSV可以直接丢进Excel做热力图,或者用matplotlib画桑基图。我一般还会算一下每个类别的净变化量,就是转出和转入的差值,这个指标比单纯的面积变化更能反映地类的动态。
3.4 精度验证的实操方法
CLCD虽然整体精度不错,但不同区域、不同年份的精度是有差异的。如果你做的分析对精度要求很高,比如要发顶刊或者要支撑政策决策,那最好做一下本地化的精度验证。方法也不复杂:用Google Earth的高分辨率影像,随机撒点,人工判读,然后跟CLCD的分类结果做混淆矩阵。
撒点的数量根据你的研究区大小来定,一般至少300个点,面积大的话500-1000个点更稳妥。撒点的时候要注意分层随机,每个类别都要有足够的样本,不然稀有类别(比如湿地、冰雪)的精度没法评估。人工判读的时候,时间要对齐,比如你验证的是2020年的CLCD,那Google Earth的影像也要选2020年前后的,别拿2015年的影像去验证2020年的数据。
混淆矩阵算出来之后,重点看两个指标:总体精度和Kappa系数。总体精度80%以上算合格,85%以上算优秀。Kappa系数0.7以上算合格,0.8以上算优秀。如果某个类别的用户精度或者生产者精度特别低,比如湿地的精度只有50%,那你在后续分析中就要对这个类别的结果保持谨慎,或者考虑合并类别。
4. 常见问题与排查技巧实录
4.1 数据下载与解压的坑
CLCD的数据文件有时候是分卷压缩的,比如part1、part2、part3,你得全部下载完才能解压。我见过有人只下了part1就急着解压,结果报错说文件损坏,折腾半天才发现是没下全。所以下载之前先看清楚文件列表,确认所有分卷都齐了再动手。
解压的时候,Windows自带的解压工具有时候会出问题,尤其是文件路径里有中文或者特殊字符的时候。我的建议是用7-Zip或者Bandizip,这两个工具对长路径和特殊字符的支持比较好。解压完之后,检查一下文件数量对不对,比如1985到2025应该是41个文件,如果少了,说明解压不完整。
还有一个坑是文件命名。CLCD的文件名有时候带版本号,有时候不带,不同年份的命名规则可能不一致。我一般会写个脚本批量重命名,统一成“CLCD_YYYY.tif”的格式,这样后续处理的时候不用每次都去查文件名。
4.2 投影和坐标系的常见错误
投影问题是GIS分析里最常见的错误来源,没有之一。CLCD默认是WGS84经纬度,但很多人在做分析的时候忘了重投影,直接拿经纬度算面积,结果出来的数字跟实际差了一大截。我举个例子:在赤道附近,1度经纬度大概是111公里,但在北纬40度,1度经度只有85公里左右,差了将近30%。如果你研究的是东北或者新疆,这个误差会非常显著。
另一个常见错误是投影定义丢失。有些GeoTIFF文件在传输或者转换过程中,投影信息会丢失,打开之后显示的是“Unknown”或者“User Defined”。这时候你得手动指定投影,CLCD的话就是EPSG:4326。指定完之后,最好再跟一个已知坐标的点做个对照,确认投影没问题。
还有一种情况是投影带不一致。比如你研究区跨了两个UTM带,裁剪的时候如果只用一个带的投影,边缘区域会有变形。这时候要么用Albers这种等面积投影,要么分带处理,别偷懒。
4.3 像素值异常的处理
前面提到过,CLCD的像素值理论上应该是1-9,但实际数据里可能会出现0、255或者其他值。0通常是NoData,255可能是填充值或者错误值。我遇到过某一年份的数据里出现了10和11,查了半天才发现是分类的时候把云影和云分别编码成了10和11,但官方文档里没写。所以拿到数据之后,第一件事永远是统计像素值的分布,用numpy的unique函数跑一下,看看有没有意料之外的值。
如果发现了异常值,处理方式取决于你的分析目的。如果只是做宏观统计,可以直接把异常值掩掉,不参与计算。如果要做精细分析,那就得搞清楚这些异常值的来源,是云污染、传感器故障还是分类错误,然后决定是修正还是剔除。
还有一个细节是像素值的类型。CLCD的数据通常是uint8,也就是0-255的整数。但有些处理工具在读写的时候会自动转成float32或者int16,导致像素值发生变化。我一般会在读取的时候显式指定dtype,比如src.read(1, masked=True),这样能保证读进来的值跟原始数据一致。
4.4 大面积分析的性能优化
全国范围的CLCD数据分析,如果不做优化,跑一次转移矩阵可能要几个小时。我总结了几条提速的经验:
第一,用分块处理。rasterio支持按窗口读取,你可以把全国数据切成若干块,逐块处理,最后合并结果。这样内存占用小,而且可以并行化。我一般用concurrent.futures开4-8个进程,速度能提升3-5倍。
第二,用numpy的向量化操作代替循环。上面转移矩阵的代码里用了双重循环,虽然逻辑清晰,但速度慢。如果类别少,可以用np.bincount或者np.histogram2d来加速。比如np.histogram2d(early_valid, late_valid, bins=range(1,11))就能直接算出转移矩阵,速度快很多。
第三,用压缩格式存储中间结果。CLCD的GeoTIFF如果不用压缩,单年份文件能到2-3GB。用LZW或者DEFLATE压缩之后,能降到几百MB,读写速度也更快。rasterio在写文件的时候可以指定compress='lzw',这个习惯能帮你省不少硬盘空间。
第四,如果只是做可视化,没必要读全分辨率数据。用GDAL的Overview或者rasterio的out_shape参数,把数据降采样到合适的分辨率再出图,速度会快很多。出图的时候用matplotlib的imshow,配合自定义的colormap,效果比ArcGIS默认的配色好看得多。
4.5 常见问题速查表
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 打开数据全黑或全白 | 像素值范围不对或NoData设置错误 | 检查dtype和NoData值,用QGIS的拉伸功能调整显示 |
| 面积统计结果偏大或偏小 | 投影不是等面积投影 | 重投影到Albers或UTM |
| 不同年份数据对不齐 | 投影或分辨率不一致 | 统一重采样到相同网格 |
| 转移矩阵出现大量0值 | 掩膜设置错误或数据范围不重叠 | 检查掩膜条件和研究区边界 |
| 下载速度极慢或断流 | 服务器限速或网络不稳定 | 用下载工具挂后台,或换镜像源 |
| 解压报错 | 分卷不完整或解压工具不兼容 | 确认分卷齐全,换7-Zip解压 |
| 像素值出现非1-9的值 | 数据版本差异或处理引入 | 统计unique值,对照官方文档确认 |
5. 几个容易被忽略的进阶用法
5.1 结合DEM做地形约束的分类后处理
CLCD本身没有用地形信息,但你可以把DEM(比如SRTM或者ASTER GDEM)拉进来做后处理。举个例子,高海拔区域(比如4000米以上)出现耕地的概率极低,如果你发现CLCD在某个高海拔区域把大片区域分成了耕地,那大概率是分类错误。这时候可以用DEM做一个掩膜,把高海拔的耕地修正为草地或者裸地。
具体操作也不复杂:用rasterio读DEM和CLCD,对齐网格,然后写个条件判断,if dem > 4000 and clcd == 1: clcd = 4。这个操作能显著提升山区数据的合理性。我试过在青藏高原边缘做这个处理,耕地面积减少了大概15%,跟实地调查的结果更接近。
5.2 用时间序列做变化检测的平滑处理
CLCD是逐年数据,如果你直接拿相邻两年做差值,会发现很多“假变化”,比如某块地今年是耕地,明年是林地,后年又变回耕地。这种跳变在现实中很少发生,大概率是分类噪声。处理方法是对每个像素的时间序列做平滑,比如用中值滤波或者众数滤波,窗口大小设为3-5年。
Python里可以用scipy.signal.medfilt对时间维度做中值滤波,但要注意内存,全国数据的时间序列是一个三维数组(年份×行×列),直接跑会爆内存。我的做法是分块处理,每次读一个窗口的所有年份数据,做平滑,再写回去。这样内存占用可控,速度也能接受。
5.3 与统计年鉴的交叉验证
CLCD的分类结果可以跟官方统计年鉴做交叉验证。比如统计年鉴里有耕地面积、林地面积、建设用地面积,你可以把CLCD的统计结果跟年鉴数据做个对比,看看趋势是否一致。如果某一年份的差异特别大,那就要警惕了,可能是数据问题,也可能是分类错误。
我一般会算一个相对偏差,(CLCD面积 - 年鉴面积) / 年鉴面积,如果偏差在10%以内,算正常;10-20%需要关注;超过20%就得仔细排查了。需要注意的是,年鉴数据的统计口径跟遥感分类不完全一致,比如年鉴里的耕地可能包括休耕地,而CLCD可能把休耕地分成了草地,所以对比的时候要心里有数,别死磕数字。
5.4 批量出图的自动化脚本
如果你要出几十张图,手动操作肯定不现实。我一般用matplotlib写个循环,批量读取、渲染、保存。配色方案我习惯用自定义的colormap,耕地用黄色,林地用深绿,水体用蓝色,不透水面用红色,这样一眼就能看出地类分布。
import rasterio import matplotlib.pyplot as plt from matplotlib.colors import ListedColormap import numpy as np colors = ['#FFFF00', '#006400', '#8B4513', '#90EE90', '#0000FF', '#FFFFFF', '#A9A9A9', '#FF0000', '#00CED1'] cmap = ListedColormap(colors) years = range(1985, 2026) for year in years: with rasterio.open(f'CLCD_{year}.tif') as src: data = src.read(1) data = np.where(data == 0, np.nan, data) plt.figure(figsize=(12, 8)) plt.imshow(data, cmap=cmap, vmin=1, vmax=9) plt.colorbar(ticks=range(1,10), label='Land Cover Class') plt.title(f'CLCD Land Cover {year}') plt.savefig(f'CLCD_{year}.png', dpi=150, bbox_inches='tight') plt.close()这个脚本跑一遍,41张图就全出来了,省时省力。出图的时候注意dpi别设太低,150-300比较合适,太低看不清细节,太高文件太大。
6. 我在实际项目里踩过的坑和总结的经验
CLCD这套数据我用在过好几个项目里,有做流域生态评估的,有做城市扩张监测的,也有做农田撂荒分析的。踩过的坑不少,挑几个有代表性的说说。
第一个坑是版本混淆。CLCD有过几次更新,不同版本之间的分类结果有细微差异。我有一次做时间序列分析,前半段用的是v1.0的数据,后半段用的是v1.1的数据,结果在拼接年份的时候发现同一块地在同一年份的分类结果不一样,差点把结论搞反。后来我养成了一个习惯:所有年份的数据必须来自同一个版本,下载的时候把版本号记清楚,分析之前再核对一遍。
第二个坑是边界效应。CLCD在国界附近的表现有时候不太稳定,因为Landsat影像在边境区域可能会有拼接问题。我做跨境流域分析的时候,发现国界两侧的分类结果有明显的不连续,后来查了一下,是因为两侧用了不同的影像源。处理方法是把研究区往内缩一点,避开边境缓冲区,或者用高分辨率影像做局部修正。
第三个坑是时间对齐。CLCD的年份标签是“年”,但实际影像的获取时间可能跨越好几个月。比如2020年的数据,可能用的是2019年12月到2020年11月的影像。如果你做的是物候相关的分析,这个时间偏差会有影响。我的做法是,如果分析对时间敏感,就去查官方文档里的影像获取时间范围,然后在论文或者报告里注明这个不确定性。
第四个坑是内存管理。全国范围的时间序列分析,如果一次性把所有年份的数据读进内存,32GB的内存都不够用。我现在的做法是,能用分块就用分块,能用磁盘缓存就用磁盘缓存,别跟内存较劲。Python的dask库对这类任务支持很好,可以试试。
最后说一个心得:CLCD虽然好用,但它不是万能的。任何遥感分类产品都有误差,关键是你要清楚误差在哪里、有多大、对你的分析有什么影响。我一般会在报告里专门写一节“数据局限性”,把精度验证的结果、已知的问题、处理的策略都写清楚,这样别人引用你的结果的时候心里有底,你自己回头看的时候也知道哪些结论是稳的、哪些是存疑的。
这套数据后续还可以跟夜间灯光、人口格网、GDP空间化数据做叠加分析,能挖出不少有意思的东西。比如不透水面的扩张跟夜间灯光增强的相关性,耕地变化跟人口迁移的关系,这些都是可以深入做的方向。数据是死的,怎么用才是活的。