做城市研究、区域经济分析或者碳排放估算的朋友,应该都有过这种经历:想画一条从改革开放初期到现在的长时序灯光变化曲线,结果发现手里的夜光遥感数据断成了两截——2000年代初到2013年靠DMSP-OLS,2012年以后靠NPP-VIIRS,两个传感器的量化位数、过饱和特性和空间分辨率都不一样,数值直接没法对接。这也是最近几年国内测绘制图圈子里"类NPP-VIIRS"数据被反复提起的原因。这套1986-2024年中国500米分辨率类NPP-VIIRS人造夜间灯光栅格数据,目的就是把这两个时代的夜光数据拼成一套连续、可比、统一尺度的长时序产品,直接解决"跨传感器拼接"这个老大难。文章下面我会把这类数据的来龙去脉、生成逻辑、文件规格、实际操作和踩坑经验一次讲清楚,不管是刚入门的学生还是常年做遥感应用的老手,都能拿去直接用。
1. "类NPP-VIIRS"到底是个什么来头,为什么非造这么一套数据不可
1.1 夜间灯光数据的两代传感器,到底差在哪
要理解"类NPP-VIIRS",得先明白我们平时说的夜光遥感数据是从哪来的。第一代广泛使用的是DMSP-OLS,美国国防气象卫星计划搭载的OLS传感器,运行时间从1992年延续到2013年,空间分辨率约1公里,但量化位数只有6位,数据的数值范围0到63。这个数值范围在城市中心很容易直接顶到63,也就是我们常说的"过饱和"——市中心无论灯再多,记录下来的值都一样。做过城市扩张研究的朋友肯定有印象,用DMSP数据提取上海、北京这类超大城市的建成区,中心区域经常是一大片连在一起的顶格值,边界区域则带着一圈圈光晕。
第二代是2011年发射的Suomi NPP卫星搭载的VIIRS传感器,从2012年4月开始获取夜间灯光数据,核心的Day/Night Band通道空间分辨率约750米,量化位数14位,动态范围宽得多,不再存在普通城市的过饱和问题。VIIRS的月度合成产品官方已经做了一些杂散光、云层、月光等干扰的过滤,数据质量比DMSP干净了一大截。但是问题来了:VIIRS只有2012年以后的数据,而很多研究需要覆盖更早的年代,尤其是80年代、90年代这种中国城市化快速起步的阶段。两部传感器在时间上只有2012到2013有短暂重叠,数值体系又完全不同,直接拼起来画时序图,会在拼接点出现一个明显的"断崖"或数值体系切换的跳动。
1.2 "类"字说明它不等于官方原始产品,而是二次加工的统一序列
所谓"类NPP-VIIRS",先说清楚一个容易误解的点:它不是NPP-VIIRS卫星直接下发的官方原始产品,而是把历史所有可用的夜光遥感数据,经过传感器定标、相互校正、模型传递之后,统一模拟成"如果当年就有VIIRS传感器,会记录到什么数值"的一套合成产品。也就是说,1986年的数据并不是1986年真的有卫星拍到了,而是利用早期的DMSP数据以及其它辅助信息,经过校正和反演得到的、与VIIRS尺度一致的时间序列结果。
这类产品的核心价值和它为什么最近几年受到关注,在于它解决了两个关键问题。第一是长时序可比性,1986年和2024年的灯光数值在同一个量纲下,可以直接做差值、算增长率、画趋势线;第二是空间分辨率统一到了500米,比原生DMSP的1公里更精细,又比需要大量预处理才能使用的VIIRS原始数据整齐得多。对做GDP空间化、人口格网化或者城市边界提取的团队来说,拿到手就能用,不需要自己去处理传感器间的复杂校正。
1.3 500米分辨率在应用里到底意味着什么
你可能想问,500米够不够用?说实话,在城市尺度上,500米意味着一个像元覆盖0.25平方公里的地表,对省级、市级甚至县域级别的分析完全够用。做全国尺度的建成区扩张、城市群灯光格局演变、经济重心迁移这些方向,500米已经是相当理想的起点。如果要做街道级别的精细识别,这个数据就偏粗了,那得回到原始VIIRS的750米甚至结合更高分辨率的影像去做降尺度。
从另一个角度看,500米这个尺度也是权衡了数据来源、计算成本和可用性之后的选择。DMSP原始数据重采样到500米不会增加信息量,但可以在与VIIRS拼接时保持网格统一;而VIIRS原始数据也不是精确的500米,通常会在重投影和网格化过程中统一到500米。这样整套数据在空间上就完全对齐了,后续做区域统计、栅格计算时,不同年份之间可以直接做像元级的代数运算,不用每一年都重新配准。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 1986到2024这38年是怎么拼出来的:从DMSP到VIIRS的衔接逻辑
2.1 两代传感器重叠期的"桥梁"作用
这类数据在技术路线上的核心,是找到一个能够把DMSP数值和VIIRS数值联系起来的"桥梁"。2012年和2013年恰好是两代传感器同时工作的年份,利用这两年同一地区的两类观测数据,可以建立从DMSP到VIIRS的转换关系。具体做法通常是在每个像元或每个统计单元上,将DMSP的DN值与VIIRS的夜光辐亮度值做回归拟合,得到一组转换系数。
这里有个细节值得留意:DMSP和VIIRS的物理量纲不一样。DMSP记录的是经过定标后的灰度值DN,无量纲或延用传感器定标后的相对值;VIIRS官方产品一般给出的是辐射亮度,单位是nW·cm⁻²·sr⁻¹。所以回归模型不只是一个线性缩放那么简单,中间还要处理DMSP的饱和区、VIIRS的低照度探测能力差异等问题。常见的做法是把DMSP做过饱和校正和相互校正之后,再与VIIRS匹配,用分段线性或幂函数拟合,确保低值区和高值区都有较好的转换效果。
2.2 早期年份如何"无中生有"地反演
很多人看到1986年这个起始年份,第一反应是:那时候有夜光卫星吗?严格说,DMSP系列卫星从1976年就开始有业务化的OLS数据了,但是早期数据存在存档不完整、定标不稳定等问题,较公开、较规范的数据集一般从1992年开始。1986年往前推的这部分,通常要依赖两类手段:一是利用更早期的数字存档胶片数据做数字化恢复,二是把已校正的夜间灯光时间序列与社会经济统计指标(如城市化率、人口、电力消耗等)建立模型,反推更早年份的灯光格局。
这部分在学术上属于"夜间灯光长时间序列重建"的范畴,各家数据产品的做法不完全一样,但思路是一致的:先用DMSP校正序列和VIIRS序列构建一套稳定的"标准时间序列",再把时间外推或内插到缺少直接观测的年份,用模型保证空间格局上的合理性和时间上的平滑性。精度肯定不如直接观测年份那么高,尤其是1980年代,更多刻画的是"城市核心区亮、外围暗"的大格局,而非精细到街区尺度。
2.3 时间连续性处理中最容易被忽略的细节
拼接长时序数据的时候,最常见的坑是只关注数值体系的转换,却忽略了年度间的噪声和突变。比如VIIRS传感器本身在不同年份会有轻微的响应衰减、卫星轨道漂移、月球光照条件差异等影响,虽然官方做了不少校正,但年度产品之间仍然可能存在几个百分点的系统性波动。所以优秀的类NPP-VIIRS产品会在重叠期做验证,还会对整条时间序列做平滑处理或去趋势化的异常检查,把那些明显不符合物理规律的"跳点"修正掉。
我自己在使用这类数据时,会特别关注2012到2014年这一段,因为那是DMSP和VIIRS交替、各种合成产品最容易出现问题的时期。如果你拿到数据后,发现某个城市在这一段出现了突然的灯光下降或倍增,先别急着当作真实变化,建议结合当时的统计年鉴、灯光影像和建成区资料交叉验证一下,很可能只是拼接处理的残留问题。
3. 数据规格的细节:分辨率、坐标系和像元值到底是什么含义
3.1 投影与网格的常见设置
国内公开的这类数据产品,通常使用两种坐标框架之一:一种是地理坐标系(WGS84或CGCS2000),栅格像元大小显示为0.005度左右(500米在赤道附近约等于0.0045度,在中国纬度约0.005-0.0055度);另一种是投影坐标系,常见的是Albers等积圆锥投影或兰伯特投影,像元大小直接就是500米乘500米。
这里要特别提醒一句:如果你拿到的数据是地理坐标下的0.005度栅格,它并不是严格的500米正方像元。地球的经线在纬度方向上收敛,纬度越高,同样0.005度的经度跨度对应的地面距离越短。所以做面积统计或者距离计算时,最好先投影到等积投影坐标系下再做,否则在黑龙江和海南做同一个统计口径,面积误差会不一样。如果数据本身已经是Albers投影下的500米网格,那就直接用,不需要再投影了。
3.2 像元值的类型和单位
类NPP-VIIRS产品的像元值,根据生产单位的不同会有几种呈现方式。有的直接采用类似VIIRS的辐射亮度单位,量级通常在0到几百nW·cm⁻²·sr⁻¹;有的为了方便存储和计算,做了线性拉伸或归一化,像元值在0到255之间浮动;还有的用无符号整型存储,把真实值乘以一个缩放系数,类似SDI或1km分辨率夜光数据的处理方式。拿到数据后,第一件事就是看头文件或者配套说明,确认像元值是不是需要乘以缩放因子,否则做出来的统计结果差几倍都不奇怪。
另外一个常见的处理细节是背景噪声和零值。城区以外并非完全黑暗,低值区会有微弱的月光反射、大气辉光等信号,许多产品会设置一个阈值,比如把某个值以下的像元直接置为0,或者归为背景值。这意味着在做裁剪统计之前,如果直接把所有非零像元都当作"灯光区域",可能会把大面积农村地区的微弱信号也算进去。建议先看一下全影像的直方图,确定一个合理的灯光阈值,再提取城市区域。
3.3 多年份数据的文件组织
这类数据集一般是按年分文件的GeoTIFF格式,文件名里会包含年份信息,比如China_NTL_500m_1990.tif这样的命名规则。有些产品会附带一个辅助文件,说明投影参数、像元值含义、生产版本号和数据修订记录。磁盘占用上,全国范围单波段的500米栅格,一个年份大约在几十到一百兆左右,38个年份加起来也就几个G,普通电脑完全带得动,不需要分布式处理。
这里多提一句版本管理的问题。这类长时序数据集常常会有V1、V2版本的更新,不同版本在早年间年份的处理上可能有明显差异。如果你写论文要用,建议固定使用某一个版本,并在方法部分写明版本号和下载日期;千万不要2019年下载一套,2024年再补充下载另一套来拼接,不同版本的年份间一致性没有保证,后期审稿人会提出质疑。
4. 拿到手之后怎么干活:栅格裁剪、网格对齐和灯光指数的计算实操
4.1 用ArcGIS做区域裁剪和投影统一
如果你习惯用ArcGIS,处理这套数据的第一步通常是按研究区裁剪。操作上有一个顺序问题:先裁剪再投影,还是先投影再裁剪?我的经验是,如果数据是地理坐标,而你只需要某个省或某个市,那么先用省界矢量数据做掩膜提取,运算量小,速度更快;但如果要做的是全国尺度的面积统计,建议先投影到Albers或Lambert等积坐标系,再开始后续计算,这样每个像元的面积才是恒定的。
裁剪时要注意栅格和矢量数据的坐标系是否一致。如果两者坐标系不同,在ArcGIS的Extract by Mask工具里很容易出现结果偏移或空白边缘。正确做法是在环境设置(Environment Settings)里把Processing Extent和Snap Raster都设置为你的夜光栅格数据,同时把栅格分析中的像元大小设为500米,这样掩膜提取后的结果才会和原始栅格完全对齐。
4.2 两个栅格如何做到原点、范围和分辨率逐格对应
这其实是实际操作里一个非常高频的问题,搜索"arcgis中如何使两个栅格的网格原点、范围、分辨率逐格对应"的人特别多。场景通常是这样的:你有一份1986年的灯光数据,还有一份2000年的数据,想计算两个年份的像元级差值,结果发现两个栅格的行列数、像元起点、范围都对不上,直接做减法得到的是错位的结果。
解决办法核心就一句话:使用Snap Raster功能,让所有参与计算的栅格以同一个栅格为基准。具体分三步走。第一步,确定一个基准栅格,比如把2020年的数据作为基准,因为它的投影和网格可能最规范;第二步,在ArcGIS的栅格计算器或Resample工具中,将环境设置的Snap Raster设为基准栅格,Cell Size设为基准栅格的像元大小,这样工具会自动把另一个栅格重采样到与基准栅格完全一致的原点和网格;第三步,用Extract by Mask或Raster Calculator做个测试,查看两个栅格的行列数和范围是否完全一致,一致了再做差值。
如果用Python做这件事,更简洁的办法是用rasterio或rioxarray读取两个栅格后,调用reproject函数并指定目标分辨率和变换矩阵,或者直接用xarray的resample和reindex方法对齐网格。核心思想一样:一定要显式指定输出网格的transform、width、height和crs,不能依赖默认参数。
4.3 灯光指数计算:从栅格到统计表格
实际研究中最常用的几个灯光指数包括:夜间灯光总强度(TNTL)、平均灯光强度(ANLI)、灯光面积(LIT)以及综合灯光指数(CNLI)。这些指标的意义我不展开说了,但在计算时有一些细节需要把关。
计算前,建议分两步整理数据。第一步,确定阈值:把明显不是人类活动灯光的微弱值设为0。比如有的研究用DN值大于某个阈值(如5或10)才认为是灯光,有的用"均值加两倍标准差"。这个阈值直接影响"灯光面积"的大小,务必在论文里交代清楚。第二步,做投影变换:如果原始数据是地理坐标,统计面积的时候要用等积投影,或者在像元面积校正表中按纬度调整每个像元的实际面积,否则高纬度地区的面积会被高估。
在Python里,如果数据是GeoTIFF,可以这样读取并统计:
python复制import rasterio
import numpy as np
# 以某一年的栅格为例
with rasterio.open("China_NTL_500m_2020.tif") as src:
data = src.read(1).astype(np.float32)
transform = src.transform
# 将无效值设为NaN
data[data <= 0] = np.nan
# 灯光总强度
total_ntl = np.nansum(data)
# 平均灯光强度
avg_ntl = np.nanmean(data)
# 灯光面积(假设像元为500m x 500m,一个像元面积=0.25平方公里)
lit_pixels = np.count_nonzero(~np.isnan(data))
lit_area = lit_pixels * 0.25
# 综合灯光指数CNLI = 灯光面积占比 * 平均灯光强度
# 灯光面积占比通常指灯光面积与研究区总面积之比,需要自行计算研究区总像元数
total_pixels = data.size
cnli = (lit_pixels / total_pixels) * avg_ntl
这段代码处理的是最常规的需求。如果数据带地理坐标且不在赤道附近,0.25平方公里的固定像元面积会有误差,此时可以先用pyproj把栅格重投影到等积投影再计算,或者用xarray结合纬度信息做逐像元面积加权。这也是为什么我前面强调,投影选择要从结果用途倒推:做总量统计就用等积投影,做空间格局可视化直接地理坐标也没问题。
4.4 逐年批量处理的工程化思路
如果你要处理38年的数据,手动一年一年在ArcGIS里点肯定不行。建议直接用Python的glob工具遍历所有年份文件,逐年的裁剪、阈值处理、指数计算,最后输出一个包含年份和各指数的CSV表格。这样不仅效率高,还能保证每年的处理参数完全一致,减少人为误差。
批处理时还有一个容易被忽略的问题:不同年份的栅格范围可能略有差异,比如有些年份产品覆盖范围多了或少了几个像元。批处理脚本里一定要统一用同一个研究区矢量掩膜,并且在循环中反复检查每个年份的裁剪结果是否正常,尤其是数据集中的最后一两年,因为卫星和产品处理流程可能发生了变更,导致数据范围突变。
5. 用这套数据必须知道的坑:从虚假变化到区域可比性
5.1 别把"数据校正残留"当成城市扩张的证据
长时序夜光数据最大的陷阱在于:你看到的变化不一定是真实地表变化,可能是传感器或产品更新造成的伪变化。2012到2014年属于DMSP和VIIRS交替期,即使做了类NPP-VIIRS处理,也难免在个别区域出现轻微的数值跳动。我的建议是,做变化检测时,不要只盯着单一年份的突变,而是看连续3到5年的趋势。如果某年出现一个孤立的尖峰或低谷,周围年份都是平滑的,先怀疑数据问题,再考虑真实事件。
另外,不同代际城市的路灯改造、色温变化也会影响灯光记录值。比如钠灯换成LED灯之后,光谱特征明显改变,VIIRS对某些波段的响应和DMSP不同,可能导致记录的数值变化与实际能耗变化不一致。做能源消耗估算时,这类灯光数据反演出来的只能作为宏观参考,不能替代统计数据。
5.2 行政边界变化和统计口径:38年的隐形变量
1986年到2024年,国内很多行政区划发生过调整:地级市合并、撤县设区、新区设立。如果你习惯用当前的市级边界去裁剪历史年份数据,再和统计年鉴做对比,很容易出现口径不一致的问题。比如某地1990年还是县级市,现在已经是市辖区,灯光增长的一大截可能只是行政单元范围变化带来的。严谨的做法是:使用与研究年份匹配的历史行政边界,或者统一使用一个稳定的空间单元(如县域单元)做时空对比,并在文中说明行政区划调整的影响。
这个问题在做城市群对比时尤其明显。如果研究的时间跨度超过十年,建议把所有年份都用同一套"基础空间单元"(比如2010年或2020年的区县级矢量),并手动检查那些边界有调整的区域,决定是合并还是剔除。
5.3 水域、云层和季节性因素的干扰处理
夜间灯光数据虽然能穿透云层吗?不能,云层会遮挡地表灯光。官方产品和许多合成产品已经做了云检测和合成处理,通常采用一个月内所有无云观测的平均值,所以年度合成产品基本可以忽略云的影响。但水域是个容易被忽视的问题:河流、湖泊表面可能会反射月光和灯光,导致水体上出现异常的亮值,尤其是宽江面、大湖面附近。做城市边界提取时,这些水体亮值会制造出虚假的"灯光明亮区",看起来就像是城市向水面扩张了一样。
处理办法很直接:准备一份全国或研究区的水体掩膜(比如利用全球30米水体数据或土地利用数据中的水域类别),在阈值处理之后,把水体像元强制设为0。如果你做的是城市化研究,还有一个细节:大城市的灯光辉光会漫散到建成区外围数公里,形成一圈低值渐变带,直接用阈值提取的"灯光面积"往往大于真实建成区面积。想拿到更贴近实际的城市面积,可以用"统计突变点"或"灯光梯度变化率最大处"来确定边界,而不是卡一个固定阈值。
5.4 做空间对比时,地区和年份之间存在"可比性陷阱"
很多论文会在全国不同省份之间比较灯光总量或灯光增长率,但这里有一个隐藏的可比性问题:灯光辐亮度本身反映的是"向上发射的光",而不是"消耗的电"或"经济产出"。同样的GDP,在北方城市可能对应更高的电力消耗和更亮的夏夜空调负荷,在南方某些城市则不然;同一城市内部,工业区、商业区、住宅区的灯光强度与经济活动的对应关系差异也很大。所以跨区域做绝对值的排名,要格外小心。
我的个人处理习惯是,优先使用灯光数据的相对变化(增长率、变化模式、空间扩展方向)来辅助分析,而不是把灯光绝对值当作某个经济指标的替代品。如果必须做区域间横向对比,建议引入省级或地级统计年鉴的关键指标(如第二产业占比、人口密度等)做校正,或者使用"灯光与统计量的比值"而不是绝对灯光值。
最后分享一个我自己的审图技巧:拿到这套数据后,别急着做统计。先用一个统一的色带把1990年、2005年、2020年这三年的全国图分别渲染出来,放在一起对比,先把那些肉眼可见的"变亮区域"、可疑的"熄灭区域"和数据拼接痕迹找出来,再进入定量分析。很多数据问题在统计表里很难发现,但在地图上一眼就能看出不对劲——这个过程花不了十分钟,却能在后面帮你省下大量的返工时间。
