一个由国家气象中心团队打造的 Python 库,如何巧妙整合国际成熟工具,为一线气象工作者赋能?本文深入解析 metradar 的架构设计思想。
在气象领域,雷达数据是最宝贵的资源之一。但对于一线气象工作者来说,处理这些数据并不容易——不同的数据格式、复杂的质量控制、深奥的反演算法、繁琐的可视化流程……这些技术门槛让许多业务人员望而却步。
当然,国内的pycinrad库已经做了不少的工作,为广大气象从业人员提供极大的便利。
2026年初,国家气象中心雷达应用团队开源了 metradar——一个专门为国内气象雷达数据处理的 Python 库。我看了下github文档,其突出一个量大管饱。数据下载,质量控制,风场反演,雷达拼图、多源综合分析一股脑塞进去了。
本文将深入解析 metradar 的架构设计,看看它是如何通过架构设计为业务提供简洁易用的接口的。
metradar 是一个专门面向中国气象雷达数据处理的 Python 库,由国家气象中心(NMC)雷达应用团队开发维护。
功能 | 说明 |
|---|---|
数据读取 | FMT 格式(中国特有)、ROSE PUP 产品、SWAN 拼图 |
数据处理 | 质量控制、拼图合并、散度涡度计算 |
综合分析 | 雷达与自动站联合分析 |
产品输出 | 风场反演、降水估测、临近预报 |
与传统科研工具不同,metradar 从诞生之初就是为了"好用"——它不一味求新,而是将成熟的工程实践封装成简洁的 API,在工程化上走得比较远。
metradar 采用了经典的分层架构(Layered Architecture),将整个系统划分为六层:
┌─────────────────────────────────────────┐
│ Application Layer (Project) │ 业务流程封装
│ (拼图制作 / 临近预报 / 风场反演) │
├─────────────────────────────────────────┤
│ Presentation Layer (Graph) │ 可视化接口
│ (雷达绘图 / 综合分析图 / 等值线图) │
├─────────────────────────────────────────┤
│ Business Logic Layer (Core) │ 核心算法
│ (质量控制 / 拼图合并 / 散度涡度计算) │
├─────────────────────────────────────────┤
│ Data Access Layer (IO) │ 数据I/O
│ (FMT解码 / PUP读取 / SWAN拼图) │
├─────────────────────────────────────────┤
│ Utility Layer (Util) │ 通用工具
│ (地理变换 / 异常定义 / 色标解析) │
├─────────────────────────────────────────┤
│ Configuration Layer (Config) │ 配置管理
│ (资源路径 / 全局参数) │
└─────────────────────────────────────────┘
为什么这样设计?
分层架构是软件工程中最经典的模式之一,它的核心思想是将系统拆分成多个层次,每一层只关注自己的职责。
这种设计看似简单,但真正执行起来并不容易。很多项目在迭代中逐渐模糊了层次边界,最终变成"意大利面条式代码"。克制是一种美德,过多的堆砌往往让简洁的项目成为屎山。metradar 在这方面做得不错,层次划分清晰,边界明确。
IO 层是 metradar 的门面,负责将各种异构的数据源统一成标准的格式。
核心问题:中国气象雷达使用 FMT 格式,与国际通用的 HDF5/NetCDF 格式完全不同。如何让这些"方言"数据能与国际工具包无缝协作?
解决方案:采用适配器模式(Adapter Pattern),将所有数据格式适配为 PyArt 的 Radar 对象。
class CNRADLevel2Reader:
"""中国雷达基数据读取器 - 适配器模式"""
def read(self, file_path):
# 1. 读取原始二进制数据
raw_data = self._read_binary(file_path)
# 2. 解析 FMT 格式(中国特有的二进制格式)
parsed = self._parse_fmt(raw_data)
# 3. 转换为 PyArt 标准对象
return pyart.Radar(
time=parsed['time'],
fields=parsed['fields'],
metadata=parsed['metadata'],
scan_type=parsed['scan_type']
)
这个设计的精妙之处在于:
除了适配器模式,IO 层还使用了工厂模式来处理不同版本的 FMT 格式:
def decode_fmt(file_path, version=None):
"""FMT 解码工厂函数"""
if version is None:
version = detect_fmt_version(file_path)
if version == 'v2.0':
decoder = FMTv2Decoder()
elif version == 'v2.1':
decoder = FMTv2_1Decoder()
else:
raise UnsupportedFMTVersion(version)
return decoder.decode(file_path)
通过这种设计,metradar 成功地将不同版本的雷达数据统一为radar对象。
Core 层是算法的核心,包括质量控制、拼图合并、散度涡度计算等。
设计特点1:函数式编程风格
核心算法以纯函数为主,输入明确、输出明确,没有副作用,极大提升了代码的可测试性和可复用性。
def calculate_divergence(u, v, dx, dy):
"""
计算散度
Args:
u: 纬向风场分量
v: 经向风场分量
dx, dy: 网格间距
Returns:
divergence: 散度场
"""
du_dx = np.gradient(u, dx, axis=1)
dv_dy = np.gradient(v, dy, axis=0)
return du_dx + dv_dy
def calculate_vorticity(u, v, dx, dy):
"""
计算涡度
"""
dv_dx = np.gradient(v, dx, axis=1)
du_dy = np.gradient(u, dy, axis=0)
return dv_dx - du_dy
这种写法的好处:
设计特点2:策略模式支持多种算法
对于拼图合并这类有多种算法的场景,metradar 使用了策略模式:
class MosaicMergeStrategy:
"""拼图合并策略接口"""
def merge(self, radars: List[Radar]) -> Radar:
raise NotImplementedError
class MaxValueStrategy(MosaicMergeStrategy):
"""最大值策略:选择各点的最大反射率"""
def merge(self, radars):
# 选择最大反射率
pass
class NearestStrategy(MosaicMergeStrategy):
"""最近邻策略:选择距离最近的雷达值"""
def merge(self, radars):
# 距离最近的雷达值
pass
class WeightedAverageStrategy(MosaicMergeStrategy):
"""加权平均策略:按距离加权平均"""
def merge(self, radars):
# 按距离加权
pass
# 使用示例
strategy = MaxValueStrategy()
mosaic = strategy.merge(radar_list)
这种设计的优势:
Graph 层负责雷达数据的可视化,这是气象工作中最直观的部分。
核心问题:绘制一个雷达图可能涉及地图投影、雷达数据显示、自动站叠加、色标设置等多个步骤,如何让这个过程既灵活又不混乱?
解决方案:采用构建器模式(Builder Pattern),让用户以链式调用方式一步步构建图形。
class RadarMapBuilder:
"""雷达地图构建器"""
def __init__(self):
self.radar = None
self.overlays = []
self.colormap = None
def set_radar(self, radar):
"""设置雷达数据"""
self.radar = radar
return self
def add_aws_overlay(self, aws_data):
"""添加自动站数据叠加"""
self.overlays.append(aws_data)
return self
def set_colormap(self, cmap):
"""设置色标"""
self.colormap = cmap
return self
def build(self):
"""构建完整图形"""
fig = self._create_base_map()
self._add_radar_data(fig)
self._add_overlays(fig)
self._add_colorbar(fig)
return fig
# 使用示例:链式调用,简洁优雅
fig = (RadarMapBuilder()
.set_radar(radar)
.add_aws_overlay(aws)
.set_colormap('NWSRef')
.build())
这种设计的妙处:
除了构建器模式,Graph 层还使用了模板方法模式来统一不同绘图器的流程:
class BaseRadarPlotter:
"""雷达绘图基类"""
def plot(self, radar, **kwargs):
"""模板方法:定义绘图流程骨架"""
self._prepare_data(radar)
self._setup_figure()
self._draw_map_background()
self._draw_radar_data()
self._add_overlays()
self._add_colorbar()
return self.fig
def _prepare_data(self, radar):
"""子类可覆盖的数据预处理"""
pass
class RadarAWSPlotter(BaseRadarPlotter):
"""雷达+自动站绘图器"""
def _add_overlays(self):
"""添加自动站观测数据"""
self._draw_aws_symbols()
模板方法确保所有绘图器遵循相同的流程,子类只需要实现差异化部分,避免了重复代码。
Project 层是 metradar 最有价值的部分,它将完整的业务流程封装成可执行的项目。
核心问题:一个典型的雷达拼图制作流程包括:数据下载 → 质量控制 → 拼图合并 → 可视化 → 输出产品。如何让这些步骤自动化、可重复?
解决方案:采用管道模式(Pipeline Pattern),将数据处理分解为一系列独立的阶段。
class MosaicPipeline:
"""拼图制作管道"""
def __init__(self, config):
self.config = config
self.stages = []
def add_stage(self, stage):
"""添加处理阶段"""
self.stages.append(stage)
return self
def run(self):
"""执行完整管道"""
data = None
for stage in self.stages:
data = stage.process(data)
return data
# 使用示例:构建完整的拼图制作流程
pipeline = (MosaicPipeline(config)
.add_stage(DataDownloadStage()) # 下载数据
.add_stage(QCStage()) # 质量控制
.add_stage(MergeStage()) # 拼图合并
.add_stage(PlotStage()) # 生成图像
.add_stage(OutputStage())) # 输出产品
pipeline.run()
管道模式的巨大价值:
metradar 提供了多个现成的项目:
项目 | 功能 |
|---|---|
make_mosaic | 拼图制作 |
make_vpr_aws | VPR 垂直剖面 + 自动站分析 |
nowcasting | 临近预报 |
qpe | 降水估测 |
wind_retrieval | 风场反演 |
每个项目都是完整的一键式解决方案,大大降低了业务人员的技术门槛。
metradar 的数据流设计清晰明了,从原始数据到最终产品,数据单向流动,每一步都有明确的输入和输出。
┌──────────────┐
│ 原始数据源 │
│ (FMT/PUP/SWAN)│
└──────┬───────┘
│ IO 层适配器
│ 解析并统一
▼
┌──────────────┐
│ PyArt 统一 │
│ Radar 对象 │
└──────┬───────┘
│ Core 层算法
│ QC/反演/分析
▼
┌──────────────┐
│ 处理结果 │
│ (质量控制的 │
│ 雷达数据) │
└──────┬───────┘
│ Graph 层可视化
│ 绘图/叠加
▼
┌──────────────┐
│ 图形产品 │
│ (PNG/动画) │
└──────┬───────┘
│ Project 层工作流
│ 批处理/输出
▼
┌──────────────┐
│ 业务输出 │
└─────────────┘
关键设计决策:
Radar 对象,这是整个架构的核心抽象层这个设计看似简单,但却是整个系统稳定性的基石。当所有模块都遵循同一个数据模型时,整个系统的复杂度大大降低。
metradar 最大的亮点之一是巧妙地集成了多个国际成熟的雷达工具包。这不是简单的"拿来主义",而是精心设计的架构整合。
┌─────────────────────────────────────┐
│ metradar 应用层 │
│ (Project + 业务流程封装) │
├─────────────────────────────────────┤
│ metradar 核心层 │
│ (Core + Graph + IO 本地化功能) │
├─────────────────────────────────────┤
│ PyArt 统一抽象层 │
│ (Radar 对象统一接口) │
├─────────────────────────────────────┤
│ 第三方工具包层 │
│ pyart | wradlib | pydda | pysteps │
└─────────────────────────────────────┘
国际工具包功能强大但接口复杂,metradar 通过包装器模式提供了简洁的本地化接口。
直接使用 wradlib(复杂):
import wradlib.dp as dp
cluttermap = dp.process_clutter(data, method='histogram',
scale=1.0, max_range=200,
beamwidth=1.0, ... )
metradar 包装器(简洁):
from metradar.core import remove_clutter
cluttermap = remove_clutter(data, method='histogram')
包装器不是简单的参数默认值,而是根据中国气象的业务需求做了针对性优化。
metradar 不是简单地调用国际工具包,而是针对中国气象的特殊需求做了本地化适配。
def chinese_radar_qc(radar):
"""中国雷达专用质量控制流程"""
# 1. 调用 PyArt 标准质量控制
radar = pyart.correct.correct_radar(radar)
# 2. 中国特有的处理
radar = _remove_sea_clutter(radar) # 海洋杂波去除
radar = _correct_beam_blockage(radar) # 地形波束遮挡订正
radar = _detect_precip_type(radar) # 降水类型识别
radar = _apply_china_qc_rules(radar) # 应用中国 QC 规则
return radar
这种本地化适配让 metradar 既能享受国际工具的成熟算法,又能满足中国气象的业务需求。
metradar 将多个工具包的算法组合成更强大的功能。
def hybrid_wind_retrieval(radar, aws_data):
"""混合风场反演:结合多种方法的优点"""
# 1. wradlib 单雷达风场反演
wind1 = wradlib.dp.vel2z(radar)
# 2. pydda 双多普勒分析(如果有多个雷达)
if len(radar_list) >= 2:
wind2 = pydda.Grid.get_dd_wind(radar_list)
wind1 = _merge_winds(wind1, wind2)
# 3. 自动站数据融合
wind_final = _assimilate_aws(wind1, aws_data)
return wind_final
通过这种组合,metradar 实现了单一工具包无法达到的效果。
metradar 的架构设计中有几个值得学习的亮点:
所有数据格式最终都转换为 PyArt 的 Radar 对象,这个设计看似简单,但影响深远:
这是架构设计中的"抽象层模式"的典型应用——通过定义一个核心抽象,将复杂性隔离在底层。
metradar 没有闭门造车,而是:
这种"洋为中用"的思路,值得很多国产科学软件学习。
metradar 的 Project 层将完整的业务流程封装成可执行的项目,这是科研工具走向工程化应用的关键一步。
从"能跑的代码"到"可用的产品",中间需要的正是这种工程化封装能力。
每个模块都有明确的职责:
模块 | 职责 |
|---|---|
IO 层 | 只管数据读取 |
Core 层 | 只管算法实现 |
Graph 层 | 只管可视化 |
Project 层 | 只管业务流程 |
清晰的边界让代码易于理解、易于维护、易于扩展。
metradar 使用了多种经典设计模式,但都没有过度设计:
模式 | 应用位置 | 解决的问题 |
|---|---|---|
适配器模式 | IO 层 | 数据格式统一 |
工厂模式 | IO 层 | 多版本解码选择 |
策略模式 | Core 层 | 多算法选择 |
构建器模式 | Graph 层 | 复杂图形构建 |
模板方法模式 | Graph 层 | 绘图流程标准化 |
管道模式 | Project 层 | 数据处理流水线 |
单例模式 | Config/Util | 资源全局管理 |
每个设计模式都有明确的问题场景和解决方案,没有为了用而用。
metradar/
├── config.py # 配置层
├── core/ # 核心算法层
│ ├── get_cross_section.py # 剖面图算法
│ ├── mosaic_merge.py # 拼图合并算法
│ └── oa_dig_func.py # 客观分析函数
├── io/ # 数据 I/O 层
│ ├── cnrad_level2.py # 中国雷达基数据
│ ├── decode_fmt_pyart.py # FMT 格式解码
│ ├── decode_pup_rose.py # PUP/ROSE 解码
│ ├── read_swan.py # SWAN 拼图读取
│ └── rose_structer.py # ROSE 数据结构
├── graph/ # 可视化层
│ ├── draw_comp_mosaic.py # 综合拼图绘制
│ ├── draw_latlon_func.py # 经纬度投影
│ ├── draw_radar_aws.py # 雷达+自动站
│ └── draw_radar_comp_func.py # 综合绘图函数
├── util/ # 工具层
│ ├── comm_func.py # 通用函数
│ ├── exceptions.py # 自定义异常
│ ├── geo_transforms_pyart.py # 地理变换
│ ├── parse_pal.py # 色标解析
│ ├── radar_common.py # 雷达通用工具
│ └── trans_new_mosaic_nc.py # 拼图格式转换
└── project/ # 应用层
├── make_mosaic/ # 拼图制作项目
├── make_vpr_aws/ # VPR+自动站项目
├── nowcasting/ # 临近预报项目
├── qpe/ # 降水估测项目
└── wind_retrieval/ # 风场反演项目
场景 | 说明 |
|---|---|
日常业务 | 快速生成雷达拼图、回波动画 |
灾害监测 | 台风跟踪、强对流预警 |
科研分析 | 风场反演、降水估测、气候分析 |
算法开发 | 基于 metradar 开发新的处理算法 |
metradar 作为一个新生的开源项目,还有很多可以完善的地方:
虽然以上功能大多是开源库的缝合,但你先别管这那,先让代码跑起来
资源 | 地址 |
|---|---|
项目主页 | https://github.com/nmcdev/metradar |
测试数据 | https://github.com/zhuwenjian/metradar_testdata |
PyArt 文档 | https://arm-doe.github.io/pyart/ |
wradlib 文档 | https://wradlib.org/ |
pysteps 文档 | https://pysteps.readthedocs.io/ |