📌 文章标签:Python | 气象可视化 | Skyborn 0.40 | 弯曲箭头 | 剖面流线图 | 大气科学 ⏱️ 阅读时间:约 12 分钟 💻 代码可运行:是
hello,大家好,这里是气python风雨,小编最近购入了空气炸锅,正在认真研究菜谱中。但skyborn的作者唐突更新了,说是带来了完全体的弯曲箭头。前几期弯曲箭头的阅读量都不错,那不得不写一篇文章了
顺带说一句有读者反馈看了教程去画了图,ta的导师觉得很满意,小编挺高兴的
Skyborn 迎来了 0.40 版本的重大更新!作为气象可视化领域的"宝藏库",这次升级不仅重构了底层风场分析管线,还大幅优化了弯曲箭头(Curly Vector)的绘制性能。最激动人心的是——剖面弯曲箭头正式登场了!🎉
本教程将带你从 ERA5 再分析数据出发,完整演示 Skyborn 0.40 的平面与剖面弯曲箭头绘制流程,一文搞定专业大气风场可视化!
在动手画箭头之前,先快速浏览一下 0.40 版本的核心变化,这能帮助你理解为什么现在要升级:
vinth2p 家族,提供更清晰的 column-major 与 C-order 入口,优化 ECMWF 处理逻辑,更新混合坐标与 Sigma 重映射辅助函数。curly_vector 绘制更快、内存更省!⚠️ 重要提醒:0.40 版本中,
curved_quiver()已更名为curly_vector(),旧名称不再可用。请检查你的历史代码并同步更新!
如果你还在使用旧版本,先升级到 0.40:
!pip install skyborn -U -i https://pypi.mirrors.ustc.edu.cn/simple/
Looking in indexes: https://pypi.mirrors.ustc.edu.cn/simple/
Collecting skyborn
Downloading https://mirrors.ustc.edu.cn/pypi/packages/f6/e1/aa6e30d216268ffdd3f71469a674ee5f5a323f926004ff0ea79e533906b2/skyborn-0.4.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (2.7 MB)
[2K [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m2.7/2.7 MB[0m [31m18.7 MB/s[0m eta [36m0:00:00[0ma [36m0:00:01[0m
[?25hRequirement already satisfied: numpy in /opt/conda/lib/python3.11/site-packages (from skyborn) (1.26.4)
Requirement already satisfied: pandas in /opt/conda/lib/python3.11/site-packages (from skyborn) (2.2.3)
Requirement already satisfied: xarray in /opt/conda/lib/python3.11/site-packages (from skyborn) (2024.3.0)
Requirement already satisfied: matplotlib in /opt/conda/lib/python3.11/site-packages (from skyborn) (3.9.1)
Requirement already satisfied: netCDF4 in /opt/conda/lib/python3.11/site-packages (from skyborn) (1.6.3)
Requirement already satisfied: metpy in /opt/conda/lib/python3.11/site-packages (from skyborn) (1.6.3)
Requirement already satisfied: tqdm in /opt/conda/lib/python3.11/site-packages (from skyborn) (4.67.0)
Requirement already satisfied: statsmodels in /opt/conda/lib/python3.11/site-packages (from skyborn) (0.14.4)
Requirement already satisfied: scipy in /opt/conda/lib/python3.11/site-packages (from skyborn) (1.14.1)
Requirement already satisfied: scikit-learn in /opt/conda/lib/python3.11/site-packages (from skyborn) (1.6.0)
Requirement already satisfied: dask in /opt/conda/lib/python3.11/site-packages (from skyborn) (2024.8.1)
Requirement already satisfied: click>=8.1 in /opt/conda/lib/python3.11/site-packages (from dask->skyborn) (8.1.7)
Requirement already satisfied: cloudpickle>=3.0.0 in /opt/conda/lib/python3.11/site-packages (from dask->skyborn) (3.1.0)
Requirement already satisfied: fsspec>=2021.09.0 in /opt/conda/lib/python3.11/site-packages (from dask->skyborn) (2025.2.0)
Requirement already satisfied: packaging>=20.0 in /opt/conda/lib/python3.11/site-packages (from dask->skyborn) (24.1)
Requirement already satisfied: partd>=1.4.0 in /opt/conda/lib/python3.11/site-packages (from dask->skyborn) (1.4.2)
Requirement already satisfied: pyyaml>=5.3.1 in /opt/conda/lib/python3.11/site-packages (from dask->skyborn) (6.0.2)
Requirement already satisfied: toolz>=0.10.0 in /opt/conda/lib/python3.11/site-packages (from dask->skyborn) (1.0.0)
Requirement already satisfied: importlib-metadata>=4.13.0 in /opt/conda/lib/python3.11/site-packages (from dask->skyborn) (8.5.0)
Requirement already satisfied: contourpy>=1.0.1 in /opt/conda/lib/python3.11/site-packages (from matplotlib->skyborn) (1.3.1)
Requirement already satisfied: cycler>=0.10 in /opt/conda/lib/python3.11/site-packages (from matplotlib->skyborn) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /opt/conda/lib/python3.11/site-packages (from matplotlib->skyborn) (4.55.3)
Requirement already satisfied: kiwisolver>=1.3.1 in /opt/conda/lib/python3.11/site-packages (from matplotlib->skyborn) (1.4.7)
Requirement already satisfied: pillow>=8 in /opt/conda/lib/python3.11/site-packages (from matplotlib->skyborn) (9.4.0)
Requirement already satisfied: pyparsing>=2.3.1 in /opt/conda/lib/python3.11/site-packages (from matplotlib->skyborn) (3.2.0)
Requirement already satisfied: python-dateutil>=2.7 in /opt/conda/lib/python3.11/site-packages (from matplotlib->skyborn) (2.9.0.post0)
Requirement already satisfied: pint>=0.17 in /opt/conda/lib/python3.11/site-packages (from metpy->skyborn) (0.24.4)
Requirement already satisfied: pooch>=1.2.0 in /opt/conda/lib/python3.11/site-packages (from metpy->skyborn) (1.8.2)
Requirement already satisfied: pyproj>=3.0.0 in /opt/conda/lib/python3.11/site-packages (from metpy->skyborn) (3.5.0)
Requirement already satisfied: traitlets>=5.0.5 in /opt/conda/lib/python3.11/site-packages (from metpy->skyborn) (5.14.3)
Requirement already satisfied: pytz>=2020.1 in /opt/conda/lib/python3.11/site-packages (from pandas->skyborn) (2024.1)
Requirement already satisfied: tzdata>=2022.7 in /opt/conda/lib/python3.11/site-packages (from pandas->skyborn) (2024.2)
Requirement already satisfied: cftime in /opt/conda/lib/python3.11/site-packages (from netCDF4->skyborn) (1.6.4)
Requirement already satisfied: joblib>=1.2.0 in /opt/conda/lib/python3.11/site-packages (from scikit-learn->skyborn) (1.4.2)
Requirement already satisfied: threadpoolctl>=3.1.0 in /opt/conda/lib/python3.11/site-packages (from scikit-learn->skyborn) (3.5.0)
Requirement already satisfied: patsy>=0.5.6 in /opt/conda/lib/python3.11/site-packages (from statsmodels->skyborn) (1.0.1)
Requirement already satisfied: zipp>=3.20 in /opt/conda/lib/python3.11/site-packages (from importlib-metadata>=4.13.0->dask->skyborn) (3.21.0)
Requirement already satisfied: locket in /opt/conda/lib/python3.11/site-packages (from partd>=1.4.0->dask->skyborn) (1.0.0)
Requirement already satisfied: platformdirs>=2.1.0 in /opt/conda/lib/python3.11/site-packages (from pint>=0.17->metpy->skyborn) (4.3.6)
Requirement already satisfied: typing_extensions>=4.0.0 in /opt/conda/lib/python3.11/site-packages (from pint>=0.17->metpy->skyborn) (4.12.2)
Requirement already satisfied: flexcache>=0.3 in /opt/conda/lib/python3.11/site-packages (from pint>=0.17->metpy->skyborn) (0.3)
Requirement already satisfied: flexparser>=0.4 in /opt/conda/lib/python3.11/site-packages (from pint>=0.17->metpy->skyborn) (0.4)
Requirement already satisfied: requests>=2.19.0 in /opt/conda/lib/python3.11/site-packages (from pooch>=1.2.0->metpy->skyborn) (2.32.3)
Requirement already satisfied: certifi in /opt/conda/lib/python3.11/site-packages (from pyproj>=3.0.0->metpy->skyborn) (2024.12.14)
Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.11/site-packages (from python-dateutil>=2.7->matplotlib->skyborn) (1.17.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /opt/conda/lib/python3.11/site-packages (from requests>=2.19.0->pooch>=1.2.0->metpy->skyborn) (3.4.0)
Requirement already satisfied: idna<4,>=2.5 in /opt/conda/lib/python3.11/site-packages (from requests>=2.19.0->pooch>=1.2.0->metpy->skyborn) (3.10)
Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.11/site-packages (from requests>=2.19.0->pooch>=1.2.0->metpy->skyborn) (2.2.3)
Installing collected packages: skyborn
Successfully installed skyborn-0.4.0
import skyborn as skb
print(f"Skyborn version: {skb.__version__}")
Skyborn version: 0.4.0
听到"弯曲箭头",你可能会想到这样:
↗️ 或者 ↘️
但 Skyborn 的弯曲流线图是这样的:
〰️ 〰️ 〰️
↘️ ↙️ 〰️ 〰️ 它绘制的是矢量场的流线(streamlines),流线上带有指示风向的箭头。相比普通箭头,流线能更好地展示风场的连续性和演变趋势,是大气科学可视化的"标配"!
📚 **名词解释**:流线是某一时刻速度场中的一条曲线,曲线上每点的切线方向与该点的速度方向一致。简单说,就是"风的轨迹线"。
之前的版本只能绘制平面(水平层)弯曲箭头。0.40 版本新增了 ncl_preset='profile' 参数支持,允许我们在垂直剖面(如纬度-高度剖面、经度-高度剖面)上绘制弯曲箭头,直观展示三维环流结构!
本教程使用 ERA5 再分析数据,包含气压层上的 u、v、w、z、t、r、q 等变量。数据范围:
import xarray as xr
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
# Load ERA5 data
ds = xr.open_dataset('/home/mw/input/era58091/ERA5-2023-08_pl.nc')
# Inspect data structure
print(ds)
print("\nTime range:", ds.time.values[0], "to", ds.time.values[-1])
print("Level range:", ds.level.values.min(), "hPa -", ds.level.values.max(), "hPa")
print("Spatial range: Lat", ds.latitude.values.min(), "°N to", ds.latitude.values.max(), "°N")
print("Spatial range: Lon", ds.longitude.values.min(), "°E to", ds.longitude.values.max(), "°E")
<xarray.Dataset> Size: 4GB
Dimensions: (longitude: 361, latitude: 241, level: 37, time: 20)
Coordinates:
* longitude (longitude) float32 1kB 90.0 90.25 90.5 ... 179.5 179.8 180.0
* latitude (latitude) float32 964B 70.0 69.75 69.5 69.25 ... 10.5 10.25 10.0
* level (level) int32 148B 1 2 3 5 7 10 20 ... 875 900 925 950 975 1000
* time (time) datetime64[ns] 160B 2023-08-02 ... 2023-08-05T16:00:00
Data variables:
z (time, level, latitude, longitude) float64 515MB ...
r (time, level, latitude, longitude) float64 515MB ...
t (time, level, latitude, longitude) float64 515MB ...
q (time, level, latitude, longitude) float64 515MB ...
u (time, level, latitude, longitude) float64 515MB ...
v (time, level, latitude, longitude) float64 515MB ...
w (time, level, latitude, longitude) float64 515MB ...
Attributes:
Conventions: CF-1.6
history: 2024-01-09 16:38:26 GMT by grib_to_netcdf-2.25.1: /opt/ecmw...
Time range: 2023-08-02T00:00:00.000000000 to 2023-08-05T16:00:00.000000000
Level range: 1 hPa - 1000 hPa
Spatial range: Lat 10.0 °N to 70.0 °N
Spatial range: Lon 90.0 °E to 180.0 °E
ds.z.min()
ds.level
from metpy.calc import vertical_velocity
from metpy.units import units
w = vertical_velocity(ds.w , ds.level, ds.t)
⚠️ API 变化提醒:Skyborn 0.40 中,
curved_quiver()已更名为curly_vector()。同时,当传入 xarray Dataset 时,请将ds作为第一个位置参数传入,不再支持ds=...关键字参数形式与ax=...混用。
# Select 500 hPa, 2023-08-02 00UTC
ds_500 = ds.sel(level=500, time='2023-08-03T00:00:00.000000000', method='nearest')
fig, ax = plt.subplots(figsize=(12, 8), subplot_kw={'projection': ccrs.PlateCarree()})
# ✅ Skyborn 0.40 新 API:curly_vector
# ds 作为第一个位置参数传入
quiver_set = skb.plot.curly_vector(
ds_500, # xarray Dataset(第一个位置参数)
x='longitude',
y='latitude',
u='u',
v='v',
density=1, # 流线密度
arrowsize=2, # 箭头大小
color='steelblue',
ref_length=0.6, # 流线颜色
linewidth=1
)
# Add map features
ax.coastlines(resolution='50m', linewidth=0.3)
ax.add_feature(cfeature.LAKES, alpha=0.5)
ax.add_feature(cfeature.RIVERS, alpha=0.5)
ax.gridlines(draw_labels=True, linewidth=0.3, linestyle='--', alpha=0.7)
ax.set_extent([90, 180, 10, 70], crs=ccrs.PlateCarree())
ax.set_title('🌐 Skyborn 0.40 平面弯曲箭头 — 500 hPa 风场\n2023-08-02 00UTC', fontsize=14)
# Add reference arrow key (0.40 new feature)
skb.plot.curly_vector_key(quiver_set, U=20, units='m/s', loc='lower right')
plt.tight_layout()
plt.show()
/opt/conda/lib/python3.11/site-packages/cartopy/mpl/geoaxes.py:527: UserWarning: Glyph 127760 (\N{GLOBE WITH MERIDIANS}) missing from font(s) Source Han Sans SC.
super()._update_title_position(renderer)
/opt/conda/lib/python3.11/site-packages/cartopy/mpl/geoaxes.py:527: UserWarning: Glyph 127760 (\N{GLOBE WITH MERIDIANS}) missing from font(s) Source Han Sans SC.
super()._update_title_position(renderer)
/opt/conda/lib/python3.11/site-packages/cartopy/mpl/geoaxes.py:524: UserWarning: Glyph 127760 (\N{GLOBE WITH MERIDIANS}) missing from font(s) Source Han Sans SC.
return super().draw(renderer=renderer, **kwargs)

output
将风速标量场作为填色背景,叠加弯曲箭头,信息密度更高:
fig, ax = plt.subplots(figsize=(14, 9), subplot_kw={'projection': ccrs.PlateCarree()}, dpi=150)
# Compute wind speed
u = ds_500['u']
v = ds_500['v']
spd = np.hypot(u, v) # 风速标量
lon = ds_500.longitude.values
lat = ds_500.latitude.values
# Wind speed shading
cf = ax.contourf(lon, lat, spd, levels=20, cmap='Spectral_r', extend='both', transform=ccrs.PlateCarree())
# Overlay white curly vectors
quiver_set = skb.plot.curly_vector(
ds_500,
x='longitude',
y='latitude',
u='u',
v='v',
density=1,
arrowsize=2,
color='white',
ref_length=0.6,
linewidth=0.3,
)
# Map elements
ax.coastlines(resolution='50m', linewidth=0.8, color='black')
gl = ax.gridlines(draw_labels=True, linewidth=0.3, linestyle='--', alpha=0.7)
gl.top_labels = False
gl.right_labels = False
ax.set_extent([90, 180, 10, 70], crs=ccrs.PlateCarree())
ax.set_title('ERA5 500 hPa 风速填色 + 弯曲箭头\n2023-08-02 00UTC', fontsize=14)
# Colorbar
cbar = plt.colorbar(cf, ax=ax, shrink=0.7, pad=0.02, aspect=30)
cbar.set_label('Wind Speed (m/s)', fontsize=12)
# Reference arrow
skb.plot.curly_vector_key(quiver_set, U=20, units='m/s', loc='lower left')
plt.tight_layout()
plt.show()

output
这是 Skyborn 0.40 最令人兴奋的新能力!通过设置 ncl_preset='profile',我们可以在垂直剖面上绘制弯曲箭头,直观展示经向环流、纬向环流等三维结构。
选取 120°E 经线,展示南北方向风场随高度的变化:
import metpy.calc as mpcalc
# Select 120°E profile, 2023-08-02 00UTC
ds_profile_lon = ds.sel(longitude=150, time='2023-08-02T00:00:00.000000000', method='nearest')
# Reverse levels so 1000 hPa is at bottom
ds_profile_lon = ds_profile_lon.isel(level=slice(None, None, -1))
# trans omega to w
ds_profile_lon['ww'] = mpcalc.vertical_velocity(
ds_profile_lon['w'],
ds_profile_lon['level'],
ds_profile_lon['t']
) *1000
fig, ax = plt.subplots(figsize=(12, 8))
# Use ncl_preset='profile' for profile curly vectors
quiver_profile = skb.plot.curly_vector(
ds_profile_lon,
x='latitude',
y='level',
u='v',
v='ww',
density=1,
arrowsize=1,
color='darkblue',
ncl_preset='profile',linewidth=0.6# ⭐ 关键参数:启用剖面预设
)
ax.set_yscale('log')
ax.invert_yaxis()
ax.set_ylim(1000, 100)
ax.set_xlim(10, 70)
ax.set_xlabel('Latitude (°N)', fontsize=12)
ax.set_ylabel('Pressure (hPa)', fontsize=12)
ax.set_title('🆕 Skyborn 0.40 剖面弯曲箭头 — 120°E 经向风场\n2023-08-02 00UTC', fontsize=14)
ax.grid(True, linestyle='--', alpha=0.5)
# Set y-axis ticks to common pressure levels
ax.set_yticks([1000, 850, 700, 500, 300, 200, 100])
ax.set_yticklabels(['1000', '850', '700', '500', '300', '200', '100'])
plt.tight_layout()
plt.show()

output
将温度场作为填色背景,叠加风场弯曲箭头
fig, ax = plt.subplots(figsize=(14, 8))
lon_vals = ds_profile_lat.longitude.values
lev_vals = ds_profile_lat.level.values
# Temperature shading
t = ds_profile_lat['t'] - 273.15# Convert to °C
cf = ax.contourf(lon_vals, lev_vals, t, levels=20, cmap='RdBu_r', extend='both')
# Overlay curly vectors
quiver_overlay = skb.plot.curly_vector(
ds_profile_lat,
x='longitude',
y='level',
u='u',
v='ww',
density=1,
arrowsize=1.5,
color='black',
ncl_preset='profile'
)
ax.set_yscale('log')
ax.invert_yaxis()
ax.set_ylim(1000, 100)
ax.set_xlim(90, 180)
ax.set_xlabel('Longitude (°E)', fontsize=12)
ax.set_ylabel('Pressure (hPa)', fontsize=12)
ax.set_title('40°N 温度填色 + 风场弯曲箭头\n2023-08-02 00UTC', fontsize=14)
ax.grid(True, linestyle='--', alpha=0.5)
ax.set_yticks([1000, 850, 700, 500, 300, 200, 100])
ax.set_yticklabels(['1000', '850', '700', '500', '300', '200', '100'])
cbar = plt.colorbar(cf, ax=ax, shrink=0.7, pad=0.02, aspect=30)
cbar.set_label('Temperature (°C)', fontsize=12)
plt.tight_layout()
plt.show()

output
Skyborn 0.40 curly_vector() 的常用参数整理如下:
参数 | 作用 | 调节技巧 |
|---|---|---|
ds | xarray 数据集 | ✅ 作为第一个位置参数传入 |
x | x 坐标变量名 | 如 'longitude'、'latitude'、'level' |
y | y 坐标变量名 | 如 'latitude'、'level' |
u | u 分量变量名 | 风的东西方向分量 |
v | v 分量变量名 | 风的南北/垂直方向分量 |
density | 流线疏密 | 平面用 2-5;剖面用 2-4 |
linewidth | 线宽 | 0.5-1.5 比较合适 |
arrowsize | 箭头大小 | 0.8-1.5,根据整体比例调整 |
color | 线条颜色 | 'steelblue'、'navy'、'white' |
arrowstyle | 箭头样式 | '->' 空心线、'- |
cmap | 颜色映射 | 当 color 传入 2D 场时使用 |
alpha | 透明度 | 0.3-1.0,叠加填色时建议 0.6-0.9 |
ncl_preset | NCL 预设模式 | None(平面默认)或 'profile'(剖面) |
integration_direction | 积分方向 | 'forward'、'backward'、'both' |
此外,0.40 新增的 curly_vector_key() 可用于在图上添加参考箭头图例,支持 U(参考风速)、units(单位)、loc(位置)等参数。 |
Skyborn 0.40 的弯曲箭头功能在继承原有优点的基础上,实现了全面提升:
curved_quiver() → curly_vector(),语义更清晰ncl_preset='profile' 让垂直剖面弯曲箭头成为可能curly_vector_key(),一键添加专业参考箭头💡 小提示:弯曲箭头对数据格式要求较严格(需 xarray Dataset 或严格对齐的数组)。如果你的数据是非标准网格,建议先用 xarray 或 metpy 进行插值和坐标规范化处理。
屏幕前的你学会了吗?