263 lines
9.5 KiB
Python
263 lines
9.5 KiB
Python
from io import BytesIO
|
||
import pandas as pd
|
||
import numpy as np
|
||
import os
|
||
from scipy.optimize import curve_fit
|
||
import matplotlib.pyplot as plt
|
||
|
||
# 解决绘图中中文不能显示的问题
|
||
import matplotlib
|
||
# 设置中文显示和负号正常显示
|
||
matplotlib.rcParams['font.sans-serif'] = ['SimHei'] # 显示中文
|
||
matplotlib.rcParams['axes.unicode_minus'] = False # 正常显示负号
|
||
|
||
# 定义所有模型
|
||
|
||
|
||
def tidal_model(t, v0, a1, b1, a2, b2, a3, b3, a4, b4):
|
||
"""潮汐波模型"""
|
||
T1, T2, T3, T4 = 6, 8, 12, 24 # 周期以小时为单位
|
||
return (v0 + a1 * np.sin((2 * np.pi / T1) * t + b1)
|
||
+ a2 * np.sin((2 * np.pi / T2) * t + b2)
|
||
+ a3 * np.sin((2 * np.pi / T3) * t + b3)
|
||
+ a4 * np.sin((2 * np.pi / T4) * t + b4))
|
||
|
||
|
||
def planetary_model_2day(t, v0, a, b):
|
||
"""2日行星波模型"""
|
||
T = 48 # 周期为2天(48小时)
|
||
return v0 + a * np.sin((2 * np.pi / T) * t + b)
|
||
|
||
|
||
def planetary_model_5day(t, v0, a, b):
|
||
"""5日行星波模型"""
|
||
T = 120 # 周期为5天(120小时)
|
||
return v0 + a * np.sin((2 * np.pi / T) * t + b)
|
||
|
||
|
||
def planetary_model_10day(t, v0, a, b):
|
||
"""10日行星波模型"""
|
||
T = 240 # 周期为10天(240小时)
|
||
return v0 + a * np.sin((2 * np.pi / T) * t + b)
|
||
|
||
|
||
def planetary_model_16day(t, v0, a, b):
|
||
"""16日行星波模型"""
|
||
T = 384 # 周期为16天(384小时)
|
||
return v0 + a * np.sin((2 * np.pi / T) * t + b)
|
||
|
||
|
||
# 统一管理模型及其参数
|
||
MODELS = {
|
||
'潮汐波': {
|
||
'function': tidal_model,
|
||
'param_names': ['v0', 'a1', 'b1', 'a2', 'b2', 'a3', 'b3', 'a4', 'b4'],
|
||
'initial_guess': [0, 1, 0, 1, 0, 1, 0, 1, 0],
|
||
'bounds': ([-np.inf, 0, -np.inf, 0, -np.inf, 0, -np.inf, 0, -np.inf],
|
||
[np.inf, np.inf, np.inf, np.inf, np.inf, np.inf, np.inf, np.inf, np.inf]),
|
||
'window': 5 # 窗口为5天
|
||
},
|
||
'2日行星波': {
|
||
'function': planetary_model_2day,
|
||
'param_names': ['v0', 'a', 'b'],
|
||
'initial_guess': [0, 1, 0],
|
||
'bounds': ([-np.inf, 0, -np.inf], [np.inf, np.inf, np.inf]),
|
||
'window': 5 # 窗口为5天
|
||
},
|
||
'5日行星波': {
|
||
'function': planetary_model_5day,
|
||
'param_names': ['v0', 'a', 'b'],
|
||
'initial_guess': [0, 1, 0],
|
||
'bounds': ([-np.inf, 0, -np.inf], [np.inf, np.inf, np.inf]),
|
||
'window': 15 # 窗口为15天
|
||
},
|
||
'10日行星波': {
|
||
'function': planetary_model_10day,
|
||
'param_names': ['v0', 'a', 'b'],
|
||
'initial_guess': [0, 1, 0],
|
||
'bounds': ([-np.inf, 0, -np.inf], [np.inf, np.inf, np.inf]),
|
||
'window': 29 # 窗口为29天
|
||
},
|
||
'16日行星波': {
|
||
'function': planetary_model_16day,
|
||
'param_names': ['v0', 'a', 'b'],
|
||
'initial_guess': [0, 1, 0],
|
||
'bounds': ([-np.inf, 0, -np.inf], [np.inf, np.inf, np.inf]),
|
||
'window': 49 # 窗口为49天
|
||
},
|
||
}
|
||
|
||
ALL_MODEL_NAMES = list(MODELS.keys())
|
||
|
||
# ----------------------------------------------------------第一大部分————————————————————————————————————————————————————
|
||
# # 根据纬向风(u)、经向风(v),得到武汉左岭镇站/黑龙江漠河站
|
||
# 固定年份(2022年)固定高度(90km)大气波动(6、8、12、24小时潮汐波,2日、5日、10日、16日行星波)随时间的变化
|
||
|
||
|
||
# 参数设置,选择高度、时间、站点
|
||
|
||
# station = '黑龙江漠河站'
|
||
def get_df(H, year, station):
|
||
# 文件路径设置
|
||
file_dir = rf'./radar/data/{station}\{year}' # 数据文件路径
|
||
output_dir = rf'./radar/out/{station}\{year}' # 参数txt、图片输出路径
|
||
os.makedirs(output_dir, exist_ok=True) # 确保输出路径存在
|
||
|
||
# 1-提取整个文件夹的数据
|
||
# 获取所有 txt 文件
|
||
file_list = [f for f in os.listdir(file_dir) if f.endswith('.txt')]
|
||
|
||
# 初始化空的 DataFrame,用于合并所有文件的数据
|
||
final_df = pd.DataFrame()
|
||
|
||
# 批量处理每个 txt 文件
|
||
for file_name in file_list:
|
||
file_path = os.path.join(file_dir, file_name) # 拼接文件路径
|
||
df = pd.read_csv(file_path, sep='\s+') # 读取文件
|
||
date_part = os.path.splitext(file_name)[0][-8:] # 提取日期部分
|
||
|
||
# 处理缺失值,将 1000000 替换为 NaN
|
||
df.loc[df['uwind'] == 1000000, 'uwind'] = np.nan
|
||
df.loc[df['vwind'] == 1000000, 'vwind'] = np.nan
|
||
|
||
# 筛选出 height 列为 90km 的行
|
||
filtered_df = df[df['height'] == H]
|
||
|
||
# 重命名列,包含日期信息
|
||
filtered_df = filtered_df.rename(columns={
|
||
'uwind': f'{date_part}_uwind',
|
||
'vwind': f'{date_part}_vwind'
|
||
})
|
||
|
||
# 只保留需要的列
|
||
filtered_df = filtered_df[['height', 'time',
|
||
f'{date_part}_uwind', f'{date_part}_vwind']]
|
||
|
||
# 如果 final_df 为空,则直接赋值
|
||
if final_df.empty:
|
||
final_df = filtered_df
|
||
else:
|
||
# 按 'height' 和 'time' 列对齐合并
|
||
final_df = pd.merge(final_df, filtered_df, on=[
|
||
'height', 'time'], how='outer')
|
||
|
||
# 生成完整日期范围的列名,补全缺失的天,使得平年365,闰年366
|
||
all_dates = pd.date_range(
|
||
f'{year}-01-01', f'{year}-12-31').strftime('%Y%m%d')
|
||
expected_columns = [f'{date}_uwind' for date in all_dates] + \
|
||
[f'{date}_vwind' for date in all_dates]
|
||
|
||
# 确保 final_df 包含所有日期的列,缺失列补为 NaN
|
||
for col in expected_columns:
|
||
if col not in final_df.columns:
|
||
final_df[col] = np.nan
|
||
|
||
# 按列名排序(确保日期顺序)
|
||
final_df = final_df[['height', 'time'] + sorted(expected_columns)]
|
||
return final_df
|
||
# 此时,final_df已经是90km包含完整天的分析数据---------------------------------------------------------
|
||
|
||
|
||
# 通用函数:处理风速数据
|
||
def final_plot_v1(wind_type, year, H, model_name, station):
|
||
|
||
final_df = get_df(H, year, station)
|
||
"""
|
||
处理风速数据并针对多种模型进行拟合和绘图。
|
||
"""
|
||
wind_data = final_df[[col for col in final_df.columns if wind_type in col]]
|
||
wind_data.columns = [col[:8] for col in wind_data.columns]
|
||
result = wind_data.to_numpy().T.flatten()
|
||
column_names = np.repeat(wind_data.columns.to_numpy(), 24)
|
||
combined = pd.DataFrame(np.vstack((column_names, result)))
|
||
|
||
dates = combined.iloc[0].values
|
||
wind_speed = combined.iloc[1].values
|
||
date_series = pd.to_datetime(dates)
|
||
data = pd.DataFrame({'date': date_series, 'wind_speed': wind_speed})
|
||
data.set_index('date', inplace=True)
|
||
unique_dates = np.unique(data.index.date)
|
||
|
||
if model_name not in MODELS:
|
||
raise ValueError(f"Model {model_name} not found")
|
||
return
|
||
|
||
model_info = MODELS[model_name]
|
||
|
||
params = []
|
||
model_func = model_info['function']
|
||
param_names = model_info['param_names']
|
||
initial_guess = model_info['initial_guess']
|
||
bounds = model_info['bounds']
|
||
window_days = model_info['window'] # 获取窗口长度
|
||
|
||
for date in unique_dates:
|
||
# 根据模型窗口调整时间范围
|
||
start_date = pd.to_datetime(
|
||
date) - pd.Timedelta(days=(window_days - 1) / 2)
|
||
end_date = pd.to_datetime(
|
||
date) + pd.Timedelta(days=(window_days - 1) / 2)
|
||
window_data = data[start_date:end_date]
|
||
t = np.arange(len(window_data)) + 1
|
||
y = pd.to_numeric(window_data['wind_speed'], errors='coerce')
|
||
valid_mask = ~np.isnan(y)
|
||
t = t[valid_mask]
|
||
y = y[valid_mask]
|
||
|
||
if len(y) < len(initial_guess) * 6:
|
||
print(
|
||
f"Not enough valid data after filtering for date: {date}")
|
||
|
||
params.append([None] * len(param_names))
|
||
continue
|
||
|
||
try:
|
||
popt, _ = curve_fit(
|
||
model_func, t, y, p0=initial_guess, bounds=bounds)
|
||
params.append(popt)
|
||
except RuntimeError:
|
||
print(f"Fitting failed for date: {date}")
|
||
|
||
params.append([None] * len(param_names))
|
||
|
||
# 保存参数
|
||
params_df = pd.DataFrame(
|
||
params, columns=param_names, index=unique_dates)
|
||
# file_name = f'{year}年_{wind_type}_{H // 1000}km_{model_name}_强度.txt'
|
||
# params_df.to_csv(os.path.join(output_dir, file_name),
|
||
# sep='\t', index=True, header=True)
|
||
|
||
# 绘图
|
||
if 'a1' in params_df.columns: # 处理潮汐波多分量
|
||
plt.figure(figsize=(12, 6))
|
||
for i, T in enumerate([6, 8, 12, 24], start=1):
|
||
plt.plot(params_df[f'a{i}'], label=f'{T}h', linewidth=2)
|
||
else: # 处理行星波单分量
|
||
plt.figure(figsize=(12, 6))
|
||
plt.plot(
|
||
params_df['a'], label=f'{model_name}', color='orange', linewidth=2)
|
||
|
||
plt.title(
|
||
f'{year}年{wind_type}_{H // 1000}km_{model_name}_强度', fontsize=16)
|
||
plt.xlabel('日期', fontsize=14)
|
||
plt.ylabel('幅度 (m/s)', fontsize=14)
|
||
plt.xticks(rotation=45)
|
||
plt.legend()
|
||
plt.tick_params(axis='both', labelsize=12) # 调整刻度字体大小
|
||
plt.tight_layout()
|
||
# picture_file_name = f'{year}年_{wind_type}_{H // 1000}km_{model_name}_强度.png'
|
||
# plt.savefig(os.path.join(output_dir, picture_file_name))
|
||
# plt.show()
|
||
buffer = BytesIO()
|
||
plt.savefig(buffer, format='png')
|
||
buffer.seek(0)
|
||
plt.close()
|
||
return buffer
|
||
|
||
|
||
# 处理纬向风和经向风,适配所有模型
|
||
if __name__ == "__main__":
|
||
pass
|
||
# final_plot_v1('uwind', year, H, "")
|
||
# final_plot_v1('vwind', year, H, "")
|