当你第一次尝试用Python实现彭曼公式计算参考蒸散量(ET0)时,可能会发现代码编写反而是最简单的部分——真正令人头疼的是那些看似简单却难以获取的气象参数。最高气温Tmax、最低气温Tmin、2米风速u2、实际水汽压ea、净辐射Rn...这些参数从哪里获取?如何验证它们的可靠性?当关键数据缺失时又该如何应对?本文将带你系统解决这些实操中的核心痛点。
地面气象站数据被视为ET0计算的黄金标准,但不同来源的数据质量差异显著。国内常见的气象数据来源包括:
注意:使用气象站数据时务必检查元数据,确认观测高度是否符合FAO标准(2米风速、1.5-2米气温)
常见的数据格式问题及处理方法:
| 数据格式 | 优点 | 缺点 | 处理建议 |
|---|---|---|---|
| CSV/TXT | 易读易写 | 无结构描述 | 用pandas.read_csv解析时指定编码 |
| NetCDF | 自带元数据 | 学习成本高 | 使用xarray库替代netCDF4 |
| HDF5 | 高效存储 | 工具链复杂 | 推荐h5py库处理 |
python复制# 典型气象站CSV数据读取示例
import pandas as pd
def load_weather_data(filepath):
df = pd.read_csv(filepath, encoding='gbk', parse_dates=['时间'])
# 统一列名映射
col_map = {
'最高气温(℃)': 'Tmax',
'最低气温(℃)': 'Tmin',
'风速(m/s)': 'u2',
'相对湿度(%)': 'RH'
}
return df.rename(columns=col_map)
当气象站数据不可得时,ECMWF的ERA5再分析数据是最可靠的替代方案。获取ERA5数据的典型流程:
python复制# ERA5数据下载示例(需安装cdsapi)
import cdsapi
c = cdsapi.Client()
c.retrieve(
'reanalysis-era5-single-levels',
{
'product_type': 'reanalysis',
'variable': [
'2m_temperature', '10m_u_component_of_wind',
'10m_v_component_of_wind', 'surface_net_solar_radiation'
],
'year': '2023',
'month': ['01', '02'],
'day': [f'{d:02d}' for d in range(1, 32)],
'time': ['00:00', '06:00', '12:00', '18:00'],
'format': 'netcdf'
},
'era5_data.nc'
)
GLDAS、MODIS等卫星产品可提供全球覆盖的数据,但需要注意:
温度数据异常可能来自仪器故障或记录错误,建议采用以下校验步骤:
python复制# 温度数据质量控制函数
def validate_temp_data(df):
# 极值过滤
df = df[(df['Tmax'] >= -50) & (df['Tmax'] <= 60)]
df = df[(df['Tmin'] >= -60) & (df['Tmin'] <= 50)]
# 逻辑检查
df = df[df['Tmax'] >= df['Tmin']]
# 日温差检查
temp_diff = df['Tmax'] - df['Tmin']
df = df[(temp_diff >= 0) & (temp_diff <= 30)]
return df
FAO要求使用2米高度风速,但许多站点观测高度为10米,需进行高度校正:
math复制u_2 = u_z \times \frac{4.87}{\ln(67.8z - 5.42)}
其中z为原始观测高度(米),u_z为该高度测得的风速。
当缺乏直接观测时,ea可通过以下方式估算:
通过露点温度计算(最准确):
python复制def ea_from_dewpoint(Tdew):
return 0.6108 * math.exp(17.27 * Tdew / (Tdew + 237.3))
通过相对湿度估算(常见替代方案):
python复制def ea_from_RH(Tmin, RHmax):
es_min = 0.6108 * math.exp(17.27 * Tmin / (Tmin + 237.3))
return es_min * RHmax / 100
通过最低温度近似(最后选择):
python复制def ea_from_Tmin(Tmin):
return 0.6108 * math.exp(17.27 * Tmin / (Tmin + 237.3))
当Rn数据不可得时,可采用FAO-56提出的分步计算法:
python复制# 完整的净辐射估算实现
import math
def calculate_Rn(lat, elev, date, Tmax, Tmin, sunshine_hours):
# 将纬度转换为弧度
phi = math.radians(lat)
# 计算日序(1-365)
doy = date.timetuple().tm_yday
# 1. 计算天文辐射Ra
dr = 1 + 0.033 * math.cos(2 * math.pi * doy / 365)
delta = 0.409 * math.sin(2 * math.pi * doy / 365 - 1.39)
ws = math.acos(-math.tan(phi) * math.tan(delta))
Ra = 24 * 60 / math.pi * 0.082 * dr * (
ws * math.sin(phi) * math.sin(delta) +
math.cos(phi) * math.cos(delta) * math.sin(ws)
)
# 2. 估算晴空辐射Rso
Rso = (0.75 + 2e-5 * elev) * Ra
# 3. 计算太阳辐射Rs
N = 24 * ws / math.pi
Rs = (0.25 + 0.5 * sunshine_hours / N) * Ra
# 4. 计算净短波辐射Rns
albedo = 0.23 # 标准草面反射率
Rns = (1 - albedo) * Rs
# 5. 计算净长波辐射Rnl
sigma = 4.903e-9 # Stefan-Boltzmann常数
Tmax_K = Tmax + 273.16
Tmin_K = Tmin + 273.16
ea = 0.6108 * math.exp(17.27 * Tmin / (Tmin + 237.3)) # 简化估算
Rnl = sigma * ((Tmax_K**4 + Tmin_K**4)/2) * (0.34 - 0.14 * math.sqrt(ea)) * (1.35 * Rs/Rso - 0.35)
# 6. 总净辐射
Rn = Rns - Rnl
return Rn
我们在华北平原某站点对比了三种辐射获取方式的误差:
| 方法 | 平均误差(MJ/m²/day) | 最大误差 | 数据需求 |
|---|---|---|---|
| 实测值 | - | - | 需辐射观测站 |
| FAO-56法 | 1.2 | 3.5 | 日照时数+温度 |
| 再分析数据 | 2.1 | 5.8 | ERA5下载 |
| 卫星反演 | 3.0 | 8.2 | MODIS产品 |
通过扰动输入参数评估ET0的敏感性:
python复制def sensitivity_analysis(base_values, variations):
"""
base_values: 字典形式的基准输入参数
variations: 各参数的扰动幅度(±%)
"""
results = {}
base_et0 = PM_ET0(**base_values)
for param in base_values:
# 正向扰动
perturbed = base_values.copy()
perturbed[param] *= (1 + variations[param]/100)
et0_high = PM_ET0(**perturbed)
# 负向扰动
perturbed[param] = base_values[param] * (1 - variations[param]/100)
et0_low = PM_ET0(**perturbed)
sensitivity = (et0_high - et0_low) / (2 * base_et0) * 100
results[param] = sensitivity
return results
# 示例使用
base_case = {
'Tmax': 30, 'Tmin': 20, 'u2': 2,
'ea': 1.5, 'Rn': 15
}
variations = {
'Tmax': 10, 'Tmin': 10, 'u2': 20,
'ea': 10, 'Rn': 10
}
print(sensitivity_analysis(base_case, variations))
典型敏感性结果(夏季条件下):
当ET0计算结果异常时,建议按以下顺序检查:
单位一致性检查
输入范围验证
特殊值处理