跳转至

23 - 上下行辐照度图

IrradianceMapSensor 把场景在水平方向划分为规则网格,记录每个格子中向下和向上的辐射通量。可用于分析复杂地形上的太阳辐射、林下光合有效辐射的分布,以及地表与冠层之间的辐射交换。

记录方式

光子从光源出发在场景中传播。每个格子(称为像元)对应一个竖直的柱体,各有一个下行和一个上行累加量:

辐照度图

  1. 光子每次与场景发生作用,把当时的通量计入该处像元的下行量;
  2. 光子从一个像元的作用点传到另一个像元时,把通量计入前一个像元的上行量;同一像元内的连续作用不计上行;
  3. 光子最终离开场景时,把通量计入最后一次作用所在像元的上行量。

对统计植被,统计叶片上的散射点落在哪个像元,就计入哪个像元。冠层可以跨越任意多个像元。

基本用法

import numpy as np
import less

scene = less.Scene()
scene.size = 40.0
scene.terrain = less.Terrain(property=less.Lambertian(reflectance=0.15))
scene.illumination = less.Illumination(
    source=less.Sun(zenith=30, azimuth=180), atmosphere=less.NoAtmosphere())

sensor = less.IrradianceMapSensor(
    bands=[550, 850],
    grid=(40, 40),          # 像元数;也可以写 resolution=1.0(m)
    photons_per_m2=400,     # 每平方米场景的光子数
    max_bounces=None,       # 记录的作用次数上限,None 表示不限
)
product = scene.simulate(sensor)

product.downwelling                 # (行, 列, 波段)每个像元柱的总通量,W nm⁻¹
product.upwelling
product.downwelling_irradiance()    # 除以像元水平面积后的辐照度,W m⁻² nm⁻¹
product.wavelengths
product.cell_size                   # 像元边长 (dx, dy),m
参数 含义
bands 波段(nm)
grid 像元数(nx, ny),默认 (100, 100)
resolution 像元边长(m),给出时 grid 不起作用
photons_per_m2 每平方米的光子数,典型值 400–4000
num_photons 总光子数,给出时代替 photons_per_m2
max_bounces 只记录前若干次作用;1 表示只记录直接到达的太阳和天空辐射

换算为辐照度

downwelling 和 upwelling 是每个像元柱的总通量。换算为辐照度时需要选择面积:

surface= 除以 含义
"horizontal"(默认) 像元水平面积 水平面上的辐照度,常用于太阳辐射图
"slope" 水平面积 / cos(坡度) 贴在坡面上的辐射表读数
"surface" 像元内地表的真实面积 按地表实际面积归一化
horizontal = product.downwelling_irradiance()
up = product.upwelling_irradiance("horizontal")
par = product.downwelling_integrated((400, 700), surface="horizontal")   # 宽波段积分,W m⁻²

起伏地形上,结果中还有每个像元的坡度 terrain_slope(弧度)和地表面积 terrain_surface_area(m²);平地上它们为 None。

检验:平坦朗伯地表

没有天空光时,平坦地表的下行辐照度等于太阳在水平面上的辐照度,上行等于它乘以反射率:

import math

horizontal_sun = np.array([1.6203, 0.7190])        # 期望的水平面辐照度,W m⁻² nm⁻¹
scene.illumination = less.Illumination(
    source=less.Sun(zenith=30, azimuth=180, wavelengths=[550, 850],
                    irradiance=horizontal_sun / math.cos(math.radians(30))),
    atmosphere=less.NoAtmosphere())

check = scene.simulate(less.IrradianceMapSensor(bands=[550, 850], resolution=1.0, photons_per_m2=400))
print("下行:", check.downwelling_irradiance().mean(axis=(0, 1)))   # 约等于 horizontal_sun
print("上行:", check.upwelling_irradiance().mean(axis=(0, 1)))     # 约等于 0.15 × horizontal_sun

Sun.irradiance 是垂直于太阳光方向的辐照度,所以这里除以 cos 30°。

起伏地形

from scipy.ndimage import gaussian_filter

rng = np.random.default_rng(42)
dem = np.zeros((128, 128))
for sigma, amplitude in [(18, 6), (9, 3), (4, 1.5), (2, 0.5)]:
    dem += amplitude * gaussian_filter(rng.standard_normal((128, 128)), sigma=sigma)
dem = (dem - dem.min()) * 12.0 / (dem.max() - dem.min())     # 起伏 12 m

hills = less.Scene()
hills.size = 100.0
hills.terrain = less.Terrain(property=less.Lambertian(reflectance=0.30), dem=dem.astype(np.float32))
hills.illumination = less.Illumination(
    source=less.Sun(zenith=45, azimuth=180), atmosphere=less.NoAtmosphere())

result = hills.simulate(less.IrradianceMapSensor(bands=[550], resolution=0.78, photons_per_m2=2000))
on_horizontal = result.downwelling_irradiance("horizontal")[:, :, 0]
on_slope = result.downwelling_irradiance("slope")[:, :, 0]
on_surface = result.downwelling_irradiance("surface")[:, :, 0]
ratio = result.upwelling_irradiance("horizontal")[:, :, 0] / np.maximum(on_horizontal, 1e-10)
print("上行/下行的平均值:", np.nanmean(ratio))

阳坡和阴影处的下行辐照度差别很大。坡面在像元内被重新截获的辐射会改变上行与下行的关系,因此在起伏地形和冠层中,上行/下行不能直接当作地表反照率,下行减上行也不等于局地吸收。

传感器网格可以与高程网格的分辨率不同。像元内的地表面积按实际三角形累加,同一范围内的总面积不随网格改变。坡度由“水平投影面积 / 三维面积”的反余弦得到;没有地表的像元面积为 0、坡度为 NaN。

与辐射场的区别

辐照度图 辐射场(第 31 章)
维度 二维网格 三维体素或每个场景元素
记录内容 进入和离开每个像元柱的通量 每个体素或元素的吸收
典型用途 地表辐射、地形辐射 冠层吸收、APAR、垂直廓线

配套脚本:scripts/23_irradiance_map.py

下一步