with tidi

This commit is contained in:
Dustella 2025-01-15 14:48:48 +08:00
parent eb7f4420ff
commit 104e253e2b
Signed by: Dustella
GPG Key ID: 35AA0AA3DC402D5C
30 changed files with 9560 additions and 9833 deletions

View File

@ -1,6 +1,7 @@
from gevent import pywsgi, monkey from gevent import pywsgi, monkey
monkey.patch_all() monkey.patch_all()
import tidi
from utils import * from utils import *
import saber import saber
import radar import radar
@ -9,13 +10,16 @@ from flask import Flask
from flask_cors import CORS from flask_cors import CORS
from typing import get_args from typing import get_args
import sys import sys
import matplotlib.font_manager as fm
app = Flask(__name__) app = Flask(__name__)
fm.fontManager.addfont("./SimHei.ttf")
app.register_blueprint(balloon.balloon_module, url_prefix="/balloon") app.register_blueprint(balloon.balloon_module, url_prefix="/balloon")
app.register_blueprint(radar.radar_module, url_prefix="/radar") app.register_blueprint(radar.radar_module, url_prefix="/radar")
app.register_blueprint(saber.saber_module, url_prefix="/saber") app.register_blueprint(saber.saber_module, url_prefix="/saber")
app.register_blueprint(tidi.tidi_module, url_prefix="/tidi")
# allow cors # allow cors
CORS(app) CORS(app)
@ -32,4 +36,4 @@ if __name__ == '__main__':
server.serve_forever() server.serve_forever()
elif 'debug' in args: elif 'debug' in args:
app.run(debug=True) app.run("0.0.0.0",debug=True)

View File

@ -1,14 +1,10 @@
import asyncio import asyncio
import glob import glob
from io import BytesIO
from flask import Blueprint, request, send_file from flask import Blueprint, request, send_file
from radar.plot_original import final_plot_v1, ALL_MODEL_NAMES from matplotlib import pyplot as plt
from radar.plot_original import final_render_v2
from radar.plot_prod import final_plot_v2 from radar.plot_prod import final_plot_v2
import threading
plot_v1 = final_plot_v1
plot_v2 = final_plot_v2
all_model_names = ALL_MODEL_NAMES
globed_all_files = glob.glob("./radar/data/**/**.txt", recursive=True) globed_all_files = glob.glob("./radar/data/**/**.txt", recursive=True)
@ -19,15 +15,15 @@ radar_module = Blueprint("Radar", __name__)
@radar_module.route("/metadata") @radar_module.route("/metadata")
def get_all_files(): def get_all_files():
return globed_all_files return final_render_v2.get_all_pathes()
@radar_module.route("/metadata/models") @radar_module.route("/metadata/models")
def get_all_models(): def get_all_models():
return all_model_names return final_render_v2.get_all_models()
@radar_module.route('/render/v1') @radar_module.route('/render/heatmap')
def render_v1(): def render_v1():
""" """
wind_type: Any, wind_type: Any,
@ -38,13 +34,31 @@ def render_v1():
wind_type = request.args.get('wind_type') wind_type = request.args.get('wind_type')
year = request.args.get('year') year = request.args.get('year')
H = request.args.get('H') H = request.args.get('H')
model_name = request.args.get('model_name')
station = request.args.get('station') station = request.args.get('station')
buf = plot_v1(wind_type, int(year), int(H), model_name, station)
model_name = request.args.get('model_name')
mode = request.args.get('mode')
renderer = final_render_v2(int(H), int(year), station, wind_type)
if mode=="day":
day = request.args.get('day')
renderer.render_day(day,model_name)
elif mode=="month":
month = request.args.get('month')
renderer.render_month(int(month),model_name)
elif mode=="year":
renderer.render_year(model_name)
else:
raise ValueError("mode not supported")
buf = BytesIO()
plt.savefig(buf, format='png')
buf.seek(0)
# close the plot
plt.close()
return send_file(buf, mimetype='image/png') return send_file(buf, mimetype='image/png')
@radar_module.route('/render/v2') @radar_module.route('/render/changes')
def render_v2(): def render_v2():
""" """
year: Any, year: Any,
@ -54,8 +68,17 @@ def render_v2():
year = request.args.get('year') year = request.args.get('year')
station = request.args.get('station') station = request.args.get('station')
model_name = request.args.get('model_name') model_name = request.args.get('model_name')
# async_plot_v2 = asyncio.coroutine(plot_v2) start_month = request.args.get('start_month')
end_month = request.args.get('end_month')
if start_month is not None and end_month is not None:
start_month = int(start_month)
end_month = int(end_month)
month_range = (start_month, end_month)
else:
month_range = (1, 12)
buffer = plot_v2(int(year), station, model_name)
buffer = final_plot_v2(int(year), station, model_name, month_range)
buffer.seek(0)
return send_file(buffer, mimetype='image/png') return send_file(buffer, mimetype='image/png')

View File

@ -1,998 +0,0 @@
import os
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
import seaborn as sns
# 解决绘图中中文不能显示的问题
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)
# ----------------------------------------------------------第二大部分————————————————————————————————————————————————————
# # 根据纬向风(u)、经向风(v),得到武汉左岭镇站/黑龙江漠河站
# 大气波动6、8、12、24小时潮汐波2日、5日、10日、16日行星波随时空变化热力图
# 设置文件路径
year = 2022
station = '武汉左岭镇站'
# station = '黑龙江漠河站'
# 文件路径设置
file_dir = rf'D:\wu\gravity wave\Meteor\流星雷达分析数据\{station}\{year}' # 数据文件路径
output_dir = rf'D:\wu\gravity wave\Meteor\输出\{station}\{year}\时空' # 参数txt、图片输出路径
os.makedirs(output_dir, exist_ok=True) # 确保输出路径存在
# 获取目录下所有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, delim_whitespace=True)
date_part = os.path.splitext(file_name)[0][-8:] # 假设文件名最后8个字符是日期
# 替换异常值为 NaN
df.loc[df['uwind'] == 1000000, 'uwind'] = np.nan
df.loc[df['vwind'] == 1000000, 'vwind'] = np.nan
# 筛选出所需的列
df = df[['height', 'time', 'uwind', 'vwind']]
df = df.rename(columns={
'uwind': f'{date_part}_uwind',
'vwind': f'{date_part}_vwind'
})
if final_df.empty:
final_df = df
else:
final_df = pd.merge(final_df, 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)]
# 此时final_df已经是全高度包含完整天的分析数据---------------------------------------------------------
# -----------------------------------------得到潮汐波拟合参数-------------------------------------------
# 定义单高度的拟合函数
def fit_wind_speed(data, window=5):
# 动态调整最小有效数据点数量:参数数量 * 6
num_params = 9 # 模型参数数量
# 转换为 pandas DataFrame 并设置时间为索引
data["date"] = pd.to_datetime(data["date"])
data.set_index("date", inplace=True)
# 获取唯一日期
unique_dates = np.unique(data.index.date)
# 存储拟合参数
params = []
for date in unique_dates:
start_date = pd.to_datetime(date) - pd.Timedelta(days=(window - 1) / 2)
end_date = pd.to_datetime(date) + pd.Timedelta(days=(window - 1) / 2)
# 获取这几天的数据
window_data = data[start_date:end_date]
t = np.arange(len(window_data)) + 1 # 时间点
y = window_data['wind_speed'].values # 风速数据
# 将 y 转换为浮点类型,以防有非数值类型数据
y = pd.to_numeric(y, errors='coerce') # 将非数值转换为 NaN
# 去掉包含nan的那一列筛选有效数据
valid_mask = ~np.isnan(y) # 创建布尔掩码,标记非 NaN 的位置
t = t[valid_mask]
y = y[valid_mask]
# 检查筛选后的数据是否足够
if len(y) < num_params*6: # 确保数据足够
# print(f"Not enough valid data after filtering for date: {date}")
params.append([None] * 9) # 如果数据不足记录None
continue
# 设置初始参数
initial_guess = [0, 1, 0, 1, 0, 1, 0, 1, 0] # v0, a1, b1, a2, b2, a3, b3, a4, b4
# 设置参数界限
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]) # 上界
# 拟合模型
try:
popt, _ = curve_fit(tidal_model, t, y, p0=initial_guess, bounds=bounds)
params.append(popt) # 存储拟合参数
except RuntimeError:
# print(f"Fitting failed for date: {date}")
params.append([None] *9) # 如果拟合失败记录None
# 转换拟合参数为 DataFrame
params_df = pd.DataFrame(
params,
columns=["v0", "a1", "b1", "a2", "b2", "a3", "b3", "a4", "b4"],
index=unique_dates,
)
return params_df
# 1-拟合纬向风------------------------------------------
# 提取高度、时间和纬向风数据
uwind_data = final_df[["height", "time"] + [col for col in final_df.columns if "_uwind" in col]]
uwind_data.columns = [col[:8] for col in uwind_data.columns]
# 遍历所有高度
heights = uwind_data["height"].unique()
u_result_dict = {}
for height in heights:
print(f"Processing height: {height} m")
# 获取当前高度的纬向风数据
height_data = uwind_data[uwind_data["height"] == height]
# 转换为长格式(适配拟合函数)
long_data = pd.melt(
height_data,
id_vars=["time", "height"],
var_name="date",
value_name="wind_speed",
)
long_data["date"] = pd.to_datetime(long_data["date"], format="%Y%m%d")
# 调用拟合函数
params_df = fit_wind_speed(long_data)
u_result_dict[height] = params_df
# 汇总结果为 DataFrame
u_final_result = pd.concat(u_result_dict, names=["height", "date"])
# 将结果保存为 TXT 文档
file_name1 = f'{year}年_纬向风_潮汐波振幅参数.txt'
u_final_result.to_csv(
os.path.join(output_dir, file_name),
sep='\t',
index=True,
float_format='%.2f', # 控制数值列保留 2 位小数
header=True)
# 2-拟合经向风------------------------------------------
# 提取高度、时间和经向风数据
vwind_data = final_df[["height", "time"] + [col for col in final_df.columns if "_vwind" in col]]
vwind_data.columns = [col[:8] for col in vwind_data.columns]
# 遍历所有高度
heights = vwind_data["height"].unique()
v_result_dict = {}
for height in heights:
print(f"Processing height: {height} m")
height_data = vwind_data[vwind_data["height"] == height]
long_data = pd.melt(
height_data,
id_vars=["time", "height"],
var_name="date",
value_name="wind_speed",
)
long_data["date"] = pd.to_datetime(long_data["date"], format="%Y%m%d")
params_df = fit_wind_speed(long_data)
v_result_dict[height] = params_df
# 汇总结果为 DataFrame
v_final_result = pd.concat(v_result_dict, names=["height", "date"])
# 将结果保存为 TXT 文档
# 保存参数
file_name2 = f'{year}年_经向风_潮汐波振幅参数.txt'
v_final_result.to_csv(
os.path.join(output_dir, file_name),
sep='\t',
index=True,
float_format='%.2f', # 控制数值列保留 2 位小数
header=True)
# 画热力图-----------------------------------------
'''
# 读取数据,设置第一列和第二列为多重索引
u_final_result = pd.read_csv(os.path.join(output_dir, file_name1), sep='\t')
u_final_result = u_final_result.set_index([u_final_result.columns[0], u_final_result.columns[1]])
v_final_result = pd.read_csv(os.path.join(output_dir, file_name2), sep='\t')
v_final_result = v_final_result.set_index([v_final_result.columns[0], v_final_result.columns[1]])
'''
# 定义振幅参数
amplitude_param = 'a4' # 代表 24 小时周期振幅 # todo: 选择a4为周日潮,a3半日潮
# 提取纬向风数据,重置索引为平坦结构
u_heatmap_data = u_final_result[amplitude_param].unstack(level=0) # 将高度作为列,日期作为行
u_heatmap_data.index = pd.to_datetime(u_heatmap_data.index) # 确保日期为时间格式
u_heatmap_data = u_heatmap_data.iloc[:, ::-1] # 按高度降序排列
# 提取经向风数据,重置索引为平坦结构
v_heatmap_data = v_final_result[amplitude_param].unstack(level=0) # 将高度作为列,日期作为行
v_heatmap_data.index = pd.to_datetime(v_heatmap_data.index) # 确保日期为时间格式
v_heatmap_data = v_heatmap_data.iloc[:, ::-1] # 按高度降序排列
# 创建画布和子图
fig, axes = plt.subplots(1, 2, figsize=(18, 8)) # 1行2列的布局
fig.suptitle('2022年周日潮汐波振幅时空变化', fontsize=20) # 添加总标题
# 绘制第一幅热力图(纬向风)
sns.heatmap(
u_heatmap_data.T, # 转置,横轴为时间,纵轴为高度
cmap="viridis", # 配色方案
vmax=100, # 设置颜色范围的最大值
cbar_kws={'label': '振幅 (m/s)'}, # 设置颜色条标签
xticklabels=True, # 保留真实的横轴刻度标签
#yticklabels=u_heatmap_data.columns.astype(str), # 显示纵轴的高度
yticklabels=[f'{height / 1000}' for height in u_heatmap_data.columns], # 转换高度为千米并格式化
ax=axes[0] # 指定子图
)
axes[0].set_title('纬向风振幅变化', fontsize=14)
axes[0].set_xlabel('日期', fontsize=12)
axes[0].set_ylabel('高度 (km)', fontsize=12)
# 绘制第二幅热力图(经向风)
sns.heatmap(
v_heatmap_data.T, # 转置,横轴为时间,纵轴为高度
cmap="viridis", # 配色方案
vmax=100, # 设置颜色范围的最大值
cbar_kws={'label': '振幅 (m/s)'}, # 设置颜色条标签
xticklabels=True, # 保留真实的横轴刻度标签
#yticklabels=v_heatmap_data.columns.astype(str), # 显示纵轴的高度
yticklabels=[f'{height / 1000}' for height in v_heatmap_data.columns], # 转换高度为千米并格式化
ax=axes[1] # 指定子图
)
axes[1].set_title('经向风振幅变化', fontsize=14)
axes[1].set_xlabel('日期', fontsize=12)
axes[1].set_ylabel('高度 (km)', fontsize=12)
# 调整布局
plt.tight_layout(rect=[0, 0, 1, 0.95]) # 为总标题留出空间
# 显示图像
plt.show()
# 输出路径和文件名
picture_file_name = f'{year}年_周日_潮汐波振幅时空变化.png'
plt.savefig(os.path.join(output_dir, picture_file_name))
# -----------------------------------------得到2日行星波拟合参数-------------------------------------------
# 定义新的拟合函数
def fit_wind_speed_v1(data, window=5):
# 动态调整最小有效数据点数量:参数数量 * 6
num_params = 3 # 模型参数数量
# 转换为 pandas DataFrame 并设置时间为索引
data["date"] = pd.to_datetime(data["date"])
data.set_index("date", inplace=True)
# 获取唯一日期
unique_dates = np.unique(data.index.date)
# 存储拟合参数
params = []
for date in unique_dates:
start_date = pd.to_datetime(date) - pd.Timedelta(days=(window - 1) / 2)
end_date = pd.to_datetime(date) + pd.Timedelta(days=(window - 1) / 2)
# 获取这几天的数据
window_data = data[start_date:end_date]
t = np.arange(len(window_data)) + 1 # 时间点
y = window_data['wind_speed'].values # 风速数据
# 将 y 转换为浮点类型,以防有非数值类型数据
y = pd.to_numeric(y, errors='coerce') # 将非数值转换为 NaN
# 去掉包含nan的那一列筛选有效数据
valid_mask = ~np.isnan(y) # 创建布尔掩码,标记非 NaN 的位置
t = t[valid_mask]
y = y[valid_mask]
# 检查筛选后的数据是否足够
if len(y) < num_params * 6: # 确保数据足够
# print(f"Not enough valid data after filtering for date: {date}")
params.append([None] * 3) # 如果数据不足记录None
continue
# 设置初始参数
initial_guess = [0, 1, 0] # 新模型的初始猜测参数
bounds = (
[-np.inf, 0, -np.inf], # 下界
[np.inf, np.inf, np.inf], # 上界
)
# 拟合模型
try:
popt, _ = curve_fit(planetary_model_2day, t, y, p0=initial_guess, bounds=bounds)
params.append(popt) # 存储拟合参数
except RuntimeError:
params.append([None] * 3) # 如果拟合失败记录None
# 转换拟合参数为 DataFrame
params_df = pd.DataFrame(
params,
columns=["v0", "a", "b"],
index=unique_dates,
)
return params_df
# 1-拟合纬向风------------------------------------------
u_result_dict = {}
for height in heights:
print(f"Processing height: {height} m")
# 获取当前高度的纬向风数据
height_data = uwind_data[uwind_data["height"] == height]
# 转换为长格式(适配拟合函数)
long_data = pd.melt(
height_data,
id_vars=["time", "height"],
var_name="date",
value_name="wind_speed",
)
long_data["date"] = pd.to_datetime(long_data["date"], format="%Y%m%d")
# 调用修改后的拟合函数 fit_wind_speed_v2
params_df = fit_wind_speed_v1(long_data)
u_result_dict[height] = params_df
# 汇总结果为 DataFrame
u_final_result = pd.concat(u_result_dict, names=["height", "date"])
# 2-拟合经向风------------------------------------------
v_result_dict = {}
for height in heights:
print(f"Processing height: {height} m")
height_data = vwind_data[vwind_data["height"] == height]
long_data = pd.melt(
height_data,
id_vars=["time", "height"],
var_name="date",
value_name="wind_speed",
)
long_data["date"] = pd.to_datetime(long_data["date"], format="%Y%m%d")
# 调用修改后的拟合函数 fit_wind_speed_v2
params_df = fit_wind_speed_v1(long_data)
v_result_dict[height] = params_df
# 汇总结果为 DataFrame
v_final_result = pd.concat(v_result_dict, names=["height", "date"])
# 定义振幅参数
amplitude_param = 'a'
# 提取纬向风数据,重置索引为平坦结构
u_heatmap_data = u_final_result[amplitude_param].unstack(level=0) # 将高度作为列,日期作为行
u_heatmap_data.index = pd.to_datetime(u_heatmap_data.index) # 确保日期为时间格式
u_heatmap_data = u_heatmap_data.iloc[:, ::-1] # 按高度降序排列
# 提取经向风数据,重置索引为平坦结构
v_heatmap_data = v_final_result[amplitude_param].unstack(level=0) # 将高度作为列,日期作为行
v_heatmap_data.index = pd.to_datetime(v_heatmap_data.index) # 确保日期为时间格式
v_heatmap_data = v_heatmap_data.iloc[:, ::-1] # 按高度降序排列
# 创建画布和子图
fig, axes = plt.subplots(1, 2, figsize=(18, 8)) # 1行2列的布局
fig.suptitle('2022年2日_行星波振幅时空变化', fontsize=20) # 添加总标题
# 绘制第一幅热力图(纬向风)
sns.heatmap(
u_heatmap_data.T, # 转置,横轴为时间,纵轴为高度
cmap="viridis", # 配色方案
vmax=100, # 设置颜色范围的最大值
cbar_kws={'label': '振幅 (m/s)'}, # 设置颜色条标签
xticklabels=False, # 禁用默认的横轴刻度标签
#yticklabels=u_heatmap_data.columns.astype(str), # 显示纵轴的高度
yticklabels=[f'{height // 1000}' for height in u_heatmap_data.columns], # 转换高度为千米并格式化
ax=axes[0] # 指定子图
)
axes[0].set_title('纬向风振幅变化', fontsize=14)
axes[0].set_xlabel('日期', fontsize=12)
axes[0].set_ylabel('高度 (km)', fontsize=12)
# 绘制第二幅热力图(经向风)
sns.heatmap(
v_heatmap_data.T, # 转置,横轴为时间,纵轴为高度
cmap="viridis", # 配色方案
vmax=100, # 设置颜色范围的最大值
cbar_kws={'label': '振幅 (m/s)'}, # 设置颜色条标签
xticklabels=False, # 禁用默认的横轴刻度标签
#yticklabels=v_heatmap_data.columns.astype(str), # 显示纵轴的高度
yticklabels=[f'{height // 1000}' for height in v_heatmap_data.columns], # 转换高度为千米并格式化
ax=axes[1] # 指定子图
)
axes[1].set_title('经向风振幅变化', fontsize=14)
axes[1].set_xlabel('日期', fontsize=12)
axes[1].set_ylabel('高度 (km)', fontsize=12)
# 调整布局
plt.tight_layout(rect=[0, 0, 1, 0.95]) # 为总标题留出空间
plt.show()
# 输出路径和文件名
picture_file_name = f'{year}年_2日_行星波振幅时空变化.png'
plt.savefig(os.path.join(output_dir, picture_file_name))
# -----------------------------------------得到5日行星波拟合参数-------------------------------------------
# 定义新的拟合函数
def fit_wind_speed_v2(data, window=15):
# 动态调整最小有效数据点数量:参数数量 * 6
num_params = 3 # 模型参数数量
# 转换为 pandas DataFrame 并设置时间为索引
data["date"] = pd.to_datetime(data["date"])
data.set_index("date", inplace=True)
# 获取唯一日期
unique_dates = np.unique(data.index.date)
# 存储拟合参数
params = []
for date in unique_dates:
start_date = pd.to_datetime(date) - pd.Timedelta(days=(window - 1) / 2)
end_date = pd.to_datetime(date) + pd.Timedelta(days=(window - 1) / 2)
# 获取这几天的数据
window_data = data[start_date:end_date]
t = np.arange(len(window_data)) + 1 # 时间点
y = window_data['wind_speed'].values # 风速数据
# 将 y 转换为浮点类型,以防有非数值类型数据
y = pd.to_numeric(y, errors='coerce') # 将非数值转换为 NaN
# 去掉包含nan的那一列筛选有效数据
valid_mask = ~np.isnan(y) # 创建布尔掩码,标记非 NaN 的位置
t = t[valid_mask]
y = y[valid_mask]
# 检查筛选后的数据是否足够
if len(y) < num_params * 6: # 确保数据足够
# print(f"Not enough valid data after filtering for date: {date}")
params.append([None] * 3) # 如果数据不足记录None
continue
# 设置初始参数
initial_guess = [0, 1, 0] # 新模型的初始猜测参数
bounds = (
[-np.inf, 0, -np.inf], # 下界
[np.inf, np.inf, np.inf], # 上界
)
# 拟合模型
try:
popt, _ = curve_fit(planetary_model_5day, t, y, p0=initial_guess, bounds=bounds)
params.append(popt) # 存储拟合参数
except RuntimeError:
params.append([None] * 3) # 如果拟合失败记录None
# 转换拟合参数为 DataFrame
params_df = pd.DataFrame(
params,
columns=["v0", "a", "b"],
index=unique_dates,
)
return params_df
# 1-拟合纬向风------------------------------------------
u_result_dict = {}
for height in heights:
print(f"Processing height: {height} m")
# 获取当前高度的纬向风数据
height_data = uwind_data[uwind_data["height"] == height]
# 转换为长格式(适配拟合函数)
long_data = pd.melt(
height_data,
id_vars=["time", "height"],
var_name="date",
value_name="wind_speed",
)
long_data["date"] = pd.to_datetime(long_data["date"], format="%Y%m%d")
# 调用修改后的拟合函数 fit_wind_speed_v2
params_df = fit_wind_speed_v2(long_data)
u_result_dict[height] = params_df
# 汇总结果为 DataFrame
u_final_result = pd.concat(u_result_dict, names=["height", "date"])
'''
# 将结果保存为 TXT 文档
file_name = f'{year}年_纬向风_5日行星波振幅参数.txt'
u_final_result.to_csv(
os.path.join(output_dir, file_name),
sep='\t',
index=True,
float_format='%.2f', # 控制数值列保留 2 位小数
header=True)
'''
# 2-拟合经向风------------------------------------------
v_result_dict = {}
for height in heights:
print(f"Processing height: {height} m")
height_data = vwind_data[vwind_data["height"] == height]
long_data = pd.melt(
height_data,
id_vars=["time", "height"],
var_name="date",
value_name="wind_speed",
)
long_data["date"] = pd.to_datetime(long_data["date"], format="%Y%m%d")
# 调用修改后的拟合函数 fit_wind_speed_v2
params_df = fit_wind_speed_v2(long_data)
v_result_dict[height] = params_df
# 汇总结果为 DataFrame
v_final_result = pd.concat(v_result_dict, names=["height", "date"])
'''
# 保存参数
file_name = f'{year}年_经向风_5日行星波振幅参数.txt'
v_final_result.to_csv(
os.path.join(output_dir, file_name),
sep='\t',
index=True,
float_format='%.2f', # 控制数值列保留 2 位小数
header=True)
'''
# 定义振幅参数
amplitude_param = 'a'
# 提取纬向风数据,重置索引为平坦结构
u_heatmap_data = u_final_result[amplitude_param].unstack(level=0) # 将高度作为列,日期作为行
u_heatmap_data.index = pd.to_datetime(u_heatmap_data.index) # 确保日期为时间格式
u_heatmap_data = u_heatmap_data.iloc[:, ::-1] # 按高度降序排列
# 提取经向风数据,重置索引为平坦结构
v_heatmap_data = v_final_result[amplitude_param].unstack(level=0) # 将高度作为列,日期作为行
v_heatmap_data.index = pd.to_datetime(v_heatmap_data.index) # 确保日期为时间格式
v_heatmap_data = v_heatmap_data.iloc[:, ::-1] # 按高度降序排列
# 创建画布和子图
fig, axes = plt.subplots(1, 2, figsize=(18, 8)) # 1行2列的布局
fig.suptitle('2022年5日_行星波振幅时空变化', fontsize=20) # 添加总标题
# 绘制第一幅热力图(纬向风)
sns.heatmap(
u_heatmap_data.T, # 转置,横轴为时间,纵轴为高度
cmap="viridis", # 配色方案
vmax=100, # 设置颜色范围的最大值
cbar_kws={'label': '振幅 (m/s)'}, # 设置颜色条标签
xticklabels=False, # 禁用默认的横轴刻度标签
#yticklabels=u_heatmap_data.columns.astype(str), # 显示纵轴的高度
yticklabels=[f'{height // 1000}' for height in u_heatmap_data.columns], # 转换高度为千米并格式化
ax=axes[0] # 指定子图
)
axes[0].set_title('纬向风振幅变化', fontsize=14)
axes[0].set_xlabel('日期', fontsize=12)
axes[0].set_ylabel('高度 (km)', fontsize=12)
# 绘制第二幅热力图(经向风)
sns.heatmap(
v_heatmap_data.T, # 转置,横轴为时间,纵轴为高度
cmap="viridis", # 配色方案
vmax=100, # 设置颜色范围的最大值
cbar_kws={'label': '振幅 (m/s)'}, # 设置颜色条标签
xticklabels=False, # 禁用默认的横轴刻度标签
#yticklabels=v_heatmap_data.columns.astype(str), # 显示纵轴的高度
yticklabels=[f'{height // 1000}' for height in v_heatmap_data.columns], # 转换高度为千米并格式化
ax=axes[1] # 指定子图
)
axes[1].set_title('经向风振幅变化', fontsize=14)
axes[1].set_xlabel('日期', fontsize=12)
axes[1].set_ylabel('高度 (km)', fontsize=12)
# 调整布局
plt.tight_layout(rect=[0, 0, 1, 0.95]) # 为总标题留出空间
plt.show()
# 输出路径和文件名
picture_file_name = f'{year}年_5日_行星波振幅时空变化.png'
plt.savefig(os.path.join(output_dir, picture_file_name))
# -----------------------------------------得到10日行星波拟合参数-------------------------------------------
# 定义新的拟合函数
def fit_wind_speed_v3(data, window=29):
# 动态调整最小有效数据点数量:参数数量 * 6
num_params = 3 # 模型参数数量
# 转换为 pandas DataFrame 并设置时间为索引
data["date"] = pd.to_datetime(data["date"])
data.set_index("date", inplace=True)
# 获取唯一日期
unique_dates = np.unique(data.index.date)
# 存储拟合参数
params = []
for date in unique_dates:
start_date = pd.to_datetime(date) - pd.Timedelta(days=(window - 1) / 2)
end_date = pd.to_datetime(date) + pd.Timedelta(days=(window - 1) / 2)
# 获取这几天的数据
window_data = data[start_date:end_date]
t = np.arange(len(window_data)) + 1 # 时间点
y = window_data['wind_speed'].values # 风速数据
# 将 y 转换为浮点类型,以防有非数值类型数据
y = pd.to_numeric(y, errors='coerce') # 将非数值转换为 NaN
# 去掉包含nan的那一列筛选有效数据
valid_mask = ~np.isnan(y) # 创建布尔掩码,标记非 NaN 的位置
t = t[valid_mask]
y = y[valid_mask]
# 检查筛选后的数据是否足够
if len(y) < num_params * 6: # 确保数据足够
# print(f"Not enough valid data after filtering for date: {date}")
params.append([None] * 3) # 如果数据不足记录None
continue
# 设置初始参数
initial_guess = [0, 1, 0] # 新模型的初始猜测参数
bounds = (
[-np.inf, 0, -np.inf], # 下界
[np.inf, np.inf, np.inf], # 上界
)
# 拟合模型
try:
popt, _ = curve_fit(planetary_model_10day, t, y, p0=initial_guess, bounds=bounds)
params.append(popt) # 存储拟合参数
except RuntimeError:
params.append([None] * 3) # 如果拟合失败记录None
# 转换拟合参数为 DataFrame
params_df = pd.DataFrame(
params,
columns=["v0", "a", "b"],
index=unique_dates,
)
return params_df
# 1-拟合纬向风------------------------------------------
u_result_dict = {}
for height in heights:
print(f"Processing height: {height} m")
# 获取当前高度的纬向风数据
height_data = uwind_data[uwind_data["height"] == height]
# 转换为长格式(适配拟合函数)
long_data = pd.melt(
height_data,
id_vars=["time", "height"],
var_name="date",
value_name="wind_speed",
)
long_data["date"] = pd.to_datetime(long_data["date"], format="%Y%m%d")
# 调用修改后的拟合函数 fit_wind_speed_v3
params_df = fit_wind_speed_v3(long_data)
u_result_dict[height] = params_df
# 汇总结果为 DataFrame
u_final_result = pd.concat(u_result_dict, names=["height", "date"])
# 2-拟合经向风------------------------------------------
v_result_dict = {}
for height in heights:
print(f"Processing height: {height} m")
height_data = vwind_data[vwind_data["height"] == height]
long_data = pd.melt(
height_data,
id_vars=["time", "height"],
var_name="date",
value_name="wind_speed",
)
long_data["date"] = pd.to_datetime(long_data["date"], format="%Y%m%d")
# 调用修改后的拟合函数 fit_wind_speed_v3
params_df = fit_wind_speed_v3(long_data)
v_result_dict[height] = params_df
# 汇总结果为 DataFrame
v_final_result = pd.concat(v_result_dict, names=["height", "date"])
# 定义振幅参数
amplitude_param = 'a'
# 提取纬向风数据,重置索引为平坦结构
u_heatmap_data = u_final_result[amplitude_param].unstack(level=0) # 将高度作为列,日期作为行
u_heatmap_data.index = pd.to_datetime(u_heatmap_data.index) # 确保日期为时间格式
u_heatmap_data = u_heatmap_data.iloc[:, ::-1] # 按高度降序排列
# 提取经向风数据,重置索引为平坦结构
v_heatmap_data = v_final_result[amplitude_param].unstack(level=0) # 将高度作为列,日期作为行
v_heatmap_data.index = pd.to_datetime(v_heatmap_data.index) # 确保日期为时间格式
v_heatmap_data = v_heatmap_data.iloc[:, ::-1] # 按高度降序排列
# 创建画布和子图
fig, axes = plt.subplots(1, 2, figsize=(18, 8)) # 1行2列的布局
fig.suptitle('2022年10日_行星波振幅时空变化', fontsize=20) # 添加总标题
# 绘制第一幅热力图(纬向风)
sns.heatmap(
u_heatmap_data.T, # 转置,横轴为时间,纵轴为高度
cmap="viridis", # 配色方案
vmax=100, # 设置颜色范围的最大值
cbar_kws={'label': '振幅 (m/s)'}, # 设置颜色条标签
xticklabels=False, # 禁用默认的横轴刻度标签
#yticklabels=u_heatmap_data.columns.astype(str), # 显示纵轴的高度
yticklabels=[f'{height // 1000}' for height in u_heatmap_data.columns], # 转换高度为千米并格式化
ax=axes[0] # 指定子图
)
axes[0].set_title('纬向风振幅变化', fontsize=14)
axes[0].set_xlabel('日期', fontsize=12)
axes[0].set_ylabel('高度 (m)', fontsize=12)
# 绘制第二幅热力图(经向风)
sns.heatmap(
v_heatmap_data.T, # 转置,横轴为时间,纵轴为高度
cmap="viridis", # 配色方案
vmax=100, # 设置颜色范围的最大值
cbar_kws={'label': '振幅 (m/s)'}, # 设置颜色条标签
xticklabels=False, # 禁用默认的横轴刻度标签
#yticklabels=v_heatmap_data.columns.astype(str), # 显示纵轴的高度
yticklabels=[f'{height // 1000}' for height in u_heatmap_data.columns], # 转换高度为千米并格式化
ax=axes[1] # 指定子图
)
axes[1].set_title('经向风振幅变化', fontsize=14)
axes[1].set_xlabel('日期', fontsize=12)
axes[1].set_ylabel('高度 (m)', fontsize=12)
# 调整布局
plt.tight_layout(rect=[0, 0, 1, 0.95]) # 为总标题留出空间
# 显示或保存图像
plt.show()
# 输出路径和文件名
picture_file_name = f'{year}年_10日_行星波振幅时空变化.png'
plt.savefig(os.path.join(output_dir, picture_file_name))
# -----------------------------------------得到16日行星波拟合参数-------------------------------------------
# 定义新的拟合函数
def fit_wind_speed_v4(data, window=49):
# 动态调整最小有效数据点数量:参数数量 * 6
num_params = 3 # 模型参数数量
# 转换为 pandas DataFrame 并设置时间为索引
data["date"] = pd.to_datetime(data["date"])
data.set_index("date", inplace=True)
# 获取唯一日期
unique_dates = np.unique(data.index.date)
# 存储拟合参数
params = []
for date in unique_dates:
start_date = pd.to_datetime(date) - pd.Timedelta(days=(window - 1) / 2)
end_date = pd.to_datetime(date) + pd.Timedelta(days=(window - 1) / 2)
# 获取这几天的数据
window_data = data[start_date:end_date]
t = np.arange(len(window_data)) + 1 # 时间点
y = window_data['wind_speed'].values # 风速数据
# 将 y 转换为浮点类型,以防有非数值类型数据
y = pd.to_numeric(y, errors='coerce') # 将非数值转换为 NaN
# 去掉包含nan的那一列筛选有效数据
valid_mask = ~np.isnan(y) # 创建布尔掩码,标记非 NaN 的位置
t = t[valid_mask]
y = y[valid_mask]
# 检查筛选后的数据是否足够
if len(y) < num_params * 6: # 确保数据足够
# print(f"Not enough valid data after filtering for date: {date}")
params.append([None] * 3) # 如果数据不足记录None
continue
# 设置初始参数
initial_guess = [0, 1, 0] # 新模型的初始猜测参数
bounds = (
[-np.inf, 0, -np.inf], # 下界
[np.inf, np.inf, np.inf], # 上界
)
# 拟合模型
try:
popt, _ = curve_fit(planetary_model_16day, t, y, p0=initial_guess, bounds=bounds)
params.append(popt) # 存储拟合参数
except RuntimeError:
params.append([None] * 3) # 如果拟合失败记录None
# 转换拟合参数为 DataFrame
params_df = pd.DataFrame(
params,
columns=["v0", "a", "b"],
index=unique_dates,
)
return params_df
# 1-拟合纬向风------------------------------------------
u_result_dict = {}
for height in heights:
print(f"Processing height: {height} m")
# 获取当前高度的纬向风数据
height_data = uwind_data[uwind_data["height"] == height]
# 转换为长格式(适配拟合函数)
long_data = pd.melt(
height_data,
id_vars=["time", "height"],
var_name="date",
value_name="wind_speed",
)
long_data["date"] = pd.to_datetime(long_data["date"], format="%Y%m%d")
# 调用修改后的拟合函数 fit_wind_speed_v4
params_df = fit_wind_speed_v4(long_data)
u_result_dict[height] = params_df
# 汇总结果为 DataFrame
u_final_result = pd.concat(u_result_dict, names=["height", "date"])
# 2-拟合经向风------------------------------------------
v_result_dict = {}
for height in heights:
print(f"Processing height: {height} m")
height_data = vwind_data[vwind_data["height"] == height]
long_data = pd.melt(
height_data,
id_vars=["time", "height"],
var_name="date",
value_name="wind_speed",
)
long_data["date"] = pd.to_datetime(long_data["date"], format="%Y%m%d")
# 调用修改后的拟合函数 fit_wind_speed_v4
params_df = fit_wind_speed_v4(long_data)
v_result_dict[height] = params_df
# 汇总结果为 DataFrame
v_final_result = pd.concat(v_result_dict, names=["height", "date"])
# 定义振幅参数
amplitude_param = 'a'
# 提取纬向风数据,重置索引为平坦结构
u_heatmap_data = u_final_result[amplitude_param].unstack(level=0) # 将高度作为列,日期作为行
u_heatmap_data.index = pd.to_datetime(u_heatmap_data.index) # 确保日期为时间格式
u_heatmap_data = u_heatmap_data.iloc[:, ::-1] # 按高度降序排列
# 提取经向风数据,重置索引为平坦结构
v_heatmap_data = v_final_result[amplitude_param].unstack(level=0) # 将高度作为列,日期作为行
v_heatmap_data.index = pd.to_datetime(v_heatmap_data.index) # 确保日期为时间格式
v_heatmap_data = v_heatmap_data.iloc[:, ::-1] # 按高度降序排列
# 创建画布和子图
fig, axes = plt.subplots(1, 2, figsize=(18, 8)) # 1行2列的布局
fig.suptitle('2022年16日_行星波振幅时空变化', fontsize=20) # 添加总标题
# 绘制第一幅热力图(纬向风)
sns.heatmap(
u_heatmap_data.T, # 转置,横轴为时间,纵轴为高度
cmap="viridis", # 配色方案
vmax=100, # 设置颜色范围的最大值
cbar_kws={'label': '振幅 (m/s)'}, # 设置颜色条标签
xticklabels=False, # 禁用默认的横轴刻度标签
#yticklabels=u_heatmap_data.columns.astype(str), # 显示纵轴的高度
yticklabels=[f'{height // 1000}' for height in u_heatmap_data.columns], # 转换高度为千米并格式化
ax=axes[0] # 指定子图
)
axes[0].set_title('纬向风振幅变化', fontsize=14)
axes[0].set_xlabel('日期', fontsize=12)
axes[0].set_ylabel('高度 (m)', fontsize=12)
# 绘制第二幅热力图(经向风)
sns.heatmap(
v_heatmap_data.T, # 转置,横轴为时间,纵轴为高度
cmap="viridis", # 配色方案
vmax=100, # 设置颜色范围的最大值
cbar_kws={'label': '振幅 (m/s)'}, # 设置颜色条标签
xticklabels=False, # 禁用默认的横轴刻度标签
#yticklabels=v_heatmap_data.columns.astype(str), # 显示纵轴的高度
yticklabels=[f'{height // 1000}' for height in v_heatmap_data.columns], # 转换高度为千米并格式化
ax=axes[1] # 指定子图
)
axes[1].set_title('经向风振幅变化', fontsize=14)
axes[1].set_xlabel('日期', fontsize=12)
axes[1].set_ylabel('高度 (m)', fontsize=12)
# 调整布局
plt.tight_layout(rect=[0, 0, 1, 0.95]) # 为总标题留出空间
# 显示或保存图像
plt.show()
# 输出路径和文件名
picture_file_name = f'{year}年_16日_行星波振幅时空变化.png'
plt.savefig(os.path.join(output_dir, picture_file_name))

View File

@ -1,9 +1,19 @@
from io import BytesIO # 此代码可以绘制某年、某高度的纬向风/经向风的,潮汐波和行星波的
# 年振幅图、月振幅图、日拟合正弦波图
# 基本思想是1-先处理某高度全年观测数据得到final_df
# 2-再选择风的类型,得到此高度全年每一天的纬向风/经向风大气长波拟合参数函数process_wind_data_with_models
# 3-下一步就是根据参数绘制图形,可选择年振幅图、月振幅图、日拟合正弦波图(#todo:上一步风的类型已确定)
# plot_yearly_params、plot_month_params、plot_sine_wave_for_day
import glob
import pandas as pd import pandas as pd
import numpy as np import numpy as np
import os import os
from scipy.optimize import curve_fit from scipy.optimize import curve_fit
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from datetime import datetime
# 解决绘图中中文不能显示的问题 # 解决绘图中中文不能显示的问题
import matplotlib import matplotlib
@ -12,8 +22,6 @@ matplotlib.rcParams['font.sans-serif'] = ['SimHei'] # 显示中文
matplotlib.rcParams['axes.unicode_minus'] = False # 正常显示负号 matplotlib.rcParams['axes.unicode_minus'] = False # 正常显示负号
# 定义所有模型 # 定义所有模型
def tidal_model(t, v0, a1, b1, a2, b2, a3, b3, a4, b4): def tidal_model(t, v0, a1, b1, a2, b2, a3, b3, a4, b4):
"""潮汐波模型""" """潮汐波模型"""
T1, T2, T3, T4 = 6, 8, 12, 24 # 周期以小时为单位 T1, T2, T3, T4 = 6, 8, 12, 24 # 周期以小时为单位
@ -22,31 +30,26 @@ def tidal_model(t, v0, a1, b1, a2, b2, a3, b3, a4, b4):
+ a3 * np.sin((2 * np.pi / T3) * t + b3) + a3 * np.sin((2 * np.pi / T3) * t + b3)
+ a4 * np.sin((2 * np.pi / T4) * t + b4)) + a4 * np.sin((2 * np.pi / T4) * t + b4))
def planetary_model_2day(t, v0, a, b): def planetary_model_2day(t, v0, a, b):
"""2日行星波模型""" """2日行星波模型"""
T = 48 # 周期为2天48小时 T = 48 # 周期为2天48小时
return v0 + a * np.sin((2 * np.pi / T) * t + b) return v0 + a * np.sin((2 * np.pi / T) * t + b)
def planetary_model_5day(t, v0, a, b): def planetary_model_5day(t, v0, a, b):
"""5日行星波模型""" """5日行星波模型"""
T = 120 # 周期为5天120小时 T = 120 # 周期为5天120小时
return v0 + a * np.sin((2 * np.pi / T) * t + b) return v0 + a * np.sin((2 * np.pi / T) * t + b)
def planetary_model_10day(t, v0, a, b): def planetary_model_10day(t, v0, a, b):
"""10日行星波模型""" """10日行星波模型"""
T = 240 # 周期为10天240小时 T = 240 # 周期为10天240小时
return v0 + a * np.sin((2 * np.pi / T) * t + b) return v0 + a * np.sin((2 * np.pi / T) * t + b)
def planetary_model_16day(t, v0, a, b): def planetary_model_16day(t, v0, a, b):
"""16日行星波模型""" """16日行星波模型"""
T = 384 # 周期为16天384小时 T = 384 # 周期为16天384小时
return v0 + a * np.sin((2 * np.pi / T) * t + b) return v0 + a * np.sin((2 * np.pi / T) * t + b)
# 统一管理模型及其参数 # 统一管理模型及其参数
MODELS = { MODELS = {
'潮汐波': { '潮汐波': {
@ -87,23 +90,298 @@ MODELS = {
}, },
} }
ALL_MODEL_NAMES = list(MODELS.keys()) ################################ 选定风的类型,得到整年的每一天拟合参数
def process_wind_data_with_models(final_df, wind_type, year, H, selected_models=None):
"""
处理风速数据并针对多种模型进行拟合和绘图
# ----------------------------------------------------------第一大部分———————————————————————————————————————————————————— :param selected_models: 可选指定使用的模型类型列表如果为 None则处理所有模型
# # 根据纬向风(u)、经向风(v),得到武汉左岭镇站/黑龙江漠河站 :return: 整年所有模型的拟合参数字典
# 固定年份2022年固定高度90km大气波动6、8、12、24小时潮汐波2日、5日、10日、16日行星波随时间的变化 """
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 selected_models is None:
models_to_process = MODELS.keys() # 默认处理所有模型
else:
models_to_process = selected_models # 使用指定的模型列表
all_params = {} # 存储所有模型的参数数据框
for model_name in models_to_process:
if model_name not in MODELS:
print(f"Model {model_name} is not valid.")
continue
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) * 2: # todo认为如果数据点个数少于参数的2倍则认为数据不够拟合之前是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)
all_params[model_name] = params_df # 将每个模型的参数数据框存入字典
return all_params # 返回包含所有模型参数的数据字典
# 外部绘制全年图形
def plot_yearly_params(params_dict, model_name,year, wind_type, H):
"""
绘制全年的参数图形
:param params_dict: 存储所有模型的参数数据框字典
:param model_name: 模型名称
"""
params_df = params_dict[model_name]
# 绘图
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()
# 绘制月振幅图
def plot_month_params(params_dict, model_name, wind_type, year, month, H):
"""
绘制指定月份的参数图形
:param params_dict: 存储所有模型的参数数据框字典
:param model_name: 模型名称
:param wind_type: 风速类型
:param year: 需要绘制的年份
:param month: 需要绘制的月份
:param output_dir: 输出目录
:param H: 水平尺度
"""
# 获取该年份的数据框
params_df = params_dict[model_name]
# 确保索引是 datetime 类型
if not pd.api.types.is_datetime64_any_dtype(params_df.index):
# print("Index is not datetime. Converting to datetime...")
params_df.index = pd.to_datetime(params_df.index)
# 筛选出指定年份和月份的数据
params_df_month = params_df[(params_df.index.year == year) & (params_df.index.month == month)]
# 检查筛选后的数据是否为空
if params_df_month.empty:
print(f"No data available for {year}-{month}.")
return
# 绘图
plt.figure(figsize=(12, 6))
# 判断是潮汐波还是行星波
if model_name == '潮汐波': # 处理潮汐波多分量
for i, T in enumerate([6, 8, 12, 24], start=1):
col_name = f'a{i}'
if col_name in params_df_month.columns:
plt.plot(params_df_month[col_name], label=f'{T}h', linewidth=2)
else:
print(f"Warning: Column '{col_name}' not found in the dataframe.")
else: # 处理行星波单分量
if 'a' in params_df_month.columns:
plt.plot(params_df_month['a'], label=f'{model_name}', color='orange', linewidth=2)
else:
print(f"Warning: Column 'a' not found in the dataframe.")
# 设置标题和标签
plt.title(f'{year}{month}{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()
# 绘制日拟合正弦波图
def plot_sine_wave_for_day(params_df, date, wind_type, H, model_name):
"""
根据指定日期的参数绘制标准的正弦曲线图
:param params_df: 存储所有模型的参数数据框
:param date: 指定日期例如 '2024-03-17'
:param wind_type: 风速类型例如 'uwind'
:param H: 水平尺度
:param model_name: 模型名称例如 '5日行星波', '10日行星波', '潮汐波'
"""
# 将字符串日期转换为 datetime 格式
try:
date = datetime.strptime(date, '%Y-%m-%d')
except ValueError:
print(f"Error: Invalid date format '{date}', expected 'YYYY-MM-DD'.")
return
# 检查 params_df.index 是否为 datetime 类型,若不是,则将其转换
if not isinstance(params_df.index, pd.DatetimeIndex):
print("Warning: params_df index is not of type datetime. Converting index to datetime...")
params_df.index = pd.to_datetime(params_df.index)
# 获取指定日期的参数
if date not in params_df.index:
print(f"Warning: {date.strftime('%Y-%m-%d')} not found in the parameter dataframe.")
return
params = params_df.loc[date]
# 检查参数是否为 NaN 或 Inf
if params.isnull().any() or np.isinf(params).any():
print(f"Warning: Parameters for {date.strftime('%Y-%m-%d')} contain NaN or Inf values. Skipping the plot.")
return
# 判断是否是潮汐波模型
if 'a1' in params: # 如果是潮汐波模型
T1, T2, T3, T4 = 6, 8, 12, 24 # 潮汐波的周期
# 生成时间数据
t = np.linspace(0, 48, 1000) # 生成0到48小时的1000个点
# 从参数中提取值
v0 = params['v0']
a1 = params['a1']
b1 = params['b1']
a2 = params['a2']
b2 = params['b2']
a3 = params['a3']
b3 = params['b3']
a4 = params['a4']
b4 = params['b4']
# 计算正弦波的值
y1 = v0 * np.ones_like(t) # 常量项
y2 = a1 * np.sin((2 * np.pi / T1) * t + b1)
y3 = a2 * np.sin((2 * np.pi / T2) * t + b2)
y4 = a3 * np.sin((2 * np.pi / T3) * t + b3)
y5 = a4 * np.sin((2 * np.pi / T4) * t + b4)
# 绘制图形
plt.figure(figsize=(12, 8))
plt.plot(t, y1, label='常量', color='purple')
plt.plot(t, y2, label='6h', color='blue')
plt.plot(t, y3, label='8h', color='orange')
plt.plot(t, y4, label='12h', color='green')
plt.plot(t, y5, label='24h', color='red')
# 添加图例和标签
plt.title(f'{date.strftime("%Y-%m-%d")} {wind_type}风速拟合潮汐波', fontsize=16)
plt.xlabel('时间 (小时)', fontsize=14)
plt.ylabel('幅度 (m/s)', fontsize=14)
plt.legend()
plt.grid(True)
plt.xlim(0, 48) # 0到48小时
plt.ylim(min(y1.min(), y2.min(), y3.min(), y4.min(), y5.min()-1),
max(y1.max(), y2.max(), y3.max(), y4.max(), y5.max())+1)
plt.tight_layout()
else: # 处理行星波模型
# 根据传入的模型名称选择周期
if model_name == '2日行星波':
T = 48 # 2日行星波的周期为48小时
elif model_name == '5日行星波':
T = 120 # 5日行星波的周期为120小时
elif model_name == '10日行星波':
T = 240 # 10日行星波的周期为240小时
elif model_name == '16日行星波':
T = 384 # 16日行星波的周期为384小时
else:
print(f"Error: Unsupported planetary wave model '{model_name}'.")
return
# 生成时间数据
t = np.linspace(0, T*2, 1000) # 根据周期生成对应的时间范围
# 从参数中提取值
v0 = params['v0']
a = params['a']
b = params['b']
# 计算正弦波的值
y1 = v0 * np.ones_like(t) # 常量项
y2 = a * np.sin((2 * np.pi / T) * t + b)
# 绘制图形
plt.figure(figsize=(12, 8))
plt.plot(t, y1, label='常量', color='purple')
plt.plot(t, y2, label=f'{model_name} ({T}小时)', color='orange')
# 添加图例和标签
plt.title(f'{date.strftime("%Y-%m-%d")} {wind_type}风速拟合 {model_name}', fontsize=16)
plt.xlabel('时间 (小时)', fontsize=14)
plt.ylabel('幅度 (m/s)', fontsize=14)
plt.legend()
plt.grid(True)
plt.xlim(0, T*2) # 根据周期调整时间范围
plt.ylim(min(y1.min(), y2.min()-1), max(y1.max(), y2.max())+1)
plt.tight_layout()
def get_final_df(H, year, station):
# 下边处理,绘图
# 参数设置,选择高度、时间、站点 # 参数设置,选择高度、时间、站点
# station = '黑龙江漠河站' # # station = '黑龙江漠河站'
def get_df(H, year, station):
# 文件路径设置 # 文件路径设置
file_dir = rf'./radar/data/{station}\{year}' # 数据文件路径 file_dir = rf'./radar/data/{station}/{year}' # 数据文件路径
output_dir = rf'./radar/out/{station}\{year}' # 参数txt、图片输出路径 output_dir = rf'./out\{station}\{year}' # 参数txt、图片输出路径
os.makedirs(output_dir, exist_ok=True) # 确保输出路径存在 os.makedirs(output_dir, exist_ok=True) # 确保输出路径存在
# 1-提取整个文件夹的数据 # 1------------------------提取整个文件夹的数据
# 获取所有 txt 文件 # 获取所有 txt 文件
file_list = [f for f in os.listdir(file_dir) if f.endswith('.txt')] file_list = [f for f in os.listdir(file_dir) if f.endswith('.txt')]
@ -113,7 +391,7 @@ def get_df(H, year, station):
# 批量处理每个 txt 文件 # 批量处理每个 txt 文件
for file_name in file_list: for file_name in file_list:
file_path = os.path.join(file_dir, file_name) # 拼接文件路径 file_path = os.path.join(file_dir, file_name) # 拼接文件路径
df = pd.read_csv(file_path, sep='\s+') # 读取文件 df = pd.read_csv(file_path, delim_whitespace=True) # 读取文件
date_part = os.path.splitext(file_name)[0][-8:] # 提取日期部分 date_part = os.path.splitext(file_name)[0][-8:] # 提取日期部分
# 处理缺失值,将 1000000 替换为 NaN # 处理缺失值,将 1000000 替换为 NaN
@ -130,22 +408,18 @@ def get_df(H, year, station):
}) })
# 只保留需要的列 # 只保留需要的列
filtered_df = filtered_df[['height', 'time', filtered_df = filtered_df[['height', 'time', f'{date_part}_uwind', f'{date_part}_vwind']]
f'{date_part}_uwind', f'{date_part}_vwind']]
# 如果 final_df 为空,则直接赋值 # 如果 final_df 为空,则直接赋值
if final_df.empty: if final_df.empty:
final_df = filtered_df final_df = filtered_df
else: else:
# 按 'height' 和 'time' 列对齐合并 # 按 'height' 和 'time' 列对齐合并
final_df = pd.merge(final_df, filtered_df, on=[ final_df = pd.merge(final_df, filtered_df, on=['height', 'time'], how='outer')
'height', 'time'], how='outer')
# 生成完整日期范围的列名补全缺失的天使得平年365闰年366 # 生成完整日期范围的列名补全缺失的天使得平年365闰年366
all_dates = pd.date_range( all_dates = pd.date_range(f'{year}-01-01', f'{year}-12-31').strftime('%Y%m%d')
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]
expected_columns = [f'{date}_uwind' for date in all_dates] + \
[f'{date}_vwind' for date in all_dates]
# 确保 final_df 包含所有日期的列,缺失列补为 NaN # 确保 final_df 包含所有日期的列,缺失列补为 NaN
for col in expected_columns: for col in expected_columns:
@ -155,108 +429,59 @@ def get_df(H, year, station):
# 按列名排序(确保日期顺序) # 按列名排序(确保日期顺序)
final_df = final_df[['height', 'time'] + sorted(expected_columns)] final_df = final_df[['height', 'time'] + sorted(expected_columns)]
return final_df return final_df
# 此时final_df已经是90km包含完整天的分析数据--------------------------------------------------------- # 此时final_df已经是90km包含完整天的观测数据---------------------------------------------------------
# 2---------------------------整年纬向风/经向风拟合参数
# params_dict = process_wind_data_with_models(final_df,'uwind', output_dir, year, H,selected_models=['潮汐波','2日行星波'])
# 通用函数:处理风速数据 # params_dict = process_wind_data_with_models(final_df,
def final_plot_v1(wind_type, year, H, model_name, station): # 'uwind', output_dir, year, H,
# selected_models=None)
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
# 处理纬向风和经向风,适配所有模型 class final_render_v2():
if __name__ == "__main__": def __init__(self, H, year, station, wind_type):
pass self.H , self.year, self.station, self.wind_type = H, year, station, wind_type
# final_plot_v1('uwind', year, H, "") final_df = get_final_df(H, year, station)
# final_plot_v1('vwind', year, H, "") self.params_dict = process_wind_data_with_models(final_df, wind_type, year, H, selected_models=None)
def render_day(self, date, model_name):
plot_sine_wave_for_day(self.params_dict[model_name], date, self.wind_type, self.H, model_name)
def render_month(self, month, model_name):
plot_month_params(self.params_dict, model_name, self.wind_type, self.year, month, self.H)
def render_year(self, model_name):
plot_yearly_params(self.params_dict, model_name, self.year, self.wind_type, self.H)
@staticmethod
def get_all_models():
return list(MODELS.keys())
@staticmethod
def get_all_pathes():
result = glob.glob(f"./radar/data/**/*.txt", recursive=True)
# normalize path
result = [os.path.normpath(path).replace("\\", "/") for path in result]
return result
# todo:此时得到的参数,年份、高度、风的类型已经固定
# 3---------------------------绘图
# 使用示例:绘制全年图形
if __name__ == '__main__':
# H = 94000 # 高度为 90km
# year = 2022
import matplotlib.font_manager as fm
fm.fontManager.addfont("./SimHei.ttf")
# station = '武汉左岭镇站'
renderer = final_render_v2(94000, 2022, '武汉左岭镇站', 'uwind')
renderer.render_year('潮汐波')
plt.show()
renderer.render_month(3, '潮汐波')
plt.show()
renderer.render_day('2022-03-17', '潮汐波')
plt.show()

View File

@ -1,5 +1,7 @@
import glob
from io import BytesIO from io import BytesIO
import os import os
import re
import pandas as pd import pandas as pd
import numpy as np import numpy as np
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
@ -30,13 +32,27 @@ def planetary_model(t, v0, a, b, period):
# ------------------------------ 数据处理工具函数 ------------------------------ # ------------------------------ 数据处理工具函数 ------------------------------
def preprocess_data(file_dir, year): def preprocess_data(file_list, year, month_range=(1,12)):
def filter_given_month(month_range, file_list):
begin_month, end_month = month_range
get_date_pattern = r'_(\d{8})\.txt'
filtered_file_list = []
for file in file_list:
date = re.search(get_date_pattern, file).group(1)
month = int(date[4:6])
if month >= begin_month and month <= end_month:
filtered_file_list.append(file)
return filtered_file_list
if file_list == []:
raise ValueError("No data files found.")
file_list = filter_given_month(month_range, file_list)
"""读取数据文件并合并成完整的 DataFrame""" """读取数据文件并合并成完整的 DataFrame"""
file_list = [f for f in os.listdir(file_dir) if f.endswith('.txt')] # file_list = [f for f in os.listdir(file_dir) if f.endswith('.txt')]
final_df = pd.DataFrame() final_df = pd.DataFrame()
for file_name in file_list: for file_path in file_list:
file_path = os.path.join(file_dir, file_name) file_name = file_path.split('/')[-1]
df = pd.read_csv(file_path, sep='\s+') df = pd.read_csv(file_path, sep='\s+')
# 替换异常值为 NaN # 替换异常值为 NaN
@ -57,8 +73,12 @@ def preprocess_data(file_dir, year):
final_df, df, on=['height', 'time'], how='outer') final_df, df, on=['height', 'time'], how='outer')
# 补全缺失列并排序 # 补全缺失列并排序
begin_month, end_month = month_range
# calculate begin and end date
begin_date = f'{year}-{begin_month:02d}-01'
end_date = f'{year}-{end_month+1:02d}-01' if end_month < 12 else f'{year}-12-31'
all_dates = pd.date_range( all_dates = pd.date_range(
f'{year}-01-01', f'{year}-12-31').strftime('%Y%m%d') begin_date, end_date).strftime('%Y%m%d')
expected_columns = [f'{date}_uwind' for date in all_dates] + \ expected_columns = [f'{date}_uwind' for date in all_dates] + \
[f'{date}_vwind' for date in all_dates] [f'{date}_vwind' for date in all_dates]
for col in expected_columns: for col in expected_columns:
@ -94,7 +114,7 @@ def fit_wind_speed(data, model_func, initial_guess, bounds, window, num_params):
t = t[valid_mask] t = t[valid_mask]
y = y[valid_mask] y = y[valid_mask]
if len(y) < num_params * 6: if len(y) < num_params * 2:
params.append([None] * len(initial_guess)) params.append([None] * len(initial_guess))
continue continue
@ -121,7 +141,7 @@ def plot_heatmaps(result_dict, param, title, vmax):
heatmap_data = data[param].unstack(level=0) heatmap_data = data[param].unstack(level=0)
heatmap_data.index = pd.to_datetime(heatmap_data.index) heatmap_data.index = pd.to_datetime(heatmap_data.index)
heatmap_data = heatmap_data.iloc[:, ::-1] heatmap_data = heatmap_data.iloc[:, ::-1]
heatmap_data.index = heatmap_data.index.strftime('%Y-%m-%d')
sns.heatmap( sns.heatmap(
heatmap_data.T, heatmap_data.T,
cmap="viridis", cmap="viridis",
@ -140,7 +160,7 @@ def plot_heatmaps(result_dict, param, title, vmax):
# ------------------------------ 主逻辑 ------------------------------ # ------------------------------ 主逻辑 ------------------------------
def final_plot_v2(year, station, model_name): def final_plot_v2(year, station, model_name, month_range=(1,12)):
""" """
主逻辑函数用于处理数据并绘制选定模型的热力图 主逻辑函数用于处理数据并绘制选定模型的热力图
@ -150,12 +170,13 @@ def final_plot_v2(year, station, model_name):
- model_name: str模型名称 '潮汐波', '2日行星波', '5日行星波', '10日行星波', '16日行星波' - model_name: str模型名称 '潮汐波', '2日行星波', '5日行星波', '10日行星波', '16日行星波'
""" """
# 配置文件路径 # 配置文件路径
file_dir = rf'./radar/data\{station}\{year}' file_dir = rf'./radar/data/{station}/{year}'
output_dir = rf'./radar/tmp\{station}\{year}\时空' output_dir = rf'./radar/tmp\{station}\{year}\时空'
os.makedirs(output_dir, exist_ok=True) os.makedirs(output_dir, exist_ok=True)
# 数据预处理 # 数据预处理
final_df = preprocess_data(file_dir, year) file_list = glob.glob(file_dir + '/**/*.txt', recursive=True)
final_df = preprocess_data(file_list, year, month_range)
uwind_data = final_df[["height", "time"] + uwind_data = final_df[["height", "time"] +
[col for col in final_df.columns if "_uwind" in col]] [col for col in final_df.columns if "_uwind" in col]]
vwind_data = final_df[["height", "time"] + vwind_data = final_df[["height", "time"] +
@ -202,7 +223,7 @@ def final_plot_v2(year, station, model_name):
plot_heatmaps( plot_heatmaps(
{"纬向风": u_final_result, "经向风": v_final_result}, {"纬向风": u_final_result, "经向风": v_final_result},
param="param_1", # 振幅参数 param="param_1", # 振幅参数
title=f'{year}{model_name}振幅时空变化', title=f'{year}{month_range[0]}-{month_range[1]}{model_name}振幅时空变化',
vmax=100, vmax=100,
) )

View File

@ -30,6 +30,8 @@ all_saber_files = glob.glob("./saber/data/**/**.nc", recursive=True)
@saber_module.route("/metadata") @saber_module.route("/metadata")
def get_files(): def get_files():
# normalizing the path, and replace \\ with /
all_saber_files = [path.replace("\\", "/") for path in all_saber_files]
return all_saber_files return all_saber_files

35
tidi/__init__.py Normal file
View File

@ -0,0 +1,35 @@
import glob
from io import BytesIO
from flask import Blueprint, request, send_file
from matplotlib import pyplot as plt
from tidi.staged.plot import tidi_render
tidi_module = Blueprint("Tidi", __name__)
@tidi_module.route('/metadata')
def get_all_years():
res = glob.glob("./tidi/data/**/**.txt", recursive=True)
# search for the folder name that is year
return {
"path": res
}
@tidi_module.route('/render/wave')
def render_wave():
mode = request.args.get('mode')
year = request.args.get('year')
k = request.args.get('k')
T = request.args.get('T')
year = int(year)
k = int(k)
T = int(T)
tidi_render(mode, year, k, T)
buffer = BytesIO()
plt.savefig(buffer, format="png")
buffer.seek(0)
return send_file(buffer, mimetype="image/png")

205
tidi/staged/plot.py Normal file
View File

@ -0,0 +1,205 @@
#此代码是对数据处理后的txt数据进行行星波参数提取绘图
#2006_TIDI_V_Meridional_data.txt也可以做同样处理一个是经向风提取的行星波一个是纬向风的
import pandas as pd
import numpy as np
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 # 正常显示负号
# 读取一年的数据文件
# 设置初始参数
# initial_guess = [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0] # v0, a1, b1, a2, b2, a3, b3
initial_guess = [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,0.5, 0.5, 0.5, 0.5,0.5, 0.5, 0.5, 0.5] # 9个 a 和 9个 b 参数
# 设置参数界限
bounds = (
[0, -np.inf, 0, -np.inf, 0, -np.inf, 0, -np.inf, 0, -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, np.inf, np.inf, np.inf, np.inf, np.inf, np.inf,
np.inf, np.inf, np.inf]) # 上界
# 定义拟合函数
def tidi_render(mode, year, k, T=10):
# 用于存储拟合参数结果的列表
all_fit_results = []
# 设置最小数据量的阈值
min_data_points = 36
if mode != 'V_Zonal' and mode != 'V_Meridional':
raise ValueError('mode must be V_Zonal or V_Meridional')
if k not in list(range(-4, 5)):
raise ValueError('k must be in range(-4, 5)')
df = pd.read_csv(f'./tidi/data/{year}/TIDI_{mode}_data.txt', sep='\s+')
# 删除有 NaN 的行
# V_Meridional
df = df.dropna(subset=[mode])
# 进行多个时间窗口的拟合
#T应该取5、10、16
# todo:对于5日波滑动窗口选择15天
def u_func(x, *params):
a1, b1, a2, b2, a3, b3, a4, b4, a5, b5, a6, b6, a7, b7, a8, b8, a9, b9 = params
return (
a1 * np.sin((2 * np.pi / T) * t - 4 * x + b1)
+ a2 * np.sin((2 * np.pi / T) * t - 3 * x + b2)
+ a3 * np.sin((2 * np.pi / T) * t - 2 * x + b3)
+ a4 * np.sin((2 * np.pi / T) * t - x + b4)
+ a5 * np.sin((2 * np.pi / T) * t + b5)
+ a6 * np.sin((2 * np.pi / T) * t + x + b6)
+ a7 * np.sin((2 * np.pi / T) * t + 2 * x + b7)
+ a8 * np.sin((2 * np.pi / T) * t + 3 * x + b8)
+ a9 * np.sin((2 * np.pi / T) * t + 4 * x + b9)
)
for start_day in range(0, 365-3*T): # 最后一个窗口为[351, 366]
end_day = start_day + 3 * T # 每个窗口的结束时间为 start_day + 3*T
# 选择当前窗口的数据
df_8 = df[(df['UTime'] >= start_day) & (df['UTime'] <= end_day)]
# 检查当前窗口的数据量
if len(df_8) < min_data_points:
# 输出数据量不足的警告
print(f"数据量不足,无法拟合:{start_day}{end_day},数据点数量:{len(df_8)}")
# 将拟合参数设置为 NaN
all_fit_results.append([np.nan] * 18)
continue # 跳过当前时间窗口,继续下一个窗口
# 提取时间、经度、温度数据
t = np.array(df_8['UTime']) # 时间
x = np.array(df_8['Longitude_Radians']) # 经度弧度制
temperature = np.array(df_8[mode]) # 经向风速,因变量
# 用T进行拟合
popt, pcov = curve_fit(u_func, x, temperature, p0=initial_guess, bounds=bounds, maxfev=50000)
# 将拟合结果添加到列表中
all_fit_results.append(popt)
# 将结果转换为DataFrame
columns = ['a1', 'b1', 'a2', 'b2', 'a3', 'b3', 'a4', 'b4', 'a5', 'b5', 'a6', 'b6', 'a7', 'b7', 'a8', 'b8', 'a9', 'b9']
fit_df = pd.DataFrame(all_fit_results, columns=columns) # fit_df即为拟合的参数汇总
# -------------------------------画图----------------------------
#a1-a9,对应波数-4、-3、-2、-1、0、1、2、3、4的行星波振幅
a_columns = ['a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9']
k_values = list(range(-4, 5)) # 从 -4 到 4
# 创建一个字典映射 k 值到 a_columns
k_to_a = {f'k={k}': a for k, a in zip(k_values, a_columns)}
# 获取索引并转换为 numpy 数组
x_values = fit_df.index.to_numpy()
# 对每一列生成独立的图
col = k_to_a[f'k={k}']
plt.figure(figsize=(8, 6)) # 创建新的图形
plt.plot(x_values, fit_df[col].values)
plt.title(f'k={k} 振幅图')
plt.xlabel('Day')
plt.ylabel('振幅')
# 设置横坐标的动态调整
adjusted_x_values = x_values + (3 * T + 1) / 2
if len(adjusted_x_values) > 50:
step = 30
tick_positions = adjusted_x_values[::step] # 选择每30个点
tick_labels = [f'{int(val)}' for val in tick_positions]
else:
tick_positions = adjusted_x_values
tick_labels = [f'{int(val)}' for val in tick_positions]
plt.xticks(ticks=tick_positions, labels=tick_labels)
# plt.show() # 显示图形
if __name__ == '__main__':
tidi_render('V_Zonal', 2015, 1)
tidi_render('V_Meridional', 2015, 1)
# # 用于存储拟合参数结果的列表
# all_fit_results = []
#
# # 设置最小数据量的阈值
# min_data_points = 36
#
# # 进行多个时间窗口的拟合
# # todo:对于5日波滑动窗口选择15天
# for start_day in range(0, 351): # 最后一个窗口为[351, 366]
# end_day = start_day + 15 # 设置每个窗口的结束时间
#
# # 选择当前窗口的数据
# df_8 = df[(df['UTime'] >= start_day) & (df['UTime'] <= end_day)]
#
# # 检查当前窗口的数据量
# if len(df_8) < min_data_points:
# # 输出数据量不足的警告
# print(f"数据量不足,无法拟合:{start_day} 到 {end_day},数据点数量:{len(df_8)}")
# # 将拟合参数设置为 NaN
# all_fit_results.append([np.nan] * 18)
# continue # 跳过当前时间窗口,继续下一个窗口
#
#
# # 提取时间、经度、温度数据
# t = np.array(df_8['UTime']) # 时间
# x = np.array(df_8['Longitude_Radians']) # 经度弧度制
# temperature = np.array(df_8['V_Zonal']) # 经向风速,因变量
#
# # 用T=5进行拟合
# for T in [5]:
# popt, pcov = curve_fit(u_func, x, temperature, p0=initial_guess, bounds=bounds, maxfev=50000)
#
# # 将拟合结果添加到列表中
# all_fit_results.append(popt)
#
# # 将结果转换为DataFrame
# columns = ['a1', 'b1', 'a2', 'b2', 'a3', 'b3', 'a4', 'b4', 'a5', 'b5', 'a6', 'b6', 'a7', 'b7', 'a8', 'b8', 'a9', 'b9']
# fit_df = pd.DataFrame(all_fit_results, columns=columns) # fit_df即为拟合的参数汇总
#
# # -------------------------------画图----------------------------
# # 定义 a1 到 a9 的列名
# a_columns = ['a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9']
# a_values = fit_df[a_columns].values
#
# # 获取索引并转换为 numpy 数组
# x_values = fit_df.index.to_numpy()
#
# # 对每一列生成独立的图
# for i, col in enumerate(a_columns):
# plt.figure(figsize=(8, 6)) # 创建新的图形
# plt.plot(x_values, a_values[:, i])
# plt.title(f'{col} 振幅图')
# plt.xlabel('Day')
# plt.ylabel('振幅')
# plt.show() # 显示图形
# # 提取 a1 到 a9 列的数据
# a_columns = ['a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9']
# a_values = fit_df[a_columns].values
# # 获取索引并转换为 numpy 数组
# x_values = fit_df.index.to_numpy()
#
# # 创建一个图形和子图
# plt.figure(figsize=(10, 6))
#
# # 绘制每一条线
# for i, col in enumerate(a_columns):
# plt.plot(x_values, a_values[:, i], label=col)
#
# # 添加标题、标签和图例
# plt.title('a1 to a9 振幅图')
# plt.xlabel('Day')
# plt.ylabel('振幅')
# plt.legend()
# # 显示图形
# plt.tight_layout()
# plt.show()

210
tidi/staged/process.py Normal file
View File

@ -0,0 +1,210 @@
import os
import numpy as np
from scipy.io import loadmat
import pandas as pd
#第156行代码纬度可以自己选择但是都是每5°做一个选择
#70km-120km每2.5km一个共21个第六个就是82.5km高度的情况,高度也可以自己选择
fixed_height_index = 6
# 初始化空列表来存储每天的数据
height_list = []
lat_list = []
lon_list = []
vmeridional_list = []
vzonal_list = []
# 文件路径
base_path = "./tidi/data/2022/"
# 循环读取从第1天到第365天的数据
for day in range(1, 366): # 365天数据确保循环次数为365
# 构建文件名(使用三位数格式)
day_str = f"{day:03d}" # 格式化数字为三位数前面补0
height_file = f"{day_str}_Height.mat"
lat_file = f"{day_str}_Lat.mat"
lon_file = f"{day_str}_Lon.mat"
vmeridional_file = f"{day_str}_VMerdional.mat"
vzonal_file = f"{day_str}_Vzonal.mat"
# 检查文件是否存在
try:
if not os.path.exists(os.path.join(base_path, height_file)):
raise FileNotFoundError(f"{height_file} not found")
if not os.path.exists(os.path.join(base_path, lat_file)):
raise FileNotFoundError(f"{lat_file} not found")
if not os.path.exists(os.path.join(base_path, lon_file)):
raise FileNotFoundError(f"{lon_file} not found")
if not os.path.exists(os.path.join(base_path, vmeridional_file)):
raise FileNotFoundError(f"{vmeridional_file} not found")
if not os.path.exists(os.path.join(base_path, vzonal_file)):
raise FileNotFoundError(f"{vzonal_file} not found")
# 读取.mat文件
height_data = loadmat(os.path.join(base_path, height_file))
lat_data = loadmat(os.path.join(base_path, lat_file))
lon_data = loadmat(os.path.join(base_path, lon_file))
vmeridional_data = loadmat(os.path.join(base_path, vmeridional_file))
vzonal_data = loadmat(os.path.join(base_path, vzonal_file))
# 将数据转换为DataFrame
height_df = pd.DataFrame(height_data['Height'])
lat_df = pd.DataFrame(lat_data['Lat'])
lon_df = pd.DataFrame(lon_data['Lon'])
vmeridional_df = pd.DataFrame(vmeridional_data['VMerdional'])
vzonal_df = pd.DataFrame(vzonal_data['Vzonal'])
# 将每天的数据添加到列表中
height_list.append(height_df)
lat_list.append(lat_df)
lon_list.append(lon_df)
vmeridional_list.append(vmeridional_df)
vzonal_list.append(vzonal_df)
except FileNotFoundError as e:
print(f"Error: {e}")
continue # 跳过当前文件,继续处理其他文件
# 合并所有天的数据
height_df = pd.concat(height_list, ignore_index=True)
lat_df = pd.concat(lat_list, ignore_index=True)
lon_df = pd.concat(lon_list, ignore_index=True)
vmeridional_df = pd.concat(vmeridional_list, axis=1) # 按列合并
vzonal_df = pd.concat(vzonal_list, axis=1) # 按列合并
# 初始化空列表来存储每天的数据
UTime_list = []
# 初始化累加的时间偏移量
time_offset = 0
# 循环读取从第1天到第365天的数据
for day in range(1, 366): # 365天数据
# 构建文件名(使用三位数格式)
day_str = f"{day:03d}" # 格式化数字为三位数前面补0
UTime_file = f"{day_str}_UTime.mat"
# 检查UTime文件是否存在
try:
if not os.path.exists(os.path.join(base_path, UTime_file)):
raise FileNotFoundError(f"{UTime_file} not found")
# 读取.mat文件
UTime_data = loadmat(os.path.join(base_path, UTime_file))
# 将数据转换为DataFrame
UTime_df = pd.DataFrame(UTime_data['UTime'])
# 对UTime进行累加处理
UTime_df += time_offset
# 将每天的数据添加到列表中
UTime_list.append(UTime_df)
# 更新时间偏移量为下一天增加24小时
time_offset += 24
except FileNotFoundError as e:
print(f"Error: {e}")
continue # 跳过当前文件,继续处理其他文件
# 合并所有天的数据
UTime_df = pd.concat(UTime_list, ignore_index=True)
# 将UTime_df的单位从小时转换为天除以24
UTime_df = UTime_df / 24
# 获取固定高度下的纬度数据
fixed_height_lat = lat_df.iloc[:, 0].values
# 获取固定高度下的经度数据
fixed_height_lon = lon_df.iloc[:, 0].values
# 获取固定高度下的经向风数据
fixed_height_vmeridional = vmeridional_df.iloc[fixed_height_index, :].values
# 获取固定高度下的纬向风数据
fixed_height_vzonal = vzonal_df.iloc[fixed_height_index, :].values
# 获取对应的世界时数据这里直接使用整个UTime数据你可以根据实际情况判断是否需要处理下格式等
fixed_height_utime = UTime_df.iloc[:, 0].values
# 将这些数据整合到一个新的DataFrame可选操作方便后续查看等
combined_data = pd.DataFrame({
'Latitude': fixed_height_lat,
'Longitude': fixed_height_lon,
'V_Meridional': fixed_height_vmeridional,
'V_Zonal': fixed_height_vzonal,
'UTime': fixed_height_utime
})
# 简单打印下整合后的数据前几行查看下内容
print(combined_data)
# 确定纬度和经度的范围
lat_min, lat_max = np.min(fixed_height_lat), np.max(fixed_height_lat)
lon_min, lon_max = np.min(fixed_height_lon), np.max(fixed_height_lon)
# 计算网格的边界
lat_bins = np.linspace(lat_min, lat_max, 10) # 纬度分为6个网格需要7个边界
lon_bins = np.linspace(lon_min, lon_max, 13) # 经度分为20个网格需要21个边界
# 将数据分配到网格中
grid_data = []
for i in range(len(fixed_height_lat)):
lat_idx = np.digitize(fixed_height_lat[i], lat_bins) - 1 # 找到纬度所在的网格索引
lon_idx = np.digitize(fixed_height_lon[i], lon_bins) - 1 # 找到经度所在的网格索引
grid_idx = lat_idx * 12 + lon_idx # 计算网格的唯一索引9x12=108
grid_data.append([fixed_height_lat[i], fixed_height_lon[i], fixed_height_vmeridional[i], fixed_height_vzonal[i], fixed_height_utime[i], grid_idx])
# 将网格数据转换为DataFrame
grid_df = pd.DataFrame(grid_data, columns=['Latitude', 'Longitude', 'V_Meridional', 'V_Zonal', 'UTime', 'Grid_Index'])
# 打印网格数据的前几行查看
print(grid_df.head())
# 筛选纬度在 0 到 5° 范围内的数据
filtered_df = grid_df[(grid_df['Latitude'] >= 0) & (grid_df['Latitude'] <= 5)]
# 将经度从度转换为弧度
filtered_df['Longitude_Radians'] = np.radians(filtered_df['Longitude'])
# 选择需要输出的列
output_columns = ['Longitude_Radians', 'UTime', 'V_Zonal']
# 将数据输出为txt文件
output_file = base_path+'TIDI_V_Zonal_data.txt'#经向风速分量
filtered_df[output_columns].to_csv(output_file, sep='\t', index=False)
# 打印结果确认
print(f"数据已保存到 {output_file}")
# 选择需要输出的列
output_columns1 = ['Longitude_Radians', 'UTime', 'V_Meridional']
# 将数据输出为txt文件
output_file1 = base_path+'TIDI_V_Meridional_data.txt'#纬向风速分量
filtered_df[output_columns1].to_csv(output_file1, sep='\t', index=False)
# 打印结果确认
print(f"数据已保存到 {output_file1}")
# # 对网格数据按照Grid_Index进行分组并计算每个网格的统计数据
# grid_stats = grid_df.groupby('Grid_Index').agg({
# 'Latitude': 'mean', # 计算纬度的平均值
# 'Longitude': 'mean', # 计算经度的平均值
# 'V_Meridional': 'mean', # 计算经向风的平均值、最大值和最小值
# 'V_Zonal': 'mean', # 计算纬向风的平均值、最大值和最小值
# 'UTime': 'mean' # 计算UTime的平均值
# }).reset_index()
#
# # 重命名列,使其更具可读性
# grid_stats.columns = ['Grid_Index', 'Mean_Latitude', 'Mean_Longitude', 'Mean_VMeridional', 'Mean_VZonal', 'Mean_UTime']
# # 将经度转换为2π范围
# grid_stats['Mean_Longitude_2pi'] = (grid_stats['Mean_Longitude'] / 360) * (2 * np.pi)
# # 定义纬度的最小值和最大值起码要40°区间否则数据太少无法拟合
# latmin =0
# latmax=40
# # 筛选出Mean_Latitude在lat_min到lat_max之间的数据
# filtered_grid_stats = grid_stats[(grid_stats['Mean_Latitude'] >= latmin) & (grid_stats['Mean_Latitude'] <= latmax)]
#
# # 检查筛选后的数据行数是否小于18
# if filtered_grid_stats.shape[0] < 18:
# print("无法拟合行星波")
# else:
# # 打印筛选后的网格数据的前几行查看
# print(filtered_grid_stats.head())