飞机在飞行过程中遭遇的晴空颠簸(Clear Air Turbulence,CAT)是航空安全的“隐形杀手”——它无云可识别,却能造成机舱人员受伤、飞机结构损伤。基于 ECMWF(欧洲中期天气预报中心)气象数据计算颠簸指数,能提前预判大气湍流风险,为航线规划、航班预警提供核心数据支撑。
本文将从原理到代码,完整拆解基于 Python 的 EC 数据颠簸指数算法,让你既能看懂原理,又能上手实操。
颠簸指数的计算依赖 EC 数据中 4 类核心气象要素(均为等压面数据),缺一不可:
颠簸指数(LP)的计算分为两步:先算湍流强度因子 li,再通过 Sigmoid 函数归一化得到最终指数(取值 0~1,越接近 1 颠簸风险越高)。
步骤 1:湍流强度因子 li 计算
li = 7.268 × dvdz + 0.718 × dtdn + 0.133 × dvdn - 2.52其中:
dvdz:风垂直切变(单位:1/s)dvdz = √[(du/dz×100)² + (dv/dz×100)²]du/dz:U 风分量随高度的变化率;dv/dz:V 风分量随高度的变化率
dtdn:温度水平梯度(单位:K/m)dtdn = √[(dtx/dx×100000)² + (dty/dy×100000)²]dtx:东西向温度差;dty:南北向温度差;dx/dy:经纬度网格距(地球半径换算)
dvdn:风水平切变(单位:1/s)dvdn = √[(du/dx×100000)² + (dv/dy×100000)²]du/dx:U 风分量东西向变化率;dv/dy:V 风分量南北向变化率
步骤 2:Sigmoid 归一化(得到颠簸指数 LP)
LP = 1 / (1 + exp(-0.59 × li))Sigmoid 函数的作用是将 li 的任意取值压缩至 0~1 区间,便于风险等级划分(如 0.8 以上为高风险)。
EC 数据的等压面以气压值(hPa)标识,需转换为高度(m),映射关系通过字典定义(如 10hPa 对应 31000m、20hPa 对应 26000m,完整映射见下文代码)。
参数解析:读取 EC 文件路径、数据时间、输出路径等入参;
文件匹配:根据要素(TEM/WIU/WIV/GPH)和时间匹配对应 GRIB1 转 NC 的文件;
NC 文件读取:解析 NC 文件中的经纬度、等压面、各要素数据;
网格计算:逐网格(经纬度 + 等压面)计算颠簸指数;
结果输出:将计算结果写入新的 NC 文件,输出文件路径。
经纬度网格距换算:地球半径 R = 6378100 m,0.25° 经纬度对应的实际距离:dx = 0.25 × π/180 × R × cos(纬度 × π/180)(经度方向,随纬度变化)dy = 0.25 × π/180 × R(纬度方向,固定值)
边界处理:网格边缘(首行/首列/末行/末列)颠簸指数设为 0,避免边界计算误差。

# ===================== 路径配置 =====================EC_FILE = "/202603121200-upper-xxxxxxxxx.nc"OUTPUT_DIR = "/"GIF_PATH = "turbulence_all_levels.gif"# =======================================================import numpy as npimport netCDF4import matplotlibmatplotlib.use("AGG")import matplotlib.pyplot as pltimport osfrom PIL import Imageimport warningswarnings.filterwarnings('ignore')# ===================== 配置 =====================EARTH_RADIUS = 6378100.0os.makedirs(OUTPUT_DIR, exist_ok=True)# 中国范围裁切LON_MIN, LON_MAX = 70, 140LAT_MIN, LAT_MAX = 15, 55# ===================== 读取数据 =====================with netCDF4.Dataset(EC_FILE) as ds:lon = ds.variables['longitude'][:]lat = ds.variables['latitude'][:]isobaric = ds.variables['isobaricInhPa'][:]t = ds.variables['t'][0]u = ds.variables['u'][0]v = ds.variables['v'][0]z = ds.variables['z'][0]# ===================== 核心算法:颠簸指数 =====================def compute_lp_fast(t, u, v, z, lat):lat_rad = np.radians(lat)dx = 0.25 * np.pi / 180 * EARTH_RADIUS * np.cos(lat_rad)dy = 0.25 * np.pi / 180 * EARTH_RADIUSdx = dx[:, None]dz = z + 1e-6dtx_full = np.zeros_like(t)dty_full = np.zeros_like(t)du_full = np.zeros_like(t)dv_full = np.zeros_like(t)dtx_full[:, :, 1:-1] = 0.5 * (t[:, :, 2:] - t[:, :, :-2])dty_full[:, 1:-1, :] = 0.5 * (t[:, 2:, :] - t[:, :-2, :])du_full[:, :, 1:-1] = 0.5 * (u[:, :, 2:] - u[:, :, :-2])dv_full[:, 1:-1, :] = 0.5 * (v[:, 2:, :] - v[:, :-2, :])dvdz = np.sqrt((du_full / dz * 100) ** 2 + (dv_full / dz * 100) ** 2)dtdn = np.sqrt((dtx_full / dx * 1e5) ** 2 + (dty_full / dy * 1e5) ** 2)dvdn = np.sqrt((du_full / dx * 1e5) ** 2 + (dv_full / dy * 1e5) ** 2)li = 7.268 * dvdz + 0.718 * dtdn + 0.133 * dvdn - 2.52lp = 1 / (1 + np.exp(-0.59 * li))lp[:, 0, :] = lp[:, -1, :] = lp[:, :, 0] = lp[:, :, -1] = 0return lpprint("🔢 计算颠簸指数...")lp = compute_lp_fast(t, u, v, z, lat)print("✅ 计算完成!开始极速绘图...")# ===================== 绘图配置 =====================lon2d, lat2d = np.meshgrid(lon, lat)levels = np.linspace(0, 1, 21)cmap = "jet"images = []# ===================== 批量绘图 =====================for idx, p in enumerate(isobaric):fig, ax = plt.subplots(figsize=(9, 6), dpi=100)# 绘制颠簸指数cf = ax.contourf(lon2d, lat2d, lp[idx], levels=levels, cmap=cmap, extend="max")# 只裁切中国区域,不画任何边界ax.set_xlim(LON_MIN, LON_MAX)ax.set_ylim(LAT_MIN, LAT_MAX)ax.set_title(f"EC Turbulence Index | {p:.0f} hPa", fontsize=13)# 底部色标,每张图都有cax = fig.add_axes([0.15, 0.05, 0.7, 0.025])fig.colorbar(cf, cax=cax, orientation="horizontal", label="Turbulence Index (LP)")# 保存img_path = f"{OUTPUT_DIR}/lp_{int(p):03d}.png"plt.savefig(img_path, bbox_inches="tight")plt.close()images.append(img_path)print(f"✅ {int(p):3d} hPa 已保存")# ===================== 生成 GIF =====================print("\n🎞️ 生成 GIF...")frames = [Image.open(f) for f in images]frames[0].save(GIF_PATH,save_all=True,append_images=frames[1:],duration=500,loop=0)
