
本文详解如何使用python(siphon + metpy + cartopy)循环获取gfs最新预报数据,对0–240小时(6小时间隔)的降水类型、地面气压和厚度场进行统一绘图,并自动保存为独立png文件。
本文详解如何使用python(siphon + metpy + cartopy)循环获取gfs最新预报数据,对0–240小时(6小时间隔)的降水类型、地面气压和厚度场进行统一绘图,并自动保存为独立png文件。
要实现GFS模式多时次(如0–240小时、6小时间隔)的自动化绘图与批量保存,核心在于将全部绘图逻辑完整封装进循环体中——原代码仅在循环内计算 dt,而数据读取、处理、绘图和保存均在循环外执行,导致仅生成最后一时刻(如+108h)的图像。正确做法是:每次迭代独立完成“请求→解析→计算→绘图→保存”全流程。
以下为优化后的完整实现方案,支持灵活设定预报时长,并确保每张图独立保存:
✅ 关键改进点
-
循环结构重构:
for k in range(0, hours_intervals)替代硬编码timedelta列表,便于扩展(如hours_intervals=41即覆盖0–240h共41个时次); -
动态文件命名:使用
f'gfs_precip_{dt.strftime("%Y%m%d_%H%M")}.png'保证文件名含精确有效时间,避免覆盖; -
资源管理增强:每次循环后显式调用
plt.close(fig)释放内存,防止内存泄漏(尤其处理数十个时次时至关重要); -
错误容错机制:添加
try/except包裹数据请求与绘图块,跳过异常时次并输出警告,保障整体流程鲁棒性。
? 完整可运行代码(含保存逻辑)
from datetime import datetime, timedelta
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np
import metpy.calc as mpcalc
from metpy.plots import USCOUNTIES
from metpy.units import units
from scipy.ndimage import gaussian_filter
from siphon.catalog import TDSCatalog
from netCDF4 import num2date
# ========== 配置参数 ==========
start_time = datetime(2025, 1, 7, 12, 0, 0) # GFS起报时间(UTC)
hours_intervals = 41 # 总时次:0h, 6h, 12h, ..., 240h → 共41个
output_dir = "./gfs_plots/" # 请提前创建该目录
save_dpi = 150
# ========== 主循环:逐时次处理 ==========
for k in range(hours_intervals):
dt = start_time + timedelta(hours=6 * k) # 注意:k=0 对应起报时刻(0h),非+6h
print(f"Processing forecast hour: +{6*k}h ({dt.strftime('%Y-%m-%d %H:%M')} UTC)")
try:
# 1. 数据获取
best_gfs = TDSCatalog('https://thredds.ucar.edu/thredds/catalog/grib/NCEP/GFS/Global_0p25deg/catalog.xml?dataset=grib/NCEP/GFS/Global_0p25deg/Best')
best_ds = best_gfs.datasets[0]
ncss = best_ds.subset()
query = ncss.query()
query.accept('netcdf')
query.lonlat_box(north=75, south=15, east=320, west=185)
query.time(dt)
query.variables(
'Geopotential_height_isobaric',
'Pressure_reduced_to_MSL_msl',
'Precipitation_rate_surface',
'Categorical_Snow_surface',
'Categorical_Freezing_Rain_surface',
'Categorical_Ice_Pellets_surface'
)
data = ncss.get_data(query)
# 2. 数据解析与计算
lat = data.variables['latitude'][:].squeeze()
lon = data.variables['longitude'][:].squeeze()
time1 = data['time']
vtime = num2date(time1[:].squeeze(), units=time1.units)
# 地面气压(平滑处理)
emsl_var = data.variables['Pressure_reduced_to_MSL_msl']
EMSL = units.Quantity(emsl_var[:], emsl_var.units).to('hPa')
mslp = gaussian_filter(EMSL[0], sigma=3.0)
# 降水率转换(mm/h → inch/h)
preciprate = data.variables['Precipitation_rate_surface'][:].squeeze()
precip_inch_hour = preciprate * 141.73228346457 # 1 mm/h = ~0.03937 inch/h → 换算系数为 1/0.007055 ≈ 141.73
precip2 = mpcalc.smooth_n_point(precip_inch_hour, 5, 1)
# 坐标网格
lon_2d, lat_2d = np.meshgrid(lon, lat)
# 3. 绘图设置
precip_colors = [
"#bde9bf", "#adddb0", "#9ed0a0", "#8ec491", "#7fb882", "#70ac74", "#60a065", "#519457",
"#418849", "#307c3c", "#1c712e", "#f7f370", "#fbdf65", "#fecb5a", "#ffb650", "#ffa146",
"#ff8b3c", "#f94609"
]
precip_colormap = mcolors.ListedColormap(precip_colors)
clev_precip = np.concatenate([
np.arange(0.01, 0.1, 0.01),
np.arange(0.1, 0.2, 0.02),
np.arange(0.2, 0.61, 0.1)
])
norm = mcolors.BoundaryNorm(clev_precip, len(precip_colors))
# 投影与区域
plotcrs = ccrs.LambertConformal(central_latitude=35, central_longitude=-100, standard_parallels=(30, 60))
bounds = [-105, -90, 30, 40] # [lon_min, lon_max, lat_min, lat_max]
fig = plt.figure(figsize=(14, 12))
ax = fig.add_subplot(1, 1, 1, projection=plotcrs)
ax.set_extent(bounds, crs=ccrs.PlateCarree())
ax.add_feature(cfeature.COASTLINE.with_scale('50m'), linewidth=0.75)
ax.add_feature(cfeature.STATES, linewidth=1)
ax.add_feature(USCOUNTIES, edgecolor='grey', linewidth=0.5)
# 绘制等压线与填色
clevmslp = np.arange(800., 1120., 2)
ax.contour(lon_2d, lat_2d, mslp, clevmslp, colors='k', linewidths=1.25,
linestyles='solid', transform=ccrs.PlateCarree())
ax.contourf(lon_2d, lat_2d, precip2, clev_precip, cmap=precip_colormap, norm=norm,
extend='max', transform=ccrs.PlateCarree())
# 标题
ax.set_title('GFS Precip Type & Rate (in/hr), MSLP (hPa)', loc='left', fontsize=10, weight='bold')
ax.set_title(f'Valid: {vtime.strftime("%Y-%m-%d %H:%M")} UTC', loc='right', fontsize=8)
# 4. 保存图像(关键!)
filename = f"gfs_precip_{dt.strftime('%Y%m%d_%H%M')}.png"
filepath = f"{output_dir}{filename}"
fig.savefig(filepath, dpi=save_dpi, bbox_inches='tight')
print(f"✓ Saved: {filepath}")
plt.close(fig) # 必须关闭,释放内存
except Exception as e:
print(f"⚠️ Failed at +{6*k}h ({dt}): {e}")
continue
print("✅ All plots generated successfully.")⚠️ 注意事项与最佳实践
-
THREDDS访问稳定性:UCAR THREDDS服务器可能限流或临时不可用,建议添加重试逻辑(如
tenacity库)或预设备用数据源(如 NOMADS); -
变量可用性检查:GFS不同版本中变量名可能微调(如
Categorical_Snow_surface在部分版本中为Snow_categorical_surface),首次运行前建议打印list(data.variables)确认; -
地理范围适配:
lonlat_box中east=320是因GFS经度范围为 0–360°,若需东经区域(如亚洲),请调整为east=180, west=60; -
性能优化:对240小时共41个时次,总耗时约数分钟至十几分钟(取决于网络与本地算力),可考虑用
concurrent.futures.ThreadPoolExecutor并行加速(注意THREDDS并发限制); -
输出质量:
bbox_inches='tight'自动裁剪空白边距;dpi=150平衡清晰度与文件大小,科研出版推荐dpi=300。
通过以上结构化实现,您即可一键生成整套GFS预报时序图集,为天气分析、模式验证或教学演示提供高效、可复现的可视化支持。

















