Manual-Labeling-Tool/windTools/ERA5ToWindDataConverter.py

352 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import xarray as xr
import numpy as np
import sys
import ctypes
from datetime import datetime, timedelta
# from WindDataHandler import DataFileInfo,WindDataHandler
from PySide6.QtWidgets import (QApplication, QMainWindow, QPushButton,
QVBoxLayout, QWidget, QProgressDialog)
from PySide6.QtCore import QTimer, Qt
from PySide6.QtWidgets import QMessageBox
import argparse
import ctypes
# 定义与 C++ 中 DataFileInfo 对应的结构体
class DataFileInfo(ctypes.Structure):
_fields_ = [
("Height", ctypes.c_int64),
("Width", ctypes.c_int64),
("Num", ctypes.c_int64),
("firstTimestamp", ctypes.c_int64),
("lastTimestamp", ctypes.c_int64),
("minLon", ctypes.c_double),
("maxLon", ctypes.c_double),
("minLat", ctypes.c_double),
("maxLat", ctypes.c_double),
("ESPGCODE", ctypes.c_int64),
("T11", ctypes.c_double),
("T12", ctypes.c_double),
("T13", ctypes.c_double),
("T21", ctypes.c_double),
("T22", ctypes.c_double),
("T23", ctypes.c_double),
("fileSize", ctypes.c_int64)
]
class WindDataHandler:
def __init__(self, dll_path='./LAMPWindData.dll'):
self.dll_path = dll_path
self.wind_lib = ctypes.CDLL(dll_path)
self._setup_prototypes()
self.wind_lib.getDataFileInfo.argtypes = [ctypes.c_char_p] # 参数是 const char*
self.wind_lib.getDataFileInfo.restype = DataFileInfo # 返回类型是 DataFileInfo 结构体
self.current_file_info = None
def _setup_prototypes(self):
"""设置 DLL 函数的参数和返回类型"""
# ... 函数原型设置代码见上一节,此处省略 ...
def get_data_file_info(self, filepath):
"""获取风场数据文件信息"""
# 将Python字符串转换为字节字符串
filepath_bytes = filepath.encode('utf-8')
info = self.wind_lib.getDataFileInfo(filepath_bytes)
self.current_file_info = info
return info
def get_wind_data_file_time_arr(self, filepath, info):
"""获取时间数组"""
filepath_bytes = filepath.encode('utf-8')
if info.Num <= 0:
return []
# 创建足够大的数组来存储时间戳
time_arr = (ctypes.c_int64 * info.Num)()
result = self.wind_lib.get_WindDataFileTimeArr(filepath_bytes, info, time_arr)
if result == 0: # 假设返回0表示成功
return list(time_arr)
else:
print(f"Error getting time array: {result}")
return []
def read_wind_data_file(self, filepath, info, time_id):
"""读取指定时间ID的风场数据 (U/V分量)"""
filepath_bytes = filepath.encode('utf-8')
layer_size = info.Height * info.Width
# 创建数组来存储U和V分量数据
u_arr = (ctypes.c_double * layer_size)()
v_arr = (ctypes.c_double * layer_size)()
result = self.wind_lib.Read_WindDataFile(filepath_bytes, info, time_id, u_arr, v_arr)
if result >= 0:
# 将ctypes数组转换为Python列表或numpy数组如果可用
return list(u_arr), list(v_arr)
else:
print(f"Error reading wind data: {result}")
return None, None
def write_wind_data_file(self, filepath, info, timeInt64,time_id, u_arr, v_arr):
"""将风场数据写入文件"""
filepath_bytes = filepath.encode('utf-8')
layer_size = info.Height * info.Width
# 确保输入数据长度正确
if len(u_arr) != layer_size or len(v_arr) != layer_size:
raise ValueError(f"UArr and VArr must have length {layer_size}")
# 将Python列表转换为ctypes数组
u_arr_ctypes = (ctypes.c_double * layer_size)(*u_arr)
v_arr_ctypes = (ctypes.c_double * layer_size)(*v_arr)
result = self.wind_lib.Write_WindDataFile(filepath_bytes, info, timeInt64,time_id, u_arr_ctypes, v_arr_ctypes)
return result
class ERA5ToWindDataConverter:
def __init__(self, dll_path='./LAMPWindData.dll'):
self.wind_handler = WindDataHandler(dll_path)
def read_era5_data(self, nc_file_path):
"""读取ERA5数据文件"""
try:
# 使用xarray读取NetCDF文件[6,8](@ref)
ds = xr.open_dataset(nc_file_path)
print("ERA5数据集变量:", list(ds.variables.keys()))
# 提取u10和v10分量10米高度风场[1,5](@ref)
u10 = ds['u10'].values # 东西风分量
v10 = ds['v10'].values # 南北风分量
lon = ds['longitude'].values # 经度
lat = ds['latitude'].values # 纬度
time = ds['time'].values # 时间
ds.close()
return u10, v10, lon, lat, time
except Exception as e:
print(f"读取ERA5数据失败: {e}")
return None, None, None, None, None
def create_data_file_info(self, lon, lat, time):
"""创建DataFileInfo结构体"""
info = DataFileInfo()
# 设置网格尺寸[1](@ref)
info.Height = len(lat) # 纬度维度
info.Width = len(lon) # 经度维度
info.Num = len(time) # 时间步数
# 设置地理范围[1](@ref)
info.minLon = float(lon.min())
info.maxLon = float(lon.max())
info.minLat = float(lat.min())
info.maxLat = float(lat.max())
time_deltas = time
info.firstTimestamp = int(time_deltas[0])
info.lastTimestamp = int(time_deltas[-1])
# 设置坐标系WGS84的EPSG代码为4326
info.ESPGCODE = 4326
# 变换参数(单位矩阵表示无变换)
info.T11, info.T12, info.T13 = lon[0],lon[1]-lon[0],0.0 # 计算 lon X_geo = GT(0) + X_col * GT(1) + Y_row * GT(2)
info.T21, info.T22, info.T23 = lat[0], 0.0, lat[1]-lat[0] # 计算 lat Y_geo = GT(3) + X_col * GT(4) + Y_row * GT(5)
return info
def convert_era5_time(self, time_values):
"""转换ERA5时间格式为datetime对象[1,5](@ref)"""
# ERA5时间通常是从1900-01-01开始的小时数
return [time_val.astype("int64") for time_val in time_values]
# base_time = datetime(1900, 1, 1, 0, 0, 0)
#
# converted_times = []
# for time_val in time_values:
# if isinstance(time_val, np.datetime64):
# # 如果是numpy datetime64格式
# dt = time_val.astype(datetime)
# else:
# # 如果是数值格式从1900-01-01开始的小时数
# hours = float(time_val)
# dt = base_time + timedelta(hours=hours)
# converted_times.append(dt)
#
# return converted_times
def convert_era5_to_winddata(self, era5_file, output_file):
"""将ERA5数据转换为WindData格式"""
progress_dialog = QProgressDialog(
"文件写入进度,请稍候...", # 标签文本
"取消", # 取消按钮文本
0, 100, # 最小值和最大值
None # 父窗口
)
# 1. 读取ERA5数据
u10, v10, lon, lat, time = self.read_era5_data(era5_file)
if u10 is None:
return False
# 2. 转换时间格式
time_datetime = self.convert_era5_time(time)
# 4. 创建文件信息头
file_info = self.create_data_file_info(lon, lat, time_datetime)
# 5. 写入WindData文件
try:
progress_dialog.setWindowTitle("文件写入进度,请稍候...")
progress_dialog.setFixedSize(500, 50)
progress_dialog.setCancelButton(None)
progress_dialog.setWindowModality(Qt.NonModal)
progress_dialog.setMinimumDuration(0)
progress_dialog.setRange(0, len(time_datetime))
progress_dialog.show()
# 逐个时间步写入数据
for time_idx in range(len(time_datetime)):
if progress_dialog is None or not progress_dialog.isVisible() or progress_dialog.wasCanceled():
print("窗口已关闭")
continue
# 获取当前时间步的u、v分量
u_slice = u10[time_idx, :, :].flatten()
v_slice = v10[time_idx, :, :].flatten()
timeInt=time_datetime[time_idx]
print("时间:",timeInt)
# 写入数据
result = self.wind_handler.write_wind_data_file(
output_file, file_info,ctypes.c_int64(timeInt), time_idx, list(u_slice), list(v_slice)
)
progress_dialog.setValue(time_idx)
QApplication.processEvents()
if result != 0:
print(f"写入时间步 {time_idx} 失败: {result}")
return False
print(f"转换成功!输出文件: {output_file}")
if progress_dialog is None or not progress_dialog.isVisible() or progress_dialog.wasCanceled():
return True
else:
progress_dialog.close()
return True
except Exception as e:
print(f"转换过程出错: {e}")
QMessageBox.critical(None, "错误", f"文件保存错误: {e}\n{output_file}")
return False
def compareResult(self,era5_file, bin_file):
u10, v10, lon, lat, time = self.read_era5_data(era5_file)
time_datetime = self.convert_era5_time(time)
info = converter.wind_handler.get_data_file_info(bin_file)
print(f"文件信息: {info.Height}x{info.Width} 网格, {info.Num} 个时间步")
# 获取时间数组
time_arr = converter.wind_handler.get_wind_data_file_time_arr(bin_file, info)
print(f"时间范围: {time_arr[0]}{time_arr[-1]}")
print("比对时间")
for tid in range(info.Num):
if time_arr[tid]-time_datetime[tid]==0:
continue
else:
print("时间错误,",time_arr[tid],time_datetime[tid])
print("比对实际数据值")
for tid in range(info.Num):
u_slice = u10[tid, :, :].flatten()
v_slice = v10[tid, :, :].flatten()
u_wind,v_wind=self.wind_handler.read_wind_data_file(bin_file,info,tid)
u_wind=np.array(u_wind)
v_wind=np.array(v_wind)
u_d=u_slice-u_wind
v_d=v_slice-v_wind
u_d_min=np.max(np.abs(u_d))
v_d_min=np.max(np.abs(v_d))
if u_d_min>0 or v_d_min>0:
print("tid:(u_d,v_d)",tid,u_d_min,v_d_min)
pass
def getParams():
parser = argparse.ArgumentParser()
parser.add_argument('-i', '--infile', type=str,default=r"D:\Programs\SpacetyLabelAIPlante\Manual-Labeling-Tool\WindFileData\data\total_precipitation202001.nc",help='输入ERA5 的netcdf文件')
parser.add_argument('-o', '--outfile', type=str, default=r"D:\Programs\SpacetyLabelAIPlante\Manual-Labeling-Tool\WindFileData\output\converted_wind_data.lampwindbin",help='输出自定义风场文件')
parser.add_argument('-d', '--dllfile', type=str, default=r"D:\Programs\SpacetyLabelAIPlante\Manual-Labeling-Tool\WindFileData\dllFolder\WindDataOperator.dll",help='输出自定义风场文件')
args = parser.parse_args()
return args
# 使用示例
if __name__ == "__main__":
app = QApplication(sys.argv)
parser = getParams()
dllpath=parser.dllfile
ncpath=parser.infile
binpath=parser.outfile
# 创建转换器实例
converter = ERA5ToWindDataConverter(dllpath)
# 执行转换
era5_file = ncpath # ERA5输入文件
output_file = binpath # 输出文件
success = converter.convert_era5_to_winddata(era5_file, output_file)
print('写入结果',success)
if success:
QMessageBox.information(None, "操作成功", "文件保存成功!\n{}".format(output_file))
# 验证转换结果
info = converter.wind_handler.get_data_file_info(output_file)
print(f"文件信息: {info.Height}x{info.Width} 网格, {info.Num} 个时间步")
else:
# 错误框
pass
sys.exit(app.exec())