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, "")