commit eb7f4420ff7b5af9c6faeeb62d434ec26ddac274 Author: Dustella Date: Wed Jan 8 12:53:55 2025 +0800 backup: version 1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..99f04b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Ignore everything +* +# But not .py files +!*.py +# Include .gitignore itself +!.gitignore +# Recursively allow .py files in subdirectories +!*/ \ No newline at end of file diff --git a/backend.py b/backend.py new file mode 100644 index 0000000..c30d6d9 --- /dev/null +++ b/backend.py @@ -0,0 +1,35 @@ +from gevent import pywsgi, monkey +monkey.patch_all() + +from utils import * +import saber +import radar +import balloon +from flask import Flask +from flask_cors import CORS +from typing import get_args +import sys + + +app = Flask(__name__) + +app.register_blueprint(balloon.balloon_module, url_prefix="/balloon") +app.register_blueprint(radar.radar_module, url_prefix="/radar") +app.register_blueprint(saber.saber_module, url_prefix="/saber") + +# allow cors +CORS(app) + + +if __name__ == '__main__': + # get args '--prod' + args = sys.argv + if 'prod' in args: + + # app.run() + # import gevent + server = pywsgi.WSGIServer(('0.0.0.0', 5000), app) + server.serve_forever() + + elif 'debug' in args: + app.run(debug=True) diff --git a/balloon/__init__.py b/balloon/__init__.py new file mode 100644 index 0000000..b130e05 --- /dev/null +++ b/balloon/__init__.py @@ -0,0 +1,77 @@ +from flask import Blueprint +import balloon.extract_wave +import balloon.read_data +import balloon.plot_once +import balloon.plot_year +from flask import Flask, jsonify, request, send_file +from balloon.plot_once_backend import render_by_mode_single +from balloon.plot_year_backend import get_all_modes, render_based_on_mode +from utils import * + +all_year_data = pd.read_parquet("./cache/ballon_data_lin.parquet") + +all_ballon_files = glob.glob("./data/探空气球/**/**.nc", recursive=True) + +def get_dataframe_between_year(start_year, end_year): + res = all_year_data + filtered_res = res[(res['file_name'].str.extract(r'LIN-(\d{4})')[0].astype(int) >= start_year) & + (res['file_name'].str.extract(r'LIN-(\d{4})')[0].astype(int) <= end_year)] + return filtered_res + + +plot_once = balloon.plot_once.plot_once +plot_year = balloon.plot_year.plot_year +is_terrain_wave = balloon.extract_wave.is_terrain_wave +read_data = balloon.read_data.read_data +extract_wave = balloon.extract_wave.extract_wave + + +balloon_module = Blueprint("Balloon", __name__) + + +@balloon_module.route('/') +def home(): + return jsonify(message="Welcome to the Flask balloon_module!") + + +@balloon_module.route("/metadata/modes") +def supermeta(): + return jsonify({ + "combos": comboType, + "comboMode": comboMode, + "date": comboDate + }) + + +@balloon_module.route("/metadata") +def list_ballon(): + return all_ballon_files + + +@balloon_module.route("/metadata/year") +def list_ballon_year_modes(): + return get_all_modes() + + +@balloon_module.route("/render/year") +def render_full_year(): + # get start_year and end_year from query + start_year = request.args.get('start_year') + end_year = request.args.get('end_year') + mode = request.args.get('mode') + season = request.args.get('season') + print(start_year, end_year) + df = get_dataframe_between_year(int(start_year), int(end_year)) + buff = render_based_on_mode(df, mode, season) + return send_file(buff, mimetype='image/png') + + +@balloon_module.route("/render/single") +def render_single_path(): + path = request.args.get('path') + mode = request.args.get('mode') + data = balloon.read_data(path) + buff = render_by_mode_single(data, mode) + if buff is None: + return jsonify(message="No wave detected") + return send_file(buff, mimetype='image/png') diff --git a/balloon/extract_wave.py b/balloon/extract_wave.py new file mode 100644 index 0000000..7730f8c --- /dev/null +++ b/balloon/extract_wave.py @@ -0,0 +1,413 @@ +import numpy as np +import pandas as pd +from scipy.optimize import curve_fit +from scipy.signal import lombscargle, hilbert +from sklearn.decomposition import PCA +import sympy as sp + + +def calculate_n(Av, Au, Bu, Bv): + # 计算 Fuv + Fuv = Av * Au * np.cos(Bu - Bv) + # 根据条件计算 n + if Au > Av: + n = 1 + elif Au < Av: + if Fuv > 0: + n = 0 + else: # Fuv <= 0 + n = 2 + else: + raise ValueError("Au 和 Av 不应相等") + return n + + +def calculate_seta(Av, Au, Bu, Bv): + # 计算 n + n = calculate_n(Av, Au, Bu, Bv) + # 计算 Fuv + Fuv = Av * Au * np.cos(Bu - Bv) + # 计算 arctan 部分 + arctan_value = np.atan2(2 * Fuv, Av**2 - Au**2) # 更通用,返回的角度范围是 ([-π, π]) + # arctan_value = np.atan(2 * Fuv / (Av ** 2 - Au ** 2)) # 返回的角度范围是 ([-π/2, π/2]) + # 计算 seta + seta = round((1 / 2) * (n * np.pi + arctan_value), 2) + return seta # 返回结果为弧度 + + +# 判断椭圆旋转方向 +def analyze_direction(df1, df2, seta_degrees): + # 初始值 + # a = 0 + # b = 0 + + # 计算元素 + element1 = df1.iloc[0, 1] * df1.iloc[1, 2] - \ + df1.iloc[0, 2] * df1.iloc[1, 1] + element2 = df2.iloc[0, 1] * df2.iloc[1, 2] - \ + df2.iloc[0, 2] * df2.iloc[1, 1] + + # 判断 element1 + if element1 < 0: + a = 1 # 表示顺时针,重力波向上传播 + else: + a = -1 + + # 判断 element2 + if element2 < 0: + b = seta_degrees # 表示顺时针,水平传播方向与假设方向同,seta + else: + b = seta_degrees + 180 # 逆时针,seta+180 + return a, b + + +# 计算u_v椭圆长短轴之比(先标准化椭圆,再计算最远距离最近距离之比) +def calculate_axial_ratio(u_fit, v_fit): + # 将 ucmp 和 vcmp 组成一个数据矩阵 + data_matrix = np.vstack((u_fit, v_fit)).T + + # 使用 PCA 进行主成分分析 + pca = PCA(n_components=2) + transformed_data = pca.fit_transform(data_matrix) + + # 提取主成分 + principal_components = pca.components_ + + # 计算每个数据点在标准椭圆上的坐标 + scaling_factors = np.linalg.norm(principal_components, axis=1) + standardized_data = transformed_data / scaling_factors + + # 创建 DataFrame + standardized_df = pd.DataFrame(standardized_data, columns=["PC1", "PC2"]) + # 获取第一列和第二列的最大值和最小值 + col1_max = standardized_df.iloc[:, 0].max() + col1_min = standardized_df.iloc[:, 0].min() + col2_max = standardized_df.iloc[:, 1].max() + col2_min = standardized_df.iloc[:, 1].min() + + # 计算长短轴比值 + axial_ratio = (col1_max - col1_min) / (col2_max - col2_min) + + return axial_ratio + + +def calculate_wave(data: pd.DataFrame, lat: float, g: float): + height = data["alt"].values + temp = data["temp"].values + ucmp = data["u"].values + vcmp = data["v"].values + wcmp = data["wspeed"].values + press = (data["press"].values) * 100 # 单位Pa + + coef_temp = np.polyfit(height, temp, 2) + poly_temp = np.poly1d(coef_temp) + coef_ucmp = np.polyfit(height, ucmp, 2) + poly_ucmp = np.poly1d(coef_ucmp) + coef_vcmp = np.polyfit(height, vcmp, 2) + poly_vcmp = np.poly1d(coef_vcmp) + coef_wcmp = np.polyfit(height, wcmp, 2) + poly_wcmp = np.poly1d(coef_wcmp) + + residual_temp = temp - poly_temp(height) + residual_ucmp = ucmp - poly_ucmp(height) + residual_vcmp = vcmp - poly_vcmp(height) + residual_wcmp = wcmp - poly_wcmp(height) + + ff = np.linspace(0.01, 25, 1000) # 横坐标范围 + pgram_temp = lombscargle(height, residual_temp, ff) + pgram_ucmp = lombscargle(height, residual_ucmp, ff) + pgram_vcmp = lombscargle(height, residual_vcmp, ff) + + # todo:我的频率等于垂直波数(单位10-3rad/m)? + main_frequency_temp = round(ff[np.argmax(pgram_temp)], 2) + main_frequency_ucmp = round(ff[np.argmax(pgram_ucmp)], 2) + main_frequency_vcmp = round(ff[np.argmax(pgram_vcmp)], 2) + + # 由波数计算波长 + λ1 = round(1 / ((main_frequency_temp / 2.5) * 0.4), 2) + λ2 = round(1 / ((main_frequency_ucmp / 2.5) * 0.4), 2) + λ3 = round(1 / ((main_frequency_vcmp / 2.5) * 0.4), 2) + + mean = round(sum([λ1, λ2, λ3]) / 3, 2) + rsd_temp = abs(round(((λ1 - mean) / λ1) * 100, 2)) + rsd_ucmp = abs(round(((λ2 - mean) / λ2) * 100, 2)) + rsd_vcmp = abs(round(((λ3 - mean) / λ3) * 100, 2)) + + if not (rsd_temp < 20 and rsd_ucmp < 20 and rsd_vcmp < 20): + return [] + + # 定义正弦波模型函数 + def sine_wave(height, A, B, omega): + omega = 2 * np.pi / mean + return A * np.sin(omega * height + B) + + def fit_sine_wave(height, data, mean): + # 提供初始猜测值,例如 A=1, B=0, omega=2 * np.pi / mean + initial_guess = [1, 0, 2 * np.pi / mean] + # 设置参数的边界:A的下限为0(确保A为正值),B和omega没有边界限制 + bounds = ([0, -np.inf, -np.inf], [np.inf, np.inf, np.inf]) + # curve_fit 函数通过调整 sine_wave 函数的参数(A、B 和 omega)来最小化模型预测值与实际数据 data 之间的残差平方和。 + params, covariance = curve_fit( + sine_wave, height, data, p0=initial_guess, bounds=bounds) + return params + + # 对 u 风、v 风、温度扰动量进行拟合 + params_u = fit_sine_wave(height, residual_ucmp, mean) # 输出3个参数分别是振幅,相位,角频率 + u_fit = sine_wave(height, *params_u) + + params_v = fit_sine_wave(height, residual_vcmp, mean) + v_fit = sine_wave(height, *params_v) + + params_T = fit_sine_wave(height, residual_temp, mean) + T_fit = sine_wave(height, *params_T) + + params_w = fit_sine_wave(height, residual_wcmp, mean) + w_fit = sine_wave(height, *params_w) + + # 将高度、u_fit、v_fit创建一个数据框df1 + df1 = pd.DataFrame({"Height": height, "u_fit": u_fit, "v_fit": v_fit}) + + # 计算假设的水平传播方向 + rad_seta = calculate_seta( + params_v[0], params_u[0], params_u[1], params_v[1]) # 输入、输出都为弧度 + # 转换为角度并保留2位小数 + seta_degrees = round(np.degrees(rad_seta), 2) # 38.41° + + # 计算水平风 + uh = u_fit * np.sin(seta_degrees * np.pi / 180) + \ + v_fit * np.cos(seta_degrees * np.pi / 180) # 水平风 + # temp = T_fit + # 将高度、水平风、温度创建一个数据框df2 + df2 = pd.DataFrame({"Height": height, "uh": uh, "temp": T_fit}) + a, b = analyze_direction(df1, df2, seta_degrees) # 计算垂直、水平传播方向 + + Axial_ratio = round(calculate_axial_ratio(u_fit, v_fit), + 2) # 计算长短轴之比并保留2位小数,后续计算本征频率会用到 + + # 计算剩余几个参数 + # 常量 + # f = 1.32e-4 # rad/s + # N = 2.13e-2 # 浮力频数,单位 rad/s + + # 根据站点纬度,计算惯性频率f + ou = 7.27e-5 # 单位rad/s,地球自转频率 + # lat = 64.5 + f = 2 * ou * np.sin(lat * np.pi / 180) + # print(f"f = {f:.3e} rad/s") + + # 求浮力频率N + df3 = pd.DataFrame({"height": height, "temp": temp, + "temp_bar": poly_temp(height)}) + + Cp = 1005 # 单位:J/kgK + df3["temp_bar_derivative"] = np.gradient( + df3["temp_bar"], (df3["height"]) * 1000) # 求背景温度对高度的偏导 + gen = (g / temp) * (df3["temp_bar_derivative"] + (g / Cp)) + sqrt_gen = np.sqrt(np.abs(gen)) # np.sqrt()对列表求根号,np.sqrt()对数字求根号 + N = sqrt_gen.mean() # 计算平均值,得到浮力频率的均值 + + # 1. 本征频率omega_upper + omega_upper = sp.symbols("omega_upper") + eq1 = sp.Eq(Axial_ratio, omega_upper / f) + omega_upper = (sp.solve(eq1, omega_upper))[0] + omega_upper = sp.N(omega_upper) + + # 求本征频率与惯性频率的比值 + w_f = omega_upper / f # todo:总是大于1 + + # 本征周期 + zhou_qi = 2 * np.pi / omega_upper / 3600 + + # 2. 垂直波长λ_z、水平波长λ_h + + # 垂直波数 + λ_z = mean # 保留两位小数 + # 在北半球,重力波向上传播,垂直波数k_z<0 + if a == 1: + k_z = -(2 * np.pi / λ_z / 1000) + else: + k_z = 2 * np.pi / λ_z / 1000 + + # 水平波数 + k_h = sp.symbols("k_h") + eq2 = sp.Eq(k_h**2, (omega_upper**2 - f**2) * (k_z**2) / N**2) + k_h = sp.solve(eq2, k_h) + k_h = [sol.evalf() for sol in k_h if sol.evalf() > 0][0] + k_h = sp.N(k_h) # 正值 + + # 在北半球,重力波向上传播,垂直波数k_h<0 + if b > 180: # 说明水平传播方向与假定方向相反 + k_h = -k_h + else: + k_h = k_h + + λ_h = abs(round((2 * np.pi / k_h / 1000), 2)) # 水平波长,取绝对值,因为波数可能为负数 + + # 纬向、经向水平波长λ_x, λ_y + # λ_x = abs(λ_h * np.sin(b * np.pi / 180)) + # λ_y = abs(λ_h * np.cos(b * np.pi / 180)) + + # 本征相速度 + c_x = omega_upper / (abs(k_h) * np.sin(b * np.pi / 180)) + c_y = omega_upper / (abs(k_h) * np.cos(b * np.pi / 180)) + c_z = omega_upper / k_z + + """ + # 群速度 + C_gz = -((omega_upper ** 2 - f ** 2)) / (omega_upper * k_z) + C_gh = sp.symbols('C_gh') + eq3 = sp.Eq(C_gz / C_gh, sp.sqrt(omega_upper ** 2 - f ** 2) / N) + C_gh = (sp.solve(eq3, C_gh))[0] + C_gh = sp.N(C_gh) # 转成数值 + """ + + # 波动能E_k + u2_mean = np.mean(np.square(u_fit)) + v2_mean = np.mean(np.square(v_fit)) + Ek = (1 / 2) * (u2_mean + v2_mean) # 3.11 J/kg + + # 势能E_p + T_normalized = T_fit / poly_temp(height) # 归一化处理 + T2_mean = np.mean(np.square(T_normalized)) + E_p = ((g**2) * T2_mean) / (2 * (N**2)) # 1.02J/kg + + # 动量通量 + # 经、纬向风动量通量 + P = press # 单位Pa + R = 287 # 单位:J/(kgK),气体常量 + T = temp # 温度已经是开尔文 + densi = P / (R * T) # 向量 + c = (omega_upper * g) / (N**2) + S = 1 - (f**2) / (omega_upper**2) # 常数 + + # 归一化温度90°相位变换 + + hilb = hilbert(T_normalized) + hilb1 = np.imag(hilb) # 获取虚部作为90°相位变换的结果 + + uT = u_fit * hilb1 # 向量 + uT1 = np.mean(uT) # 均值 + MFu = (np.mean(-densi * c * uT1 * S)) * 1000 # 单位mPa + + vT = v_fit * hilb1 + vT1 = np.mean(vT) + MFv = (np.mean(-densi * c * vT1 * S)) * 1000 + + # 调整动量通量符号与本征相速度符号一致 + # 调整b的符号与a一致 + MFu1 = abs(MFu) if c_x >= 0 else -abs(MFu) + MFv1 = abs(MFv) if c_y >= 0 else -abs(MFv) + + omega_upper = float(omega_upper) + # make wf, c_x, c_y, c_z, MFu, MFv, zhouqi float + w_f = float(w_f) + c_x = float(c_x) + c_y = float(c_y) + c_z = float(c_z) + MFu1 = float(MFu1) + MFv1 = float(MFv1) + zhou_qi = float(zhou_qi) + + return [ + a, + b, + omega_upper, + w_f, + λ_z, + λ_h, + c_x, + c_y, + c_z, + Ek, + E_p, + MFu1, + MFv1, + params_u[0], + params_v[0], + params_T[0], + ucmp, + vcmp, + temp, + height, + poly_ucmp, + poly_vcmp, + poly_wcmp, + poly_temp, + residual_ucmp, + residual_vcmp, + residual_temp, + u_fit, + v_fit, + T_fit, + uh, + zhou_qi, + ] + + +def extract_wave(data: pd.DataFrame, lat: float, g: float): + wave = calculate_wave( + data[(data["alt"] >= 15) & (data["alt"] <= 25)], lat, g) + if len(wave) == 0: + return [] + return [ + wave[0], + wave[1], + round(wave[2], 6), + round(wave[3], 2), + round(wave[4], 2), + round(wave[5], 2), + round(wave[6], 2), + round(wave[7], 2), + round(wave[8], 3), + round(wave[9], 4), + round(wave[10], 4), + round(wave[11], 4), + round(wave[12], 4), + round(wave[13], 2), + round(wave[14], 2), + round(wave[15], 2), + round(wave[31], 2), + ] + + +def is_terrain_wave(data: pd.DataFrame, lat: float, g: float): + wave = calculate_wave( + data[(data["alt"] >= 2) & (data["alt"] <= 10)], lat, g) + if len(wave) == 0: + return False + + # 计算观测u风v风的均值,为后边合成背景速度 + mean_ucmp = np.mean(wave[16]) # 11.34 + mean_vcmp = np.mean(wave[17]) # 0.38 + + x1, y1 = mean_ucmp, mean_vcmp # 背景风的分量 + x2, y2 = float(wave[6]), float(wave[7]) # 相速度的分量 + # wave[18](wave[19]) + + """ + # 二阶拟合的u风v风 + mean_ucmp = np.mean(poly_ucmp(height)) + mean_vcmp = np.mean(poly_vcmp(height)) + + x1, y1 = mean_ucmp, mean_vcmp # 背景风的分量 + x2, y2 = float(c_x), float(c_y) # 相速度的分量 + """ + # 计算两个风的方向 + direction1 = np.arctan2(y1, x1) + direction2 = np.arctan2(y2, x2) + # 转化为角度(该角度是以x轴逆时针旋转),取特殊值用于验证。 + # direction1_degrees = np.degrees(direction1) + # direction2_degrees = np.degrees(direction2) + + # 计算夹角(弧度) + angle_radians = np.abs(direction1 - direction2) + # 确保夹角在0到π之间 + if angle_radians > np.pi: + angle_radians = 2 * np.pi - angle_radians + # 将夹角转换为度 + angle_degrees = np.degrees(angle_radians) + + # 判断是否超过150,或者165 + if round(angle_degrees, 2) > 150: + return True + return False diff --git a/balloon/plot_once.py b/balloon/plot_once.py new file mode 100644 index 0000000..a4c4fea --- /dev/null +++ b/balloon/plot_once.py @@ -0,0 +1,167 @@ +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +import numpy as np +from balloon.extract_wave import calculate_wave, is_terrain_wave + + +def plot_once(data: pd.DataFrame, path: str, lat: float, g: float): + wave = calculate_wave(data[(data["alt"] >= 15) & (data["alt"] <= 25)], lat, g) + if len(wave) == 0: + return [] + + c = is_terrain_wave(data, lat, g) + a, b, omega_upper, w_f, λ_z, λ_h, c_x, c_y, c_z, Ek, E_p, MFu1, MFv1, params_u, params_v, params_T = wave[:16] + ucmp, vcmp, temp, height, poly_ucmp, poly_vcmp, _, poly_temp, residual_ucmp, residual_vcmp, residual_temp, u_fit, v_fit, T_fit, uh, _ = wave[16:] + + plt.figure(figsize=(16, 10)) + + # 二阶多项式拟合曲线 + + # u 风扰动量 + plt.subplot(2, 6, 1) + plt.plot(ucmp, height, label="", linestyle="-") + plt.plot(poly_ucmp(height), height, label="") + plt.ylabel("Height(km)") + plt.xlabel("Zonal wind (m/s)") + plt.grid(True) + + # v 风扰动量 + plt.subplot(2, 6, 2) + plt.plot(vcmp, height, label="", linestyle="-") + plt.plot(poly_vcmp(height), height, label="") + plt.ylabel("Height(km)") + plt.xlabel("Meridional wind (m/s)") + plt.grid(True) + + plt.title("观测的二阶多项式拟合", fontsize=16) + + # 温度扰动量 + plt.subplot(2, 6, 3) + plt.plot(temp, height, label="", linestyle="-") + plt.plot(poly_temp(height), height, label="") + plt.ylabel("Height(km)") + plt.xlabel("Temperature(K)") + plt.grid(True) + + # 绘制正弦拟合图 + + # u 风扰动量 + plt.subplot(2, 6, 4) + plt.plot(residual_ucmp, height, label="", marker="o", linestyle="None") + plt.plot(u_fit, height, label="") + plt.ylabel("Height(km)") + plt.xlabel("Zonal wind (m/s)") + plt.grid(True) + + # v 风扰动量 + plt.subplot(2, 6, 5) + plt.plot(residual_vcmp, height, label="", marker="o", linestyle="None") + plt.plot(v_fit, height, label="") + plt.ylabel("Height(km)") + plt.xlabel("Meridional wind (m/s)") + plt.grid(True) + + plt.title("扰动分量的正弦波拟合", fontsize=16) + + # 温度扰动量 + plt.subplot(2, 6, 6) + plt.plot(residual_temp, height, label="", marker="o", linestyle="None") + plt.plot(T_fit, height, label="") + plt.ylabel("Height(km)") + plt.xlabel("Temperature(K)") + plt.grid(True) + + # 标记3个特定高度 + specified_heights = [15, 15.05, 15.1] + markers = ["*", "D", "^"] # 星号,菱形,三角形 + + # a. U-V矢量曲线 + plt.subplot(2, 2, 3) + plt.plot(u_fit, v_fit) # 绘制U-V曲线 + for h, marker in zip(specified_heights, markers): + index = np.abs(height - h).argmin() # 找到离指定高度最近的索引 + plt.scatter(u_fit[index], v_fit[index], marker=marker, s=100, label=f"{h} km") + + # 设置坐标轴范围和比例 + plt.xlim(-8, 8) + plt.ylim(-4, 4) + # plt.gca().set_aspect("equal") # 确保横纵坐标刻度长短一致 + plt.axvline(0, color="gray", linestyle="--") + plt.axhline(0, color="gray", linestyle="--") + plt.xlabel("Zonal Wind (m/s)") + plt.ylabel("Meridional Wind (m/s)") + plt.legend() # 显示图例 + plt.grid(True) # 显示网格线 + plt.text(0.05, 1, "(a)", transform=plt.gca().transAxes, fontsize=14, verticalalignment="top") + plt.title("径向风-纬向风矢量图") + + # b. 水平风-温度的矢端曲线 + plt.subplot(2, 2, 4) + plt.plot(uh, T_fit) # 绘制水平风-温度曲线 + for h, marker in zip(specified_heights, markers): + index = np.abs(height - h).argmin() # 找到离指定高度最近的索引 + plt.scatter(uh[index], T_fit[index], marker=marker, s=100, label=f"{h} km") + + # 设置坐标轴范围和比例 + plt.xlim(-4, 4) + plt.ylim(-2, 2) + # plt.gca().set_aspect("equal") # 确保横纵坐标刻度长短一致 + plt.axvline(0, color="gray", linestyle="--") + plt.axhline(0, color="gray", linestyle="--") + plt.xlabel("Horizontal wind (m/s)") + plt.ylabel("Temp (K)") + plt.legend() + plt.grid(True) + plt.text(0.05, 1, "(b)", transform=plt.gca().transAxes, fontsize=14, verticalalignment="top") + plt.title("温度-水平风矢量图") + + # 设置中文显示和负号正常显示 + plt.rcParams["font.sans-serif"] = ["SimHei"] # 显示中文 + plt.rcParams["axes.unicode_minus"] = False # 正常显示负号 + + plt.tight_layout() + # plt.savefig(path, transparent=True) + plt.savefig(path) + plt.close() + + """ + 地形重力波 c 是否是地形重力波 + 垂直传播方向 a 1上 + 水平传播方向 b degree + 本征(固有)频率 round(omega_upper, 6) rad/s + 本征周期 round(2 * np.pi / omega_upper / 3600, 2) h + 本征频率与固有频率的比值(即长短轴之比) round(w_f, 2) + 垂直波长 round(λ_z, 2) km + 水平波长 round(λ_h, 2) km + 纬向本征相速度 round(c_x, 2) m/s + 经向本征相速度 round(c_y, 2) m/s + 垂直本征相速度 round(c_z, 3) m/s + 波动能 round(Ek, 4) J/kg + 势能 round(E_p, 4) J/kg + 纬向动量通量 round(MFu1, 4) mPa + 经向动量通量 round(MFv1, 4) mPa + 纬向风扰动振幅 round(params_u, 2) m/s + 经向风扰动振幅 round(params_v, 2) m/s + 温度扰动振幅 round(params_T, 2) K + """ + return [ + c, + a, + b, + round(omega_upper, 6), + round(2 * np.pi / omega_upper / 3600, 2), + round(w_f, 2), + round(λ_z, 2), + round(λ_h, 2), + round(c_x, 2), + round(c_y, 2), + round(c_z, 3), + round(Ek, 4), + round(E_p, 4), + round(MFu1, 4), + round(MFv1, 4), + round(params_u, 2), + round(params_v, 2), + round(params_T, 2), + ] diff --git a/balloon/plot_once_backend.py b/balloon/plot_once_backend.py new file mode 100644 index 0000000..e3ef76a --- /dev/null +++ b/balloon/plot_once_backend.py @@ -0,0 +1,145 @@ +from io import BytesIO +import matplotlib.pyplot as plt +import numpy as np + +from balloon.extract_wave import calculate_wave, is_terrain_wave + +lat = 52.21 +g = 9.76 + + +def plot_polynomial_fit(ucmp, vcmp, temp, height, poly_ucmp, poly_vcmp, poly_temp): + plt.figure(figsize=(16, 5)) + + # u 风扰动量 + plt.subplot(1, 3, 1) + plt.plot(ucmp, height, linestyle="-") + plt.plot(poly_ucmp(height), height) + plt.ylabel("Height(km)") + plt.xlabel("Zonal wind (m/s)") + plt.grid(True) + + # v 风扰动量 + plt.subplot(1, 3, 2) + plt.plot(vcmp, height, linestyle="-") + plt.plot(poly_vcmp(height), height) + plt.ylabel("Height(km)") + plt.xlabel("Meridional wind (m/s)") + plt.grid(True) + + plt.title("观测的二阶多项式拟合", fontsize=16) + + # 温度扰动量 + plt.subplot(1, 3, 3) + plt.plot(temp, height, linestyle="-") + plt.plot(poly_temp(height), height) + plt.ylabel("Height(km)") + plt.xlabel("Temperature(K)") + plt.grid(True) + + plt.tight_layout() + + +def plot_sine_fit(residual_ucmp, residual_vcmp, residual_temp, height, u_fit, v_fit, T_fit): + plt.figure(figsize=(16, 5)) + + # u 风扰动量 + plt.subplot(1, 3, 1) + plt.plot(residual_ucmp, height, marker="o", linestyle="None") + plt.plot(u_fit, height) + plt.ylabel("Height(km)") + plt.xlabel("Zonal wind (m/s)") + plt.grid(True) + + # v 风扰动量 + plt.subplot(1, 3, 2) + plt.plot(residual_vcmp, height, marker="o", linestyle="None") + plt.plot(v_fit, height) + plt.ylabel("Height(km)") + plt.xlabel("Meridional wind (m/s)") + plt.grid(True) + + plt.title("扰动分量的正弦波拟合", fontsize=16) + + # 温度扰动量 + plt.subplot(1, 3, 3) + plt.plot(residual_temp, height, marker="o", linestyle="None") + plt.plot(T_fit, height) + plt.ylabel("Height(km)") + plt.xlabel("Temperature(K)") + plt.grid(True) + + plt.tight_layout() + + +def plot_uv_vector(u_fit, v_fit, height, specified_heights, markers): + plt.figure(figsize=(8, 6)) + plt.plot(u_fit, v_fit) + for h, marker in zip(specified_heights, markers): + index = np.abs(height - h).argmin() + plt.scatter(u_fit[index], v_fit[index], + marker=marker, s=100, label=f"{h} km") + + plt.xlim(-8, 8) + plt.ylim(-4, 4) + plt.axvline(0, color="gray", linestyle="--") + plt.axhline(0, color="gray", linestyle="--") + plt.xlabel("Zonal Wind (m/s)") + plt.ylabel("Meridional Wind (m/s)") + plt.legend() + plt.grid(True) + plt.title("径向风-纬向风矢量图") + + +def plot_temp_horizontal_wind(uh, T_fit, height, specified_heights, markers): + plt.figure(figsize=(8, 6)) + plt.plot(uh, T_fit) + for h, marker in zip(specified_heights, markers): + index = np.abs(height - h).argmin() + plt.scatter(uh[index], T_fit[index], + marker=marker, s=100, label=f"{h} km") + + plt.xlim(-4, 4) + plt.ylim(-2, 2) + plt.axvline(0, color="gray", linestyle="--") + plt.axhline(0, color="gray", linestyle="--") + plt.xlabel("Horizontal wind (m/s)") + plt.ylabel("Temp (K)") + plt.legend() + plt.grid(True) + plt.title("温度-水平风矢量图") + + +def render_by_mode_single(data, mode): + wave = calculate_wave( + data[(data["alt"] >= 15) & (data["alt"] <= 25)], lat, g) + if len(wave) == 0: + return None + + c = is_terrain_wave(data, lat, g) + a, b, omega_upper, w_f, λ_z, λ_h, c_x, c_y, c_z, Ek, E_p, MFu1, MFv1, params_u, params_v, params_T = wave[ + :16] + ucmp, vcmp, temp, height, poly_ucmp, poly_vcmp, _, poly_temp, residual_ucmp, residual_vcmp, residual_temp, u_fit, v_fit, T_fit, uh, _ = wave[ + 16:] + + if mode == "观测的二阶多项式拟合": + plot_polynomial_fit(ucmp, vcmp, temp, height, poly_ucmp, + poly_vcmp, poly_temp) + elif mode == "扰动分量的正弦波拟合": + plot_sine_fit(residual_ucmp, residual_vcmp, residual_temp, + height, u_fit, v_fit, T_fit) + elif mode == "径向风-纬向风矢量图": + plot_uv_vector(u_fit, v_fit, height, [15, 20, 25], [ + "o", "s", "D"]) + elif mode == "温度-水平风矢量图": + plot_temp_horizontal_wind(uh, T_fit, height, [15, 20, 25], [ + "o", "s", "D"]) + else: + raise ValueError("Unknown mode") + plt.rcParams["font.sans-serif"] = ["SimHei"] # 显示中文 + buff = BytesIO() + plt.savefig(buff, format="png") + plt.close() + + buff.seek(0) + return buff diff --git a/balloon/plot_year.py b/balloon/plot_year.py new file mode 100644 index 0000000..80b7921 --- /dev/null +++ b/balloon/plot_year.py @@ -0,0 +1,359 @@ +import matplotlib.pyplot as plt +import matplotlib.gridspec as gridspec +import numpy as np +import pandas as pd +import seaborn as sns +from windrose import WindroseAxes + + +def plot_year(data: pd.DataFrame, path: str, lat: float, g: float): + if data.size == 0: + return False + + data.loc[:, "date"] = data["file_name"].str[:15] + filtered_df = data[["date"] + [col for col in data.columns if col != "file_name" and col != "date"]] + filtered_df.reset_index(drop=True, inplace=True) + + filtered_df = filtered_df.drop_duplicates(subset="date", keep="last") # 使用 drop_duplicates 函数,保留每组重复中的最后一个记录 + filtered_df.reset_index(drop=True, inplace=True) + + filtered_df = filtered_df[filtered_df["w_f"] < 10] # 筛选 w_f 值小于 10 的数据 + + # todo:1-删除不合理的数据 + # 1.先剔除明显异常的值 + for column in filtered_df.columns[1:]: # 不考虑第一列日期列 + filtered_df = filtered_df[(filtered_df[column] >= -9999) & (filtered_df[column] <= 9999)] # 460 + + # 2.再用四分位数法,适合所有数据集 + def remove_outliers_iqr(df): + for column in df.columns[9:]: # 从第10列开始 + Q1 = df[column].quantile(0.25) + Q3 = df[column].quantile(0.75) + IQR = Q3 - Q1 + lower_bound = Q1 - 5 * IQR + upper_bound = Q3 + 5 * IQR + + # 过滤掉异常值 + df = df[(df[column] >= lower_bound) & (df[column] <= upper_bound)] + return df + + filtered_df = remove_outliers_iqr(filtered_df) # 408 + + # 画图 + + fig = plt.figure(figsize=(36, 20)) + gs = gridspec.GridSpec(4, 12) + + ax1 = fig.add_subplot(gs[0, 0:3]) + ax2 = fig.add_subplot(gs[0, 3:6]) + ax3 = fig.add_subplot(gs[0, 6:9]) + ax4 = fig.add_subplot(gs[0, 9:12]) + + ax5_1 = fig.add_subplot(gs[1, 0:2]) + ax5_2 = fig.add_subplot(gs[1, 2:4]) + ax5_3 = fig.add_subplot(gs[1, 4:6]) + ax6_1 = fig.add_subplot(gs[1, 6:8]) + ax6_2 = fig.add_subplot(gs[1, 8:10]) + ax6_3 = fig.add_subplot(gs[1, 10:12]) + + ax7_1 = fig.add_subplot(gs[2, 0:2]) + ax7_2 = fig.add_subplot(gs[2, 2:4]) + ax8 = fig.add_subplot(gs[2, 4:8]) + ax9 = fig.add_subplot(gs[2, 8:12]) + + ax10 = [] + for i in range(0, 10, 3): + ax10.append(fig.add_subplot(gs[3, i : i + 3], projection="windrose")) + + sns.set_theme(style="whitegrid", font="SimHei") # 设置绘图样式为白色背景和网格线 + + # 1、w/f比值 + # 计算bins的边界 + min_val = 1 + max_val = 10 + bin_width = 0.5 + # 创建bins的边界 + bins = np.arange(min_val, max_val + bin_width, bin_width) + + sns.histplot(filtered_df["w_f"], bins=bins, kde=False, edgecolor="black", stat="percent", ax=ax1) # 加上stat='percent'表示算的是频率 + # 设置x轴范围 + ax1.set_xlim(min_val, max_val) + # 添加标题和标签 + ax1.set_title("w/f值统计结果", fontsize=24) + ax1.set_xlabel("w/f") + ax1.set_ylabel("Occurrence(%)") + + # 2、周期 + # 计算bins的边界 + min_val = 1 + max_val = 10 + bin_width = 0.5 + # 创建bins的边界 + bins = np.arange(min_val, max_val + bin_width, bin_width) + + min_val = np.floor(filtered_df["zhou_qi"].min()) # 向下取整 + max_val = np.ceil(filtered_df["zhou_qi"].max()) # 向上取整 + bin_width = 1 + bins = np.arange(min_val, max_val + bin_width, bin_width) + + sns.histplot(filtered_df["zhou_qi"], bins=bins, kde=False, edgecolor="black", stat="percent", ax=ax2) # 加上stat='percent'表示算的是频率 + # 设置x轴范围 + ax2.set_xlim(min_val, max_val) + # 添加标题和标签 + ax2.set_title("周期统计结果", fontsize=24) + ax2.set_xlabel("h") + ax2.set_ylabel("Occurrence(%)") + + # 3、垂直波长 + # 创建bins的边界 + min_val = np.floor(filtered_df["ver_wave_len"].min()) # 向下取整 + max_val = np.ceil(filtered_df["ver_wave_len"].max()) # 向上取整 + bin_width = 0.5 + bins = np.arange(min_val, max_val + bin_width, bin_width) + + sns.histplot(filtered_df["ver_wave_len"], bins=bins, kde=False, edgecolor="black", stat="percent", ax=ax3) # 加上stat='percent'表示算的是频率 + # 设置x轴范围 + ax3.set_xlim(min_val, max_val) + # 添加标题和标签 + ax3.set_title("垂直波长分布", fontsize=24) + ax3.set_xlabel("Vertical wavelength(km)") + ax3.set_ylabel("Occurrence(%)") + + # 4、水平波长 + # 创建bins的边界 + min_val = np.floor(filtered_df["hori_wave_len"].min()) # 向下取整 + max_val = np.ceil(filtered_df["hori_wave_len"].max()) # 向上取整 + bin_width = 100 + bins = np.arange(min_val, max_val + bin_width, bin_width) + + sns.histplot(filtered_df["hori_wave_len"], bins=bins, kde=False, edgecolor="black", stat="percent", ax=ax4) # 加上stat='percent'表示算的是频率 + # 设置x轴范围 + ax4.set_xlim(min_val, max_val) + # 添加标题和标签 + ax4.set_title("水平波长分布", fontsize=24) + ax4.set_xlabel("Horizontal wavelength(km)") + ax4.set_ylabel("Occurrence(%)") + + # 5、本征相速度 + + # 纬向本征相速度 + # 计算bins的边界 + min_val = np.floor(filtered_df["c_x"].min()) # 向下取整 + max_val = np.ceil(filtered_df["c_x"].max()) # 向上取整 + bin_width = 10 + bins = np.arange(min_val, max_val + bin_width, bin_width) + # 创建纬向直方图 + sns.histplot(filtered_df["c_x"], bins=bins, kde=False, edgecolor="black", stat="percent", ax=ax5_1) + ax5_1.set_xlim(min_val, max_val) + ax5_1.set_title("纬向本征相速度", fontsize=24) + ax5_1.set_xlabel("Zonal phase speed (m/s)") + ax5_1.set_ylabel("Occurrence (%)") + + # 经向本征相速度 + # 计算bins的边界 + min_val = np.floor(filtered_df["c_y"].min()) # 向下取整 + max_val = np.ceil(filtered_df["c_y"].max()) # 向上取整 + bin_width = 10 + bins = np.arange(min_val, max_val + bin_width, bin_width) + # 创建经向直方图 + sns.histplot(filtered_df["c_y"], bins=bins, kde=False, edgecolor="black", stat="percent", ax=ax5_2) + ax5_2.set_xlim(min_val, max_val) + ax5_2.set_title("经向本征相速度", fontsize=24) + ax5_2.set_xlabel("Meridional phase speed (m/s)") + ax5_2.set_ylabel("Occurrence (%)") + + # 垂直本征相速度 + # 计算bins的边界 + min_val = filtered_df["c_z"].min() # -1.148 + max_val = filtered_df["c_z"].max() # 0.624 + bin_width = 0.1 + bins = np.arange(min_val, max_val + bin_width, bin_width) + # 创建垂直直方图 + sns.histplot(filtered_df["c_z"], bins=bins, kde=False, edgecolor="black", stat="percent", ax=ax5_3) + ax5_3.set_xlim(min_val, max_val) + ax5_3.set_title("垂直本征相速度", fontsize=24) + ax5_3.set_xlabel("Vertical phase speed (m/s)") + ax5_3.set_ylabel("Occurrence (%)") + + # 6、扰动振幅 + + # 纬向扰动振幅 + # 计算bins的边界 + min_val = np.floor(filtered_df["u1"].min()) # 向下取整 + max_val = np.ceil(filtered_df["u1"].max()) # 向上取整 + bin_width = 0.5 + bins = np.arange(min_val, max_val + bin_width, bin_width) + # 创建纬向直方图 + sns.histplot(filtered_df["u1"], bins=bins, kde=False, edgecolor="black", stat="percent", ax=ax6_1) + ax6_1.set_xlim(min_val, max_val) + ax6_1.set_title(" ", fontsize=24) + ax6_1.set_xlabel("Zonal wind amplitude (m/s)") + ax6_1.set_ylabel("Occurrence (%)") + + # 经向扰动振幅 + # 计算bins的边界 + min_val = np.floor(filtered_df["v1"].min()) # 向下取整 + max_val = np.ceil(filtered_df["v1"].max()) # 向上取整 + bin_width = 0.5 + bins = np.arange(min_val, max_val + bin_width, bin_width) + # 创建经向直方图 + sns.histplot(filtered_df["v1"], bins=bins, kde=False, edgecolor="black", stat="percent", ax=ax6_2) + ax6_2.set_xlim(min_val, max_val) + ax6_2.set_title("扰动振幅统计结果", fontsize=24) + ax6_2.set_xlabel("Meridional wind amplitude (m/s)") + ax6_2.set_ylabel("Occurrence (%)") + + # 垂直扰动振幅 + # 计算bins的边界 + min_val = np.floor(filtered_df["T1"].min()) # 向下取整 + max_val = np.ceil(filtered_df["T1"].max()) # 向上取整 + bin_width = 0.5 + bins = np.arange(min_val, max_val + bin_width, bin_width) + # 创建垂直直方图 + sns.histplot(filtered_df["T1"], bins=bins, kde=False, edgecolor="black", stat="percent", ax=ax6_3) + ax6_3.set_xlim(min_val, max_val) + ax6_3.set_title(" ", fontsize=24) + ax6_3.set_xlabel("Temperature amplitude (K)") + ax6_3.set_ylabel("Occurrence (%)") + + # 7、动量通量 + # 挑选出向上传的重力波 + filtered_df1 = filtered_df[filtered_df["a"] == 1] + + # 绘制第一个子图 + # 计算bins的边界 + min_val = np.floor(filtered_df1["MFu"].min()) # 向下取整 + max_val = np.ceil(filtered_df1["MFu"].max()) # 向上取整 + bin_width = 0.1 + bins = np.arange(min_val, max_val + bin_width, bin_width) + + sns.histplot(filtered_df1["MFu"], bins=bins, kde=False, edgecolor="black", stat="percent", ax=ax7_1) + ax7_1.set_xlim(min_val, max_val) # 设置x轴范围 + ax7_1.set_title("纬向动量通量统计结果", fontsize=24) # 添加标题 + ax7_1.set_xlabel("Zonal momentum flux(mPa)") # x轴标签 + ax7_1.set_ylabel("Occurrence(%)") # y轴标签 + + # 绘制第二个子图 + # 计算bins的边界 + min_val = np.floor(filtered_df1["MFv"].min()) # 向下取整 + max_val = np.ceil(filtered_df1["MFv"].max()) # 向上取整 + bin_width = 0.1 + bins = np.arange(min_val, max_val + bin_width, bin_width) + + sns.histplot(filtered_df1["MFv"], bins=bins, kde=False, edgecolor="black", stat="percent", ax=ax7_2) + ax7_2.set_xlim(min_val, max_val) # 设置x轴范围 + ax7_2.set_title("经向动量通量统计结果", fontsize=24) # 添加标题 + ax7_2.set_xlabel("Meridional momentum flux(mPa)") # x轴标签 + ax7_2.set_ylabel("Occurrence(%)") # y轴标签 + + # 10、水平传播方向 + + filtered_df["date1"] = filtered_df["date"].str.split("T").str[0] # 再加一列,只保留日期部分 + filtered_df["date1"] = pd.to_datetime(filtered_df["date1"], format="%Y%m%d") # 确保 'date1' 列是日期格式 + + # 添加季节列 + def get_season(date): + month = date.month + if month in [12, 1, 2]: + return "Winter" + elif month in [3, 4, 5]: + return "Spring" + elif month in [6, 7, 8]: + return "Summer" + else: + return "Fall" + + filtered_df["season"] = filtered_df["date1"].apply(get_season) # 添加季节列 + seasons = ["Winter", "Spring", "Summer", "Fall"] + + for ax, season in zip(ax10, seasons): + season_data = filtered_df[filtered_df["season"] == season] + windrose = WindroseAxes.from_ax(ax) + ax.set_title(season, fontsize=18) + windrose.bar(season_data["b"], np.ones_like(season_data["b"]), normed=False) # normed=True表示占每个季节的占比 + + # # 添加总标题 + # fig.suptitle("水平传播方向在各个季节的分布变化", fontsize=16, fontweight="bold") + + # 8、垂直传播方向 + # 设置 日期'date1' 列为索引 + filtered_df.set_index("date1", inplace=True) + + # 按月份分组并计算百分比 + monthly_stats_df = ( + filtered_df.groupby([filtered_df.index.month, filtered_df.index.year]) + .apply(lambda x: pd.Series({"Upload (%)": (x["a"] == 1).mean() * 100, "Downward (%)": (x["a"] == -1).mean() * 100})) + .reset_index(level=1, drop=True) + ) + + # 按月份汇总这些年的数据 + monthly_avg_stats_df = monthly_stats_df.groupby(level=0).mean() + # 确保索引是 numpy 数组 + dates = monthly_avg_stats_df.index.to_numpy() + + # 绘制折线图 + ax8.plot(dates, monthly_avg_stats_df["Upload (%)"].to_numpy(), marker="o", label="Up (%)") + ax8.plot(dates, monthly_avg_stats_df["Downward (%)"].to_numpy(), marker="o", label="Down (%)") + + # 设置月份标签 + ax8.set_xticks( + ticks=np.arange(1, 13), labels=["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], rotation=0 + ) # 不倾斜,rotation=0表示倾斜45° + # 添加图例、标题和坐标轴标签 + ax8.legend() + ax8.set_title("每月上传/下传重力波占比", fontsize=24) + ax8.set_xlabel("Month") + ax8.set_ylabel("Percentage (%)") + + # 9、动能和势能 + filtered_df.reset_index(inplace=True) # 取消索引并恢复为默认整数索引 + # 提取年月 + filtered_df["year_month"] = filtered_df["date1"].dt.to_period("M") + # 计算每个月的动能和势能的平均值 + monthly_avg = filtered_df.groupby("year_month")[["Ek", "E_p"]].mean() + + # 创建完整的月份范围(因为有的月份没数据) + full_range = pd.period_range(start=monthly_avg.index.min(), end=monthly_avg.index.max(), freq="M") + + # 创建一个新的 DataFrame 使用完整的年份月份范围 + full_monthly_avg = pd.DataFrame(index=full_range) + # 将原始数据合并到新的 DataFrame 中 + full_monthly_avg = full_monthly_avg.join(monthly_avg) + + # 确保 'Ek' 和 'E_p' 列为数值型 + full_monthly_avg["Ek"] = pd.to_numeric(full_monthly_avg["Ek"], errors="coerce") + full_monthly_avg["E_p"] = pd.to_numeric(full_monthly_avg["E_p"], errors="coerce") + + # 只显示每年6月、12月,简化图形 + + # 绘制 Ek、E_p + ax9.plot(full_monthly_avg.index.values.astype(str), full_monthly_avg["Ek"].values, marker="o", linestyle="-", color="r", label="动能月平均值") + ax9.plot(full_monthly_avg.index.values.astype(str), full_monthly_avg["E_p"].values, marker="o", linestyle="-", color="b", label="势能月平均值") + + # 添加标题和标签 + ax9.set_title("动能和势能分布情况", fontsize=24) + ax9.set_xlabel("Month", fontsize=14) + ax9.set_ylabel("Wave energy (J/kg)", fontsize=14) + + # 设定横轴标签 + months = full_monthly_avg.index.values.astype(str) + # 获取所有年份的6月和12月的索引 + june_indices = [i for i, date in enumerate(months) if date.endswith("-06")] + december_indices = [i for i, date in enumerate(months) if date.endswith("-12")] + selected_indices = june_indices + december_indices + + # 只显示选定的标签 + ax9.set_xticks(ticks=selected_indices, labels=[months[i] for i in selected_indices], rotation=45) + + # 添加网格和图例 + # plt.grid() + ax9.legend() # 显示图例 + + plt.rcParams["font.sans-serif"] = ["SimHei"] # 显示中文 + plt.rcParams["axes.unicode_minus"] = False # 正常显示负号 + + plt.tight_layout() + plt.savefig(path) + plt.close() + + return True diff --git a/balloon/plot_year_backend.py b/balloon/plot_year_backend.py new file mode 100644 index 0000000..7409352 --- /dev/null +++ b/balloon/plot_year_backend.py @@ -0,0 +1,286 @@ +from io import BytesIO +import numpy as np +import pandas as pd +import seaborn as sns +from windrose import WindroseAxes + +import matplotlib.pyplot as plt +import matplotlib.gridspec as gridspec + + +def plot_w_f(filtered_df, ax): + min_val = 1 + max_val = 10 + bin_width = 0.5 + bins = np.arange(min_val, max_val + bin_width, bin_width) + sns.histplot(filtered_df["w_f"], bins=bins, kde=False, + edgecolor="black", stat="percent", ax=ax) + ax.set_xlim(min_val, max_val) + ax.set_title("w/f值统计结果", fontsize=24) + ax.set_xlabel("w/f") + ax.set_ylabel("Occurrence(%)") + + +def plot_zhou_qi(filtered_df, ax): + min_val = np.floor(filtered_df["zhou_qi"].min()) + max_val = np.ceil(filtered_df["zhou_qi"].max()) + bin_width = 1 + bins = np.arange(min_val, max_val + bin_width, bin_width) + sns.histplot(filtered_df["zhou_qi"], bins=bins, + kde=False, edgecolor="black", stat="percent", ax=ax) + ax.set_xlim(min_val, max_val) + ax.set_title("周期统计结果", fontsize=24) + ax.set_xlabel("h") + ax.set_ylabel("Occurrence(%)") + + +def plot_ver_wave_len(filtered_df, ax): + min_val = np.floor(filtered_df["ver_wave_len"].min()) + max_val = np.ceil(filtered_df["ver_wave_len"].max()) + bin_width = 0.5 + bins = np.arange(min_val, max_val + bin_width, bin_width) + sns.histplot(filtered_df["ver_wave_len"], bins=bins, + kde=False, edgecolor="black", stat="percent", ax=ax) + ax.set_xlim(min_val, max_val) + ax.set_title("垂直波长分布", fontsize=24) + ax.set_xlabel("Vertical wavelength(km)") + ax.set_ylabel("Occurrence(%)") + + +def plot_hori_wave_len(filtered_df, ax): + min_val = np.floor(filtered_df["hori_wave_len"].min()) + max_val = np.ceil(filtered_df["hori_wave_len"].max()) + bin_width = 100 + bins = np.arange(min_val, max_val + bin_width, bin_width) + sns.histplot(filtered_df["hori_wave_len"], bins=bins, + kde=False, edgecolor="black", stat="percent", ax=ax) + ax.set_xlim(min_val, max_val) + ax.set_title("水平波长分布", fontsize=24) + ax.set_xlabel("Horizontal wavelength(km)") + ax.set_ylabel("Occurrence(%)") + + +def plot_c_x(filtered_df, ax): + min_val = np.floor(filtered_df["c_x"].min()) + max_val = np.ceil(filtered_df["c_x"].max()) + bin_width = 10 + bins = np.arange(min_val, max_val + bin_width, bin_width) + sns.histplot(filtered_df["c_x"], bins=bins, kde=False, + edgecolor="black", stat="percent", ax=ax) + ax.set_xlim(min_val, max_val) + ax.set_title("纬向本征相速度", fontsize=24) + ax.set_xlabel("Zonal phase speed (m/s)") + ax.set_ylabel("Occurrence (%)") + + +def plot_c_y(filtered_df, ax): + min_val = np.floor(filtered_df["c_y"].min()) + max_val = np.ceil(filtered_df["c_y"].max()) + bin_width = 10 + bins = np.arange(min_val, max_val + bin_width, bin_width) + sns.histplot(filtered_df["c_y"], bins=bins, kde=False, + edgecolor="black", stat="percent", ax=ax) + ax.set_xlim(min_val, max_val) + ax.set_title("经向本征相速度", fontsize=24) + ax.set_xlabel("Meridional phase speed (m/s)") + ax.set_ylabel("Occurrence (%)") + + +def plot_c_z(filtered_df, ax): + min_val = filtered_df["c_z"].min() + max_val = filtered_df["c_z"].max() + bin_width = 0.1 + bins = np.arange(min_val, max_val + bin_width, bin_width) + sns.histplot(filtered_df["c_z"], bins=bins, kde=False, + edgecolor="black", stat="percent", ax=ax) + ax.set_xlim(min_val, max_val) + ax.set_title("垂直本征相速度", fontsize=24) + ax.set_xlabel("Vertical phase speed (m/s)") + ax.set_ylabel("Occurrence (%)") + + +def plot_u1(filtered_df, ax): + min_val = np.floor(filtered_df["u1"].min()) + max_val = np.ceil(filtered_df["u1"].max()) + bin_width = 0.5 + bins = np.arange(min_val, max_val + bin_width, bin_width) + sns.histplot(filtered_df["u1"], bins=bins, kde=False, + edgecolor="black", stat="percent", ax=ax) + ax.set_xlim(min_val, max_val) + ax.set_title(" ", fontsize=24) + ax.set_xlabel("Zonal wind amplitude (m/s)") + ax.set_ylabel("Occurrence (%)") + + +def plot_v1(filtered_df, ax): + min_val = np.floor(filtered_df["v1"].min()) + max_val = np.ceil(filtered_df["v1"].max()) + bin_width = 0.5 + bins = np.arange(min_val, max_val + bin_width, bin_width) + sns.histplot(filtered_df["v1"], bins=bins, kde=False, + edgecolor="black", stat="percent", ax=ax) + ax.set_xlim(min_val, max_val) + ax.set_title("扰动振幅统计结果", fontsize=24) + ax.set_xlabel("Meridional wind amplitude (m/s)") + ax.set_ylabel("Occurrence (%)") + + +def plot_T1(filtered_df, ax): + min_val = np.floor(filtered_df["T1"].min()) + max_val = np.ceil(filtered_df["T1"].max()) + bin_width = 0.5 + bins = np.arange(min_val, max_val + bin_width, bin_width) + sns.histplot(filtered_df["T1"], bins=bins, kde=False, + edgecolor="black", stat="percent", ax=ax) + ax.set_xlim(min_val, max_val) + ax.set_title(" ", fontsize=24) + ax.set_xlabel("Temperature amplitude (K)") + ax.set_ylabel("Occurrence (%)") + + +def plot_MFu(filtered_df1, ax): + min_val = np.floor(filtered_df1["MFu"].min()) + max_val = np.ceil(filtered_df1["MFu"].max()) + bin_width = 0.1 + bins = np.arange(min_val, max_val + bin_width, bin_width) + sns.histplot(filtered_df1["MFu"], bins=bins, kde=False, + edgecolor="black", stat="percent", ax=ax) + ax.set_xlim(min_val, max_val) + ax.set_title("纬向动量通量统计结果", fontsize=24) + ax.set_xlabel("Zonal momentum flux(mPa)") + ax.set_ylabel("Occurrence(%)") + + +def plot_MFv(filtered_df1, ax): + min_val = np.floor(filtered_df1["MFv"].min()) + max_val = np.ceil(filtered_df1["MFv"].max()) + bin_width = 0.1 + bins = np.arange(min_val, max_val + bin_width, bin_width) + sns.histplot(filtered_df1["MFv"], bins=bins, kde=False, + edgecolor="black", stat="percent", ax=ax) + ax.set_xlim(min_val, max_val) + ax.set_title("经向动量通量统计结果", fontsize=24) + ax.set_xlabel("Meridional momentum flux(mPa)") + ax.set_ylabel("Occurrence(%)") + + +def plot_horizontal_propagation(filtered_df, ax, season): + season_data = filtered_df[filtered_df["season"] == season] + windrose = WindroseAxes.from_ax(ax) + ax.set_title(season, fontsize=18) + windrose.bar(season_data["b"], np.ones_like( + season_data["b"]), normed=False) + + +def plot_vertical_propagation(filtered_df, ax): + filtered_df.set_index("date1", inplace=True) + monthly_stats_df = ( + filtered_df.groupby([filtered_df.index.month, filtered_df.index.year]) + .apply(lambda x: pd.Series({"Upload (%)": (x["a"] == 1).mean() * 100, "Downward (%)": (x["a"] == -1).mean() * 100})) + .reset_index(level=1, drop=True) + ) + monthly_avg_stats_df = monthly_stats_df.groupby(level=0).mean() + dates = monthly_avg_stats_df.index.to_numpy() + ax.plot(dates, monthly_avg_stats_df["Upload (%)"].to_numpy( + ), marker="o", label="Up (%)") + ax.plot(dates, monthly_avg_stats_df["Downward (%)"].to_numpy( + ), marker="o", label="Down (%)") + ax.set_xticks( + ticks=np.arange(1, 13), labels=["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], rotation=0 + ) + ax.legend() + ax.set_title("每月上传/下传重力波占比", fontsize=24) + ax.set_xlabel("Month") + ax.set_ylabel("Percentage (%)") + + +def plot_energy_distribution(filtered_df, ax): + filtered_df.reset_index(inplace=True) + filtered_df["year_month"] = filtered_df["date1"].dt.to_period("M") + monthly_avg = filtered_df.groupby("year_month")[["Ek", "E_p"]].mean() + full_range = pd.period_range( + start=monthly_avg.index.min(), end=monthly_avg.index.max(), freq="M") + full_monthly_avg = pd.DataFrame(index=full_range).join(monthly_avg) + full_monthly_avg["Ek"] = pd.to_numeric( + full_monthly_avg["Ek"], errors="coerce") + full_monthly_avg["E_p"] = pd.to_numeric( + full_monthly_avg["E_p"], errors="coerce") + ax.plot(full_monthly_avg.index.values.astype( + str), full_monthly_avg["Ek"].values, marker="o", linestyle="-", color="r", label="动能月平均值") + ax.plot(full_monthly_avg.index.values.astype( + str), full_monthly_avg["E_p"].values, marker="o", linestyle="-", color="b", label="势能月平均值") + ax.set_title("动能和势能分布情况", fontsize=24) + ax.set_xlabel("Month", fontsize=14) + ax.set_ylabel("Wave energy (J/kg)", fontsize=14) + months = full_monthly_avg.index.values.astype(str) + june_indices = [i for i, date in enumerate(months) if date.endswith("-06")] + december_indices = [i for i, date in enumerate( + months) if date.endswith("-12")] + selected_indices = june_indices + december_indices + ax.set_xticks(ticks=selected_indices, labels=[ + months[i] for i in selected_indices], rotation=45) + ax.legend() + + +# def render_based_on_mode(df, mode, seaon = None): +def render_based_on_mode(df, mode, season=None): + buf = BytesIO() + fig, ax = plt.subplots(figsize=(10, 6)) + if mode == "w/f值统计结果": + plot_w_f(df, ax) + elif mode == "周期统计结果": + plot_zhou_qi(df, ax) + elif mode == "垂直波长分布": + plot_ver_wave_len(df, ax) + elif mode == "水平波长分布": + plot_hori_wave_len(df, ax) + elif mode == "纬向本征相速度": + plot_c_x(df, ax) + elif mode == "经向本征相速度": + plot_c_y(df, ax) + elif mode == "垂直本征相速度": + plot_c_z(df, ax) + elif mode == "Zonal wind amplitude (m/s)": + plot_u1(df, ax) + elif mode == "扰动振幅统计结果": + plot_v1(df, ax) + elif mode == "Temperature amplitude (K)": + plot_T1(df, ax) + elif mode == "纬向动量通量统计结果": + plot_MFu(df, ax) + elif mode == "经向动量通量统计结果": + plot_MFv(df, ax) + elif mode == "horizontal propagation": + plot_horizontal_propagation(df, ax, season) + elif mode == "每月上传/下传重力波占比": + plot_vertical_propagation(df, ax) + elif mode == "动能和势能分布情况": + plot_energy_distribution(df, ax) + else: + raise ValueError("Invalid mode") + plt.rcParams["font.sans-serif"] = ["SimHei"] # 显示中文 + fig.savefig(buf) + buf.seek(0) + plt.close() + return buf +# if mode == "" + + +def get_all_modes(): + return [ + "w/f值统计结果", + "周期统计结果", + "垂直波长分布", + "水平波长分布", + "纬向本征相速度", + "经向本征相速度", + "垂直本征相速度", + "Zonal wind amplitude (m/s)", + "扰动振幅统计结果", + "Temperature amplitude (K)", + "纬向动量通量统计结果", + "经向动量通量统计结果", + "horizontal propagation", + "每月上传/下传重力波占比", + "动能和势能分布情况", + ] diff --git a/balloon/read_data.py b/balloon/read_data.py new file mode 100644 index 0000000..ceaf4a6 --- /dev/null +++ b/balloon/read_data.py @@ -0,0 +1,41 @@ +import xarray as xr +import pandas as pd +import numpy as np +from scipy.interpolate import interp1d + + +# 定义四舍五入函数 +def round_to_nearest_multiple(value, multiple): + return round(value / multiple) * multiple + + +def read_data(path): + # 读取数据 + with open(path, "rb") as f: + df = xr.open_dataset(f).to_dataframe().dropna(how="any") + + # 提取指定列 + columns_to_extract = ["alt", "press", "temp", "rh", "u", "v", "wspeed"] + extracted_df = df[columns_to_extract].copy() + + # 进行单位转换 + extracted_df["alt"] = extracted_df["alt"] / 1000 # km + extracted_df["rh"] = extracted_df["rh"] * 100 # % + + # 移除重复的高度值 + extracted_df = extracted_df.drop_duplicates(subset=["alt"]) + new_height = np.arange(extracted_df["alt"].min(), extracted_df["alt"].max() + 0.05, 0.05) + + # 将每个高度值转换为最接近0.05的整数倍,并转化为数组 + rounded_heights = [round_to_nearest_multiple(height, 0.05) for height in new_height] + rounded_heights_np = np.array(rounded_heights) + + # 初始化一个新的 DataFrame 用于存储插值结果 + interpolated_data = pd.DataFrame({"alt": rounded_heights_np}) + + # 对每个因变量进行线性插值 + for col in ["press", "temp", "rh", "u", "v", "wspeed"]: + interp_func = interp1d(extracted_df["alt"], extracted_df[col], kind="linear", fill_value="extrapolate") + interpolated_data[col] = interp_func(rounded_heights_np) + + return interpolated_data diff --git a/cosmic/multiple.py b/cosmic/multiple.py new file mode 100644 index 0000000..ddb932b --- /dev/null +++ b/cosmic/multiple.py @@ -0,0 +1,538 @@ +import os +import numpy as np +import pandas as pd +from scipy.interpolate import interp1d +from scipy.optimize import curve_fit +import netCDF4 as nc +import matplotlib.pyplot as plt +import seaborn as sns +# 设置支持中文的字体 +plt.rcParams['font.sans-serif'] = ['Microsoft YaHei'] # 设置字体为微软雅黑 +plt.rcParams['axes.unicode_minus'] = False # 正常显示负号 + +# 定义处理单个文件的函数 + + +def process_single_file(base_folder_path, i): + # 构建当前文件夹的路径 + folder_name = f"atmPrf_repro2021_2018_00{i}" if i < 10 else f"atmPrf_repro2021_2008_0{i}" + folder_path = os.path.join(base_folder_path, folder_name) + # 检查文件夹是否存在 + if os.path.exists(folder_path): + dfs = [] + # 遍历文件夹中的文件 + for file_name in os.listdir(folder_path): + if file_name.endswith('.0390_nc'): + finfo = os.path.join(folder_path, file_name) + print(f"正在处理文件: {finfo}") + try: + dataset = nc.Dataset(finfo, 'r') + # 提取变量数据 + temp = dataset.variables['Temp'][:] + altitude = dataset.variables['MSL_alt'][:] + lat = dataset.variables['Lat'][:] + lon = dataset.variables['Lon'][:] + # 创建DataFrame + df = pd.DataFrame({ + 'Longitude': lon, + 'Latitude': lat, + 'Altitude': altitude, + 'Temperature': temp + }) + dataset.close() + # 剔除高度大于60的行 + df = df[df['Altitude'] <= 60] + # 对每个文件的数据进行插值 + alt_interp = np.linspace( + df['Altitude'].min(), df['Altitude'].max(), 3000) + f_alt = interp1d( + df['Altitude'], df['Altitude'], kind='linear', fill_value="extrapolate") + f_lon = interp1d( + df['Altitude'], df['Longitude'], kind='linear', fill_value="extrapolate") + f_lat = interp1d( + df['Altitude'], df['Latitude'], kind='linear', fill_value="extrapolate") + f_temp = interp1d( + df['Altitude'], df['Temperature'], kind='linear', fill_value="extrapolate") + # 计算插值结果 + interpolated_alt = f_alt(alt_interp) + interpolated_lon = f_lon(alt_interp) + interpolated_lat = f_lat(alt_interp) + interpolated_temp = f_temp(alt_interp) + # 创建插值后的DataFrame + interpolated_df = pd.DataFrame({ + 'Altitude': interpolated_alt, + 'Longitude': interpolated_lon, + 'Latitude': interpolated_lat, + 'Temperature': interpolated_temp + }) + # 将插值后的DataFrame添加到列表中 + dfs.append(interpolated_df) + except Exception as e: + print(f"处理文件 {finfo} 时出错: {e}") + # 按行拼接所有插值后的DataFrame + final_df = pd.concat(dfs, axis=0, ignore_index=True) + # 获取 DataFrame 的长度 + num_rows = len(final_df) + # 生成一个每3000个数从0到2999的序列并重复 + altitude_values = np.tile( + np.arange(3000), num_rows // 3000 + 1)[:num_rows] + # 将生成的值赋给 DataFrame 的 'Altitude' 列 + final_df['Altitude'] = altitude_values + # 摄氏度换算开尔文 + final_df['Temperature'] = final_df['Temperature'] + 273.15 + + # 筛选出纬度在30到40度之间的数据 + latitude_filtered_df = final_df[( + final_df['Latitude'] >= 30) & (final_df['Latitude'] <= 40)] + + # 划分经度网格,20°的网格 + lon_min, lon_max = latitude_filtered_df['Longitude'].min( + ), latitude_filtered_df['Longitude'].max() + lon_bins = np.arange(lon_min, lon_max + 20, 20) # 创建经度网格边界 + # 将数据分配到网格中 + latitude_filtered_df['Longitude_Grid'] = np.digitize( + latitude_filtered_df['Longitude'], lon_bins) - 1 + + # 对相同高度的温度取均值,忽略NaN + altitude_temperature_mean = latitude_filtered_df.groupby( + 'Altitude')['Temperature'].mean().reset_index() + # 重命名列,使其更具可读性 + altitude_temperature_mean.columns = ['Altitude', 'Mean_Temperature'] + + # 定义高度的范围(这里从0到最短段) + altitude_range = range(0, 3000) + all_heights_mean_temperature = [] # 用于存储所有高度下的温度均值结果 + for altitude in altitude_range: + # 筛选出当前高度的所有数据 + altitude_df = latitude_filtered_df[latitude_filtered_df['Altitude'] == altitude] + # 对Longitude_Grid同一区间的温度取均值 + temperature_mean_by_grid = altitude_df.groupby( + 'Longitude_Grid')['Temperature'].mean().reset_index() + # 重命名列,使其更具可读性 + temperature_mean_by_grid.columns = [ + 'Longitude_Grid', 'Mean_Temperature'] + # 添加高度信息列,方便后续区分不同高度的结果 + temperature_mean_by_grid['Altitude'] = altitude + # 将当前高度的结果添加到列表中 + all_heights_mean_temperature.append(temperature_mean_by_grid) + # 将所有高度的结果合并为一个DataFrame + combined_mean_temperature_df = pd.concat( + all_heights_mean_temperature, ignore_index=True) + + # 基于Altitude列合并两个DataFrame,只保留能匹配上的行 + merged_df = pd.merge(combined_mean_temperature_df, + altitude_temperature_mean, on='Altitude', how='inner') + # 计算差值(减去wn0的扰动) + merged_df['Temperature_Difference'] = merged_df['Mean_Temperature_x'] - \ + merged_df['Mean_Temperature_y'] + + # 按Altitude分组 + grouped = merged_df.groupby('Altitude') + + def single_harmonic(x, A, phi): + return A * np.sin(2 * np.pi / (18 / k) * x + phi) + # 初始化存储每个高度的最佳拟合参数、拟合曲线、残差值以及背景温度的字典 + fit_results = {} + fitted_curves = {} + residuals = {} + background_temperatures = {} + for altitude, group in grouped: + y_data = group['Temperature_Difference'].values + x_data = np.arange(len(y_data)) + wn0_data = group['Mean_Temperature_y'].values # 获取同一高度下的wn0数据 + # 检查Temperature_Difference列是否全部为NaN + if np.all(np.isnan(y_data)): + fit_results[altitude] = { + 'A': [np.nan] * 5, 'phi': [np.nan] * 5} + fitted_curves[altitude] = [np.nan * x_data] * 5 + residuals[altitude] = np.nan * x_data + background_temperatures[altitude] = np.nan * x_data + else: + # 替换NaN值为非NaN值的均值 + y_data = np.where(np.isnan(y_data), np.nanmean(y_data), y_data) + # 初始化存储WN参数和曲线的列表 + wn_params = [] + wn_curves = [] + # 计算wn0(使用Mean_Temperature_y列数据) + wn0 = wn0_data + + # 对WN1至WN5进行拟合 + for k in range(1, 6): + # 更新单谐波函数中的k值 + def harmonic_func( + x, A, phi): return single_harmonic(x, A, phi) + # 使用curve_fit进行拟合 + popt, pcov = curve_fit(harmonic_func, x_data, y_data, p0=[ + np.nanmax(y_data) - np.nanmin(y_data), 0]) + A_fit, phi_fit = popt + # 存储拟合结果 + wn_params.append({'A': A_fit, 'phi': phi_fit}) + # 使用拟合参数生成拟合曲线 + WN = harmonic_func(x_data, A_fit, phi_fit) + wn_curves.append(WN) + # 计算残差值 + y_data = y_data - WN # 使用残差值作为下一次拟合的y_data + # 存储结果 + fit_results[altitude] = wn_params + fitted_curves[altitude] = wn_curves + residuals[altitude] = y_data + # 计算同一高度下的背景温度(wn0 + wn1 + wn2 + wn3 + wn4 + wn5) + wn_sum = np.sum([wn0] + wn_curves, axis=0) + background_temperatures[altitude] = wn_sum + + # 将每个字典转换成一个 DataFrame + df = pd.DataFrame(residuals) + # 使用前向填充(用上一个有效值填充 NaN) + df.ffill(axis=1, inplace=True) + # 初始化一个新的字典来保存处理结果 + result = {} + # 定义滤波范围 + lambda_low = 2 # 2 km + lambda_high = 15 # 15 km + f_low = 2 * np.pi / lambda_high + f_high = 2 * np.pi / lambda_low + # 循环处理df的每一行(每个高度) + for idx, residuals_array in df.iterrows(): + # 提取有效值 + valid_values = np.ma.masked_array( + residuals_array, np.isnan(residuals_array)) + compressed_values = valid_values.compressed() # 去除NaN值后的数组 + N = len(compressed_values) # 有效值的数量 + # 如果有效值为空(即所有值都是NaN),则将结果设置为NaN + if N == 0: + result[idx] = np.full_like(residuals_array, np.nan) + else: + # 时间序列和频率 + dt = 0.02 # 假设的时间间隔 + n = np.arange(N) + f = n / (N * dt) + # 傅里叶变换 + y = np.fft.fft(compressed_values) + # 频率滤波 + yy = y.copy() + freq_filter = (f >= f_low) & (f <= f_high) + yy[~freq_filter] = 0 + # 逆傅里叶变换 + perturbation_after = np.real(np.fft.ifft(yy)) + # 将处理结果插回到result字典中 + result[idx] = perturbation_after + + # 处理背景温度和扰动温度数据格式 + heights = list(background_temperatures.keys()) + data_length = len(next(iter(background_temperatures.values()))) + background_matrix = np.zeros((data_length, len(heights))) + for idx, height in enumerate(heights): + background_matrix[:, idx] = background_temperatures[height] + + heights = list(result.keys()) + data_length = len(next(iter(result.values()))) + perturbation_matrix = np.zeros((data_length, len(heights))) + for idx, height in enumerate(heights): + perturbation_matrix[:, idx] = result[height] + perturbation_matrix = perturbation_matrix.T + + # 计算 Brunt-Väisälä 频率和势能 + heights_for_calc = np.linspace(0, 60, 3000) * 1000 + + def brunt_vaisala_frequency(g, BT_z, c_p, heights): + # 计算位温随高度的变化率 + dBT_z_dz = np.gradient(BT_z, heights) + # 计算 Brunt-Väisälä 频率,根号内取绝对值 + frequency_squared = (g / BT_z) * ((g / c_p) + dBT_z_dz) + frequency = np.sqrt(np.abs(frequency_squared)) + return frequency + + # 计算势能 + def calculate_gravitational_potential_energy(g, BT_z, N_z, PT_z): + # 计算势能 + return 0.5 * ((g / N_z) ** 2) * ((PT_z / BT_z) ** 2) + g = 9.81 # 重力加速度 + c_p = 1004.5 # 比热容 + N_z_matrix = [] + PT_z_matrix = [] + for i in range(background_matrix.shape[0]): + BT_z = np.array(background_matrix[i]) + PT_z = np.array(perturbation_matrix[i]) + N_z = brunt_vaisala_frequency(g, BT_z, c_p, heights_for_calc) + PW = calculate_gravitational_potential_energy(g, BT_z, N_z, PT_z) + N_z_matrix.append(N_z) + PT_z_matrix.append(PW) + ktemp_Nz = np.vstack(N_z_matrix) + ktemp_Ptz = np.vstack(PT_z_matrix) + mean_ktemp_Nz = np.mean(ktemp_Nz, axis=0) + mean_ktemp_Ptz = np.mean(ktemp_Ptz, axis=0) + + return mean_ktemp_Nz, mean_ktemp_Ptz + else: + print(f"文件夹 {folder_path} 不存在。") + return None, None + + +# 主循环,处理1到3个文件 +base_folder_path = r"./cosmic/data/2018" +all_mean_ktemp_Nz = [] +all_mean_ktemp_Ptz = [] +for file_index in range(1, 365): + mean_ktemp_Nz, mean_ktemp_Ptz = process_single_file( + base_folder_path, file_index) + if mean_ktemp_Nz is not None and mean_ktemp_Ptz is not None: + all_mean_ktemp_Nz.append(mean_ktemp_Nz) + all_mean_ktemp_Ptz.append(mean_ktemp_Ptz) +# 转换每个数组为二维形状 +final_mean_ktemp_Nz = np.vstack([arr.reshape(1, -1) + for arr in all_mean_ktemp_Nz]) +final_mean_ktemp_Ptz = np.vstack( + [arr.reshape(1, -1) for arr in all_mean_ktemp_Ptz]) +# 使用条件索引替换大于50的值为NaN +final_mean_ktemp_Ptz[final_mean_ktemp_Ptz > 50] = np.nan +# heights 为每个高度的值 +heights = np.linspace(0, 60, 3000) +df_final_mean_ktemp_Ptz = pd.DataFrame(final_mean_ktemp_Ptz) +df_final_mean_ktemp_Nz = pd.DataFrame(final_mean_ktemp_Nz) +# -------------------------------------------------绘制年统计图------------------------------------ +# -----------绘制浮力频率年统计图----------------------- +data = df_final_mean_ktemp_Nz.T +# 对每个元素进行平方(计算N2) +data = data ** 2 +data = data*10000 # (绘图好看) +# 将大于 10 的值替换为 NaN(个别异常值) +data[data > 10] = np.nan +# 绘制热力图的函数 + + +def plot_heatmap(data, heights, title): + plt.figure(figsize=(10, 8)) + # 绘制热力图,数据中的行代表高度,列代表天数 + sns.heatmap(data, cmap='coolwarm', xticklabels=1, + yticklabels=50, cbar_kws={'label': 'Value'}) + plt.xlabel('Day') + plt.ylabel('Height (km)') + plt.title(title) + # 设置x轴的刻度,使其每30天显示一个标签 + num_days = data.shape[1] + x_tick_positions = np.arange(0, num_days, 30) + x_tick_labels = np.arange(0, num_days, 30) + plt.xticks(x_tick_positions, x_tick_labels) + # 设置y轴的刻度,使其显示对应的高度 + plt.yticks(np.linspace( + 0, data.shape[0] - 1, 6), np.round(np.linspace(heights[0], heights[-1], 6), 2)) + # 反转 y 轴,使 0 在底部 + plt.gca().invert_yaxis() + plt.show() + + +# 调用函数绘制热力图 +plot_heatmap(data, heights, 'Heatmap of final_mean_ktemp_Nz(10^(-4))') +# ----------------------------------------------------------------------------- +# -------------绘制重力势能年统计图------------------------------------------------ +data1 = df_final_mean_ktemp_Ptz.T +# 绘制热力图的函数 + + +def plot_heatmap(data, heights, title): + plt.figure(figsize=(10, 8)) + # 绘制热力图,数据中的行代表高度,列代表天数 + sns.heatmap(data, cmap='coolwarm', xticklabels=1, + yticklabels=50, cbar_kws={'label': 'Value'}) + plt.xlabel('Day') + plt.ylabel('Height (km)') + plt.title(title) + # 设置x轴的刻度,使其每30天显示一个标签 + num_days = data.shape[1] + x_tick_positions = np.arange(0, num_days, 30) + x_tick_labels = np.arange(0, num_days, 30) + plt.xticks(x_tick_positions, x_tick_labels) + # 设置y轴的刻度,使其显示对应的高度 + plt.yticks(np.linspace( + 0, data.shape[0] - 1, 6), np.round(np.linspace(heights[0], heights[-1], 6), 2)) + # 反转 y 轴,使 0 在底部 + plt.gca().invert_yaxis() + plt.show() + + +# 调用函数绘制热力图 +plot_heatmap(data1, heights, 'Heatmap of final_mean_ktemp_Ptz(J/kg)') +# ------------------------绘制月统计图--------------------------------------------------------------------------------- +# ----------绘制浮力频率月统计图------------------------------------------------- +# 获取总列数 +num_columns = data.shape[1] +# 按30列分组计算均值 +averaged_df = [] +# 逐步处理每30列 +for i in range(0, num_columns, 30): + # 获取当前范围内的列,并计算均值 + subset = data.iloc[:, i:i+30] # 获取第i到i+29列 + mean_values = subset.mean(axis=1) # 对每行计算均值 + averaged_df.append(mean_values) # 将均值添加到列表 +# 将结果转化为一个新的 DataFrame +averaged_df = pd.DataFrame(averaged_df).T +# 1. 每3000行取一个均值 +# 获取总行数 +num_rows = averaged_df.shape[0] +# 创建一个新的列表来存储每3000行的均值 +averaged_by_rows_df = [] +# 逐步处理每3000行 +for i in range(0, num_rows, 3000): + # 获取当前范围内的行 + subset = averaged_df.iloc[i:i+3000, :] # 获取第i到i+99行 + mean_values = subset.mean(axis=0) # 对每列计算均值 + averaged_by_rows_df.append(mean_values) # 将均值添加到列表 +# 将结果转化为一个新的 DataFrame +averaged_by_rows_df = pd.DataFrame(averaged_by_rows_df) +# 绘制折线图 +plt.figure(figsize=(10, 6)) # 设置图形的大小 +plt.plot(averaged_by_rows_df.columns, averaged_by_rows_df.mean( + axis=0), marker='o', color='b', label='平均值') +# 添加标题和标签 +plt.title('每月平均N^2的折线图') +plt.xlabel('月份') +plt.ylabel('N^2(10^-4)') +plt.legend() +# 显示图形 +plt.grid(True) +plt.xticks(rotation=45) # 让x轴标签(月份)倾斜,以便更清晰显示 +plt.tight_layout() +plt.show() +# ------------重力势能的月统计----------------------------------- +# 获取总列数 +num_columns = data1.shape[1] +# 按30列分组计算均值 +averaged_df = [] +# 逐步处理每30列 +for i in range(0, num_columns, 30): + # 获取当前范围内的列,并计算均值 + subset = data1.iloc[:, i:i+30] # 获取第i到i+29列 + mean_values = subset.mean(axis=1) # 对每行计算均值 + averaged_df.append(mean_values) # 将均值添加到列表 +# 将结果转化为一个新的 DataFrame +averaged_df = pd.DataFrame(averaged_df).T +# 1. 每3000行取一个均值 +# 获取总行数 +num_rows = averaged_df.shape[0] +# 创建一个新的列表来存储每3000行的均值 +averaged_by_rows_df = [] +# 逐步处理每3000行 +for i in range(0, num_rows, 3000): + # 获取当前范围内的行 + subset = averaged_df.iloc[i:i+3000, :] # 获取第i到i+99行 + mean_values = subset.mean(axis=0) # 对每列计算均值 + averaged_by_rows_df.append(mean_values) # 将均值添加到列表 +# 将结果转化为一个新的 DataFrame +averaged_by_rows_df = pd.DataFrame(averaged_by_rows_df) +# 绘制折线图 +plt.figure(figsize=(10, 6)) # 设置图形的大小 +plt.plot(averaged_by_rows_df.columns, averaged_by_rows_df.mean( + axis=0), marker='o', color='b', label='平均值') +# 添加标题和标签 +plt.title('每月平均重力势能的折线图') +plt.xlabel('月份') +plt.ylabel('重力势能(J/Kg)') +plt.legend() +# 显示图形 +plt.grid(True) +plt.xticks(rotation=45) # 让x轴标签(月份)倾斜,以便更清晰显示 +plt.tight_layout() +plt.show() + + +# 获取总列数 +total_columns = data.shape[1] +# 用于存储每一组30列计算得到的均值列数据(最终会构成新的DataFrame) +mean_columns = [] +# 分组序号,用于生成列名时区分不同的均值列,从1开始 +group_index = 1 +# 按照每30列一组进行划分(不滑动) +for start_col in range(0, total_columns, 30): + end_col = start_col + 30 + if end_col > total_columns: + end_col = total_columns + # 选取当前组的30列(如果不足30列,按实际剩余列数选取) + group_data = data.iloc[:, start_col:end_col] + # 按行对当前组的列数据求和 + sum_per_row = group_data.sum(axis=1) + # 计算平均(每一组的平均,每行都有一个平均结果) + mean_per_row = sum_per_row / (end_col - start_col) + # 生成新的列名,格式为'Mean_分组序号',例如'Mean_1'、'Mean_2'等 + new_column_name = f'Mean_{group_index}' + group_index += 1 + # 将当前组计算得到的均值列添加到列表中 + mean_columns.append(mean_per_row) +# 将所有的均值列合并为一个新的DataFrame(列方向合并) +new_mean_df = pd.concat(mean_columns, axis=1) +# 按行对new_mean_df所有列的数据进行求和,得到一个Series,索引与new_mean_df的索引一致,每个元素是每行的总和 +row_sums = new_mean_df.sum(axis=1) +# 计算所有行总和的均值 +mean_value = row_sums.mean() +# 设置中文字体为黑体,解决中文显示问题(Windows系统下),如果是其他系统或者有其他字体需求可适当调整 +plt.rcParams['font.sans-serif'] = ['SimHei'] +# 解决负号显示问题 +plt.rcParams['axes.unicode_minus'] = False +# 提取月份作为x轴标签(假设mean_value的索引就是月份信息) +months = mean_value.index.tolist() +# 提取均值数据作为y轴数据 +energy_values = mean_value.tolist() +# 创建图形和坐标轴对象 +fig, ax = plt.subplots(figsize=(10, 6)) +# 绘制折线图 +ax.plot(months, energy_values, marker='o', linestyle='-', color='b') +# 设置坐标轴标签和标题 +ax.set_xlabel('月份') +ax.set_ylabel('平均浮力频率') +ax.set_title('每月浮力频率变化趋势') +# 设置x轴刻度,让其旋转一定角度以便更好地显示所有月份标签,避免重叠 +plt.xticks(rotation=45) +# 显示网格线,增强图表可读性 +ax.grid(True) +# 显示图形 +plt.show() +# --------------------------------绘制重力势能月统计图------------------------------ +# 获取总列数 +total_columns = data1.shape[1] +# 用于存储每一组30列计算得到的均值列数据(最终会构成新的DataFrame) +mean_columns = [] +# 分组序号,用于生成列名时区分不同的均值列,从1开始 +group_index = 1 +# 按照每30列一组进行划分(不滑动) +for start_col in range(0, total_columns, 30): + end_col = start_col + 30 + if end_col > total_columns: + end_col = total_columns + # 选取当前组的30列(如果不足30列,按实际剩余列数选取) + group_data = data1.iloc[:, start_col:end_col] + # 按行对当前组的列数据求和 + sum_per_row = group_data.sum(axis=1) + # 计算平均(每一组的平均,每行都有一个平均结果) + mean_per_row = sum_per_row / (end_col - start_col) + # 生成新的列名,格式为'Mean_分组序号',例如'Mean_1'、'Mean_2'等 + new_column_name = f'Mean_{group_index}' + group_index += 1 + # 将当前组计算得到的均值列添加到列表中 + mean_columns.append(mean_per_row) +# 将所有的均值列合并为一个新的DataFrame(列方向合并) +new_mean_df = pd.concat(mean_columns, axis=1) +# 按行对new_mean_df所有列的数据进行求和,得到一个Series,索引与new_mean_df的索引一致,每个元素是每行的总和 +row_sums = new_mean_df.sum(axis=1) +# 计算所有行总和的均值 +mean_value = row_sums.mean() +# 设置中文字体为黑体,解决中文显示问题(Windows系统下),如果是其他系统或者有其他字体需求可适当调整 +plt.rcParams['font.sans-serif'] = ['SimHei'] +# 解决负号显示问题 +plt.rcParams['axes.unicode_minus'] = False +# 提取月份作为x轴标签(假设mean_value的索引就是月份信息) +months = mean_value.index.tolist() +# 提取均值数据作为y轴数据 +energy_values = mean_value.tolist() +# 创建图形和坐标轴对象 +fig, ax = plt.subplots(figsize=(10, 6)) +# 绘制折线图 +ax.plot(months, energy_values, marker='o', linestyle='-', color='b') +# 设置坐标轴标签和标题 +ax.set_xlabel('月份') +ax.set_ylabel('平均浮力频率') +ax.set_title('每月浮力频率变化趋势') +# 设置x轴刻度,让其旋转一定角度以便更好地显示所有月份标签,避免重叠 +plt.xticks(rotation=45) +# 显示网格线,增强图表可读性 +ax.grid(True) +# 显示图形 +plt.show() diff --git a/cosmic/single.py b/cosmic/single.py new file mode 100644 index 0000000..23f3332 --- /dev/null +++ b/cosmic/single.py @@ -0,0 +1,393 @@ +import os +import numpy as np +import pandas as pd +from scipy.interpolate import interp1d +from scipy.optimize import curve_fit +import netCDF4 as nc +import matplotlib.pyplot as plt + +# 模块1: 数据读取与预处理 + + +def read_and_preprocess_data(base_folder_path, day_num): + """ + 读取指定路径下指定天数的相关数据文件,进行插值等预处理操作 + + 参数: + base_folder_path (str): 基础文件夹路径 + day_num (int): 表示天数的序号(范围按代码中原本逻辑处理) + + 返回: + final_df (pd.DataFrame): 预处理后的最终数据框 + """ + dfs = [] + # 构建当前文件夹的路径 + folder_name = f"atmPrf_repro2021_2018_00{day_num}" if day_num < 10 else f"atmPrf_repro2021_2018_0{day_num}" + folder_path = os.path.join(base_folder_path, folder_name) + # 检查文件夹是否存在 + if os.path.exists(folder_path): + # 遍历文件夹中的文件 + for file_name in os.listdir(folder_path): + if file_name.endswith('.0390_nc'): + finfo = os.path.join(folder_path, file_name) + print(f"正在处理文件: {finfo}") + try: + dataset = nc.Dataset(finfo, 'r') + # 提取变量数据 + temp = dataset.variables['Temp'][:] + altitude = dataset.variables['MSL_alt'][:] + lat = dataset.variables['Lat'][:] + lon = dataset.variables['Lon'][:] + # 创建DataFrame + df = pd.DataFrame({ + 'Longitude': lon, + 'Latitude': lat, + 'Altitude': altitude, + 'Temperature': temp + }) + dataset.close() + # 剔除高度大于60的行 + df = df[df['Altitude'] <= 60] + + # 对每个文件的数据进行插值 + alt_interp = np.linspace( + df['Altitude'].min(), df['Altitude'].max(), 3000) + f_alt = interp1d( + df['Altitude'], df['Altitude'], kind='linear', fill_value="extrapolate") + f_lon = interp1d( + df['Altitude'], df['Longitude'], kind='linear', fill_value="extrapolate") + f_lat = interp1d( + df['Altitude'], df['Latitude'], kind='linear', fill_value="extrapolate") + f_temp = interp1d( + df['Altitude'], df['Temperature'], kind='linear', fill_value="extrapolate") + + # 计算插值结果 + interpolated_alt = f_alt(alt_interp) + interpolated_lon = f_lon(alt_interp) + interpolated_lat = f_lat(alt_interp) + interpolated_temp = f_temp(alt_interp) + + # 创建插值后的DataFrame + interpolated_df = pd.DataFrame({ + 'Altitude': interpolated_alt, + 'Longitude': interpolated_lon, + 'Latitude': interpolated_lat, + 'Temperature': interpolated_temp + }) + + # 将插值后的DataFrame添加到列表中 + dfs.append(interpolated_df) + except Exception as e: + print(f"处理文件 {finfo} 时出错: {e}") + else: + print(f"文件夹 {folder_path} 不存在。") + # 按行拼接所有插值后的DataFrame + final_df = pd.concat(dfs, axis=0, ignore_index=True) + # 获取 DataFrame 的长度 + num_rows = len(final_df) + # 生成一个每3000个数从0到2999的序列并重复 + altitude_values = np.tile(np.arange(3000), num_rows // 3000 + 1)[:num_rows] + # 将生成的值赋给 DataFrame 的 'Altitude' 列 + final_df['Altitude'] = altitude_values + # 摄氏度换算开尔文 + final_df['Temperature'] = final_df['Temperature'] + 273.15 + return final_df + + +# 模块2: 数据筛选与网格划分 +def filter_and_grid_data(final_df): + """ + 对输入的数据框进行纬度筛选以及经度网格划分等操作 + + 参数: + final_df (pd.DataFrame): 预处理后的完整数据框 + + 返回: + latitude_filtered_df (pd.DataFrame): 经过纬度筛选和经度网格划分后的数据框 + altitude_temperature_mean (pd.DataFrame): 按高度分组求平均温度后的数据框 + """ + # 筛选出纬度在30到40度之间的数据 + latitude_filtered_df = final_df[( + final_df['Latitude'] >= 30) & (final_df['Latitude'] <= 40)] + # 划分经度网格,20°的网格 + lon_min, lon_max = latitude_filtered_df['Longitude'].min( + ), latitude_filtered_df['Longitude'].max() + lon_bins = np.arange(lon_min, lon_max + 20, 20) # 创建经度网格边界 + # 将数据分配到网格中 + latitude_filtered_df['Longitude_Grid'] = np.digitize( + latitude_filtered_df['Longitude'], lon_bins) - 1 + # 对相同高度的温度取均值,忽略NaN + altitude_temperature_mean = latitude_filtered_df.groupby( + 'Altitude')['Temperature'].mean().reset_index() + # 重命名列,使其更具可读性 + altitude_temperature_mean.columns = ['Altitude', 'Mean_Temperature'] + return latitude_filtered_df, altitude_temperature_mean + + +# 模块3: 计算wn0和初始扰动相关操作 +def calculate_wn0_and_perturbation(latitude_filtered_df, altitude_temperature_mean): + """ + 计算wn0以及相关的温度差值等操作 + + 参数: + latitude_filtered_df (pd.DataFrame): 经过纬度筛选和经度网格划分后的数据框 + altitude_temperature_mean (pd.DataFrame): 按高度分组求平均温度后的数据框 + + 返回: + merged_df (pd.DataFrame): 合并相关数据后的结果数据框,包含温度差值等列 + """ + altitude_range = range(0, 3000) + all_heights_mean_temperature = [] # 用于存储所有高度下的温度均值结果 + for altitude in altitude_range: + # 筛选出当前高度的所有数据 + altitude_df = latitude_filtered_df[latitude_filtered_df['Altitude'] == altitude] + # 对Longitude_Grid同一区间的温度取均值 + temperature_mean_by_grid = altitude_df.groupby( + 'Longitude_Grid')['Temperature'].mean().reset_index() + # 重命名列,使其更具可读性 + temperature_mean_by_grid.columns = [ + 'Longitude_Grid', 'Mean_Temperature'] + # 添加高度信息列,方便后续区分不同高度的结果 + temperature_mean_by_grid['Altitude'] = altitude + # 将当前高度的结果添加到列表中 + all_heights_mean_temperature.append(temperature_mean_by_grid) + # 将所有高度的结果合并为一个DataFrame + combined_mean_temperature_df = pd.concat( + all_heights_mean_temperature, ignore_index=True) + # 基于Altitude列合并两个DataFrame,只保留能匹配上的行 + merged_df = pd.merge(combined_mean_temperature_df, + altitude_temperature_mean, on='Altitude', how='inner') + # 计算差值(减去wn0的扰动) + merged_df['Temperature_Difference'] = merged_df['Mean_Temperature_x'] - \ + merged_df['Mean_Temperature_y'] + return merged_df + + +# 模块4: 高度相同下不同区间网格数据的波拟合和滤波处理 +def wave_fitting_and_filtering(merged_df): + """ + 对合并后的数据框按高度进行波拟合以及滤波处理 + + 参数: + merged_df (pd.DataFrame): 合并相关数据后的结果数据框,包含温度差值等列 + + 返回: + fit_results (dict): 存储每个高度的最佳拟合参数的字典 + residuals (dict): 存储每个高度的残差值的字典 + background_temperatures (dict): 存储每个高度的背景温度的字典 + """ + def single_harmonic(x, A, phi): + return A * np.sin(2 * np.pi / (18 / k) * x + phi) + + fit_results = {} + fitted_curves = {} + residuals = {} + background_temperatures = {} + grouped = merged_df.groupby('Altitude') + for altitude, group in grouped: + y_data = group['Temperature_Difference'].values + x_data = np.arange(len(y_data)) + wn0_data = group['Mean_Temperature_y'].values # 获取同一高度下的wn0数据 + # 检查Temperature_Difference列是否全部为NaN + if np.all(np.isnan(y_data)): + fit_results[altitude] = {'A': [np.nan] * 5, 'phi': [np.nan] * 5} + fitted_curves[altitude] = [np.nan * x_data] * 5 + residuals[altitude] = np.nan * x_data + background_temperatures[altitude] = np.nan * \ + x_data # 对应高度全部为NaN时,背景温度也设为NaN + else: + # 替换NaN值为非NaN值的均值 + y_data = np.where(np.isnan(y_data), np.nanmean(y_data), y_data) + # 初始化存储WN参数和曲线的列表 + wn_params = [] + wn_curves = [] + # 计算wn0(使用Mean_Temperature_y列数据) + wn0 = wn0_data + # 对WN1至WN5进行拟合 + for k in range(1, 6): + # 更新单谐波函数中的k值 + def harmonic_func(x, A, phi): return single_harmonic(x, A, phi) + # 使用curve_fit进行拟合 + popt, pcov = curve_fit(harmonic_func, x_data, y_data, p0=[ + np.nanmax(y_data) - np.nanmin(y_data), 0]) + A_fit, phi_fit = popt + # 存储拟合结果 + wn_params.append({'A': A_fit, 'phi': phi_fit}) + # 使用拟合参数生成拟合曲线 + WN = harmonic_func(x_data, A_fit, phi_fit) + wn_curves.append(WN) + # 计算残差值 + y_data = y_data - WN # 使用残差值作为下一次拟合的y_data + # 存储结果 + fit_results[altitude] = wn_params + fitted_curves[altitude] = wn_curves + residuals[altitude] = y_data # 最终残差值 + # 计算同一高度下的背景温度(wn0 + wn1 + wn2 + wn3 + wn4 + wn5) + wn_sum = np.sum([wn0] + wn_curves, axis=0) + background_temperatures[altitude] = wn_sum + return fit_results, residuals, background_temperatures + + +# 模块5: 带通滤波处理 +def bandpass_filtering(residuals): + """ + 对残差数据进行带通滤波处理 + + 参数: + residuals (dict): 存储每个高度的残差值的字典 + + 返回: + result (dict): 存储滤波后每个高度结果的字典 + """ + df = pd.DataFrame(residuals) + # 使用前向填充(用上一个有效值填充 NaN) + df.ffill(axis=1, inplace=True) + result = {} + lambda_low = 2 # 2 km + lambda_high = 15 # 15 km + f_low = 2 * np.pi / lambda_high + f_high = 2 * np.pi / lambda_low + for idx, residuals_array in df.iterrows(): + # 提取有效值 + valid_values = np.ma.masked_array( + residuals_array, np.isnan(residuals_array)) + compressed_values = valid_values.compressed() # 去除NaN值后的数组 + N = len(compressed_values) # 有效值的数量 + # 如果有效值为空(即所有值都是NaN),则将结果设置为NaN + if N == 0: + result[idx] = np.full_like(residuals_array, np.nan) + else: + # 时间序列和频率 + dt = 0.02 # 假设的时间间隔 + n = np.arange(N) + f = n / (N * dt) + # 傅里叶变换 + y = np.fft.fft(compressed_values) # 使用去除NaN后的数组进行FFT + # 频率滤波 + yy = y.copy() + freq_filter = (f >= f_low) & (f <= f_high) # 保留指定频段 + yy[~freq_filter] = 0 # 过滤掉指定频段外的值 + # 逆傅里叶变换 + perturbation_after = np.real(np.fft.ifft(yy)) + # 将处理结果插回到result字典中 + result[idx] = perturbation_after + return result + + +# 模块6: 计算势能相关操作 +def calculate_potential_energy(background_temperatures, result, heights): + """ + 基于背景温度、滤波后的扰动以及高度数据计算浮力频率和势能,并求平均 + + 参数: + background_temperatures (dict): 存储每个高度的背景温度的字典 + result (dict): 存储滤波后每个高度结果的字典 + heights (np.ndarray): 高度数据数组 + + 返回: + mean_ktemp_Nz (np.ndarray): 平均浮力频率数据 + mean_ktemp_Ptz (np.ndarray): 平均势能数据 + """ + # 处理背景温度和扰动温度数据格式 + heights1 = list(background_temperatures.keys()) + data_length1 = len(next(iter(background_temperatures.values()))) + background_matrix = np.zeros((data_length1, len(heights1))) + for idx, height in enumerate(heights1): + background_matrix[:, idx] = background_temperatures[height] + heights2 = list(result.keys()) + data_length2 = len(next(iter(result.values()))) + perturbation_matrix = np.zeros((data_length2, len(heights2))) + for idx, height in enumerate(heights2): + perturbation_matrix[:, idx] = result[height] + perturbation_matrix = perturbation_matrix.T + + def brunt_vaisala_frequency(g, BT_z, c_p, heights): + # 计算位温随高度的变化率 + dBT_z_dz = np.gradient(BT_z, heights) + # 计算 Brunt-Väisälä 频率,根号内取绝对值 + frequency_squared = (g / BT_z) * ((g / c_p) + dBT_z_dz) + frequency = np.sqrt(np.abs(frequency_squared)) + return frequency + + def calculate_gravitational_potential_energy(g, BT_z, N_z, PT_z): + # 计算势能 + return 0.5 * ((g / N_z) ** 2) * ((PT_z / BT_z) ** 2) + + g = 9.81 # 重力加速度 + c_p = 1004.5 # 比热容 + N_z_matrix = [] + PT_z_matrix = [] + for i in range(background_matrix.shape[0]): + BT_z = np.array(background_matrix[i]) + PT_z = np.array(perturbation_matrix[i]) # 滤波后的扰动 + + # 调用Brunt-Väisälä频率函数 + N_z = brunt_vaisala_frequency(g, BT_z, c_p, heights) + + # 调用势能函数 + PW = calculate_gravitational_potential_energy(g, BT_z, N_z, PT_z) + + # 将结果添加到矩阵中 + N_z_matrix.append(N_z) + PT_z_matrix.append(PW) + ktemp_Nz = np.vstack(N_z_matrix) + ktemp_Ptz = np.vstack(PT_z_matrix) + mean_ktemp_Nz = np.mean(ktemp_Nz, axis=0) + mean_ktemp_Ptz = np.mean(ktemp_Ptz, axis=0) + # 对mean_ktemp_Ptz中的值做处理,如果超过40,除以10 + mean_ktemp_Ptz[mean_ktemp_Ptz > 40] /= 10 + return mean_ktemp_Nz, mean_ktemp_Ptz +# 模块7 绘制重力波势能和浮力频率日图 + + +def plot_results(mean_ktemp_Nz, mean_ktemp_Ptz, heights): + """ + 绘制平均浮力频率和平均势能随高度变化的图像 + + 参数: + mean_ktemp_Nz (np.ndarray): 平均浮力频率数据 + mean_ktemp_Ptz (np.ndarray): 平均势能数据 + heights (np.ndarray): 高度数据数组 + """ + # 绘制平均浮力频率(mean_ktemp_Nz)随高度变化的图像 + plt.figure(figsize=(10, 6)) + plt.plot(mean_ktemp_Nz, heights / 1000) # 高度单位换算为km,方便展示 + plt.xlabel('Average (N_z)') + plt.ylabel('H(km)') + # plt.gca().invert_yaxis() # 使高度坐标轴从上到下递增,符合常规习惯 + plt.show() + + # 绘制平均势能(mean_ktemp_Ptz)随高度变化的图像 + plt.figure(figsize=(10, 6)) + plt.plot(mean_ktemp_Ptz, heights / 1000) # 高度单位换算为km,方便展示 + plt.xlabel('Average (PT_z)') + plt.ylabel('H (km)') + # plt.gca().invert_yaxis() # 使高度坐标轴从上到下递增,符合常规习惯 + plt.show() + + +if __name__ == "__main__": + base_folder_path = r"./cosmic/data/2018" + day_num = 1 + # 模块1调用 + final_df = read_and_preprocess_data(base_folder_path, day_num) + # 模块2调用 + latitude_filtered_df, altitude_temperature_mean = filter_and_grid_data( + final_df) + # 模块3调用 + merged_df = calculate_wn0_and_perturbation( + latitude_filtered_df, altitude_temperature_mean) + # 模块4调用 + fit_results, residuals, background_temperatures = wave_fitting_and_filtering( + merged_df) + # 模块5调用 + result = bandpass_filtering(residuals) + # 创建从1到80km的高度列表(按照代码中原本逻辑转换为对应单位的数组) + heights = np.linspace(0, 60000, 3000) + # 模块6调用,计算势能相关数据并获取平均浮力频率和平均势能 + mean_ktemp_Nz, mean_ktemp_Ptz = calculate_potential_energy( + background_temperatures, result, heights) + print("平均浮力频率(mean_ktemp_Nz):", mean_ktemp_Nz) + print("平均势能(mean_ktemp_Ptz):", mean_ktemp_Ptz) + # 调用绘图模块函数进行绘图 + plot_results(mean_ktemp_Nz, mean_ktemp_Ptz, heights) diff --git a/file_stuff.py b/file_stuff.py new file mode 100644 index 0000000..a24b845 --- /dev/null +++ b/file_stuff.py @@ -0,0 +1,11 @@ +import pandas as pd + + +all_year_data = pd.read_parquet("./cache/ballon_data_lin.parquet") + + +def get_dataframe_between_year(start_year, end_year): + res = all_year_data + filtered_res = res[(res['file_name'].str.extract(r'LIN-(\d{4})')[0].astype(int) >= start_year) & + (res['file_name'].str.extract(r'LIN-(\d{4})')[0].astype(int) <= end_year)] + return filtered_res diff --git a/main.py b/main.py new file mode 100644 index 0000000..b8ce1e3 --- /dev/null +++ b/main.py @@ -0,0 +1,506 @@ +from PySide6.QtWidgets import QApplication, QMainWindow, QTableWidget, QTableWidgetItem, QHeaderView +from PySide6.QtGui import QPixmap +from PySide6.QtCore import Qt +from qt import Ui_MainWindow +from qt_material import apply_stylesheet +import pandas as pd +import sys +import os +import re +import balloon + + +lat = 52.21 +g = 9.76 + +tag_balloon_once = [ + [ + "地形重力波", + "垂直传播方向", + "水平传播方向", + "本征(固有)频率", + "本征周期", + "本征频率与固有频率的比值(即长短轴之比)", + ], + [ + "垂直波长", + "水平波长", + "纬向本征相速度", + "经向本征相速度", + "垂直本征相速度", + "波动能", + ], + [ + "势能", + "纬向动量通量", + "经向动量通量", + "纬向风扰动振幅", + "经向风扰动振幅", + "温度扰动振幅", + ], +] + + +def set_table_balloon_once(table: QTableWidget, data: list): + for i in range(3): + for j in range(6): + table.setItem(i, j * 2, QTableWidgetItem(tag_balloon_once[i][j])) + if data[0] == True: + data[0] = "是" + else: + data[0] = "否" + if data[1] == 1: + data[1] = "上" + else: + data[1] = "下" + for i in range(2, len(data)): + data[i] = f"{data[i]} " + data[2] += "°" + data[3] += "rad/s" + data[4] += "h" + data[6] += "km" + data[7] += "km" + for i in [8, 9, 10, 15, 16]: + data[i] += "m/s" + data[11] += "J/kg" + data[12] += "J/kg" + data[13] += "mPa" + data[14] += "mPa" + data[17] += "K" + for i in range(18): + table.setItem(i // 6, (i % 6) * 2 + 1, QTableWidgetItem(data[i])) + + +class MainWindow(QMainWindow, Ui_MainWindow): + comboType = [ + "探空气球", + "流星雷达", + "Saber", + "TIDI", + "COSMIC", + ] + + comboMode = [ + ["重力波单次", "重力波统计"], + ["重力波月统计", "潮汐波单次", "潮汐波月统计"], + ["行星波月统计", "重力波单次", "重力波月统计"], + ["行星波月统计"], + ["行星波月统计"], + ] + + comboDate = [ + [["年", "时间"], ["起始年", "终止年"]], + [["年", "月"], ["年", "日期"], ["年", "月"]], + [["起始月", "-"], ["月", "日"], ["月", "-"]], + [["起始月", "-"]], + [["起始月", "-"]], + ] + + comboMap = [ + [1, 2], + [3, 4, 5], + [6, 7, 8], + [9], + [10], + ] + + def get_date0_items(self): + match self.dataType: + case 0: + try: + self.currentDate0 = os.listdir( + f"./data/{self.comboType[self.dataType]}/{self.dataStation}") + except FileNotFoundError: + return [] + return [i[-4:] for i in self.currentDate0] + case 1: + self.currentDate0 = ["2024"] + return self.currentDate0 + case 2: + try: + self.currentDate0 = os.listdir( + f"./data/{self.comboType[self.dataType]}/{self.dataStation}") + except FileNotFoundError: + return [] + return [re.search(r"_([A-z]+)\d{4}_", i).group(1) for i in self.currentDate0] + case _: + self.currentDate0 = ["Feb"] + return self.currentDate0 + + def get_date1_items(self): + if self.dataType == 0 and self.dataMode == 0: + try: + self.currentDate1 = os.listdir( + f"./data/{self.comboType[self.dataType]}/{self.dataStation}/{self.currentDate0[self.dataDate0]}") + except FileNotFoundError: + return [] + return [re.search(r"_(\d{8}T\d{6}_\d+)", i).group(1) for i in self.currentDate1] + elif self.dataType == 0 and self.dataMode == 1: + return [i[-4:] for i in [end for j, end in enumerate(self.currentDate0) if j >= self.dataDate0]] + elif self.dataType == 1 and self.dataMode == 0: + self.currentDate1 = ["01"] + return self.currentDate1 + elif self.dataType == 1 and self.dataMode == 1: + self.currentDate1 = ["20240317T000000"] + return self.currentDate1 + elif self.dataType == 1 and self.dataMode == 2: + if self.dataStation == "黑龙江漠河": + self.currentDate1 = ["01"] + else: + self.currentDate1 = ["03"] + return self.currentDate1 + elif self.dataType == 2 and self.dataMode == 1: + return [f"{i:02d}" for i in range(1, 31)] + else: + self.currentDate1 = [f"{i:02d}" for i in range(1, 13)] + return self.currentDate1 + + def __init__(self): + super(MainWindow, self).__init__() + self.setupUi(self) + + self.stackedWidget.setCurrentIndex(0) + self.pixmap_logo = QPixmap("./data/logo.png") + + self.dataType = -1 + self.dataMode = -1 + self.currentDate0 = [] + self.currentDate1 = [] + + self.funcMap = [ + [self.on_balloon_once_change, self.on_balloon_year_change], + [self.on_meteor_g_change, self.on_meteor_once_change, + self.on_meteor_month_change], + [self.on_Saber_twice_change, self.on_Saber_g_once_change, + self.on_Saber_g_month_change], + [self.on_TIDI_twice_change], + [self.on_COSMIC_twice_change], + ] + + self.clear_combo(0) + self.combo_type.addItems(self.comboType) + + self.combo_type.currentTextChanged.connect(self.on_change_type) + self.combo_mode.currentTextChanged.connect(self.on_change_mode) + self.combo_station.currentTextChanged.connect(self.on_change_station) + self.combo_date0.currentTextChanged.connect(self.on_change_date0) + self.combo_date1.currentTextChanged.connect(self.on_change_date1) + + s = self.size() + self.resize(s.width() + 1, s.height() + 1) + self.resize(s) + + def resizeEvent(self, event): + self.on_resize() + super().resizeEvent(event) + + def on_resize(self): + tmp = [ + None, + None, + None, + self.label_meteor_g, + self.label_meteor_once, + self.label_meteor_month, + self.label_Saber_twice, + self.label_Saber_g_once, + self.label_Saber_g_month, + self.label_TIDI_twice, + self.label_COSMIC_twice, + ] + match self.stackedWidget.currentIndex(): + case 0: + l = self.label_idle_logo + p = self.pixmap_logo + l.setPixmap(p.scaled(l.size(), Qt.KeepAspectRatio, + Qt.SmoothTransformation)) + case 1: + l = self.label_balloon_once + p = self.pixmap_balloon_once + if p.isNull(): + self.clear_pixmap() + l.setText("无重力波") + else: + l.setPixmap( + p.scaled(l.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) + case 2: + l = self.label_balloon_year + p = self.pixmap_balloon_year + if p.isNull(): + self.clear_pixmap() + l.setText("无数据") + else: + l.setPixmap( + p.scaled(l.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) + case 5: + i = self.stackedWidget.currentIndex() + l = tmp[i] + + if self.dataStation == "黑龙江漠河": + p = QPixmap("./output/tmp_5_01.png") + else: + p = QPixmap("./output/tmp_5_03.png") + + l.setPixmap(p.scaled(l.size(), Qt.KeepAspectRatio, + Qt.SmoothTransformation)) + l.setCursor(Qt.CursorShape.PointingHandCursor) + case _: + i = self.stackedWidget.currentIndex() + l = tmp[i] + + p = QPixmap(f"./output/tmp_{i}.png") + l.setPixmap(p.scaled(l.size(), Qt.KeepAspectRatio, + Qt.SmoothTransformation)) + l.setCursor(Qt.CursorShape.PointingHandCursor) + + def clear_pixmap(self): + self.pixmap_balloon_once = QPixmap() + self.pixmap_balloon_year = QPixmap() + self.pixmap_meteor_g = QPixmap() + self.pixmap_meteor_once = QPixmap() + self.pixmap_meteor_month = QPixmap() + self.pixmap_Saber_twice = QPixmap() + self.pixmap_Saber_g_once = QPixmap() + self.pixmap_Saber_g_month = QPixmap() + self.pixmap_TIDI_twice = QPixmap() + self.pixmap_COSMIC_twice = QPixmap() + + self.table_balloon_once.setVisible(False) + + for l in [ + self.label_balloon_once, + self.label_balloon_year, + self.label_meteor_g, + self.label_meteor_once, + self.label_meteor_month, + self.label_Saber_twice, + self.label_Saber_g_once, + self.label_Saber_g_month, + self.label_TIDI_twice, + self.label_COSMIC_twice, + ]: + l.setCursor(Qt.CursorShape.ArrowCursor) + + def clear_combo(self, level: int): + self.clear_pixmap() + + self.on_resize() + self.stackedWidget.setCurrentIndex(0) + + for i, combo in enumerate( + [ + self.combo_type, + self.combo_mode, + self.combo_station, + self.combo_date0, + self.combo_date1, + ] + ): + if i >= level: + combo.clear() + combo.setEnabled(i <= level) + + def on_change_type(self): + if self.combo_type.currentIndex() < 0: + return + self.clear_combo(1) + self.dataType = self.combo_type.currentIndex() + self.combo_mode.addItems(self.comboMode[self.dataType]) + + self.combo_date0.setPlaceholderText("-") + self.combo_date1.setPlaceholderText("-") + + def on_change_mode(self): + if self.combo_mode.currentIndex() < 0: + return + self.clear_combo(2) + self.dataMode = self.combo_mode.currentIndex() + + if self.dataType < 2: + self.combo_station.setPlaceholderText("台站") + else: + self.combo_station.setPlaceholderText("年") + list_station = [] + try: + # list_station = [i for i in os.listdir(f"./data/{self.comboType[self.dataType]}") if i.encode("utf-8").isupper()] + list_station = [i for i in os.listdir( + f"./data/{self.comboType[self.dataType]}")] + except FileNotFoundError: + return + self.combo_station.addItems(list_station) + + date0, date1 = self.comboDate[self.dataType][self.dataMode] + self.combo_date0.setPlaceholderText(date0) + self.combo_date1.setPlaceholderText(date1) + + def on_change_station(self): + if self.combo_station.currentIndex() < 0: + return + self.clear_combo(3) + self.dataStation = self.combo_station.currentText() + self.combo_date0.addItems(self.get_date0_items()) + + def on_change_date0(self): + if self.combo_date0.currentIndex() < 0: + return + self.clear_combo(4) + self.dataDate0 = self.combo_date0.currentIndex() + if self.dataType < 2 or (self.dataType == 2 and self.dataMode == 1): + self.combo_date1.addItems(self.get_date1_items()) + return + self.combo_date1.setEnabled(False) + self.stackedWidget.setCurrentIndex( + self.comboMap[self.dataType][self.dataMode]) + if f := self.funcMap[self.dataType][self.dataMode]: + f() + + def on_change_date1(self): + if self.combo_date1.currentIndex() < 0: + return + self.dataDate1 = self.combo_date1.currentIndex() + self.stackedWidget.setCurrentIndex( + self.comboMap[self.dataType][self.dataMode]) + if f := self.funcMap[self.dataType][self.dataMode]: + f() + + def on_balloon_once_change(self): + path = os.path.join("./data/探空气球", self.dataStation, + self.currentDate0[self.dataDate0], self.currentDate1[self.dataDate1]) + + try: + data = balloon.read_data(path) + except Exception as e: + print(e) + return + + path = "./output" + path[6:-2] + "png" + os.makedirs(os.path.dirname(path), exist_ok=True) + result = balloon.plot_once(data, path, lat, g) + + if len(result) == 0: + self.pixmap_balloon_once = QPixmap() + self.on_resize() + return + + self.pixmap_balloon_once = QPixmap(path) + self.label_balloon_once.setCursor(Qt.CursorShape.PointingHandCursor) + + t = self.table_balloon_once + t.clear() + t.setRowCount(3) + t.setColumnCount(12) + t.setVisible(True) + t.verticalHeader().setHidden(True) + t.horizontalHeader().setHidden(True) + t.verticalScrollBar().setHidden(True) + t.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) + set_table_balloon_once(t, result) + t.setMaximumHeight(t.rowHeight(0) * 3) + + s = self.size() + self.resize(s.width() + 1, s.height() + 1) + self.resize(s) + + def on_balloon_year_change(self): + columns = [ + "file_name", + "c", + "a", + "b", + "omega_upper", + "w_f", + "ver_wave_len", + "hori_wave_len", + "c_x", + "c_y", + "c_z", + "Ek", + "E_p", + "MFu", + "MFv", + "u1", + "v1", + "T1", + "zhou_qi", + ] + combined_df = pd.DataFrame() + for i in range(self.dataDate1 + 1): + path = f"./output/探空气球/{self.dataStation}/{self.currentDate0[self.dataDate0 + i]}.csv" + os.makedirs(os.path.dirname(path), exist_ok=True) + if os.path.exists(path): + year_df = pd.read_csv(path) + combined_df = pd.concat([combined_df, year_df]) + continue + folder = "./data" + path[8:-4] + year_df = pd.DataFrame() + for file in os.listdir(folder): + print(file) + data = balloon.read_data(os.path.join(folder, file)) + try: + wave = balloon.extract_wave(data, lat, g) + except Exception: + wave = [] + if len(wave) == 0: + continue + c = balloon.is_terrain_wave(data, lat, g) + wave.insert(0, c) + wave.insert(0, file) + line = pd.DataFrame([wave], columns=columns) + year_df = pd.concat([year_df, line]) + combined_df = pd.concat([combined_df, year_df]) + year_df.to_csv(path, index=False) + path = f"./output/探空气球/{self.dataStation}/{self.currentDate0[self.dataDate0]}_{self.dataDate1}.png" + if not balloon.plot_year(combined_df, path, lat, g): + self.pixmap_balloon_year = QPixmap() + self.on_resize() + return + + self.pixmap_balloon_year = QPixmap(path) + self.label_balloon_year.setCursor(Qt.CursorShape.PointingHandCursor) + + s = self.size() + self.resize(s.width() + 1, s.height() + 1) + self.resize(s) + + def on_meteor_g_change(self): + self.on_resize() + + def on_meteor_once_change(self): + self.on_resize() + + def on_meteor_month_change(self): + self.on_resize() + + def on_Saber_twice_change(self): + self.spin_Saber_twice_lat.setValue(0) + self.spin_Saber_twice_alt.setValue(90) + self.on_resize() + + def on_Saber_g_once_change(self): + self.spin_Saber_g_once_lat.setValue(0) + self.spin_Saber_g_once_lon.setValue(0) + self.spin_Saber_g_once_alt.setValue(90) + self.on_resize() + + def on_Saber_g_month_change(self): + self.spin_Saber_g_month_lat.setValue(0) + self.spin_Saber_g_month_alt.setValue(90) + self.on_resize() + + def on_TIDI_twice_change(self): + self.spin_TIDI_twice_lat.setValue(0) + self.spin_TIDI_twice_alt.setValue(90) + self.on_resize() + + def on_COSMIC_twice_change(self): + self.spin_COSMIC_twice_lat.setValue(0) + self.spin_COSMIC_twice_alt.setValue(40) + self.on_resize() + + +if __name__ == "__main__": + app = QApplication(sys.argv) + window = MainWindow() + apply_stylesheet(app, theme="light_blue.xml", invert_secondary=True) + window.show() + sys.exit(app.exec()) diff --git a/qt.py b/qt.py new file mode 100644 index 0000000..3eedbf5 --- /dev/null +++ b/qt.py @@ -0,0 +1,616 @@ +# -*- coding: utf-8 -*- + +################################################################################ +# Form generated from reading UI file 'qt.ui' +## +# Created by: Qt User Interface Compiler version 6.8.0 +## +# WARNING! All changes made in this file will be lost when recompiling UI file! +################################################################################ + +from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, + QMetaObject, QObject, QPoint, QRect, + QSize, QTime, QUrl, Qt) +from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, + QFont, QFontDatabase, QGradient, QIcon, + QImage, QKeySequence, QLinearGradient, QPainter, + QPalette, QPixmap, QRadialGradient, QTransform) +from PySide6.QtWidgets import (QApplication, QComboBox, QHBoxLayout, QHeaderView, + QLabel, QMainWindow, QPushButton, QSizePolicy, + QSpinBox, QStackedWidget, QStatusBar, QTableWidget, + QTableWidgetItem, QVBoxLayout, QWidget) + + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + if not MainWindow.objectName(): + MainWindow.setObjectName(u"MainWindow") + MainWindow.resize(800, 600) + MainWindow.setMinimumSize(QSize(800, 600)) + self.centralwidget = QWidget(MainWindow) + self.centralwidget.setObjectName(u"centralwidget") + self.verticalLayout = QVBoxLayout(self.centralwidget) + self.verticalLayout.setObjectName(u"verticalLayout") + self.horizontalLayout = QHBoxLayout() + self.horizontalLayout.setObjectName(u"horizontalLayout") + self.combo_type = QComboBox(self.centralwidget) + self.combo_type.setObjectName(u"combo_type") + sizePolicy = QSizePolicy( + QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth( + self.combo_type.sizePolicy().hasHeightForWidth()) + self.combo_type.setSizePolicy(sizePolicy) + + self.horizontalLayout.addWidget(self.combo_type) + + self.combo_mode = QComboBox(self.centralwidget) + self.combo_mode.setObjectName(u"combo_mode") + sizePolicy.setHeightForWidth( + self.combo_mode.sizePolicy().hasHeightForWidth()) + self.combo_mode.setSizePolicy(sizePolicy) + + self.horizontalLayout.addWidget(self.combo_mode) + + self.combo_station = QComboBox(self.centralwidget) + self.combo_station.setObjectName(u"combo_station") + sizePolicy.setHeightForWidth( + self.combo_station.sizePolicy().hasHeightForWidth()) + self.combo_station.setSizePolicy(sizePolicy) + + self.horizontalLayout.addWidget(self.combo_station) + + self.combo_date0 = QComboBox(self.centralwidget) + self.combo_date0.setObjectName(u"combo_date0") + sizePolicy.setHeightForWidth( + self.combo_date0.sizePolicy().hasHeightForWidth()) + self.combo_date0.setSizePolicy(sizePolicy) + + self.horizontalLayout.addWidget(self.combo_date0) + + self.combo_date1 = QComboBox(self.centralwidget) + self.combo_date1.setObjectName(u"combo_date1") + sizePolicy.setHeightForWidth( + self.combo_date1.sizePolicy().hasHeightForWidth()) + self.combo_date1.setSizePolicy(sizePolicy) + + self.horizontalLayout.addWidget(self.combo_date1) + + self.verticalLayout.addLayout(self.horizontalLayout) + + self.stackedWidget = QStackedWidget(self.centralwidget) + self.stackedWidget.setObjectName(u"stackedWidget") + self.page_idle = QWidget() + self.page_idle.setObjectName(u"page_idle") + self.verticalLayout_0 = QVBoxLayout(self.page_idle) + self.verticalLayout_0.setObjectName(u"verticalLayout_0") + self.label_idle_logo = QLabel(self.page_idle) + self.label_idle_logo.setObjectName(u"label_idle_logo") + sizePolicy1 = QSizePolicy( + QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + sizePolicy1.setHorizontalStretch(0) + sizePolicy1.setVerticalStretch(0) + sizePolicy1.setHeightForWidth( + self.label_idle_logo.sizePolicy().hasHeightForWidth()) + self.label_idle_logo.setSizePolicy(sizePolicy1) + font = QFont() + font.setPointSize(40) + self.label_idle_logo.setFont(font) + self.label_idle_logo.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.verticalLayout_0.addWidget(self.label_idle_logo) + + self.stackedWidget.addWidget(self.page_idle) + self.page_balloon_once = QWidget() + self.page_balloon_once.setObjectName(u"page_balloon_once") + self.verticalLayout_1 = QVBoxLayout(self.page_balloon_once) + self.verticalLayout_1.setObjectName(u"verticalLayout_1") + self.table_balloon_once = QTableWidget(self.page_balloon_once) + self.table_balloon_once.setObjectName(u"table_balloon_once") + sizePolicy2 = QSizePolicy( + QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) + sizePolicy2.setHorizontalStretch(0) + sizePolicy2.setVerticalStretch(0) + sizePolicy2.setHeightForWidth( + self.table_balloon_once.sizePolicy().hasHeightForWidth()) + self.table_balloon_once.setSizePolicy(sizePolicy2) + + self.verticalLayout_1.addWidget(self.table_balloon_once) + + self.label_balloon_once = QLabel(self.page_balloon_once) + self.label_balloon_once.setObjectName(u"label_balloon_once") + sizePolicy1.setHeightForWidth( + self.label_balloon_once.sizePolicy().hasHeightForWidth()) + self.label_balloon_once.setSizePolicy(sizePolicy1) + self.label_balloon_once.setMinimumSize(QSize(0, 0)) + self.label_balloon_once.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.verticalLayout_1.addWidget(self.label_balloon_once) + + self.stackedWidget.addWidget(self.page_balloon_once) + self.page_balloon_year = QWidget() + self.page_balloon_year.setObjectName(u"page_balloon_year") + self.verticalLayout_2 = QVBoxLayout(self.page_balloon_year) + self.verticalLayout_2.setObjectName(u"verticalLayout_2") + self.label_balloon_year = QLabel(self.page_balloon_year) + self.label_balloon_year.setObjectName(u"label_balloon_year") + sizePolicy1.setHeightForWidth( + self.label_balloon_year.sizePolicy().hasHeightForWidth()) + self.label_balloon_year.setSizePolicy(sizePolicy1) + self.label_balloon_year.setMinimumSize(QSize(0, 0)) + self.label_balloon_year.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.verticalLayout_2.addWidget(self.label_balloon_year) + + self.stackedWidget.addWidget(self.page_balloon_year) + self.page_meteor_g = QWidget() + self.page_meteor_g.setObjectName(u"page_meteor_g") + self.verticalLayout_3 = QVBoxLayout(self.page_meteor_g) + self.verticalLayout_3.setObjectName(u"verticalLayout_3") + self.label_meteor_g = QLabel(self.page_meteor_g) + self.label_meteor_g.setObjectName(u"label_meteor_g") + sizePolicy1.setHeightForWidth( + self.label_meteor_g.sizePolicy().hasHeightForWidth()) + self.label_meteor_g.setSizePolicy(sizePolicy1) + self.label_meteor_g.setMinimumSize(QSize(0, 0)) + self.label_meteor_g.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.verticalLayout_3.addWidget(self.label_meteor_g) + + self.stackedWidget.addWidget(self.page_meteor_g) + self.page_meteor_once = QWidget() + self.page_meteor_once.setObjectName(u"page_meteor_once") + self.verticalLayout_4 = QVBoxLayout(self.page_meteor_once) + self.verticalLayout_4.setObjectName(u"verticalLayout_4") + self.label_meteor_once = QLabel(self.page_meteor_once) + self.label_meteor_once.setObjectName(u"label_meteor_once") + sizePolicy1.setHeightForWidth( + self.label_meteor_once.sizePolicy().hasHeightForWidth()) + self.label_meteor_once.setSizePolicy(sizePolicy1) + self.label_meteor_once.setMinimumSize(QSize(0, 0)) + self.label_meteor_once.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.verticalLayout_4.addWidget(self.label_meteor_once) + + self.stackedWidget.addWidget(self.page_meteor_once) + self.page_meteor_month = QWidget() + self.page_meteor_month.setObjectName(u"page_meteor_month") + self.verticalLayout_5 = QVBoxLayout(self.page_meteor_month) + self.verticalLayout_5.setObjectName(u"verticalLayout_5") + self.label_meteor_month = QLabel(self.page_meteor_month) + self.label_meteor_month.setObjectName(u"label_meteor_month") + sizePolicy1.setHeightForWidth( + self.label_meteor_month.sizePolicy().hasHeightForWidth()) + self.label_meteor_month.setSizePolicy(sizePolicy1) + self.label_meteor_month.setMinimumSize(QSize(0, 0)) + self.label_meteor_month.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.verticalLayout_5.addWidget(self.label_meteor_month) + + self.stackedWidget.addWidget(self.page_meteor_month) + self.page_Saber_twice = QWidget() + self.page_Saber_twice.setObjectName(u"page_Saber_twice") + self.verticalLayout_6 = QVBoxLayout(self.page_Saber_twice) + self.verticalLayout_6.setObjectName(u"verticalLayout_6") + self.horizontalLayout_6 = QHBoxLayout() + self.horizontalLayout_6.setObjectName(u"horizontalLayout_6") + self.label_Saber_twice_lat = QLabel(self.page_Saber_twice) + self.label_Saber_twice_lat.setObjectName(u"label_Saber_twice_lat") + sizePolicy3 = QSizePolicy( + QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Preferred) + sizePolicy3.setHorizontalStretch(0) + sizePolicy3.setVerticalStretch(0) + sizePolicy3.setHeightForWidth( + self.label_Saber_twice_lat.sizePolicy().hasHeightForWidth()) + self.label_Saber_twice_lat.setSizePolicy(sizePolicy3) + + self.horizontalLayout_6.addWidget(self.label_Saber_twice_lat) + + self.spin_Saber_twice_lat = QSpinBox(self.page_Saber_twice) + self.spin_Saber_twice_lat.setObjectName(u"spin_Saber_twice_lat") + self.spin_Saber_twice_lat.setMaximum(80) + self.spin_Saber_twice_lat.setMinimum(-80) + self.spin_Saber_twice_lat.setSingleStep(5) + + self.horizontalLayout_6.addWidget(self.spin_Saber_twice_lat) + + self.label_Saber_twice_alt = QLabel(self.page_Saber_twice) + self.label_Saber_twice_alt.setObjectName(u"label_Saber_twice_alt") + sizePolicy3.setHeightForWidth( + self.label_Saber_twice_alt.sizePolicy().hasHeightForWidth()) + self.label_Saber_twice_alt.setSizePolicy(sizePolicy3) + + self.horizontalLayout_6.addWidget(self.label_Saber_twice_alt) + + self.spin_Saber_twice_alt = QSpinBox(self.page_Saber_twice) + self.spin_Saber_twice_alt.setObjectName(u"spin_Saber_twice_alt") + self.spin_Saber_twice_alt.setMaximum(100) + self.spin_Saber_twice_alt.setMinimum(25) + self.spin_Saber_twice_alt.setSingleStep(5) + + self.horizontalLayout_6.addWidget(self.spin_Saber_twice_alt) + + self.button_Saber_twice = QPushButton(self.page_Saber_twice) + self.button_Saber_twice.setObjectName(u"button_Saber_twice") + sizePolicy4 = QSizePolicy( + QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Fixed) + sizePolicy4.setHorizontalStretch(0) + sizePolicy4.setVerticalStretch(0) + sizePolicy4.setHeightForWidth( + self.button_Saber_twice.sizePolicy().hasHeightForWidth()) + self.button_Saber_twice.setSizePolicy(sizePolicy4) + + self.horizontalLayout_6.addWidget(self.button_Saber_twice) + + self.verticalLayout_6.addLayout(self.horizontalLayout_6) + + self.label_Saber_twice = QLabel(self.page_Saber_twice) + self.label_Saber_twice.setObjectName(u"label_Saber_twice") + sizePolicy1.setHeightForWidth( + self.label_Saber_twice.sizePolicy().hasHeightForWidth()) + self.label_Saber_twice.setSizePolicy(sizePolicy1) + self.label_Saber_twice.setMinimumSize(QSize(0, 0)) + self.label_Saber_twice.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.verticalLayout_6.addWidget(self.label_Saber_twice) + + self.stackedWidget.addWidget(self.page_Saber_twice) + self.page_Saber_g_once = QWidget() + self.page_Saber_g_once.setObjectName(u"page_Saber_g_once") + self.verticalLayout_7 = QVBoxLayout(self.page_Saber_g_once) + self.verticalLayout_7.setObjectName(u"verticalLayout_7") + self.horizontalLayout_7 = QHBoxLayout() + self.horizontalLayout_7.setObjectName(u"horizontalLayout_7") + self.label_Saber_g_once_lat = QLabel(self.page_Saber_g_once) + self.label_Saber_g_once_lat.setObjectName(u"label_Saber_g_once_lat") + sizePolicy3.setHeightForWidth( + self.label_Saber_g_once_lat.sizePolicy().hasHeightForWidth()) + self.label_Saber_g_once_lat.setSizePolicy(sizePolicy3) + + self.horizontalLayout_7.addWidget(self.label_Saber_g_once_lat) + + self.spin_Saber_g_once_lat = QSpinBox(self.page_Saber_g_once) + self.spin_Saber_g_once_lat.setObjectName(u"spin_Saber_g_once_lat") + self.spin_Saber_g_once_lat.setMaximum(80) + self.spin_Saber_g_once_lat.setMinimum(-80) + self.spin_Saber_g_once_lat.setSingleStep(5) + + self.horizontalLayout_7.addWidget(self.spin_Saber_g_once_lat) + + self.label_Saber_g_once_lon = QLabel(self.page_Saber_g_once) + self.label_Saber_g_once_lon.setObjectName(u"label_Saber_g_once_lon") + sizePolicy3.setHeightForWidth( + self.label_Saber_g_once_lon.sizePolicy().hasHeightForWidth()) + self.label_Saber_g_once_lon.setSizePolicy(sizePolicy3) + + self.horizontalLayout_7.addWidget(self.label_Saber_g_once_lon) + + self.spin_Saber_g_once_lon = QSpinBox(self.page_Saber_g_once) + self.spin_Saber_g_once_lon.setObjectName(u"spin_Saber_g_once_lon") + self.spin_Saber_g_once_lon.setMaximum(180) + self.spin_Saber_g_once_lon.setMinimum(-180) + self.spin_Saber_g_once_lon.setSingleStep(25) + + self.horizontalLayout_7.addWidget(self.spin_Saber_g_once_lon) + + self.label_Saber_g_once_alt = QLabel(self.page_Saber_g_once) + self.label_Saber_g_once_alt.setObjectName(u"label_Saber_g_once_alt") + sizePolicy3.setHeightForWidth( + self.label_Saber_g_once_alt.sizePolicy().hasHeightForWidth()) + self.label_Saber_g_once_alt.setSizePolicy(sizePolicy3) + + self.horizontalLayout_7.addWidget(self.label_Saber_g_once_alt) + + self.spin_Saber_g_once_alt = QSpinBox(self.page_Saber_g_once) + self.spin_Saber_g_once_alt.setObjectName(u"spin_Saber_g_once_alt") + self.spin_Saber_g_once_alt.setMaximum(100) + self.spin_Saber_g_once_alt.setMinimum(25) + self.spin_Saber_g_once_alt.setSingleStep(5) + + self.horizontalLayout_7.addWidget(self.spin_Saber_g_once_alt) + + self.button_Saber_g_once = QPushButton(self.page_Saber_g_once) + self.button_Saber_g_once.setObjectName(u"button_Saber_g_once") + sizePolicy4.setHeightForWidth( + self.button_Saber_g_once.sizePolicy().hasHeightForWidth()) + self.button_Saber_g_once.setSizePolicy(sizePolicy4) + + self.horizontalLayout_7.addWidget(self.button_Saber_g_once) + + self.verticalLayout_7.addLayout(self.horizontalLayout_7) + + self.label_Saber_g_once = QLabel(self.page_Saber_g_once) + self.label_Saber_g_once.setObjectName(u"label_Saber_g_once") + sizePolicy1.setHeightForWidth( + self.label_Saber_g_once.sizePolicy().hasHeightForWidth()) + self.label_Saber_g_once.setSizePolicy(sizePolicy1) + self.label_Saber_g_once.setMinimumSize(QSize(0, 0)) + self.label_Saber_g_once.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.verticalLayout_7.addWidget(self.label_Saber_g_once) + + self.stackedWidget.addWidget(self.page_Saber_g_once) + self.page_Saber_g_month = QWidget() + self.page_Saber_g_month.setObjectName(u"page_Saber_g_month") + self.verticalLayout_8 = QVBoxLayout(self.page_Saber_g_month) + self.verticalLayout_8.setObjectName(u"verticalLayout_8") + self.horizontalLayout_8 = QHBoxLayout() + self.horizontalLayout_8.setObjectName(u"horizontalLayout_8") + self.label_Saber_g_month_lat = QLabel(self.page_Saber_g_month) + self.label_Saber_g_month_lat.setObjectName(u"label_Saber_g_month_lat") + sizePolicy3.setHeightForWidth( + self.label_Saber_g_month_lat.sizePolicy().hasHeightForWidth()) + self.label_Saber_g_month_lat.setSizePolicy(sizePolicy3) + + self.horizontalLayout_8.addWidget(self.label_Saber_g_month_lat) + + self.spin_Saber_g_month_lat = QSpinBox(self.page_Saber_g_month) + self.spin_Saber_g_month_lat.setObjectName(u"spin_Saber_g_month_lat") + self.spin_Saber_g_month_lat.setMaximum(80) + self.spin_Saber_g_month_lat.setMinimum(-80) + self.spin_Saber_g_month_lat.setSingleStep(5) + + self.horizontalLayout_8.addWidget(self.spin_Saber_g_month_lat) + + self.label_Saber_g_month_alt = QLabel(self.page_Saber_g_month) + self.label_Saber_g_month_alt.setObjectName(u"label_Saber_g_month_alt") + sizePolicy3.setHeightForWidth( + self.label_Saber_g_month_alt.sizePolicy().hasHeightForWidth()) + self.label_Saber_g_month_alt.setSizePolicy(sizePolicy3) + + self.horizontalLayout_8.addWidget(self.label_Saber_g_month_alt) + + self.spin_Saber_g_month_alt = QSpinBox(self.page_Saber_g_month) + self.spin_Saber_g_month_alt.setObjectName(u"spin_Saber_g_month_alt") + self.spin_Saber_g_month_alt.setMaximum(100) + self.spin_Saber_g_month_alt.setMinimum(25) + self.spin_Saber_g_month_alt.setSingleStep(5) + + self.horizontalLayout_8.addWidget(self.spin_Saber_g_month_alt) + + self.button_Saber_g_month = QPushButton(self.page_Saber_g_month) + self.button_Saber_g_month.setObjectName(u"button_Saber_g_month") + sizePolicy4.setHeightForWidth( + self.button_Saber_g_month.sizePolicy().hasHeightForWidth()) + self.button_Saber_g_month.setSizePolicy(sizePolicy4) + + self.horizontalLayout_8.addWidget(self.button_Saber_g_month) + + self.verticalLayout_8.addLayout(self.horizontalLayout_8) + + self.label_Saber_g_month = QLabel(self.page_Saber_g_month) + self.label_Saber_g_month.setObjectName(u"label_Saber_g_month") + sizePolicy1.setHeightForWidth( + self.label_Saber_g_month.sizePolicy().hasHeightForWidth()) + self.label_Saber_g_month.setSizePolicy(sizePolicy1) + self.label_Saber_g_month.setMinimumSize(QSize(0, 0)) + self.label_Saber_g_month.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.verticalLayout_8.addWidget(self.label_Saber_g_month) + + self.stackedWidget.addWidget(self.page_Saber_g_month) + self.page_TIDI_twice = QWidget() + self.page_TIDI_twice.setObjectName(u"page_TIDI_twice") + self.verticalLayout_9 = QVBoxLayout(self.page_TIDI_twice) + self.verticalLayout_9.setObjectName(u"verticalLayout_9") + self.horizontalLayout_9 = QHBoxLayout() + self.horizontalLayout_9.setObjectName(u"horizontalLayout_9") + self.label_TIDI_twice_lat = QLabel(self.page_TIDI_twice) + self.label_TIDI_twice_lat.setObjectName(u"label_TIDI_twice_lat") + sizePolicy3.setHeightForWidth( + self.label_TIDI_twice_lat.sizePolicy().hasHeightForWidth()) + self.label_TIDI_twice_lat.setSizePolicy(sizePolicy3) + + self.horizontalLayout_9.addWidget(self.label_TIDI_twice_lat) + + self.spin_TIDI_twice_lat = QSpinBox(self.page_TIDI_twice) + self.spin_TIDI_twice_lat.setObjectName(u"spin_TIDI_twice_lat") + self.spin_TIDI_twice_lat.setMaximum(80) + self.spin_TIDI_twice_lat.setMinimum(-80) + self.spin_TIDI_twice_lat.setSingleStep(5) + + self.horizontalLayout_9.addWidget(self.spin_TIDI_twice_lat) + + self.label_TIDI_twice_alt = QLabel(self.page_TIDI_twice) + self.label_TIDI_twice_alt.setObjectName(u"label_TIDI_twice_alt") + sizePolicy3.setHeightForWidth( + self.label_TIDI_twice_alt.sizePolicy().hasHeightForWidth()) + self.label_TIDI_twice_alt.setSizePolicy(sizePolicy3) + + self.horizontalLayout_9.addWidget(self.label_TIDI_twice_alt) + + self.spin_TIDI_twice_alt = QSpinBox(self.page_TIDI_twice) + self.spin_TIDI_twice_alt.setObjectName(u"spin_TIDI_twice_alt") + self.spin_TIDI_twice_alt.setMaximum(100) + self.spin_TIDI_twice_alt.setMinimum(25) + self.spin_TIDI_twice_alt.setSingleStep(5) + + self.horizontalLayout_9.addWidget(self.spin_TIDI_twice_alt) + + self.button_TIDI_twice = QPushButton(self.page_TIDI_twice) + self.button_TIDI_twice.setObjectName(u"button_TIDI_twice") + sizePolicy4.setHeightForWidth( + self.button_TIDI_twice.sizePolicy().hasHeightForWidth()) + self.button_TIDI_twice.setSizePolicy(sizePolicy4) + + self.horizontalLayout_9.addWidget(self.button_TIDI_twice) + + self.verticalLayout_9.addLayout(self.horizontalLayout_9) + + self.label_TIDI_twice = QLabel(self.page_TIDI_twice) + self.label_TIDI_twice.setObjectName(u"label_TIDI_twice") + sizePolicy1.setHeightForWidth( + self.label_TIDI_twice.sizePolicy().hasHeightForWidth()) + self.label_TIDI_twice.setSizePolicy(sizePolicy1) + self.label_TIDI_twice.setMinimumSize(QSize(0, 0)) + self.label_TIDI_twice.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.verticalLayout_9.addWidget(self.label_TIDI_twice) + + self.stackedWidget.addWidget(self.page_TIDI_twice) + self.page_COSMIC_twice = QWidget() + self.page_COSMIC_twice.setObjectName(u"page_COSMIC_twice") + self.verticalLayout_10 = QVBoxLayout(self.page_COSMIC_twice) + self.verticalLayout_10.setObjectName(u"verticalLayout_10") + self.horizontalLayout_10 = QHBoxLayout() + self.horizontalLayout_10.setObjectName(u"horizontalLayout_10") + self.label_COSMIC_twice_lat = QLabel(self.page_COSMIC_twice) + self.label_COSMIC_twice_lat.setObjectName(u"label_COSMIC_twice_lat") + sizePolicy3.setHeightForWidth( + self.label_COSMIC_twice_lat.sizePolicy().hasHeightForWidth()) + self.label_COSMIC_twice_lat.setSizePolicy(sizePolicy3) + + self.horizontalLayout_10.addWidget(self.label_COSMIC_twice_lat) + + self.spin_COSMIC_twice_lat = QSpinBox(self.page_COSMIC_twice) + self.spin_COSMIC_twice_lat.setObjectName(u"spin_COSMIC_twice_lat") + self.spin_COSMIC_twice_lat.setMaximum(60) + self.spin_COSMIC_twice_lat.setMinimum(-60) + self.spin_COSMIC_twice_lat.setSingleStep(5) + + self.horizontalLayout_10.addWidget(self.spin_COSMIC_twice_lat) + + self.label_COSMIC_twice_alt = QLabel(self.page_COSMIC_twice) + self.label_COSMIC_twice_alt.setObjectName(u"label_COSMIC_twice_alt") + sizePolicy3.setHeightForWidth( + self.label_COSMIC_twice_alt.sizePolicy().hasHeightForWidth()) + self.label_COSMIC_twice_alt.setSizePolicy(sizePolicy3) + + self.horizontalLayout_10.addWidget(self.label_COSMIC_twice_alt) + + self.spin_COSMIC_twice_alt = QSpinBox(self.page_COSMIC_twice) + self.spin_COSMIC_twice_alt.setObjectName(u"spin_COSMIC_twice_alt") + self.spin_COSMIC_twice_alt.setMaximum(100) + self.spin_COSMIC_twice_alt.setMinimum(75) + self.spin_COSMIC_twice_alt.setSingleStep(5) + + self.horizontalLayout_10.addWidget(self.spin_COSMIC_twice_alt) + + self.button_COSMIC_twice = QPushButton(self.page_COSMIC_twice) + self.button_COSMIC_twice.setObjectName(u"button_COSMIC_twice") + sizePolicy4.setHeightForWidth( + self.button_COSMIC_twice.sizePolicy().hasHeightForWidth()) + self.button_COSMIC_twice.setSizePolicy(sizePolicy4) + + self.horizontalLayout_10.addWidget(self.button_COSMIC_twice) + + self.verticalLayout_10.addLayout(self.horizontalLayout_10) + + self.label_COSMIC_twice = QLabel(self.page_COSMIC_twice) + self.label_COSMIC_twice.setObjectName(u"label_COSMIC_twice") + sizePolicy1.setHeightForWidth( + self.label_COSMIC_twice.sizePolicy().hasHeightForWidth()) + self.label_COSMIC_twice.setSizePolicy(sizePolicy1) + self.label_COSMIC_twice.setMinimumSize(QSize(0, 0)) + self.label_COSMIC_twice.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.verticalLayout_10.addWidget(self.label_COSMIC_twice) + + self.stackedWidget.addWidget(self.page_COSMIC_twice) + + self.verticalLayout.addWidget(self.stackedWidget) + + MainWindow.setCentralWidget(self.centralwidget) + self.statusbar = QStatusBar(MainWindow) + self.statusbar.setObjectName(u"statusbar") + MainWindow.setStatusBar(self.statusbar) + + self.retranslateUi(MainWindow) + + self.stackedWidget.setCurrentIndex(6) + + QMetaObject.connectSlotsByName(MainWindow) + # setupUi + + def retranslateUi(self, MainWindow): + MainWindow.setWindowTitle(QCoreApplication.translate( + "MainWindow", u"\u4e2d\u9ad8\u5c42\u5927\u6c14\u6ce2\u52a8\u89e3\u6790\u8bc6\u522b\u6280\u672f\u7cfb\u7edf", None)) + self.combo_type.setCurrentText("") + self.combo_type.setPlaceholderText(QCoreApplication.translate( + "MainWindow", u"\u89c2\u6d4b\u8bbe\u5907", None)) + self.combo_mode.setPlaceholderText(QCoreApplication.translate( + "MainWindow", u"\u5904\u7406\u6a21\u5f0f", None)) + self.combo_station.setPlaceholderText( + QCoreApplication.translate("MainWindow", u"\u53f0\u7ad9", None)) + self.combo_date0.setPlaceholderText( + QCoreApplication.translate("MainWindow", u"-", None)) + self.combo_date1.setPlaceholderText( + QCoreApplication.translate("MainWindow", u"-", None)) + self.label_idle_logo.setText( + QCoreApplication.translate("MainWindow", u"logo", None)) + self.label_balloon_once.setText(QCoreApplication.translate( + "MainWindow", u"\u65e0\u6570\u636e", None)) + self.label_balloon_year.setText(QCoreApplication.translate( + "MainWindow", u"\u65e0\u6570\u636e", None)) + self.label_meteor_g.setText(QCoreApplication.translate( + "MainWindow", u"\u65e0\u6570\u636e", None)) + self.label_meteor_once.setText(QCoreApplication.translate( + "MainWindow", u"\u65e0\u6570\u636e", None)) + self.label_meteor_month.setText(QCoreApplication.translate( + "MainWindow", u"\u65e0\u6570\u636e", None)) + self.label_Saber_twice_lat.setText(QCoreApplication.translate( + "MainWindow", u"\u7eac\u5ea6\uff1a", None)) + self.spin_Saber_twice_lat.setSuffix( + QCoreApplication.translate("MainWindow", u" \u00b0", None)) + self.label_Saber_twice_alt.setText(QCoreApplication.translate( + "MainWindow", u"\u9ad8\u5ea6\uff1a", None)) + self.spin_Saber_twice_alt.setSuffix( + QCoreApplication.translate("MainWindow", u" km", None)) + self.button_Saber_twice.setText( + QCoreApplication.translate("MainWindow", u"\u66f4\u65b0", None)) + self.label_Saber_twice.setText(QCoreApplication.translate( + "MainWindow", u"\u65e0\u6570\u636e", None)) + self.label_Saber_g_once_lat.setText(QCoreApplication.translate( + "MainWindow", u"\u7eac\u5ea6\u5e26\u8d77\u70b9\uff1a", None)) + self.spin_Saber_g_once_lat.setSuffix( + QCoreApplication.translate("MainWindow", u" \u00b0", None)) + self.label_Saber_g_once_lon.setText(QCoreApplication.translate( + "MainWindow", u"\u7ecf\u5ea6\u5e26\u8d77\u70b9\uff1a", None)) + self.spin_Saber_g_once_lon.setSuffix( + QCoreApplication.translate("MainWindow", u" \u00b0", None)) + self.label_Saber_g_once_alt.setText(QCoreApplication.translate( + "MainWindow", u"\u9ad8\u5ea6\uff1a", None)) + self.spin_Saber_g_once_alt.setSuffix( + QCoreApplication.translate("MainWindow", u" km", None)) + self.button_Saber_g_once.setText( + QCoreApplication.translate("MainWindow", u"\u66f4\u65b0", None)) + self.label_Saber_g_once.setText(QCoreApplication.translate( + "MainWindow", u"\u65e0\u6570\u636e", None)) + self.label_Saber_g_month_lat.setText( + QCoreApplication.translate("MainWindow", u"\u7eac\u5ea6\uff1a", None)) + self.spin_Saber_g_month_lat.setSuffix( + QCoreApplication.translate("MainWindow", u" \u00b0", None)) + self.label_Saber_g_month_alt.setText( + QCoreApplication.translate("MainWindow", u"\u9ad8\u5ea6\uff1a", None)) + self.spin_Saber_g_month_alt.setSuffix( + QCoreApplication.translate("MainWindow", u" km", None)) + self.button_Saber_g_month.setText( + QCoreApplication.translate("MainWindow", u"\u66f4\u65b0", None)) + self.label_Saber_g_month.setText(QCoreApplication.translate( + "MainWindow", u"\u65e0\u6570\u636e", None)) + self.label_TIDI_twice_lat.setText(QCoreApplication.translate( + "MainWindow", u"\u7eac\u5ea6\uff1a", None)) + self.spin_TIDI_twice_lat.setSuffix( + QCoreApplication.translate("MainWindow", u" \u00b0", None)) + self.label_TIDI_twice_alt.setText(QCoreApplication.translate( + "MainWindow", u"\u9ad8\u5ea6\uff1a", None)) + self.spin_TIDI_twice_alt.setSuffix( + QCoreApplication.translate("MainWindow", u" km", None)) + self.button_TIDI_twice.setText( + QCoreApplication.translate("MainWindow", u"\u66f4\u65b0", None)) + self.label_TIDI_twice.setText(QCoreApplication.translate( + "MainWindow", u"\u65e0\u6570\u636e", None)) + self.label_COSMIC_twice_lat.setText(QCoreApplication.translate( + "MainWindow", u"\u7eac\u5ea6\uff1a", None)) + self.spin_COSMIC_twice_lat.setSuffix( + QCoreApplication.translate("MainWindow", u" \u00b0", None)) + self.label_COSMIC_twice_alt.setText(QCoreApplication.translate( + "MainWindow", u"\u9ad8\u5ea6\uff1a", None)) + self.spin_COSMIC_twice_alt.setSuffix( + QCoreApplication.translate("MainWindow", u" km", None)) + self.button_COSMIC_twice.setText( + QCoreApplication.translate("MainWindow", u"\u66f4\u65b0", None)) + self.label_COSMIC_twice.setText(QCoreApplication.translate( + "MainWindow", u"\u65e0\u6570\u636e", None)) + # retranslateUi diff --git a/radar/__init__.py b/radar/__init__.py new file mode 100644 index 0000000..3d998d7 --- /dev/null +++ b/radar/__init__.py @@ -0,0 +1,61 @@ +import asyncio +import glob +from flask import Blueprint, request, send_file +from radar.plot_original import final_plot_v1, ALL_MODEL_NAMES +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) + + +radar_module = Blueprint("Radar", __name__) + + +@radar_module.route("/metadata") +def get_all_files(): + return globed_all_files + + +@radar_module.route("/metadata/models") +def get_all_models(): + return all_model_names + + +@radar_module.route('/render/v1') +def render_v1(): + """ + wind_type: Any, + year: Any, + H: Any, + model_name: Any + """ + wind_type = request.args.get('wind_type') + year = request.args.get('year') + H = request.args.get('H') + model_name = request.args.get('model_name') + station = request.args.get('station') + buf = plot_v1(wind_type, int(year), int(H), model_name, station) + return send_file(buf, mimetype='image/png') + + +@radar_module.route('/render/v2') +def render_v2(): + """ + year: Any, + station: Any, + model_name: Any + """ + year = request.args.get('year') + station = request.args.get('station') + model_name = request.args.get('model_name') + # async_plot_v2 = asyncio.coroutine(plot_v2) + + buffer = plot_v2(int(year), station, model_name) + + return send_file(buffer, mimetype='image/png') diff --git a/radar/plot2.py b/radar/plot2.py new file mode 100644 index 0000000..afd214a --- /dev/null +++ b/radar/plot2.py @@ -0,0 +1,998 @@ +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)) + + + diff --git a/radar/plot_original.py b/radar/plot_original.py new file mode 100644 index 0000000..61dca83 --- /dev/null +++ b/radar/plot_original.py @@ -0,0 +1,262 @@ +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, "") diff --git a/radar/plot_prod.py b/radar/plot_prod.py new file mode 100644 index 0000000..5739e34 --- /dev/null +++ b/radar/plot_prod.py @@ -0,0 +1,213 @@ +from io import BytesIO +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(t, v0, a, b, period): + """行星波模型""" + return v0 + a * np.sin((2 * np.pi / period) * t + b) + +# ------------------------------ 数据处理工具函数 ------------------------------ + + +def preprocess_data(file_dir, year): + """读取数据文件并合并成完整的 DataFrame""" + file_list = [f for f in os.listdir(file_dir) if f.endswith('.txt')] + final_df = pd.DataFrame() + + for file_name in file_list: + file_path = os.path.join(file_dir, file_name) + df = pd.read_csv(file_path, sep='\s+') + + # 替换异常值为 NaN + df.loc[df['uwind'] == 1000000, 'uwind'] = np.nan + df.loc[df['vwind'] == 1000000, 'vwind'] = np.nan + + # 筛选所需列并重命名 + date_part = os.path.splitext(file_name)[0][-8:] + df = df[['height', 'time', 'uwind', 'vwind']].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') + + # 补全缺失列并排序 + 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] + 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 + +# ------------------------------ 拟合工具函数 ------------------------------ + + +def fit_wind_speed(data, model_func, initial_guess, bounds, window, num_params): + """对风速数据进行滑动窗口拟合""" + # replace "_uwind" and "_vwind" in date column + data["date"] = data["date"].str.replace( + "_uwind", "").str.replace("_vwind", "") + 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 + + valid_mask = ~np.isnan(y) + t = t[valid_mask] + y = y[valid_mask] + + if len(y) < num_params * 6: + params.append([None] * len(initial_guess)) + continue + + try: + popt, _ = curve_fit( + model_func, t, y, p0=initial_guess, bounds=bounds) + params.append(popt) + except RuntimeError: + params.append([None] * len(initial_guess)) + + params_df = pd.DataFrame(params, columns=[ + "v0"] + [f"param_{i}" for i in range(1, len(initial_guess))], index=unique_dates) + return params_df + +# ------------------------------ 热力图绘制函数 ------------------------------ + + +def plot_heatmaps(result_dict, param, title, vmax): + """绘制热力图""" + fig, axes = plt.subplots(1, 2, figsize=(18, 8)) + fig.suptitle(title, fontsize=20) + + for i, (wind_type, data) in enumerate(result_dict.items()): + heatmap_data = data[param].unstack(level=0) + heatmap_data.index = pd.to_datetime(heatmap_data.index) + heatmap_data = heatmap_data.iloc[:, ::-1] + + sns.heatmap( + heatmap_data.T, + cmap="viridis", + vmax=vmax, + cbar_kws={'label': '振幅 (m/s)'}, + yticklabels=[ + f'{height / 1000:.1f}' for height in heatmap_data.columns], + ax=axes[i] + ) + axes[i].set_title(f'{wind_type}振幅变化', fontsize=14) + axes[i].set_xlabel('日期', fontsize=12) + axes[i].set_ylabel('高度 (km)', fontsize=12) + + plt.tight_layout(rect=[0, 0, 1, 0.95]) + # plt.show() + + +# ------------------------------ 主逻辑 ------------------------------ +def final_plot_v2(year, station, model_name): + """ + 主逻辑函数,用于处理数据并绘制选定模型的热力图。 + + 参数: + - year: int,年份 + - station: str,站点名称 + - model_name: str,模型名称(如 '潮汐波', '2日行星波', '5日行星波', '10日行星波', '16日行星波') + """ + # 配置文件路径 + file_dir = rf'./radar/data\{station}\{year}' + output_dir = rf'./radar/tmp\{station}\{year}\时空' + os.makedirs(output_dir, exist_ok=True) + + # 数据预处理 + final_df = preprocess_data(file_dir, year) + uwind_data = final_df[["height", "time"] + + [col for col in final_df.columns if "_uwind" in col]] + vwind_data = final_df[["height", "time"] + + [col for col in final_df.columns if "_vwind" in col]] + heights = uwind_data["height"].unique() + + # 模型配置 + models = { + "潮汐波": {"func": tidal_model, "params": 9, "initial": [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] * 9), "window": 5}, + "2日行星波": {"func": lambda t, v0, a, b: planetary_model(t, v0, a, b, 48), "params": 3, "initial": [0, 1, 0], "bounds": ([-np.inf, 0, -np.inf], [np.inf, np.inf, np.inf]), "window": 5}, + "5日行星波": {"func": lambda t, v0, a, b: planetary_model(t, v0, a, b, 120), "params": 3, "initial": [0, 1, 0], "bounds": ([-np.inf, 0, -np.inf], [np.inf, np.inf, np.inf]), "window": 15}, + "10日行星波": {"func": lambda t, v0, a, b: planetary_model(t, v0, a, b, 240), "params": 3, "initial": [0, 1, 0], "bounds": ([-np.inf, 0, -np.inf], [np.inf, np.inf, np.inf]), "window": 29}, + "16日行星波": {"func": lambda t, v0, a, b: planetary_model(t, v0, a, b, 384), "params": 3, "initial": [0, 1, 0], "bounds": ([-np.inf, 0, -np.inf], [np.inf, np.inf, np.inf]), "window": 49} + } + + # 检查模型名称是否有效 + if model_name not in models: + raise ValueError( + f"无效的模型名称: '{model_name}'。可选模型为: {list(models.keys())}") + + # 获取模型配置 + config = models[model_name] + + # 拟合纬向风和经向风 + u_result_dict, v_result_dict = {}, {} + for height in heights: + # 纬向风数据 + height_data_u = uwind_data[uwind_data["height"] == height].melt( + id_vars=["time", "height"], var_name="date", value_name="wind_speed") + u_result_dict[height] = fit_wind_speed( + height_data_u, config["func"], config["initial"], config["bounds"], config["window"], config["params"]) + + # 经向风数据 + height_data_v = vwind_data[vwind_data["height"] == height].melt( + id_vars=["time", "height"], var_name="date", value_name="wind_speed") + v_result_dict[height] = fit_wind_speed( + height_data_v, config["func"], config["initial"], config["bounds"], config["window"], config["params"]) + + # 汇总结果 + u_final_result = pd.concat(u_result_dict, names=["height", "date"]) + v_final_result = pd.concat(v_result_dict, names=["height", "date"]) + + # 绘制热力图 + plot_heatmaps( + {"纬向风": u_final_result, "经向风": v_final_result}, + param="param_1", # 振幅参数 + title=f'{year}年{model_name}振幅时空变化', + vmax=100, + ) + + buffer = BytesIO() + plt.savefig(buffer, format='png') + buffer.seek(0) + plt.close() + return buffer diff --git a/saber/__init__.py b/saber/__init__.py new file mode 100644 index 0000000..6dd65a7 --- /dev/null +++ b/saber/__init__.py @@ -0,0 +1,103 @@ +import glob +from io import BytesIO +from flask import Blueprint, request, send_file +from matplotlib import pyplot as plt +from saber.process import DataProcessor +from saber.render import Renderer +from saber.utils import * + + +saber_module = Blueprint('saber', __name__) +lat_range = (30.0, 40.0) +alt_range = (20.0, 105.0) +lambda_range = (2, 15) +lvboin = True + +processor = DataProcessor( + lat_range=lat_range, alt_range=alt_range, lambda_range=lambda_range, lvboin=lvboin) +renderer = Renderer() + + +def extract_payload(): + buffer = BytesIO() + plt.savefig(buffer, format="png") + buffer.seek(0) + return send_file(buffer, mimetype="image/png") + + +all_saber_files = glob.glob("./saber/data/**/**.nc", recursive=True) + + +@saber_module.route("/metadata") +def get_files(): + return all_saber_files + + +@saber_module.route("/metadata/list_days") +def get_days(): + path = request.args.get("path") + + ncfile = data_nc_load(path) + + return ncfile.date_time.tolist() + + +@saber_module.route("/render/plot_wave_fitting") +def do_plot_wave_day_fitting(): + path = request.args.get("path") + day = request.args.get("day") + height = request.args.get("height") + + ncfile = data_nc_load(path) + data = processor.process_day(ncfile, int(day)) + renderer.plot_wave_fitting(data, int(height)) + return extract_payload() + + +@saber_module.route("/render/day_fft_ifft_plot") +def do_day_fft_ifft_plot(): + path = request.args.get("path") + day = request.args.get("day") + cycle_no = request.args.get("cycle_no") + + ncfile = data_nc_load(path) + + data = processor.process_day(ncfile, int(day)) + renderer.day_fft_ifft_plot(wave_data=data, cycle_no=int(cycle_no)) + return extract_payload() + + +@saber_module.route("/render/day_cycle_power_wave_plot") +def do_day_cycle_power_wave_plot(): + path = request.args.get("path") + day = request.args.get("day") + cycle_no = request.args.get("cycle_no") + + ncfile = data_nc_load(path) + + data = processor.process_day(ncfile, int(day)) + renderer.day_cycle_power_wave_plot(wave_data=data, cycle_no=int(cycle_no)) + return extract_payload() + + +@saber_module.route("/render/month_power_wave_plot") +def do_month_power_wave_plot(): + path = request.args.get("path") + month = request.args.get("month") + + ncfile = data_nc_load(path) + + data = processor.process_month(ncfile) + renderer.month_power_wave_plot(wave_data=data, date_time=ncfile.date_time) + return extract_payload() + + +@saber_module.route("/render/year_power_wave_plot") +def do_year_power_wave_plot(): + year = request.args.get("year") + + data = processor.process_year([ + data_nc_load(path) for path in glob.glob(f"./saber/data/{year}/*.nc") + ]) + renderer.year_power_wave_plot(year_wave=data) + return extract_payload() diff --git a/saber/archive/legacy.py b/saber/archive/legacy.py new file mode 100644 index 0000000..9d33f03 --- /dev/null +++ b/saber/archive/legacy.py @@ -0,0 +1,1077 @@ +import numpy as np +import matplotlib.pyplot as plt +import netCDF4 as nc +import pandas as pd +from scipy.optimize import curve_fit +import matplotlib.dates as mdates +# from matplotlib.colors import LinearSegmentedColormap +# 设置字体为支持中文的字体 +plt.rcParams['font.family'] = 'SimHei' # 设置为黑体(需要你的环境中有该字体) +plt.rcParams['axes.unicode_minus'] = False # 解决负号'-'显示为方块的问题 + +# ---------------------------------------------------------------------------------------------------------------------------- +# 1---打开文件并读取不同变量数据 - +# ---------------------------------------------------------------------------------------------------------------------------- + + +def data_nc_load(file_path): + + dataset = nc.Dataset(file_path, 'r') + + # 纬度数据,二维数组形状为(42820,379) 42820为事件,379则为不同高度 + tplatitude = dataset.variables['tplatitude'][:, :] + tplongitude = dataset.variables['tplongitude'][:, :] # 经度数据 + tpaltitude = dataset.variables['tpaltitude'][:, :] # 高度,二维数组形状为(42820,379) + time = dataset.variables['time'][:, :] # 二维数组形状为(42820,379) + date = dataset.variables['date'][:] + date_time = np.unique(date) # 输出数据时间信息 + ktemp = dataset.variables['ktemp'][:, :] # 温度数据,二维数组形状为(42820,379) + + return dataset, tplatitude, tplongitude, tpaltitude, ktemp, time, date, date_time +# ---------------------------------------------------------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------------------------------------------------------- +# 2---筛选某一天、某个纬度和高度范围15个不同cycle的温度数据 - +# ---------------------------------------------------------------------------------------------------------------------------- +# 2-1 读取某一天的所有事件及其对应的纬度数据 + + +def day_data_read(date, day_read, tplatitude): + + events = np.where(date == day_read)[0] # 读取筛选天的事件编号 4294-5714位置,从0开始编号 + time_events = date[date == day_read] # 读取筛选天的事件编号 4294-5714位置,从0开始编号 + latitudes = tplatitude[events, 189] # 输出每个事件中间位置 即第189个经纬度 + df = pd.DataFrame([ # 创建一个包含事件编号、纬度的列表,共1421个事件 + {'time': time, 'event': event, 'latitude': lat} + for time, event, lat in zip(time_events, events, latitudes)]) + + # print(df.head()) # 打印前几行数据以检查 + + return df +# ---------------------------------------------------------------------------------------------------------------------------- +# 2-2 将事件按照卫星轨迹周期进行输出处理,并输出落在纬度范围内的每个周期的事件的行号 + + +def data_cycle_identify(df, latitude_min, latitude_max): + + cycles = [] # 存储每个周期的事件编号列表 + # 存储当前周期的事件编号 + current_cycle_events = [] + prev_latitude = None + + # 遍历DataFrame中的每一行以识别周期和筛选事件 + for index, row in df.iterrows(): + current_event = int(row['event']) + current_latitude = row['latitude'] + + if prev_latitude is not None and prev_latitude < 0 and current_latitude >= 0: # 检查是否是新周期的开始(纬度从负变正,且首次变正) + # 重置当前周期的事件编号列表 + current_cycle_events = [] + + if latitude_min <= current_latitude <= latitude_max: # 如果事件的纬度在指定范围内,添加到当前周期的事件编号列表 + current_cycle_events.append(current_event) + + if prev_latitude is not None and prev_latitude >= 0 and current_latitude < 0: # 检查是否是周期的结束(纬度从正变负) + + # 添加当前周期的事件编号列表到周期列表 + if current_cycle_events: # 确保当前周期有事件编号 + cycles.append(current_cycle_events) + current_cycle_events = [] # 重置当前周期的事件编号列表 + prev_latitude = current_latitude + + if current_cycle_events: # 处理最后一个周期,如果存在的话 + cycles.append(current_cycle_events) + + print(f"一天周期为 {len(cycles)}") + for cycle_index, cycle in enumerate(cycles, start=0): + # 屏幕显示每个循环周期的事件 + print(f"周期 {cycle_index} 包含事件个数: {len(cycle)} 具体事件为: {cycle} ") + + return cycles +# ---------------------------------------------------------------------------------------------------------------------------- +# 2-3---按照循环周期合并同周期数据,并输出处理后的温度数据、对应的高度数据 + + +def data_cycle_generate(cycles, ktemp, tpaltitude, altitude_min, altitude_max): + if not cycles: # 如果周期列表为空,跳过当前迭代 + return None, None + + ktemp_cycles = [] # 初始化列表存储每个周期的温度 + altitude_cycles = [] # 初始化每个循环周期的高度数据 + for event in cycles: + ktemp_cycles_events = np.array(ktemp[event, :]) # 获取每个周期各个事件的ktemp数据 + ktemp_cycles_events[ + np.logical_or(ktemp_cycles_events == -999, ktemp_cycles_events > 999)] = np.nan # 缺失值处理,避免影响结果 + ktemp_cycles_mean = np.nanmean( + ktemp_cycles_events, axis=0) # 对所有周期的 ktemp 数据取均值 + + altitude_cycles_mean = tpaltitude[event[0], :] # 使用第一个的高度来表征所有的 + altitude_indices = np.where((altitude_cycles_mean >= altitude_min) & ( + altitude_cycles_mean <= altitude_max))[0] + + ktemp_cycles.append(np.array(ktemp_cycles_mean[altitude_indices])) + altitude_cycles.append( + np.array(altitude_cycles_mean[altitude_indices])) + + min_length = 157 # min(len(arr) for arr in ktemp_cycles) # 找到最短列表的长度 + ktemp_cycles = np.vstack([arr[:min_length] + for arr in ktemp_cycles]) # 创建新的列表,将每个子列表截断为最短长度 + altitude_cycles = np.vstack([arr[:min_length] for arr in altitude_cycles]) + + return ktemp_cycles, altitude_cycles + +# ---------------------------------------------------------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------------------------------------------------------- +# 4---高度相同下不同循环周期数据的波拟合和滤波处理 - +# ---------------------------------------------------------------------------------------------------------------------------- + + +# 对输入数据按照行(即纬向)进行波数为k的滤波,数据为15*157 +def fit_wave(ktemp_wn0, k): + + wave_fit = [] + + def single_harmonic(x, A, phi, C, k): + return A * np.sin(2 * np.pi / (15/k) * x + phi) + C + + # 数据转置并对每行进行操作,按照同高度数据进行处理 + for rtemp in ktemp_wn0.T: + # 为当前高度层创建索引数组 + indices = np.arange(rtemp.size) + def fit_temp(x, A, phi, C): return single_harmonic( + x, A, phi, C, k) # 定义只拟合 A, phi, C 的 lambda 函数,k 固定 + params, params_covariance = curve_fit( + fit_temp, indices, rtemp) # 使用 curve_fit 进行拟合 + # 提取拟合参数 A, phi, C + A, phi, C = params + # 使用拟合参数计算 wn3 + rtemp1 = single_harmonic(indices, A, phi, C, k) + # 存储拟合参数和拟合曲线 + wave_fit.append(np.array(rtemp1)) + wave_fit = np.vstack(wave_fit) + + wave_fit = wave_fit.T + wave_wn = ktemp_wn0-wave_fit + + return wave_fit, wave_wn +# ---------------------------------------------------------------------------------------------------------------------------- + + +# ---------------------------------------------------------------------------------------------------------------------------- +# 4---同周期下不同高度数据的波拟合和滤波处理 - +# ---------------------------------------------------------------------------------------------------------------------------- +# 对输入数据按照列(即纬向)进行滤波,滤除波长2和15km以内的波, 数据为15*157 +def fft_ifft_wave(ktemp_wn5, lamda_low, lamda_high, altitude_min, altitude_max, lvboin): + + ktemp_fft = [] + ktemp_ifft = [] + ktemp_fft_lvbo = [] + + for rtemp in ktemp_wn5: + # 采样点数或长度 + N = len(rtemp) + # 采样时间间隔,其倒数等于采用频率,以1km为标准尺度等同于1s,假设波的速度为1km/s + dt = (altitude_max-altitude_min)/(N-1) + # 时间序列索引 + n = np.arange(N) + # # t = altitude_min + n * dt # 时间向量 + # t = np.round(np.linspace(altitude_min, altitude_max, N),2) + # 频率索引向量 + f = n / (N * dt) + # 对输入信号进行傅里叶变换 + y = np.fft.fft(rtemp) + + # 定义波长滤波范围(以频率计算) # 高频截止频率 + f_low = 2*np.pi / lamda_high + f_high = 2*np.pi / lamda_low + + # f_low = 1 / lamda_high # 定义波长滤波范围(以频率计算) # 高频截止频率 + # f_high = 1 / lamda_low # 低频截止频率 + + # 创建滤波后的频域信号 + yy = y.copy() + + # 使用逻辑索引过滤特定频段(未确定) + if lvboin: + freq_filter = (f > f_low) & (f < f_high) # 创建逻辑掩码 + else: + freq_filter = (f < f_low) | (f > f_high) # 创建逻辑掩码 + + yy[freq_filter] = 0 # 过滤掉指定频段 + yy_ifft = np.real(np.fft.ifft(yy)) + + ktemp_fft.append(y) + # 存储拟合参数和拟合曲线 + ktemp_ifft.append(np.array(yy_ifft)) + ktemp_fft_lvbo.append(yy) + + ktemp_fft = np.vstack(ktemp_fft) + ktemp_ifft = np.vstack(ktemp_ifft) + ktemp_fft_lvbo = np.vstack(ktemp_fft_lvbo) + + return ktemp_fft, ktemp_fft_lvbo, ktemp_ifft +# ---------------------------------------------------------------------------------------------------------------------------- +# ---------------------------------------------------------------------------------------------------------------------------- +# 5---同周期下不同高度数据的BT_z背景位等指标计算 - +# ---------------------------------------------------------------------------------------------------------------------------- +# ---------------------------------------------------------------------------------- + + +def power_indices(ktemp_cycles, ktemp_wn5, ktemp_ifft, altitude_min, altitude_max): + + # 定义Brunt-Väisälä频率计算函数 + def brunt_vaisala_frequency(g, BT_z, c_p, height): + # 计算位温随高度的变化率 + dBT_z_dz = np.gradient(BT_z, height) + # 计算 Brunt-Väisälä 频率 + return np.sqrt((g / BT_z) * ((g / c_p) + dBT_z_dz)) + + # 定义势能计算函数 + def calculate_gravitational_potential_energy(g, BT_z, N_z, PT_z): + return 0.5 * ((g / N_z) ** 2) * ((PT_z / BT_z) ** 2) + + # 重力加速度 + g = 9.81 + # 空气比热容 + c_p = 1004.5 + + height = np.linspace(altitude_min, altitude_max, + ktemp_cycles.shape[1]) * 1000 # 高度 + background = ktemp_cycles - ktemp_wn5 + + # 初始化结果矩阵 + N_z_matrix = [] + # 初始化结果矩阵 + PT_z_matrix = [] + + # 循环处理background和filtered_perturbation所有行 + for i in range(background.shape[0]): + BT_z = np.array(background[i]) + # 滤波后的扰动 + PT_z = np.array(ktemp_ifft[i]) + + # 调用Brunt-Väisälä频率函数 + N_z = brunt_vaisala_frequency(g, BT_z, c_p, height) + PT_z = calculate_gravitational_potential_energy( + g, BT_z, N_z, PT_z) # 调用势能函数 + N_z_matrix.append(N_z) + PT_z_matrix.append(PT_z) + + ktemp_Nz = np.vstack(N_z_matrix) + ktemp_Ptz = np.vstack(PT_z_matrix) + return ktemp_Nz, ktemp_Ptz +# ---------------------------------------------------------------------------------------------------------------------------- +# 5---main程序环节 - +# ---------------------------------------------------------------------------------------------------------------------------- +# 按日处理资料 + + +def day_process_main(file_path, day_read, latitude_min, latitude_max, altitude_min, altitude_max, lamda_low, lamda_high, lvboin): + + dataset, tplatitude, tplongitude, tpaltitude, ktemp, time, date, date_time = data_nc_load( + file_path) + # 2018年的第94天 # 4月4日的日期,以年份和年内的第几天表示 + df = day_data_read(date, day_read, tplatitude) + cycles = data_cycle_identify(df, latitude_min, latitude_max) + ktemp_cycles, altitude_cycles = data_cycle_generate( + cycles, ktemp, tpaltitude, altitude_min, altitude_max) + ktemp_wn0 = ktemp_cycles - \ + np.mean(ktemp_cycles, axis=0) # 观测值-平均温度 + ktemp_fit_wn1, ktemp_wn1 = fit_wave(ktemp_wn0, 1) + ktemp_fit_wn2, ktemp_wn2 = fit_wave(ktemp_wn1, 2) + ktemp_fit_wn3, ktemp_wn3 = fit_wave(ktemp_wn2, 3) + ktemp_fit_wn4, ktemp_wn4 = fit_wave(ktemp_wn3, 4) + ktemp_fit_wn5, ktemp_wn5 = fit_wave(ktemp_wn4, 5) + ktemp_fft, ktemp_fft_lvbo, ktemp_ifft = fft_ifft_wave( + ktemp_wn5, lamda_low, lamda_high, altitude_min, altitude_max, lvboin) + ktemp_Nz, ktemp_Ptz = power_indices( + ktemp_cycles, ktemp_wn5, ktemp_ifft, altitude_min, altitude_max) + + return ktemp_cycles, altitude_cycles, ktemp_wn0, ktemp_fit_wn1, ktemp_wn1, ktemp_fit_wn2, ktemp_wn2, ktemp_fit_wn3, ktemp_wn3, ktemp_fit_wn4, ktemp_wn4, ktemp_fit_wn5, ktemp_wn5, ktemp_fft, ktemp_fft_lvbo, ktemp_ifft, ktemp_Nz, ktemp_Ptz +# ---------------------------------------------------------------------------------------------------------------------------- +# 按文件中单日处理资料 + + +def day_process_maing(dataset, tplatitude, tplongitude, tpaltitude, ktemp, time, date, date_time, day_read, + latitude_min, latitude_max, altitude_min, altitude_max, lamda_low, lamda_high, lvboin): + df = day_data_read(date, day_read, tplatitude) + cycles = data_cycle_identify(df, latitude_min, latitude_max) + + if not cycles: # 如果周期列表为空,返回18个None值 + return (None,) * 18 + + ktemp_cycles, altitude_cycles = data_cycle_generate( + cycles, ktemp, tpaltitude, altitude_min, altitude_max) + + if ktemp_cycles is None or altitude_cycles is None: # 再次检查周期数据是否为空 + return (None,) * 18 + + ktemp_wn0 = ktemp_cycles - \ + np.mean(ktemp_cycles, axis=0) # 按照纬向计算平均温度 wn0_temp + ktemp_fit_wn1, ktemp_wn1 = fit_wave(ktemp_wn0, 1) + ktemp_fit_wn2, ktemp_wn2 = fit_wave(ktemp_wn1, 2) + ktemp_fit_wn3, ktemp_wn3 = fit_wave(ktemp_wn2, 3) + ktemp_fit_wn4, ktemp_wn4 = fit_wave(ktemp_wn3, 4) + ktemp_fit_wn5, ktemp_wn5 = fit_wave(ktemp_wn4, 5) + ktemp_fft, ktemp_fft_lvbo, ktemp_ifft = fft_ifft_wave(ktemp_wn5, lamda_low, lamda_high, altitude_min, altitude_max, + lvboin) + ktemp_Nz, ktemp_Ptz = power_indices( + ktemp_cycles, ktemp_wn5, ktemp_ifft, altitude_min, altitude_max) + + return ktemp_cycles, altitude_cycles, ktemp_wn0, ktemp_fit_wn1, ktemp_wn1, ktemp_fit_wn2, ktemp_wn2, ktemp_fit_wn3, ktemp_wn3, ktemp_fit_wn4, ktemp_wn4, ktemp_fit_wn5, ktemp_wn5, ktemp_fft, ktemp_fft_lvbo, ktemp_ifft, ktemp_Nz, ktemp_Ptz + +# ---------------------------------------------------------------------------------------------------------------------------- +# 按月处理资料 + + +def mon_process_main(file_path, latitude_min, latitude_max, altitude_min, altitude_max, lamda_low, lamda_high, lvboin): + # 打开文件并读取数据 + dataset, tplatitude, tplongitude, tpaltitude, ktemp, time, date, date_time = data_nc_load( + file_path) + + ktemp_cycles_mon = [] + altitude_cycles_mon = [] + ktemp_wn0_mon = [] + ktemp_fit_wn1_mon = [] + ktemp_wn1_mon = [] + ktemp_fit_wn2_mon = [] + ktemp_wn2_mon = [] + ktemp_fit_wn3_mon = [] + ktemp_wn3_mon = [] + ktemp_fit_wn4_mon = [] + ktemp_wn4_mon = [] + ktemp_fit_wn5_mon = [] + ktemp_wn5_mon = [] + ktemp_fft_mon = [] + ktemp_fft_lvbo_mon = [] + ktemp_ifft_mon = [] + ktemp_Nz_mon = [] + ktemp_Ptz_mon = [] + + # 遍历每一天,处理数据 + for day_read in date_time: + print(f"读取日期 {day_read}") + # 处理单日数据 + results = day_process_maing( + dataset, tplatitude, tplongitude, tpaltitude, ktemp, time, date, date_time, + day_read, latitude_min, latitude_max, altitude_min, altitude_max, lamda_low, lamda_high, lvboin + ) + + # 检查结果是否包含有效数据 + if results is not None and len(results) == 18: + ktemp_cycles0, altitude_cycles0, ktemp_wn00, ktemp_fit_wn10, ktemp_wn10, ktemp_fit_wn20, ktemp_wn20, ktemp_fit_wn30, ktemp_wn30, ktemp_fit_wn40, ktemp_wn40, ktemp_fit_wn50, ktemp_wn50, ktemp_fft0, ktemp_fft_lvbo0, ktemp_ifft0, ktemp_Nz0, ktemp_Ptz0 = results + + # 将有效数据添加到月度列表中 + ktemp_cycles_mon.append(ktemp_cycles0) + altitude_cycles_mon.append(altitude_cycles0) + ktemp_wn0_mon.append(ktemp_wn00) + ktemp_fit_wn1_mon.append(ktemp_fit_wn10) + ktemp_wn1_mon.append(ktemp_wn10) + ktemp_fit_wn2_mon.append(ktemp_fit_wn20) + ktemp_wn2_mon.append(ktemp_wn20) + ktemp_fit_wn3_mon.append(ktemp_fit_wn30) + ktemp_wn3_mon.append(ktemp_wn30) + ktemp_fit_wn4_mon.append(ktemp_fit_wn40) + ktemp_wn4_mon.append(ktemp_wn40) + ktemp_fit_wn5_mon.append(ktemp_fit_wn50) + ktemp_wn5_mon.append(ktemp_wn50) + ktemp_fft_mon.append(ktemp_fft0) + ktemp_fft_lvbo_mon.append(ktemp_fft_lvbo0) + ktemp_ifft_mon.append(ktemp_ifft0) + ktemp_Nz_mon.append(ktemp_Nz0) + ktemp_Ptz_mon.append(ktemp_Ptz0) + + # 返回整个月的数据 + return (date_time, ktemp_cycles_mon, altitude_cycles_mon, ktemp_wn0_mon, ktemp_fit_wn1_mon, ktemp_wn1_mon, + ktemp_fit_wn2_mon, ktemp_wn2_mon, ktemp_fit_wn3_mon, ktemp_wn3_mon, ktemp_fit_wn4_mon, ktemp_wn4_mon, + ktemp_fit_wn5_mon, ktemp_wn5_mon, ktemp_fft_mon, ktemp_fft_lvbo_mon, ktemp_ifft_mon, ktemp_Nz_mon, + ktemp_Ptz_mon) + # 滤波后NZ、PTZ重力波势能指标计算 +# ---------------------------------------------------------------------------------------------------------------------------- +# 按年处理资料 + + +def process_yearly_data(year, path, latitude_min, latitude_max, altitude_min, altitude_max, lamda_low, lamda_high, lvboin): + # 创建空列表来存储每月的数据 + date_time_list = [] + ktemp_cycles_mon_list = [] + altitude_cycles_mon_list = [] + ktemp_wn0_mon_list = [] + ktemp_fit_wn1_mon_list = [] + ktemp_wn1_mon_list = [] + ktemp_fit_wn2_mon_list = [] + ktemp_wn2_mon_list = [] + ktemp_fit_wn3_mon_list = [] + ktemp_wn3_mon_list = [] + ktemp_fit_wn4_mon_list = [] + ktemp_wn4_mon_list = [] + ktemp_fit_wn5_mon_list = [] + ktemp_wn5_mon_list = [] + ktemp_fft_mon_list = [] + ktemp_fft_lvbo_mon_list = [] + ktemp_ifft_mon_list = [] + ktemp_Nz_mon_list = [] + ktemp_Ptz_mon_list = [] + + # 循环处理每个月的数据 + for month in range(1, 13): + # 获取当前月的文件路径 + file_path = filename_read(year, month, path) + + try: + # 调用 mon_process_main 函数处理文件并获取结果 + (date_time, ktemp_cycles_mon, altitude_cycles_mon, ktemp_wn0_mon, ktemp_fit_wn1_mon, + ktemp_wn1_mon, ktemp_fit_wn2_mon, ktemp_wn2_mon, ktemp_fit_wn3_mon, ktemp_wn3_mon, + ktemp_fit_wn4_mon, ktemp_wn4_mon, ktemp_fit_wn5_mon, ktemp_wn5_mon, ktemp_fft_mon, + ktemp_fft_lvbo_mon, ktemp_ifft_mon, ktemp_Nz_mon, ktemp_Ptz_mon) = mon_process_main( + file_path, latitude_min, latitude_max, altitude_min, altitude_max, lamda_low, lamda_high, lvboin + ) + + # 将每月的结果添加到相应的列表中 + date_time_list.extend(date_time) + ktemp_cycles_mon_list.extend(ktemp_cycles_mon) + altitude_cycles_mon_list.extend(altitude_cycles_mon) + ktemp_wn0_mon_list.extend(ktemp_wn0_mon) + ktemp_fit_wn1_mon_list.extend(ktemp_fit_wn1_mon) + ktemp_wn1_mon_list.extend(ktemp_wn1_mon) + ktemp_fit_wn2_mon_list.extend(ktemp_fit_wn2_mon) + ktemp_wn2_mon_list.extend(ktemp_wn2_mon) + ktemp_fit_wn3_mon_list.extend(ktemp_fit_wn3_mon) + ktemp_wn3_mon_list.extend(ktemp_wn3_mon) + ktemp_fit_wn4_mon_list.extend(ktemp_fit_wn4_mon) + ktemp_wn4_mon_list.extend(ktemp_wn4_mon) + ktemp_fit_wn5_mon_list.extend(ktemp_fit_wn5_mon) + ktemp_wn5_mon_list.extend(ktemp_wn5_mon) + ktemp_fft_mon_list.extend(ktemp_fft_mon) + ktemp_fft_lvbo_mon_list.extend(ktemp_fft_lvbo_mon) + ktemp_ifft_mon_list.extend(ktemp_ifft_mon) + ktemp_Nz_mon_list.extend(ktemp_Nz_mon) + ktemp_Ptz_mon_list.extend(ktemp_Ptz_mon) + + except Exception as e: + print(f"处理文件 {file_path} 时出错(月份:{month}):{e}") + continue # 出现错误时跳过当前月份,继续处理下个月 + + # 返回所有月份的处理结果 + return { + "date_time_list": date_time_list, + "ktemp_cycles_mon_list": ktemp_cycles_mon_list, + "altitude_cycles_mon_list": altitude_cycles_mon_list, + "ktemp_wn0_mon_list": ktemp_wn0_mon_list, + "ktemp_fit_wn1_mon_list": ktemp_fit_wn1_mon_list, + "ktemp_wn1_mon_list": ktemp_wn1_mon_list, + "ktemp_fit_wn2_mon_list": ktemp_fit_wn2_mon_list, + "ktemp_wn2_mon_list": ktemp_wn2_mon_list, + "ktemp_fit_wn3_mon_list": ktemp_fit_wn3_mon_list, + "ktemp_wn3_mon_list": ktemp_wn3_mon_list, + "ktemp_fit_wn4_mon_list": ktemp_fit_wn4_mon_list, + "ktemp_wn4_mon_list": ktemp_wn4_mon_list, + "ktemp_fit_wn5_mon_list": ktemp_fit_wn5_mon_list, + "ktemp_wn5_mon_list": ktemp_wn5_mon_list, + "ktemp_fft_mon_list": ktemp_fft_mon_list, + "ktemp_fft_lvbo_mon_list": ktemp_fft_lvbo_mon_list, + "ktemp_ifft_mon_list": ktemp_ifft_mon_list, + "ktemp_Nz_mon_list": ktemp_Nz_mon_list, + "ktemp_Ptz_mon_list": ktemp_Ptz_mon_list + } + +# ----------------------------------------------------------------------------------------------------------------------------- +# 6-主要统计分析图绘制 - +# ----------------------------------------------------------------------------------------------------------------------------- +# 6-1 示例的逐层滤波效果图---不同波数 曲线图 + + +def day_fit_wave_plot(height_no, ktemp_wn0, ktemp_fit_wn1, ktemp_wn1, ktemp_fit_wn2, ktemp_wn2, ktemp_fit_wn3, ktemp_wn3, ktemp_fit_wn4, ktemp_wn4, ktemp_fit_wn5, ktemp_wn5): + + N = len(ktemp_wn0[:, height_no]) + # 循环周期索引 + x = np.arange(N) + + y1_1 = ktemp_wn0[:, height_no] + y1_2 = ktemp_fit_wn1[:, height_no] + + y2_1 = ktemp_wn1[:, height_no] + y2_2 = ktemp_fit_wn2[:, height_no] + + y3_1 = ktemp_wn2[:, height_no] + y3_2 = ktemp_fit_wn3[:, height_no] + + y4_1 = ktemp_wn3[:, height_no] + y4_2 = ktemp_fit_wn4[:, height_no] + + y5_1 = ktemp_wn4[:, height_no] + y5_2 = ktemp_fit_wn5[:, height_no] + + y6 = ktemp_wn5[:, height_no] + + plt.figure(figsize=(16, 10)) # 调整图形大小 + # 原始信号的时间序列 + plt.subplot(2, 3, 1) + plt.plot(x, y1_1, label='原始信号') + plt.plot(x, y1_2, label='拟合信号', linestyle='--') + plt.title('(a)波数k=1') + plt.xlabel('Cycles', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距 + plt.legend() + plt.tight_layout() # 调整子图参数以适应图形区域 + + plt.subplot(2, 3, 2) + plt.plot(x, y2_1, label='原始信号') + plt.plot(x, y2_2, label='拟合信号', linestyle='--') + plt.title('(b)波数k=2') + plt.xlabel('Cycles', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距 + plt.legend() + plt.tight_layout() # 调整子图参数以适应图形区域 + + plt.subplot(2, 3, 3) + plt.plot(x, y3_1, label='原始信号') + plt.plot(x, y3_2, label='拟合信号', linestyle='--') + plt.title('(c)波数k=3') + plt.xlabel('Cycles', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距 + plt.legend() + plt.tight_layout() # 调整子图参数以适应图形区域 + + plt.subplot(2, 3, 4) + plt.plot(x, y4_1, label='原始信号') + plt.plot(x, y4_2, label='拟合信号', linestyle='--') + plt.title('(d)波数k=4') + plt.xlabel('Cycles', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距 + plt.legend() + plt.tight_layout() # 调整子图参数以适应图形区域 + + plt.subplot(2, 3, 5) + plt.plot(x, y5_1, label='原始信号') + plt.plot(x, y5_2, label='拟合信号', linestyle='--') + plt.title('(e)波数k=5') + plt.xlabel('Cycles', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距 + plt.legend() + plt.tight_layout() # 调整子图参数以适应图形区域 + + plt.subplot(2, 3, 6) + plt.plot(x, y6, label='滤波信号') + plt.title('(f)滤波1-5后信号') + plt.xlabel('Cycles', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距 + plt.tight_layout() # 调整子图参数以适应图形区域 + + # 调整子图之间的边距 + plt.subplots_adjust(top=0.8, bottom=0.2, left=0.1, + right=0.8, hspace=0.3, wspace=0.2) + + plt.show() + + +def day_fit_wave_plotg(height_no, ktemp_wn0, ktemp_fit_wn1, ktemp_wn1, ktemp_fit_wn2, + ktemp_wn2, ktemp_fit_wn3, ktemp_wn3, ktemp_fit_wn4, + ktemp_wn4, ktemp_fit_wn5, ktemp_wn5): + + N = len(ktemp_wn0[:, height_no]) + x = np.arange(N) + + y1_1, y1_2 = ktemp_wn0[:, height_no], ktemp_fit_wn1[:, height_no] + y2_1, y2_2 = ktemp_wn1[:, height_no], ktemp_fit_wn2[:, height_no] + y3_1, y3_2 = ktemp_wn2[:, height_no], ktemp_fit_wn3[:, height_no] + y4_1, y4_2 = ktemp_wn3[:, height_no], ktemp_fit_wn4[:, height_no] + y5_1, y5_2 = ktemp_wn4[:, height_no], ktemp_fit_wn5[:, height_no] + y6 = ktemp_wn5[:, height_no] + + plt.figure(figsize=(16, 10)) + + y_limits = (min(min(y1_1), min(y2_1), min(y3_1), min(y4_1), min(y5_1), min(y6)), + max(max(y1_1), max(y2_1), max(y3_1), max(y4_1), max(y5_1), max(y6))) + + for i, (y1, y2) in enumerate([(y1_1, y1_2), (y2_1, y2_2), (y3_1, y3_2), + (y4_1, y4_2), (y5_1, y5_2), (y6, None)]): + plt.subplot(2, 3, i + 1) + plt.plot(x, y1, label='原始信号') + if y2 is not None: + plt.plot(x, y2, label='拟合信号', linestyle='--') + plt.title(f'({"abcdef"[i]})波数k={i + 1 if i < 5 else "滤波0-5"}') + plt.xlabel('Cycles', labelpad=10) + plt.ylabel('温度 (K)', labelpad=10) + plt.legend() + plt.xticks(x) # 设置横坐标为整数 + plt.ylim(y_limits) # 设置统一纵坐标范围 + plt.tight_layout() + + plt.subplots_adjust(top=0.8, bottom=0.2, left=0.1, + right=0.8, hspace=0.3, wspace=0.2) + plt.show() + + +# ----------------------------------------------------------------------------------------------------------------------------- +# 6-2 示例的高度滤波处理--不同循环周期 曲线图 +def day_fft_ifft_plot(cycle_no, ktemp_wn5, ktemp_fft, ktemp_ifft, altitude_min, altitude_max, lamda_low, lamda_high): + + N = len(ktemp_wn5[cycle_no, :]) + # 采样时间间隔,其倒数等于采用频率,以1km为标准尺度等同于1s,假设波的速度为1km/s + dt = (altitude_max-altitude_min)/(N-1) + # 时间序列索引 + n = np.arange(N) + f = n / (N * dt) + t = np.round(np.linspace(altitude_min, altitude_max, N), 2) + + # 原始扰动温度 + x = ktemp_wn5[cycle_no, :] + # 傅里叶变换频谱分析 + y = ktemp_fft[cycle_no, :] + # 滤波后的傅里叶变换频谱分析 + yy = ktemp_fft_lvbo[cycle_no, :] + # 傅里叶逆变换后的扰动温度 + yyy = ktemp_ifft[cycle_no, :] + + plt.figure(figsize=(15, 10)) # 调整图形大小 + # 原始信号的时间序列 + plt.subplot(2, 2, 1) + plt.plot(t, x) + plt.title('(a)原始信号') + plt.xlabel('高度 (km)', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距 + # 原始振幅谱 + plt.subplot(2, 2, 2) + plt.plot(f, np.abs(y) * 2 / N) + plt.title('(b))原始振幅谱') + plt.xlabel('频率/Hz', labelpad=10) # 增加标签间距 + plt.ylabel('振幅', labelpad=10) # 增加标签间距 + + # 通过IFFT回到时间域 + plt.subplot(2, 2, 3) + plt.plot(t, yyy) + plt.title('(c))傅里叶逆变换') + plt.xlabel('高度 (km)', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距 + + # 滤波后的振幅谱 + plt.subplot(2, 2, 4) + plt.plot(f, np.abs(yy) * 2 / N) + plt.title(f'(d)滤除波长 < {lamda_low} km, > {lamda_high} km的波') + plt.xlabel('频率/Hz', labelpad=10) # 增加标签间距 + plt.ylabel('振幅', labelpad=10) # 增加标签间距 + + # 调整子图之间的边距 + plt.subplots_adjust(top=0.8, bottom=0.2, left=0.2, + right=0.8, hspace=0.3, wspace=0.2) + + plt.show() + + # 绘制原始信号与滤波后信号 + plt.figure(figsize=(12, 6)) # 调整图形大小 + plt.plot(t, x, label='原始信号') + plt.plot(t, yyy, label='滤波后信号', linestyle='--') + plt.title('信号比较') + plt.xlabel('高度 (km)', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距 + plt.legend() + plt.tight_layout() # 调整子图参数以适应图形区域 + plt.show() + + +def day_fft_ifft_plotg(cycle_no, ktemp_wn5, ktemp_fft, ktemp_ifft, altitude_min, altitude_max, lamda_low, lamda_high): + + N = len(ktemp_wn5[cycle_no, :]) + dt = (altitude_max - altitude_min) / (N - 1) # 采样时间间隔 + n = np.arange(N) # 时间序列索引 + f = n / (N * dt) + t = np.round(np.linspace(altitude_min, altitude_max, N), 2) + + x = ktemp_wn5[cycle_no, :] # 原始扰动温度 + y = ktemp_fft[cycle_no, :] # 傅里叶变换频谱分析 + yy = ktemp_fft_lvbo[cycle_no, :] # 滤波后的傅里叶变换频谱分析 + yyy = ktemp_ifft[cycle_no, :] # 傅里叶逆变换后的扰动温度 + + plt.figure(figsize=(15, 10)) # 调整图形大小 + + # 计算纵坐标范围 + temp_limits = (min(min(x), min(yyy)), max(max(x), max(yyy))) + amp_limits = (0, max(np.max(np.abs(y) * 2 / N), + np.max(np.abs(yy) * 2 / N))) + + # 原始信号的时间序列 + plt.subplot(2, 2, 1) + plt.plot(t, x) + plt.title('(a)原始信号') + plt.xlabel('高度 (km)', labelpad=10) + plt.ylabel('温度 (K)', labelpad=10) + plt.ylim(temp_limits) # 设置统一纵坐标范围 + + # 原始振幅谱 + plt.subplot(2, 2, 2) + plt.plot(f, np.abs(y) * 2 / N) + plt.title('(b)原始振幅谱') + plt.xlabel('频率/Hz', labelpad=10) + plt.ylabel('振幅', labelpad=10) + plt.ylim(amp_limits) # 设置统一纵坐标范围 + + # 通过IFFT回到时间域 + plt.subplot(2, 2, 3) + plt.plot(t, yyy) + plt.title('(c)傅里叶逆变换') + plt.xlabel('高度 (km)', labelpad=10) + plt.ylabel('温度 (K)', labelpad=10) + plt.ylim(temp_limits) # 设置统一纵坐标范围 + + # 滤波后的振幅谱 + plt.subplot(2, 2, 4) + plt.plot(f, np.abs(yy) * 2 / N) + plt.title(f'(d)滤除波长 < {lamda_low} km, > {lamda_high} km的波') + plt.xlabel('频率/Hz', labelpad=10) + plt.ylabel('振幅', labelpad=10) + plt.ylim(amp_limits) # 设置统一纵坐标范围 + + # 调整子图之间的边距 + plt.subplots_adjust(top=0.8, bottom=0.2, left=0.2, + right=0.8, hspace=0.3, wspace=0.2) + plt.show() + + # 绘制原始信号与滤波后信号 + plt.figure(figsize=(6, 8)) # 调整图形大小 + plt.plot(x, t, label='原始信号') + plt.plot(yyy, t, label='滤波后信号', linestyle='--') + plt.title('信号比较') + plt.ylabel('高度 (km)', labelpad=10) # 增加标签间距 + plt.xlabel('温度 (K)', labelpad=10) # 增加标签间距 + plt.legend() + plt.xlim(temp_limits) # 设置统一纵坐标范围 + plt.tight_layout() + plt.show() + +# ----------------------------------------------------------------------------------------------------------------------------- +# 6-3 示例的按高度的重力波势能变化曲线图 + + +def day_cycle_power_wave_plot(cycle_no, altitude_min, altitude_max, ktemp_Nz, ktemp_Ptz): + + N = len(ktemp_Nz[cycle_no, :]) + y = np.round(np.linspace(altitude_min, altitude_max, N), 2) + x1 = ktemp_Nz[cycle_no, :] + x2 = ktemp_Ptz[cycle_no, :] + + plt.figure(figsize=(12, 10)) # 调整图形大小 + # 原始信号的时间序列 + plt.subplot(1, 2, 1) + plt.plot(x1[::-1], y, label='原始信号') + plt.title('(a)Nz') + plt.xlabel('Nz', labelpad=10) # 增加标签间距 + plt.ylabel('高度 (km)', labelpad=10) # 增加标签间距 + + # 原始信号的时间序列 + plt.subplot(1, 2, 2) + plt.plot(x2[::-1], y, label='原始信号') + plt.title('(b)Ptz') + plt.xlabel('Ptz', labelpad=10) # 增加标签间距 + plt.ylabel('高度 (km)', labelpad=10) # 增加标签间距 + + # 调整子图之间的边距 + plt.subplots_adjust(top=0.8, bottom=0.2, left=0.1, + right=0.8, hspace=0.3, wspace=0.2) + plt.tight_layout() # 调整子图参数以适应图形区域 + + plt.show() +# ----------------------------------------------------------------------------------------------------------------------------- +# 6-4 按日统计的按周期计算的不同高度的重力波势能 平面图 + + +def day_power_wave_plot(altitude_min, altitude_max, ktemp_Nz, ktemp_Ptz): + # 假设 ktemp_Nz 和 ktemp_Ptz 以及 altitude_min, altitude_max 已经定义好 + x = np.arange(ktemp_Nz.shape[0]) + y = np.round(np.linspace(altitude_min, altitude_max, ktemp_Nz.shape[1]), 2) + + # 创建一个图形,并指定两个子图 + fig, axs = plt.subplots(1, 2, figsize=(15, 10)) + + # 第一幅图 (a) NZ + cax1 = axs[0].imshow(ktemp_Nz.T[::-1], aspect='auto', cmap='viridis', + origin='lower', extent=[x[0], x[-1], y[0], y[-1]]) + fig.colorbar(cax1, ax=axs[0]) # 为第一幅图添加颜色条 + axs[0].set_title('(a) NZ') + axs[0].set_ylabel('Height (km)') + axs[0].set_xlabel('Cycles') + axs[0].set_yticks(np.linspace(30, 90, 7)) + axs[0].set_yticklabels(np.round(np.linspace(30, 90, 7), 1)) + axs[0].set_xticks(np.arange(15)) + axs[0].set_xticklabels(np.arange(15)) + + # 第二幅图 (b) PTZ + cax2 = axs[1].imshow(ktemp_Ptz.T[::-1], aspect='auto', cmap='viridis', + origin='lower', extent=[x[0], x[-1], y[0], y[-1]]) + fig.colorbar(cax2, ax=axs[1]) # 为第二幅图添加颜色条 + axs[1].set_title('(b) PTZ') + axs[1].set_ylabel('Height (km)') + axs[1].set_xlabel('Cycles') + axs[1].set_yticks(np.linspace(30, 90, 7)) + axs[1].set_yticklabels(np.round(np.linspace(30, 90, 7), 1)) + axs[1].set_xticks(np.arange(15)) + axs[1].set_xticklabels(np.arange(15)) + + # 调整子图之间的边距 + plt.subplots_adjust(top=0.9, bottom=0.1, left=0.05, + right=0.95, hspace=0.3, wspace=0.3) + plt.tight_layout() # 调整布局以避免重叠 + plt.show() +# ----------------------------------------------------------------------------------------------------------------------------- +# 6-5 按月统计的每日重力波势能随天变化的图 + + +def month_power_wave_plot(altitude_min, altitude_max, ktemp_Nz_mon, ktemp_Ptz_mon): + if ktemp_Nz_mon and ktemp_Nz_mon[0] is not None: + nz_shape = np.array(ktemp_Nz_mon[0]).shape + else: + nz_shape = (15, 157) + if ktemp_Ptz_mon and ktemp_Ptz_mon[0] is not None: + ptz_shape = np.array(ktemp_Ptz_mon[0]).shape + else: + ptz_shape = (15, 157) + y = np.round(np.linspace(altitude_min, altitude_max, nz_shape[1]), 2) + x = np.arange(len(date_time.data)) + # 处理 ktemp_Nz_mon + ktemp_Nz_plot = np.array([np.mean(day_data if day_data is not None else np.zeros( + nz_shape), axis=0) for day_data in ktemp_Nz_mon]) + ktemp_Ptz_plot = np.array( + [np.mean(day_data if day_data is not None else np.zeros(nz_shape), axis=0) for day_data in ktemp_Ptz_mon]) + # 处理 ktemp_Ptz_mon(以100为界剔除异常值) + # ktemp_Ptz_plot = np.array([np.mean(day_data if day_data is not None and np.all(day_data <= 100) else np.zeros(ptz_shape), axis=0) for day_data in ktemp_Ptz_mon]) + # 创建一个图形,并指定两个子图 + fig, axs = plt.subplots(1, 2, figsize=(15, 10)) + + # 第一幅图 (a) NZ + cax1 = axs[0].imshow(ktemp_Nz_plot.T[::-1], aspect='auto', cmap='rainbow', origin='lower', + extent=[x[0], x[-1], y[0], y[-1]]) + fig.colorbar(cax1, ax=axs[0]) # 为第一幅图添加颜色条 + axs[0].set_title('(a) NZ') + axs[0].set_xlabel('Time') + axs[0].set_ylabel('Height') + axs[0].set_yticks(np.linspace(30, 100, 8)) + axs[0].set_yticklabels(np.round(np.linspace(30, 100, 8), 1)) + axs[0].set_xticks(x) + axs[0].set_xticklabels(x) + + # 第二幅图 (b) PTZ + cax2 = axs[1].imshow(np.log(ktemp_Ptz_plot.T[::-1]), aspect='auto', + cmap='rainbow', origin='lower', extent=[x[0], x[-1], y[0], y[-1]]) + fig.colorbar(cax2, ax=axs[1]) # 为第二幅图添加颜色条 + axs[1].set_title('(b) PTZ') + axs[1].set_xlabel('Time') + axs[1].set_ylabel('Height') + axs[1].set_yticks(np.linspace(30, 100, 8)) + axs[1].set_yticklabels(np.round(np.linspace(30, 100, 8), 1)) + axs[1].set_xticks(x) + axs[1].set_xticklabels(x) + + # 调整子图之间的边距 + plt.subplots_adjust(top=0.9, bottom=0.1, left=0.05, + right=0.95, hspace=0.3, wspace=0.3) + plt.tight_layout() # 调整布局以避免重叠 + plt.show() + + +# ----------------------------------------------------------------------------------------------------------------------------- +# 6-6 按年统计的每月重力波势能随月变化的图 + + +def year_power_wave_plot(year, path, latitude_min, latitude_max, altitude_min, altitude_max, lamda_low, lamda_high, + lvboin): + # 假设我们已经从process_yearly_data函数中获取了一年的Nz和Ptz数据 + results = process_yearly_data(year, path, latitude_min, latitude_max, altitude_min, altitude_max, lamda_low, + lamda_high, lvboin) + ktemp_Nz_mon_list = results["ktemp_Nz_mon_list"] + ktemp_Ptz_mon_list = results["ktemp_Ptz_mon_list"] + ktemp_Ptz_mon_list.pop(0) + ktemp_Nz_mon_list.pop(0) + + # 准备日期数据,这里假设date_time_list是一年中的所有日期 + date_time_list = results["date_time_list"] + date_time_list.pop(0) + date_nums = mdates.date2num(date_time_list) # 将日期转换为matplotlib可以理解的数字格式 + + # 获取date_time_list长度作为横坐标新的依据 + x_ticks_length = len(date_time_list) + x_ticks = np.arange(0, x_ticks_length, 30) + x_labels = [date_time_list[i] if i < len( + date_time_list) else "" for i in x_ticks] + + # 准备高度数据 + # 假设高度数据有157个点 + y = np.round(np.linspace(altitude_min, altitude_max, 157), 2) + + # 创建一个图形,并指定两个子图 + fig, axs = plt.subplots(1, 2, figsize=(15, 10)) + + # 处理 ktemp_Nz_mon + ktemp_Nz_plot = np.array( + [np.mean(day_data if day_data is not None else np.zeros((15, 157)), axis=0) for day_data in ktemp_Nz_mon_list]) + # 处理 ktemp_Ptz_mon + ktemp_Ptz_plot = np.array( + [np.mean(day_data if day_data is not None else np.zeros((15, 157)), axis=0) for day_data in ktemp_Ptz_mon_list]) + # ktemp_Ptz_plot = np.array( + # [np.mean(day_data if day_data is not None and np.all(day_data <= 100) else np.zeros((15, 157)), axis=0) for + # day_data in ktemp_Ptz_mon_list]) + + # 第一幅图 (a) NZ + cax1 = axs[0].imshow(ktemp_Nz_plot.T[::-1], aspect='auto', cmap='rainbow', origin='lower', + extent=[0, x_ticks_length - 1, y[0], y[-1]]) + fig.colorbar(cax1, ax=axs[0]) # 为第一幅图添加颜色条 + axs[0].set_title('(a) NZ') + axs[0].set_xlabel('Time') + axs[0].set_ylabel('Height') + axs[0].set_yticks(np.linspace(30, 100, 8)) + axs[0].set_yticklabels(np.round(np.linspace(30, 100, 8), 1)) + axs[0].set_xticks(x_ticks) # 设置新的横坐标刻度 + axs[0].set_xticklabels(x_labels, rotation=45) + + # 第二幅图 (b) PTZ + cax2 = axs[1].imshow(np.log(ktemp_Ptz_plot.T[::-1]), aspect='auto', cmap='rainbow', origin='lower', + extent=[0, x_ticks_length - 1, y[0], y[-1]]) + fig.colorbar(cax2, ax=axs[1]) # 为第二幅图添加颜色条 + axs[1].set_title('(b) PTZ') + axs[1].set_xlabel('Time') + axs[1].set_ylabel('Height') + axs[1].set_yticks(np.linspace(30, 100, 8)) + axs[1].set_yticklabels(np.round(np.linspace(30, 100, 8), 1)) + axs[1].set_xticks(x_ticks) # 设置新的横坐标刻度 + axs[1].set_xticklabels(x_labels, rotation=45) + + # 调整子图之间的边距 + plt.subplots_adjust(top=0.9, bottom=0.1, left=0.05, + right=0.95, hspace=0.3, wspace=0.3) + plt.tight_layout() # 调整布局以避免重叠 + plt.show() + + +# ----------------------------------------------------------------------------------------------------------------------------- +# 7 主程序,运行数据,并输出主要统计分析图 - +# ----------------------------------------------------------------------------------------------------------------------------- +# 7-1 挑选某一天进行运行主要程序 +file_path = "E:\\SABER\\2016\\SABER_Temp_O3_April2016_v2.0.nc" +# day_read=2018113, +day_read = 2016094 +# 初始化某一天、某个纬度、高度范围等参数 +latitude_min = 30.0 +latitude_max = 40.0 +altitude_min = 30.0 +altitude_max = 100.0 +lamda_low = 2 +lamda_high = 15 +lvboin = True + +(ktemp_cycles, altitude_cycles, + ktemp_wn0, + ktemp_fit_wn1, ktemp_wn1, + ktemp_fit_wn2, ktemp_wn2, + ktemp_fit_wn3, ktemp_wn3, + ktemp_fit_wn4, ktemp_wn4, + ktemp_fit_wn5, ktemp_wn5, + ktemp_fft, ktemp_fft_lvbo, ktemp_ifft, + ktemp_Nz, ktemp_Ptz) = day_process_main( + file_path, + # day_read=2018113, + day_read, + # 初始化某一天、某个纬度、高度范围等参数 + latitude_min, + latitude_max, + altitude_min, + altitude_max, + lamda_low, lamda_high, lvboin) +# ----------------------------------------------------------------------------------------------------------------------------- +# 7-2 挑选某一个月进行运行主要程序 +(date_time, ktemp_cycles_mon, altitude_cycles_mon, # 某月份 逐日时间 每日不同循环周期温度 不同循环周期高度数据 + # 距平扰动温度 + ktemp_wn0_mon, + # 波数为1的拟合温度 滤波后的扰动温度 + ktemp_fit_wn1_mon, ktemp_wn1_mon, + # 波数为2的拟合温度 滤波后的扰动温度 + ktemp_fit_wn2_mon, ktemp_wn2_mon, + # 波数为3的拟合温度 滤波后的扰动温度 + ktemp_fit_wn3_mon, ktemp_wn3_mon, + # 波数为4的拟合温度 滤波后的扰动温度 + ktemp_fit_wn4_mon, ktemp_wn4_mon, + # 波数为5的拟合温度 滤波后的扰动温度 + ktemp_fit_wn5_mon, ktemp_wn5_mon, + # 滤波0-5后扰动温度傅里叶频谱分析 滤除波长为2km-15km内后频谱分析 滤波后的扰动温度 + ktemp_fft_mon, ktemp_fft_lvbo_mon, ktemp_ifft_mon, + # 滤波后NZ、PTZ重力波势能指标计算 + ktemp_Nz_mon, ktemp_Ptz_mon) = mon_process_main( + file_path, + # 初始化某一天、某个纬度、高度范围等参数 + latitude_min, + latitude_max, + altitude_min, + altitude_max, + lamda_low, lamda_high, lvboin) + +# ----------------------------------------------------------------------------------------------------------------------------- +# 7-3挑选某一年进行运行主要程序 + + +def filename_read(year, month_num, path): + # 月份映射 + month_map = { + 1: "January", 2: "February", 3: "March", 4: "April", + 5: "May", 6: "June", 7: "July", 8: "August", + 9: "September", 10: "October", 11: "November", 12: "December"} + + # 获取月份名称 + month = month_map.get(month_num, "Invalid month number") + + # 构造文件路径:E:\SABER\year\SABER_Temp_O3_MonthYear_v2.0.nc + file_path = f"{path}\\{year}\\SABER_Temp_O3_{month}{year}_v2.0.nc" + + # 打印路径 + print(file_path) + + # 返回文件路径 + return file_path + + +year = 2018 +path = "E:\\SABER" +# 初始化某一天、某个纬度、高度范围等参数 +latitude_min = 30.0 +latitude_max = 40.0 +altitude_min = 20.0 +altitude_max = 105.0 +lamda_low = 2 +lamda_high = 15 +lvboin = True +# 调用函数处理所有月份 +results = process_yearly_data(year, path, latitude_min, latitude_max, + altitude_min, altitude_max, lamda_low, lamda_high, lvboin) + +# 解包返回的字典 +date_time_list = results["date_time_list"] +ktemp_cycles_mon_list = results["ktemp_cycles_mon_list"] +altitude_cycles_mon_list = results["altitude_cycles_mon_list"] +ktemp_wn0_mon_list = results["ktemp_wn0_mon_list"] +ktemp_fit_wn1_mon_list = results["ktemp_fit_wn1_mon_list"] +ktemp_wn1_mon_list = results["ktemp_wn1_mon_list"] +ktemp_fit_wn2_mon_list = results["ktemp_fit_wn2_mon_list"] +ktemp_wn2_mon_list = results["ktemp_wn2_mon_list"] +ktemp_fit_wn3_mon_list = results["ktemp_fit_wn3_mon_list"] +ktemp_wn3_mon_list = results["ktemp_wn3_mon_list"] +ktemp_fit_wn4_mon_list = results["ktemp_fit_wn4_mon_list"] +ktemp_wn4_mon_list = results["ktemp_wn4_mon_list"] +ktemp_fit_wn5_mon_list = results["ktemp_fit_wn5_mon_list"] +ktemp_wn5_mon_list = results["ktemp_wn5_mon_list"] +ktemp_fft_mon_list = results["ktemp_fft_mon_list"] +ktemp_fft_lvbo_mon_list = results["ktemp_fft_lvbo_mon_list"] +ktemp_ifft_mon_list = results["ktemp_ifft_mon_list"] +ktemp_Nz_mon_list = results["ktemp_Nz_mon_list"] +ktemp_Ptz_mon_list = results["ktemp_Ptz_mon_list"] +# ----------------------------------------------------------------------------------------------------------------------------- +# 7-3 绘制不同结果图 +height_no = 1 +day_fit_wave_plotg(height_no, ktemp_wn0, ktemp_fit_wn1, ktemp_wn1, ktemp_fit_wn2, ktemp_wn2, + ktemp_fit_wn3, ktemp_wn3, ktemp_fit_wn4, ktemp_wn4, ktemp_fit_wn5, ktemp_wn5) +cycle_no = 1 +day_fft_ifft_plotg(cycle_no, ktemp_wn5, ktemp_fft, ktemp_ifft, + altitude_min, altitude_max, lamda_low, lamda_high) +day_cycle_power_wave_plot(cycle_no, altitude_min, + altitude_max, ktemp_Nz, ktemp_Ptz) +day_power_wave_plot(altitude_min, altitude_max, ktemp_Nz, ktemp_Ptz) +month_power_wave_plot(altitude_min, altitude_max, ktemp_Nz_mon, ktemp_Ptz_mon) +year_power_wave_plot(year, path, latitude_min, latitude_max, + altitude_min, altitude_max, lamda_low, lamda_high, lvboin) diff --git a/saber/archive/saber_render.py b/saber/archive/saber_render.py new file mode 100644 index 0000000..72955b1 --- /dev/null +++ b/saber/archive/saber_render.py @@ -0,0 +1,825 @@ +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.dates as mdates + +from saber.utils import * +# from matplotlib.colors import LinearSegmentedColormap +# 设置字体为支持中文的字体 +plt.rcParams['font.family'] = 'SimHei' # 设置为黑体(需要你的环境中有该字体) +plt.rcParams['axes.unicode_minus'] = False # 解决负号'-'显示为方块的问题 + + +# ---------------------------------------------------------------------------------------------------------------------------- +# 5---main程序环节 - +# ---------------------------------------------------------------------------------------------------------------------------- +# 按日处理资料 + + +def day_process_main(file_path, day_read, latitude_min, latitude_max, altitude_min, altitude_max, lamda_low, lamda_high, lvboin): + + dataset, tplatitude, tplongitude, tpaltitude, ktemp, time, date, date_time = data_nc_load( + file_path) + # 2018年的第94天 # 4月4日的日期,以年份和年内的第几天表示 + df = day_data_read(date, day_read, tplatitude) + cycles = data_cycle_identify(df, latitude_min, latitude_max) + ktemp_cycles, altitude_cycles = data_cycle_generate( + cycles, ktemp, tpaltitude, altitude_min, altitude_max) + ktemp_wn0 = ktemp_cycles - \ + np.mean(ktemp_cycles, axis=0) # 观测值-平均温度 + ktemp_fit_wn1, ktemp_wn1 = fit_wave(ktemp_wn0, 1) + ktemp_fit_wn2, ktemp_wn2 = fit_wave(ktemp_wn1, 2) + ktemp_fit_wn3, ktemp_wn3 = fit_wave(ktemp_wn2, 3) + ktemp_fit_wn4, ktemp_wn4 = fit_wave(ktemp_wn3, 4) + ktemp_fit_wn5, ktemp_wn5 = fit_wave(ktemp_wn4, 5) + ktemp_fft, ktemp_fft_lvbo, ktemp_ifft = fft_ifft_wave( + ktemp_wn5, lamda_low, lamda_high, altitude_min, altitude_max, lvboin) + ktemp_Nz, ktemp_Ptz = power_indices( + ktemp_cycles, ktemp_wn5, ktemp_ifft, altitude_min, altitude_max) + + return ktemp_cycles, altitude_cycles, ktemp_wn0, ktemp_fit_wn1, ktemp_wn1, ktemp_fit_wn2, ktemp_wn2, ktemp_fit_wn3, ktemp_wn3, ktemp_fit_wn4, ktemp_wn4, ktemp_fit_wn5, ktemp_wn5, ktemp_fft, ktemp_fft_lvbo, ktemp_ifft, ktemp_Nz, ktemp_Ptz +# ---------------------------------------------------------------------------------------------------------------------------- +# 按文件中单日处理资料 + + +def day_process_maing(dataset, tplatitude, tplongitude, tpaltitude, ktemp, time, date, date_time, day_read, + latitude_min, latitude_max, altitude_min, altitude_max, lamda_low, lamda_high, lvboin): + df = day_data_read(date, day_read, tplatitude) + cycles = data_cycle_identify(df, latitude_min, latitude_max) + + if not cycles: # 如果周期列表为空,返回18个None值 + return (None,) * 18 + + ktemp_cycles, altitude_cycles = data_cycle_generate( + cycles, ktemp, tpaltitude, altitude_min, altitude_max) + + if ktemp_cycles is None or altitude_cycles is None: # 再次检查周期数据是否为空 + return (None,) * 18 + + ktemp_wn0 = ktemp_cycles - \ + np.mean(ktemp_cycles, axis=0) # 按照纬向计算平均温度 wn0_temp + ktemp_fit_wn1, ktemp_wn1 = fit_wave(ktemp_wn0, 1) + ktemp_fit_wn2, ktemp_wn2 = fit_wave(ktemp_wn1, 2) + ktemp_fit_wn3, ktemp_wn3 = fit_wave(ktemp_wn2, 3) + ktemp_fit_wn4, ktemp_wn4 = fit_wave(ktemp_wn3, 4) + ktemp_fit_wn5, ktemp_wn5 = fit_wave(ktemp_wn4, 5) + ktemp_fft, ktemp_fft_lvbo, ktemp_ifft = fft_ifft_wave(ktemp_wn5, lamda_low, lamda_high, altitude_min, altitude_max, + lvboin) + ktemp_Nz, ktemp_Ptz = power_indices( + ktemp_cycles, ktemp_wn5, ktemp_ifft, altitude_min, altitude_max) + + return ktemp_cycles, altitude_cycles, ktemp_wn0, ktemp_fit_wn1, ktemp_wn1, ktemp_fit_wn2, ktemp_wn2, ktemp_fit_wn3, ktemp_wn3, ktemp_fit_wn4, ktemp_wn4, ktemp_fit_wn5, ktemp_wn5, ktemp_fft, ktemp_fft_lvbo, ktemp_ifft, ktemp_Nz, ktemp_Ptz + +# ---------------------------------------------------------------------------------------------------------------------------- +# 按月处理资料 + + +def mon_process_main(file_path, latitude_min, latitude_max, altitude_min, altitude_max, lamda_low, lamda_high, lvboin): + # 打开文件并读取数据 + dataset, tplatitude, tplongitude, tpaltitude, ktemp, time, date, date_time = data_nc_load( + file_path) + + ktemp_cycles_mon = [] + altitude_cycles_mon = [] + ktemp_wn0_mon = [] + ktemp_fit_wn1_mon = [] + ktemp_wn1_mon = [] + ktemp_fit_wn2_mon = [] + ktemp_wn2_mon = [] + ktemp_fit_wn3_mon = [] + ktemp_wn3_mon = [] + ktemp_fit_wn4_mon = [] + ktemp_wn4_mon = [] + ktemp_fit_wn5_mon = [] + ktemp_wn5_mon = [] + ktemp_fft_mon = [] + ktemp_fft_lvbo_mon = [] + ktemp_ifft_mon = [] + ktemp_Nz_mon = [] + ktemp_Ptz_mon = [] + + # 遍历每一天,处理数据 + for day_read in date_time: + print(f"读取日期 {day_read}") + # 处理单日数据 + results = day_process_maing( + dataset, tplatitude, tplongitude, tpaltitude, ktemp, time, date, date_time, + day_read, latitude_min, latitude_max, altitude_min, altitude_max, lamda_low, lamda_high, lvboin + ) + + # 检查结果是否包含有效数据 + if results is not None and len(results) == 18: + ktemp_cycles0, altitude_cycles0, ktemp_wn00, ktemp_fit_wn10, ktemp_wn10, ktemp_fit_wn20, ktemp_wn20, ktemp_fit_wn30, ktemp_wn30, ktemp_fit_wn40, ktemp_wn40, ktemp_fit_wn50, ktemp_wn50, ktemp_fft0, ktemp_fft_lvbo0, ktemp_ifft0, ktemp_Nz0, ktemp_Ptz0 = results + + # 将有效数据添加到月度列表中 + ktemp_cycles_mon.append(ktemp_cycles0) + altitude_cycles_mon.append(altitude_cycles0) + ktemp_wn0_mon.append(ktemp_wn00) + ktemp_fit_wn1_mon.append(ktemp_fit_wn10) + ktemp_wn1_mon.append(ktemp_wn10) + ktemp_fit_wn2_mon.append(ktemp_fit_wn20) + ktemp_wn2_mon.append(ktemp_wn20) + ktemp_fit_wn3_mon.append(ktemp_fit_wn30) + ktemp_wn3_mon.append(ktemp_wn30) + ktemp_fit_wn4_mon.append(ktemp_fit_wn40) + ktemp_wn4_mon.append(ktemp_wn40) + ktemp_fit_wn5_mon.append(ktemp_fit_wn50) + ktemp_wn5_mon.append(ktemp_wn50) + ktemp_fft_mon.append(ktemp_fft0) + ktemp_fft_lvbo_mon.append(ktemp_fft_lvbo0) + ktemp_ifft_mon.append(ktemp_ifft0) + ktemp_Nz_mon.append(ktemp_Nz0) + ktemp_Ptz_mon.append(ktemp_Ptz0) + + # 返回整个月的数据 + return (date_time, ktemp_cycles_mon, altitude_cycles_mon, ktemp_wn0_mon, ktemp_fit_wn1_mon, ktemp_wn1_mon, + ktemp_fit_wn2_mon, ktemp_wn2_mon, ktemp_fit_wn3_mon, ktemp_wn3_mon, ktemp_fit_wn4_mon, ktemp_wn4_mon, + ktemp_fit_wn5_mon, ktemp_wn5_mon, ktemp_fft_mon, ktemp_fft_lvbo_mon, ktemp_ifft_mon, ktemp_Nz_mon, + ktemp_Ptz_mon) + # 滤波后NZ、PTZ重力波势能指标计算 +# ---------------------------------------------------------------------------------------------------------------------------- +# 按年处理资料 + + +def process_yearly_data(year, path, latitude_min, latitude_max, altitude_min, altitude_max, lamda_low, lamda_high, lvboin): + # 创建空列表来存储每月的数据 + date_time_list = [] + ktemp_cycles_mon_list = [] + altitude_cycles_mon_list = [] + ktemp_wn0_mon_list = [] + ktemp_fit_wn1_mon_list = [] + ktemp_wn1_mon_list = [] + ktemp_fit_wn2_mon_list = [] + ktemp_wn2_mon_list = [] + ktemp_fit_wn3_mon_list = [] + ktemp_wn3_mon_list = [] + ktemp_fit_wn4_mon_list = [] + ktemp_wn4_mon_list = [] + ktemp_fit_wn5_mon_list = [] + ktemp_wn5_mon_list = [] + ktemp_fft_mon_list = [] + ktemp_fft_lvbo_mon_list = [] + ktemp_ifft_mon_list = [] + ktemp_Nz_mon_list = [] + ktemp_Ptz_mon_list = [] + + # 循环处理每个月的数据 + for month in range(1, 13): + # 获取当前月的文件路径 + file_path = filename_read(year, month, path) + + try: + # 调用 mon_process_main 函数处理文件并获取结果 + (date_time, ktemp_cycles_mon, altitude_cycles_mon, ktemp_wn0_mon, ktemp_fit_wn1_mon, + ktemp_wn1_mon, ktemp_fit_wn2_mon, ktemp_wn2_mon, ktemp_fit_wn3_mon, ktemp_wn3_mon, + ktemp_fit_wn4_mon, ktemp_wn4_mon, ktemp_fit_wn5_mon, ktemp_wn5_mon, ktemp_fft_mon, + ktemp_fft_lvbo_mon, ktemp_ifft_mon, ktemp_Nz_mon, ktemp_Ptz_mon) = mon_process_main( + file_path, latitude_min, latitude_max, altitude_min, altitude_max, lamda_low, lamda_high, lvboin + ) + + # 将每月的结果添加到相应的列表中 + date_time_list.extend(date_time) + ktemp_cycles_mon_list.extend(ktemp_cycles_mon) + altitude_cycles_mon_list.extend(altitude_cycles_mon) + ktemp_wn0_mon_list.extend(ktemp_wn0_mon) + ktemp_fit_wn1_mon_list.extend(ktemp_fit_wn1_mon) + ktemp_wn1_mon_list.extend(ktemp_wn1_mon) + ktemp_fit_wn2_mon_list.extend(ktemp_fit_wn2_mon) + ktemp_wn2_mon_list.extend(ktemp_wn2_mon) + ktemp_fit_wn3_mon_list.extend(ktemp_fit_wn3_mon) + ktemp_wn3_mon_list.extend(ktemp_wn3_mon) + ktemp_fit_wn4_mon_list.extend(ktemp_fit_wn4_mon) + ktemp_wn4_mon_list.extend(ktemp_wn4_mon) + ktemp_fit_wn5_mon_list.extend(ktemp_fit_wn5_mon) + ktemp_wn5_mon_list.extend(ktemp_wn5_mon) + ktemp_fft_mon_list.extend(ktemp_fft_mon) + ktemp_fft_lvbo_mon_list.extend(ktemp_fft_lvbo_mon) + ktemp_ifft_mon_list.extend(ktemp_ifft_mon) + ktemp_Nz_mon_list.extend(ktemp_Nz_mon) + ktemp_Ptz_mon_list.extend(ktemp_Ptz_mon) + + except Exception as e: + print(f"处理文件 {file_path} 时出错(月份:{month}):{e}") + continue # 出现错误时跳过当前月份,继续处理下个月 + + # 返回所有月份的处理结果 + return { + "date_time_list": date_time_list, + "ktemp_cycles_mon_list": ktemp_cycles_mon_list, + "altitude_cycles_mon_list": altitude_cycles_mon_list, + "ktemp_wn0_mon_list": ktemp_wn0_mon_list, + "ktemp_fit_wn1_mon_list": ktemp_fit_wn1_mon_list, + "ktemp_wn1_mon_list": ktemp_wn1_mon_list, + "ktemp_fit_wn2_mon_list": ktemp_fit_wn2_mon_list, + "ktemp_wn2_mon_list": ktemp_wn2_mon_list, + "ktemp_fit_wn3_mon_list": ktemp_fit_wn3_mon_list, + "ktemp_wn3_mon_list": ktemp_wn3_mon_list, + "ktemp_fit_wn4_mon_list": ktemp_fit_wn4_mon_list, + "ktemp_wn4_mon_list": ktemp_wn4_mon_list, + "ktemp_fit_wn5_mon_list": ktemp_fit_wn5_mon_list, + "ktemp_wn5_mon_list": ktemp_wn5_mon_list, + "ktemp_fft_mon_list": ktemp_fft_mon_list, + "ktemp_fft_lvbo_mon_list": ktemp_fft_lvbo_mon_list, + "ktemp_ifft_mon_list": ktemp_ifft_mon_list, + "ktemp_Nz_mon_list": ktemp_Nz_mon_list, + "ktemp_Ptz_mon_list": ktemp_Ptz_mon_list + } + +# ----------------------------------------------------------------------------------------------------------------------------- +# 6-主要统计分析图绘制 - +# ----------------------------------------------------------------------------------------------------------------------------- +# 6-1 示例的逐层滤波效果图---不同波数 曲线图 + + +def day_fit_wave_plot(height_no, ktemp_wn0, ktemp_fit_wn1, ktemp_wn1, ktemp_fit_wn2, ktemp_wn2, ktemp_fit_wn3, ktemp_wn3, ktemp_fit_wn4, ktemp_wn4, ktemp_fit_wn5, ktemp_wn5): + + N = len(ktemp_wn0[:, height_no]) + # 循环周期索引 + x = np.arange(N) + + y1_1 = ktemp_wn0[:, height_no] + y1_2 = ktemp_fit_wn1[:, height_no] + + y2_1 = ktemp_wn1[:, height_no] + y2_2 = ktemp_fit_wn2[:, height_no] + + y3_1 = ktemp_wn2[:, height_no] + y3_2 = ktemp_fit_wn3[:, height_no] + + y4_1 = ktemp_wn3[:, height_no] + y4_2 = ktemp_fit_wn4[:, height_no] + + y5_1 = ktemp_wn4[:, height_no] + y5_2 = ktemp_fit_wn5[:, height_no] + + y6 = ktemp_wn5[:, height_no] + + plt.figure(figsize=(16, 10)) # 调整图形大小 + # 原始信号的时间序列 + plt.subplot(2, 3, 1) + plt.plot(x, y1_1, label='原始信号') + plt.plot(x, y1_2, label='拟合信号', linestyle='--') + plt.title('(a)波数k=1') + plt.xlabel('Cycles', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距 + plt.legend() + plt.tight_layout() # 调整子图参数以适应图形区域 + + plt.subplot(2, 3, 2) + plt.plot(x, y2_1, label='原始信号') + plt.plot(x, y2_2, label='拟合信号', linestyle='--') + plt.title('(b)波数k=2') + plt.xlabel('Cycles', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距 + plt.legend() + plt.tight_layout() # 调整子图参数以适应图形区域 + + plt.subplot(2, 3, 3) + plt.plot(x, y3_1, label='原始信号') + plt.plot(x, y3_2, label='拟合信号', linestyle='--') + plt.title('(c)波数k=3') + plt.xlabel('Cycles', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距 + plt.legend() + plt.tight_layout() # 调整子图参数以适应图形区域 + + plt.subplot(2, 3, 4) + plt.plot(x, y4_1, label='原始信号') + plt.plot(x, y4_2, label='拟合信号', linestyle='--') + plt.title('(d)波数k=4') + plt.xlabel('Cycles', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距 + plt.legend() + plt.tight_layout() # 调整子图参数以适应图形区域 + + plt.subplot(2, 3, 5) + plt.plot(x, y5_1, label='原始信号') + plt.plot(x, y5_2, label='拟合信号', linestyle='--') + plt.title('(e)波数k=5') + plt.xlabel('Cycles', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距 + plt.legend() + plt.tight_layout() # 调整子图参数以适应图形区域 + + plt.subplot(2, 3, 6) + plt.plot(x, y6, label='滤波信号') + plt.title('(f)滤波1-5后信号') + plt.xlabel('Cycles', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距 + plt.tight_layout() # 调整子图参数以适应图形区域 + + # 调整子图之间的边距 + plt.subplots_adjust(top=0.8, bottom=0.2, left=0.1, + right=0.8, hspace=0.3, wspace=0.2) + + plt.show() + + +def day_fit_wave_plotg(height_no, ktemp_wn0, ktemp_fit_wn1, ktemp_wn1, ktemp_fit_wn2, + ktemp_wn2, ktemp_fit_wn3, ktemp_wn3, ktemp_fit_wn4, + ktemp_wn4, ktemp_fit_wn5, ktemp_wn5): + + N = len(ktemp_wn0[:, height_no]) + x = np.arange(N) + + y1_1, y1_2 = ktemp_wn0[:, height_no], ktemp_fit_wn1[:, height_no] + y2_1, y2_2 = ktemp_wn1[:, height_no], ktemp_fit_wn2[:, height_no] + y3_1, y3_2 = ktemp_wn2[:, height_no], ktemp_fit_wn3[:, height_no] + y4_1, y4_2 = ktemp_wn3[:, height_no], ktemp_fit_wn4[:, height_no] + y5_1, y5_2 = ktemp_wn4[:, height_no], ktemp_fit_wn5[:, height_no] + y6 = ktemp_wn5[:, height_no] + + plt.figure(figsize=(16, 10)) + + y_limits = (min(min(y1_1), min(y2_1), min(y3_1), min(y4_1), min(y5_1), min(y6)), + max(max(y1_1), max(y2_1), max(y3_1), max(y4_1), max(y5_1), max(y6))) + + for i, (y1, y2) in enumerate([(y1_1, y1_2), (y2_1, y2_2), (y3_1, y3_2), + (y4_1, y4_2), (y5_1, y5_2), (y6, None)]): + plt.subplot(2, 3, i + 1) + plt.plot(x, y1, label='原始信号') + if y2 is not None: + plt.plot(x, y2, label='拟合信号', linestyle='--') + plt.title(f'({"abcdef"[i]})波数k={i + 1 if i < 5 else "滤波0-5"}') + plt.xlabel('Cycles', labelpad=10) + plt.ylabel('温度 (K)', labelpad=10) + plt.legend() + plt.xticks(x) # 设置横坐标为整数 + plt.ylim(y_limits) # 设置统一纵坐标范围 + plt.tight_layout() + + plt.subplots_adjust(top=0.8, bottom=0.2, left=0.1, + right=0.8, hspace=0.3, wspace=0.2) + plt.show() + + +# ----------------------------------------------------------------------------------------------------------------------------- +# 6-2 示例的高度滤波处理--不同循环周期 曲线图 +def day_fft_ifft_plot(cycle_no, ktemp_wn5, ktemp_fft, ktemp_ifft, altitude_min, altitude_max, lamda_low, lamda_high): + + N = len(ktemp_wn5[cycle_no, :]) + # 采样时间间隔,其倒数等于采用频率,以1km为标准尺度等同于1s,假设波的速度为1km/s + dt = (altitude_max-altitude_min)/(N-1) + # 时间序列索引 + n = np.arange(N) + f = n / (N * dt) + t = np.round(np.linspace(altitude_min, altitude_max, N), 2) + + # 原始扰动温度 + x = ktemp_wn5[cycle_no, :] + # 傅里叶变换频谱分析 + y = ktemp_fft[cycle_no, :] + # 滤波后的傅里叶变换频谱分析 + yy = ktemp_fft_lvbo[cycle_no, :] + # 傅里叶逆变换后的扰动温度 + yyy = ktemp_ifft[cycle_no, :] + + plt.figure(figsize=(15, 10)) # 调整图形大小 + # 原始信号的时间序列 + plt.subplot(2, 2, 1) + plt.plot(t, x) + plt.title('(a)原始信号') + plt.xlabel('高度 (km)', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距 + # 原始振幅谱 + plt.subplot(2, 2, 2) + plt.plot(f, np.abs(y) * 2 / N) + plt.title('(b))原始振幅谱') + plt.xlabel('频率/Hz', labelpad=10) # 增加标签间距 + plt.ylabel('振幅', labelpad=10) # 增加标签间距 + + # 通过IFFT回到时间域 + plt.subplot(2, 2, 3) + plt.plot(t, yyy) + plt.title('(c))傅里叶逆变换') + plt.xlabel('高度 (km)', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距 + + # 滤波后的振幅谱 + plt.subplot(2, 2, 4) + plt.plot(f, np.abs(yy) * 2 / N) + plt.title(f'(d)滤除波长 < {lamda_low} km, > {lamda_high} km的波') + plt.xlabel('频率/Hz', labelpad=10) # 增加标签间距 + plt.ylabel('振幅', labelpad=10) # 增加标签间距 + + # 调整子图之间的边距 + plt.subplots_adjust(top=0.8, bottom=0.2, left=0.2, + right=0.8, hspace=0.3, wspace=0.2) + + plt.show() + + # 绘制原始信号与滤波后信号 + plt.figure(figsize=(12, 6)) # 调整图形大小 + plt.plot(t, x, label='原始信号') + plt.plot(t, yyy, label='滤波后信号', linestyle='--') + plt.title('信号比较') + plt.xlabel('高度 (km)', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距 + plt.legend() + plt.tight_layout() # 调整子图参数以适应图形区域 + plt.show() + + +def day_fft_ifft_plotg(cycle_no, ktemp_wn5, ktemp_fft, ktemp_ifft, altitude_min, altitude_max, lamda_low, lamda_high): + + N = len(ktemp_wn5[cycle_no, :]) + dt = (altitude_max - altitude_min) / (N - 1) # 采样时间间隔 + n = np.arange(N) # 时间序列索引 + f = n / (N * dt) + t = np.round(np.linspace(altitude_min, altitude_max, N), 2) + + x = ktemp_wn5[cycle_no, :] # 原始扰动温度 + y = ktemp_fft[cycle_no, :] # 傅里叶变换频谱分析 + yy = ktemp_fft_lvbo[cycle_no, :] # 滤波后的傅里叶变换频谱分析 + yyy = ktemp_ifft[cycle_no, :] # 傅里叶逆变换后的扰动温度 + + plt.figure(figsize=(15, 10)) # 调整图形大小 + + # 计算纵坐标范围 + temp_limits = (min(min(x), min(yyy)), max(max(x), max(yyy))) + amp_limits = (0, max(np.max(np.abs(y) * 2 / N), + np.max(np.abs(yy) * 2 / N))) + + # 原始信号的时间序列 + plt.subplot(2, 2, 1) + plt.plot(t, x) + plt.title('(a)原始信号') + plt.xlabel('高度 (km)', labelpad=10) + plt.ylabel('温度 (K)', labelpad=10) + plt.ylim(temp_limits) # 设置统一纵坐标范围 + + # 原始振幅谱 + plt.subplot(2, 2, 2) + plt.plot(f, np.abs(y) * 2 / N) + plt.title('(b)原始振幅谱') + plt.xlabel('频率/Hz', labelpad=10) + plt.ylabel('振幅', labelpad=10) + plt.ylim(amp_limits) # 设置统一纵坐标范围 + + # 通过IFFT回到时间域 + plt.subplot(2, 2, 3) + plt.plot(t, yyy) + plt.title('(c)傅里叶逆变换') + plt.xlabel('高度 (km)', labelpad=10) + plt.ylabel('温度 (K)', labelpad=10) + plt.ylim(temp_limits) # 设置统一纵坐标范围 + + # 滤波后的振幅谱 + plt.subplot(2, 2, 4) + plt.plot(f, np.abs(yy) * 2 / N) + plt.title(f'(d)滤除波长 < {lamda_low} km, > {lamda_high} km的波') + plt.xlabel('频率/Hz', labelpad=10) + plt.ylabel('振幅', labelpad=10) + plt.ylim(amp_limits) # 设置统一纵坐标范围 + + # 调整子图之间的边距 + plt.subplots_adjust(top=0.8, bottom=0.2, left=0.2, + right=0.8, hspace=0.3, wspace=0.2) + plt.show() + + # 绘制原始信号与滤波后信号 + plt.figure(figsize=(6, 8)) # 调整图形大小 + plt.plot(x, t, label='原始信号') + plt.plot(yyy, t, label='滤波后信号', linestyle='--') + plt.title('信号比较') + plt.ylabel('高度 (km)', labelpad=10) # 增加标签间距 + plt.xlabel('温度 (K)', labelpad=10) # 增加标签间距 + plt.legend() + plt.xlim(temp_limits) # 设置统一纵坐标范围 + plt.tight_layout() + plt.show() + +# ----------------------------------------------------------------------------------------------------------------------------- +# 6-3 示例的按高度的重力波势能变化曲线图 + + +def day_cycle_power_wave_plot(cycle_no, altitude_min, altitude_max, ktemp_Nz, ktemp_Ptz): + + N = len(ktemp_Nz[cycle_no, :]) + y = np.round(np.linspace(altitude_min, altitude_max, N), 2) + x1 = ktemp_Nz[cycle_no, :] + x2 = ktemp_Ptz[cycle_no, :] + + plt.figure(figsize=(12, 10)) # 调整图形大小 + # 原始信号的时间序列 + plt.subplot(1, 2, 1) + plt.plot(x1[::-1], y, label='原始信号') + plt.title('(a)Nz') + plt.xlabel('Nz', labelpad=10) # 增加标签间距 + plt.ylabel('高度 (km)', labelpad=10) # 增加标签间距 + + # 原始信号的时间序列 + plt.subplot(1, 2, 2) + plt.plot(x2[::-1], y, label='原始信号') + plt.title('(b)Ptz') + plt.xlabel('Ptz', labelpad=10) # 增加标签间距 + plt.ylabel('高度 (km)', labelpad=10) # 增加标签间距 + + # 调整子图之间的边距 + plt.subplots_adjust(top=0.8, bottom=0.2, left=0.1, + right=0.8, hspace=0.3, wspace=0.2) + plt.tight_layout() # 调整子图参数以适应图形区域 + + plt.show() +# ----------------------------------------------------------------------------------------------------------------------------- +# 6-4 按日统计的按周期计算的不同高度的重力波势能 平面图 + + +def day_power_wave_plot(altitude_min, altitude_max, ktemp_Nz, ktemp_Ptz): + # 假设 ktemp_Nz 和 ktemp_Ptz 以及 altitude_min, altitude_max 已经定义好 + x = np.arange(ktemp_Nz.shape[0]) + y = np.round(np.linspace(altitude_min, altitude_max, ktemp_Nz.shape[1]), 2) + + # 创建一个图形,并指定两个子图 + fig, axs = plt.subplots(1, 2, figsize=(15, 10)) + + # 第一幅图 (a) NZ + cax1 = axs[0].imshow(ktemp_Nz.T[::-1], aspect='auto', cmap='viridis', + origin='lower', extent=[x[0], x[-1], y[0], y[-1]]) + fig.colorbar(cax1, ax=axs[0]) # 为第一幅图添加颜色条 + axs[0].set_title('(a) NZ') + axs[0].set_ylabel('Height (km)') + axs[0].set_xlabel('Cycles') + axs[0].set_yticks(np.linspace(30, 90, 7)) + axs[0].set_yticklabels(np.round(np.linspace(30, 90, 7), 1)) + axs[0].set_xticks(np.arange(15)) + axs[0].set_xticklabels(np.arange(15)) + + # 第二幅图 (b) PTZ + cax2 = axs[1].imshow(ktemp_Ptz.T[::-1], aspect='auto', cmap='viridis', + origin='lower', extent=[x[0], x[-1], y[0], y[-1]]) + fig.colorbar(cax2, ax=axs[1]) # 为第二幅图添加颜色条 + axs[1].set_title('(b) PTZ') + axs[1].set_ylabel('Height (km)') + axs[1].set_xlabel('Cycles') + axs[1].set_yticks(np.linspace(30, 90, 7)) + axs[1].set_yticklabels(np.round(np.linspace(30, 90, 7), 1)) + axs[1].set_xticks(np.arange(15)) + axs[1].set_xticklabels(np.arange(15)) + + # 调整子图之间的边距 + plt.subplots_adjust(top=0.9, bottom=0.1, left=0.05, + right=0.95, hspace=0.3, wspace=0.3) + plt.tight_layout() # 调整布局以避免重叠 + plt.show() +# ----------------------------------------------------------------------------------------------------------------------------- +# 6-5 按月统计的每日重力波势能随天变化的图 + + +def month_power_wave_plot(altitude_min, altitude_max, ktemp_Nz_mon, ktemp_Ptz_mon): + if ktemp_Nz_mon and ktemp_Nz_mon[0] is not None: + nz_shape = np.array(ktemp_Nz_mon[0]).shape + else: + nz_shape = (15, 157) + if ktemp_Ptz_mon and ktemp_Ptz_mon[0] is not None: + ptz_shape = np.array(ktemp_Ptz_mon[0]).shape + else: + ptz_shape = (15, 157) + y = np.round(np.linspace(altitude_min, altitude_max, nz_shape[1]), 2) + x = np.arange(len(date_time.data)) + # 处理 ktemp_Nz_mon + ktemp_Nz_plot = np.array([np.mean(day_data if day_data is not None else np.zeros( + nz_shape), axis=0) for day_data in ktemp_Nz_mon]) + ktemp_Ptz_plot = np.array( + [np.mean(day_data if day_data is not None else np.zeros(nz_shape), axis=0) for day_data in ktemp_Ptz_mon]) + # 处理 ktemp_Ptz_mon(以100为界剔除异常值) + # ktemp_Ptz_plot = np.array([np.mean(day_data if day_data is not None and np.all(day_data <= 100) else np.zeros(ptz_shape), axis=0) for day_data in ktemp_Ptz_mon]) + # 创建一个图形,并指定两个子图 + fig, axs = plt.subplots(1, 2, figsize=(15, 10)) + + # 第一幅图 (a) NZ + cax1 = axs[0].imshow(ktemp_Nz_plot.T[::-1], aspect='auto', cmap='rainbow', origin='lower', + extent=[x[0], x[-1], y[0], y[-1]]) + fig.colorbar(cax1, ax=axs[0]) # 为第一幅图添加颜色条 + axs[0].set_title('(a) NZ') + axs[0].set_xlabel('Time') + axs[0].set_ylabel('Height') + axs[0].set_yticks(np.linspace(30, 100, 8)) + axs[0].set_yticklabels(np.round(np.linspace(30, 100, 8), 1)) + axs[0].set_xticks(x) + axs[0].set_xticklabels(x) + + # 第二幅图 (b) PTZ + cax2 = axs[1].imshow(np.log(ktemp_Ptz_plot.T[::-1]), aspect='auto', + cmap='rainbow', origin='lower', extent=[x[0], x[-1], y[0], y[-1]]) + fig.colorbar(cax2, ax=axs[1]) # 为第二幅图添加颜色条 + axs[1].set_title('(b) PTZ') + axs[1].set_xlabel('Time') + axs[1].set_ylabel('Height') + axs[1].set_yticks(np.linspace(30, 100, 8)) + axs[1].set_yticklabels(np.round(np.linspace(30, 100, 8), 1)) + axs[1].set_xticks(x) + axs[1].set_xticklabels(x) + + # 调整子图之间的边距 + plt.subplots_adjust(top=0.9, bottom=0.1, left=0.05, + right=0.95, hspace=0.3, wspace=0.3) + plt.tight_layout() # 调整布局以避免重叠 + plt.show() + + +# ----------------------------------------------------------------------------------------------------------------------------- +# 6-6 按年统计的每月重力波势能随月变化的图 + + +def year_power_wave_plot(year, path, latitude_min, latitude_max, altitude_min, altitude_max, lamda_low, lamda_high, + lvboin): + # 假设我们已经从process_yearly_data函数中获取了一年的Nz和Ptz数据 + results = process_yearly_data(year, path, latitude_min, latitude_max, altitude_min, altitude_max, lamda_low, + lamda_high, lvboin) + ktemp_Nz_mon_list = results["ktemp_Nz_mon_list"] + ktemp_Ptz_mon_list = results["ktemp_Ptz_mon_list"] + ktemp_Ptz_mon_list.pop(0) + ktemp_Nz_mon_list.pop(0) + + # 准备日期数据,这里假设date_time_list是一年中的所有日期 + date_time_list = results["date_time_list"] + date_time_list.pop(0) + date_nums = mdates.date2num(date_time_list) # 将日期转换为matplotlib可以理解的数字格式 + + # 获取date_time_list长度作为横坐标新的依据 + x_ticks_length = len(date_time_list) + x_ticks = np.arange(0, x_ticks_length, 30) + x_labels = [date_time_list[i] if i < len( + date_time_list) else "" for i in x_ticks] + + # 准备高度数据 + # 假设高度数据有157个点 + y = np.round(np.linspace(altitude_min, altitude_max, 157), 2) + + # 创建一个图形,并指定两个子图 + fig, axs = plt.subplots(1, 2, figsize=(15, 10)) + + # 处理 ktemp_Nz_mon + ktemp_Nz_plot = np.array( + [np.mean(day_data if day_data is not None else np.zeros((15, 157)), axis=0) for day_data in ktemp_Nz_mon_list]) + # 处理 ktemp_Ptz_mon + ktemp_Ptz_plot = np.array( + [np.mean(day_data if day_data is not None else np.zeros((15, 157)), axis=0) for day_data in ktemp_Ptz_mon_list]) + # ktemp_Ptz_plot = np.array( + # [np.mean(day_data if day_data is not None and np.all(day_data <= 100) else np.zeros((15, 157)), axis=0) for + # day_data in ktemp_Ptz_mon_list]) + + # 第一幅图 (a) NZ + cax1 = axs[0].imshow(ktemp_Nz_plot.T[::-1], aspect='auto', cmap='rainbow', origin='lower', + extent=[0, x_ticks_length - 1, y[0], y[-1]]) + fig.colorbar(cax1, ax=axs[0]) # 为第一幅图添加颜色条 + axs[0].set_title('(a) NZ') + axs[0].set_xlabel('Time') + axs[0].set_ylabel('Height') + axs[0].set_yticks(np.linspace(30, 100, 8)) + axs[0].set_yticklabels(np.round(np.linspace(30, 100, 8), 1)) + axs[0].set_xticks(x_ticks) # 设置新的横坐标刻度 + axs[0].set_xticklabels(x_labels, rotation=45) + + # 第二幅图 (b) PTZ + cax2 = axs[1].imshow(np.log(ktemp_Ptz_plot.T[::-1]), aspect='auto', cmap='rainbow', origin='lower', + extent=[0, x_ticks_length - 1, y[0], y[-1]]) + fig.colorbar(cax2, ax=axs[1]) # 为第二幅图添加颜色条 + axs[1].set_title('(b) PTZ') + axs[1].set_xlabel('Time') + axs[1].set_ylabel('Height') + axs[1].set_yticks(np.linspace(30, 100, 8)) + axs[1].set_yticklabels(np.round(np.linspace(30, 100, 8), 1)) + axs[1].set_xticks(x_ticks) # 设置新的横坐标刻度 + axs[1].set_xticklabels(x_labels, rotation=45) + + # 调整子图之间的边距 + plt.subplots_adjust(top=0.9, bottom=0.1, left=0.05, + right=0.95, hspace=0.3, wspace=0.3) + plt.tight_layout() # 调整布局以避免重叠 + plt.show() + + +# ----------------------------------------------------------------------------------------------------------------------------- +# 7 主程序,运行数据,并输出主要统计分析图 - +# ----------------------------------------------------------------------------------------------------------------------------- +# 7-1 挑选某一天进行运行主要程序 +file_path = "./SABER/data\\2016\\SABER_Temp_O3_April2016_v2.0.nc" +# day_read=2018113, +day_read = 2016094 +# 初始化某一天、某个纬度、高度范围等参数 +latitude_min = 30.0 +latitude_max = 40.0 +altitude_min = 30.0 +altitude_max = 100.0 +lamda_low = 2 +lamda_high = 15 +lvboin = True + +(ktemp_cycles, altitude_cycles, + ktemp_wn0, + ktemp_fit_wn1, ktemp_wn1, + ktemp_fit_wn2, ktemp_wn2, + ktemp_fit_wn3, ktemp_wn3, + ktemp_fit_wn4, ktemp_wn4, + ktemp_fit_wn5, ktemp_wn5, + ktemp_fft, ktemp_fft_lvbo, ktemp_ifft, + ktemp_Nz, ktemp_Ptz) = day_process_main( + file_path, + # day_read=2018113, + day_read, + # 初始化某一天、某个纬度、高度范围等参数 + latitude_min, + latitude_max, + altitude_min, + altitude_max, + lamda_low, lamda_high, lvboin) +# ----------------------------------------------------------------------------------------------------------------------------- +# 7-2 挑选某一个月进行运行主要程序 +(date_time, ktemp_cycles_mon, altitude_cycles_mon, # 某月份 逐日时间 每日不同循环周期温度 不同循环周期高度数据 + # 距平扰动温度 + ktemp_wn0_mon, + # 波数为1的拟合温度 滤波后的扰动温度 + ktemp_fit_wn1_mon, ktemp_wn1_mon, + # 波数为2的拟合温度 滤波后的扰动温度 + ktemp_fit_wn2_mon, ktemp_wn2_mon, + # 波数为3的拟合温度 滤波后的扰动温度 + ktemp_fit_wn3_mon, ktemp_wn3_mon, + # 波数为4的拟合温度 滤波后的扰动温度 + ktemp_fit_wn4_mon, ktemp_wn4_mon, + # 波数为5的拟合温度 滤波后的扰动温度 + ktemp_fit_wn5_mon, ktemp_wn5_mon, + # 滤波0-5后扰动温度傅里叶频谱分析 滤除波长为2km-15km内后频谱分析 滤波后的扰动温度 + ktemp_fft_mon, ktemp_fft_lvbo_mon, ktemp_ifft_mon, + # 滤波后NZ、PTZ重力波势能指标计算 + ktemp_Nz_mon, ktemp_Ptz_mon) = mon_process_main( + file_path, + # 初始化某一天、某个纬度、高度范围等参数 + latitude_min, + latitude_max, + altitude_min, + altitude_max, + lamda_low, lamda_high, lvboin) + +# ----------------------------------------------------------------------------------------------------------------------------- +# 7-3挑选某一年进行运行主要程序 + + +def filename_read(year, month_num, path): + # 月份映射 + month_map = { + 1: "January", 2: "February", 3: "March", 4: "April", + 5: "May", 6: "June", 7: "July", 8: "August", + 9: "September", 10: "October", 11: "November", 12: "December"} + + # 获取月份名称 + month = month_map.get(month_num, "Invalid month number") + + # 构造文件路径:E:\SABER\year\SABER_Temp_O3_MonthYear_v2.0.nc + file_path = f"{path}\\{year}\\SABER_Temp_O3_{month}{year}_v2.0.nc" + + # 打印路径 + print(file_path) + + # 返回文件路径 + return file_path + + +year = 2018 +path = "./saber/data" +# 初始化某一天、某个纬度、高度范围等参数 +latitude_min = 30.0 +latitude_max = 40.0 +altitude_min = 20.0 +altitude_max = 105.0 +lamda_low = 2 +lamda_high = 15 +lvboin = True +# 调用函数处理所有月份 +results = process_yearly_data(year, path, latitude_min, latitude_max, + altitude_min, altitude_max, lamda_low, lamda_high, lvboin) + +# 解包返回的字典 +date_time_list = results["date_time_list"] +ktemp_cycles_mon_list = results["ktemp_cycles_mon_list"] +altitude_cycles_mon_list = results["altitude_cycles_mon_list"] +ktemp_wn0_mon_list = results["ktemp_wn0_mon_list"] +ktemp_fit_wn1_mon_list = results["ktemp_fit_wn1_mon_list"] +ktemp_wn1_mon_list = results["ktemp_wn1_mon_list"] +ktemp_fit_wn2_mon_list = results["ktemp_fit_wn2_mon_list"] +ktemp_wn2_mon_list = results["ktemp_wn2_mon_list"] +ktemp_fit_wn3_mon_list = results["ktemp_fit_wn3_mon_list"] +ktemp_wn3_mon_list = results["ktemp_wn3_mon_list"] +ktemp_fit_wn4_mon_list = results["ktemp_fit_wn4_mon_list"] +ktemp_wn4_mon_list = results["ktemp_wn4_mon_list"] +ktemp_fit_wn5_mon_list = results["ktemp_fit_wn5_mon_list"] +ktemp_wn5_mon_list = results["ktemp_wn5_mon_list"] +ktemp_fft_mon_list = results["ktemp_fft_mon_list"] +ktemp_fft_lvbo_mon_list = results["ktemp_fft_lvbo_mon_list"] +ktemp_ifft_mon_list = results["ktemp_ifft_mon_list"] +ktemp_Nz_mon_list = results["ktemp_Nz_mon_list"] +ktemp_Ptz_mon_list = results["ktemp_Ptz_mon_list"] +# ----------------------------------------------------------------------------------------------------------------------------- +# 7-3 绘制不同结果图 +height_no = 1 +day_fit_wave_plotg(height_no, ktemp_wn0, ktemp_fit_wn1, ktemp_wn1, ktemp_fit_wn2, ktemp_wn2, + ktemp_fit_wn3, ktemp_wn3, ktemp_fit_wn4, ktemp_wn4, ktemp_fit_wn5, ktemp_wn5) +cycle_no = 1 +day_fft_ifft_plotg(cycle_no, ktemp_wn5, ktemp_fft, ktemp_ifft, + altitude_min, altitude_max, lamda_low, lamda_high) +day_cycle_power_wave_plot(cycle_no, altitude_min, + altitude_max, ktemp_Nz, ktemp_Ptz) +day_power_wave_plot(altitude_min, altitude_max, ktemp_Nz, ktemp_Ptz) +month_power_wave_plot(altitude_min, altitude_max, ktemp_Nz_mon, ktemp_Ptz_mon) +year_power_wave_plot(year, path, latitude_min, latitude_max, + altitude_min, altitude_max, lamda_low, lamda_high, lvboin) diff --git a/saber/archive/zjy_wave_fit_plot.py b/saber/archive/zjy_wave_fit_plot.py new file mode 100644 index 0000000..6f87e9e --- /dev/null +++ b/saber/archive/zjy_wave_fit_plot.py @@ -0,0 +1,840 @@ +from io import BytesIO +import netCDF4 as nc +import numpy as np +import matplotlib.pyplot as plt +import pandas as pd +from scipy.optimize import curve_fit +# from matplotlib.colors import LinearSegmentedColormap +# 设置字体为支持中文的字体 +plt.rcParams['font.family'] = 'SimHei' # 设置为黑体(需要你的环境中有该字体) +plt.rcParams['axes.unicode_minus'] = False # 解决负号'-'显示为方块的问题 + +# ---------------------------------------------------------------------------------------------------------------------------- +# 1---打开文件并读取不同变量数据 - +# ---------------------------------------------------------------------------------------------------------------------------- + + +def data_nc_load(file_path): + + dataset = nc.Dataset(file_path, 'r') + + # 纬度数据,二维数组形状为(42820,379) 42820为事件,379则为不同高度 + tplatitude = dataset.variables['tplatitude'][:, :] + tplongitude = dataset.variables['tplongitude'][:, :] # 经度数据 + tpaltitude = dataset.variables['tpaltitude'][:, :] # 高度,二维数组形状为(42820,379) + time = dataset.variables['time'][:, :] # 二维数组形状为(42820,379) + date = dataset.variables['date'][:] + date_time = np.unique(date) # 输出数据时间信息 + ktemp = dataset.variables['ktemp'][:, :] # 温度数据,二维数组形状为(42820,379) + + return dataset, tplatitude, tplongitude, tpaltitude, ktemp, time, date, date_time +# ---------------------------------------------------------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------------------------------------------------------- +# 2---筛选某一天、某个纬度和高度范围15个不同cycle的温度数据 - +# ---------------------------------------------------------------------------------------------------------------------------- +# 2-1 读取某一天的所有事件及其对应的纬度数据 + + +def day_data_read(date, day_read, tplatitude): + + events = np.where(date == day_read)[0] # 读取筛选天的事件编号 4294-5714位置,从0开始编号 + time_events = date[date == day_read] # 读取筛选天的事件编号 4294-5714位置,从0开始编号 + latitudes = tplatitude[events, 189] # 输出每个事件中间位置 即第189个经纬度 + df = pd.DataFrame([ # 创建一个包含事件编号、纬度的列表,共1421个事件 + {'time': time, 'event': event, 'latitude': lat} + for time, event, lat in zip(time_events, events, latitudes)]) + + # print(df.head()) # 打印前几行数据以检查 + + return df +# ---------------------------------------------------------------------------------------------------------------------------- +# 2-2 将事件按照卫星轨迹周期进行输出处理,并输出落在纬度范围内的每个周期的事件的行号 + + +def data_cycle_identify(df, latitude_min, latitude_max): + + cycles = [] # 存储每个周期的事件编号列表 + # 存储当前周期的事件编号 + current_cycle_events = [] + prev_latitude = None + + # 遍历DataFrame中的每一行以识别周期和筛选事件 + for index, row in df.iterrows(): + current_event = int(row['event']) + current_latitude = row['latitude'] + + if prev_latitude is not None and prev_latitude < 0 and current_latitude >= 0: # 检查是否是新周期的开始(纬度从负变正,且首次变正) + # 重置当前周期的事件编号列表 + current_cycle_events = [] + + if latitude_min <= current_latitude <= latitude_max: # 如果事件的纬度在指定范围内,添加到当前周期的事件编号列表 + current_cycle_events.append(current_event) + + if prev_latitude is not None and prev_latitude >= 0 and current_latitude < 0: # 检查是否是周期的结束(纬度从正变负) + + # 添加当前周期的事件编号列表到周期列表 + if current_cycle_events: # 确保当前周期有事件编号 + cycles.append(current_cycle_events) + current_cycle_events = [] # 重置当前周期的事件编号列表 + prev_latitude = current_latitude + + if current_cycle_events: # 处理最后一个周期,如果存在的话 + cycles.append(current_cycle_events) + + print(f"一天周期为 {len(cycles)}") + for cycle_index, cycle in enumerate(cycles, start=0): + # 屏幕显示每个循环周期的事件 + print(f"周期 {cycle_index} 包含事件个数: {len(cycle)} 具体事件为: {cycle} ") + + return cycles +# ---------------------------------------------------------------------------------------------------------------------------- +# 2-3---按照循环周期合并同周期数据,并输出处理后的温度数据、对应的高度数据 + + +def data_cycle_generate(cycles, ktemp, tpaltitude, altitude_min, altitude_max): + + # 初始化列表存储每个周期的温度 + ktemp_cycles = [] + # 初始化每个循环周期的高度数据 + altitude_cycles = [] + print(f"周期数为 {len(cycles)}") + if cycles is not None: # 检查周期是否有事件编号 + for event in cycles: + # 获取每个周期各个事件的ktemp数据 + ktemp_cycles_events = np.array(ktemp[event, :]) + ktemp_cycles_events[np.logical_or( + ktemp_cycles_events == -999, ktemp_cycles_events > 999)] = np.NaN # 缺失值处理,避免影响结果 + ktemp_cycles_mean = np.nanmean( + ktemp_cycles_events, axis=0) # 对所有周期的 ktemp 数据取均值 + + # altitude_cycles_events =np.array(tpaltitude[event,:]) # 获取每个周期各个事件的tpaltitude数据 + # altitude_cycles_mean = np.nanmean(altitude_cycles_events, axis=0) # 对所有周期的 altitude 数据取均值 + # 使用第一个的高度来表征所有的 + altitude_cycles_mean = tpaltitude[event[0], :] + altitude_indices = np.where((altitude_cycles_mean >= altitude_min) & ( + altitude_cycles_mean <= altitude_max))[0] + + ktemp_cycles.append(np.array(ktemp_cycles_mean[altitude_indices])) + altitude_cycles.append( + np.array(altitude_cycles_mean[altitude_indices])) + + # 找到最短列表的长度 + min_length = min(len(arr) for arr in ktemp_cycles) + # 创建新的列表,将每个子列表截断为最短长度 + ktemp_cycles = np.vstack([arr[:min_length] for arr in ktemp_cycles]) + altitude_cycles = np.vstack([arr[:min_length] for arr in altitude_cycles]) + + return ktemp_cycles, altitude_cycles +# ---------------------------------------------------------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------------------------------------------------------- +# 4---高度相同下不同循环周期数据的波拟合和滤波处理 - +# ---------------------------------------------------------------------------------------------------------------------------- + + +# 对输入数据按照行(即纬向)进行波数为k的滤波,数据为15*157 +def fit_wave(ktemp_wn0, k): + + wave_fit = [] + + def single_harmonic(x, A, phi, C, k): + return A * np.sin(2 * np.pi / (15/k) * x + phi) + C + + # 数据转置并对每行进行操作,按照同高度数据进行处理 + for rtemp in ktemp_wn0.T: + # 为当前高度层创建索引数组 + indices = np.arange(rtemp.size) + def fit_temp(x, A, phi, C): return single_harmonic( + x, A, phi, C, k) # 定义只拟合 A, phi, C 的 lambda 函数,k 固定 + params, params_covariance = curve_fit( + fit_temp, indices, rtemp) # 使用 curve_fit 进行拟合 + # 提取拟合参数 A, phi, C + A, phi, C = params + # 使用拟合参数计算 wn3 + rtemp1 = single_harmonic(indices, A, phi, C, k) + # 存储拟合参数和拟合曲线 + wave_fit.append(np.array(rtemp1)) + wave_fit = np.vstack(wave_fit) + + wave_fit = wave_fit.T + wave_wn = ktemp_wn0-wave_fit + + return wave_fit, wave_wn +# ---------------------------------------------------------------------------------------------------------------------------- + + +# ---------------------------------------------------------------------------------------------------------------------------- +# 4---同周期下不同高度数据的波拟合和滤波处理 - +# ---------------------------------------------------------------------------------------------------------------------------- +# 对输入数据按照列(即纬向)进行滤波,滤除波长2和15km以内的波, 数据为15*157 +def fft_ifft_wave(ktemp_wn5, lamda_low, lamda_high, altitude_min, altitude_max, lvboin): + + ktemp_fft = [] + ktemp_ifft = [] + ktemp_fft_lvbo = [] + + for rtemp in ktemp_wn5: + # 采样点数或长度 + N = len(rtemp) + # 采样时间间隔,其倒数等于采用频率,以1km为标准尺度等同于1s,假设波的速度为1km/s + dt = (altitude_max-altitude_min)/(N-1) + # 时间序列索引 + n = np.arange(N) + # # t = altitude_min + n * dt # 时间向量 + # t = np.round(np.linspace(altitude_min, altitude_max, N),2) + # 频率索引向量 + f = n / (N * dt) + # 对输入信号进行傅里叶变换 + y = np.fft.fft(rtemp) + + # f_low = 2*np.pi / lamda_high # 定义波长滤波范围(以频率计算) # 高频截止频率 + # f_high = 2*np.pi / lamda_low + + # 定义波长滤波范围(以频率计算) # 高频截止频率 + f_low = 1 / lamda_high + # 低频截止频率 + f_high = 1 / lamda_low + + # 创建滤波后的频域信号 + yy = y.copy() + + # 使用逻辑索引过滤特定频段(未确定) + if lvboin: + freq_filter = (f > f_low) & (f < f_high) # 创建逻辑掩码 + else: + freq_filter = (f < f_low) | (f > f_high) # 创建逻辑掩码 + + yy[freq_filter] = 0 # 过滤掉指定频段 + yy_ifft = np.real(np.fft.ifft(yy)) + + ktemp_fft.append(y) + # 存储拟合参数和拟合曲线 + ktemp_ifft.append(np.array(yy_ifft)) + ktemp_fft_lvbo.append(yy) + + ktemp_fft = np.vstack(ktemp_fft) + ktemp_ifft = np.vstack(ktemp_ifft) + ktemp_fft_lvbo = np.vstack(ktemp_fft_lvbo) + + return ktemp_fft, ktemp_fft_lvbo, ktemp_ifft +# ---------------------------------------------------------------------------------------------------------------------------- +# ---------------------------------------------------------------------------------------------------------------------------- +# 5---同周期下不同高度数据的BT_z背景位等指标计算 - +# ---------------------------------------------------------------------------------------------------------------------------- +# ---------------------------------------------------------------------------------- + + +def power_indices(ktemp_cycles, ktemp_wn5, ktemp_ifft, altitude_min, altitude_max): + + # 定义Brunt-Väisälä频率计算函数 + def brunt_vaisala_frequency(g, BT_z, c_p, height): + # 计算位温随高度的变化率 + dBT_z_dz = np.gradient(BT_z, height) + # 计算 Brunt-Väisälä 频率 + return np.sqrt((g / BT_z) * ((g / c_p) + dBT_z_dz)) + + # 定义势能计算函数 + def calculate_gravitational_potential_energy(g, BT_z, N_z, PT_z): + return 0.5 * ((g / N_z) ** 2) * ((PT_z / BT_z) ** 2) + + # 重力加速度 + g = 9.81 + # 空气比热容 + c_p = 1004.5 + + height = np.linspace(altitude_min, altitude_max, + ktemp_cycles.shape[1]) * 1000 # 高度 + background = ktemp_cycles - ktemp_wn5 + + # 初始化结果矩阵 + N_z_matrix = [] + # 初始化结果矩阵 + PT_z_matrix = [] + + # 循环处理background和filtered_perturbation所有行 + for i in range(background.shape[0]): + BT_z = np.array(background[i]) + # 滤波后的扰动 + PT_z = np.array(ktemp_ifft[i]) + + # 调用Brunt-Väisälä频率函数 + N_z = brunt_vaisala_frequency(g, BT_z, c_p, height) + PT_z = calculate_gravitational_potential_energy( + g, BT_z, N_z, PT_z) # 调用势能函数 + N_z_matrix.append(N_z) + PT_z_matrix.append(PT_z) + + ktemp_Nz = np.vstack(N_z_matrix) + ktemp_Ptz = np.vstack(PT_z_matrix) + return ktemp_Nz, ktemp_Ptz +# ---------------------------------------------------------------------------------------------------------------------------- +# 5---main程序环节 - +# ---------------------------------------------------------------------------------------------------------------------------- +# 按日处理资料 + + +def day_process_main(file_path, day_read, latitude_min, latitude_max, altitude_min, altitude_max, lamda_low, lamda_high, lvboin): + + dataset, tplatitude, tplongitude, tpaltitude, ktemp, time, date, date_time = data_nc_load( + file_path) + # 2018年的第94天 # 4月4日的日期,以年份和年内的第几天表示 + df = day_data_read(date, day_read, tplatitude) + cycles = data_cycle_identify(df, latitude_min, latitude_max) + ktemp_cycles, altitude_cycles = data_cycle_generate( + cycles, ktemp, tpaltitude, altitude_min, altitude_max) + # 按照纬向计算平均温度 wn0_temp + ktemp_wn0 = ktemp_cycles - np.mean(ktemp_cycles, axis=0) + ktemp_fit_wn1, ktemp_wn1 = fit_wave(ktemp_wn0, 1) + ktemp_fit_wn2, ktemp_wn2 = fit_wave(ktemp_wn1, 2) + ktemp_fit_wn3, ktemp_wn3 = fit_wave(ktemp_wn2, 3) + ktemp_fit_wn4, ktemp_wn4 = fit_wave(ktemp_wn3, 4) + ktemp_fit_wn5, ktemp_wn5 = fit_wave(ktemp_wn4, 5) + ktemp_fft, ktemp_fft_lvbo, ktemp_ifft = fft_ifft_wave( + ktemp_wn5, lamda_low, lamda_high, altitude_min, altitude_max, lvboin) + ktemp_Nz, ktemp_Ptz = power_indices( + ktemp_cycles, ktemp_wn5, ktemp_ifft, altitude_min, altitude_max) + + return ktemp_cycles, altitude_cycles, ktemp_wn0, ktemp_fit_wn1, ktemp_wn1, ktemp_fit_wn2, ktemp_wn2, ktemp_fit_wn3, ktemp_wn3, ktemp_fit_wn4, ktemp_wn4, ktemp_fit_wn5, ktemp_wn5, ktemp_fft, ktemp_fft_lvbo, ktemp_ifft, ktemp_Nz, ktemp_Ptz +# ---------------------------------------------------------------------------------------------------------------------------- +# 按文件中单日处理资料 + + +def day_process_maing(dataset, tplatitude, tplongitude, tpaltitude, ktemp, time, date, date_time, day_read, latitude_min, latitude_max, altitude_min, altitude_max, lamda_low, lamda_high, lvboin): + + # dataset, tplatitude, tplongitude, tpaltitude, ktemp, time, date, date_time = data_nc_load (file_path) + # 2018年的第94天 # 4月4日的日期,以年份和年内的第几天表示 + df = day_data_read(date, day_read, tplatitude) + cycles = data_cycle_identify(df, latitude_min, latitude_max) + ktemp_cycles, altitude_cycles = data_cycle_generate( + cycles, ktemp, tpaltitude, altitude_min, altitude_max) + # 按照纬向计算平均温度 wn0_temp + ktemp_wn0 = ktemp_cycles - np.mean(ktemp_cycles, axis=0) + ktemp_fit_wn1, ktemp_wn1 = fit_wave(ktemp_wn0, 1) + ktemp_fit_wn2, ktemp_wn2 = fit_wave(ktemp_wn1, 2) + ktemp_fit_wn3, ktemp_wn3 = fit_wave(ktemp_wn2, 3) + ktemp_fit_wn4, ktemp_wn4 = fit_wave(ktemp_wn3, 4) + ktemp_fit_wn5, ktemp_wn5 = fit_wave(ktemp_wn4, 5) + ktemp_fft, ktemp_fft_lvbo, ktemp_ifft = fft_ifft_wave( + ktemp_wn5, lamda_low, lamda_high, altitude_min, altitude_max, lvboin) + ktemp_Nz, ktemp_Ptz = power_indices( + ktemp_cycles, ktemp_wn5, ktemp_ifft, altitude_min, altitude_max) + + return ktemp_cycles, altitude_cycles, ktemp_wn0, ktemp_fit_wn1, ktemp_wn1, ktemp_fit_wn2, ktemp_wn2, ktemp_fit_wn3, ktemp_wn3, ktemp_fit_wn4, ktemp_wn4, ktemp_fit_wn5, ktemp_wn5, ktemp_fft, ktemp_fft_lvbo, ktemp_ifft, ktemp_Nz, ktemp_Ptz + +# ---------------------------------------------------------------------------------------------------------------------------- +# 按月处理资料 + + +def mon_process_main(file_path, latitude_min, latitude_max, altitude_min, altitude_max, lamda_low, lamda_high, lvboin): + + dataset, tplatitude, tplongitude, tpaltitude, ktemp, time, date, date_time = data_nc_load( + file_path) + + ktemp_cycles_mon = [] + altitude_cycles_mon = [] + ktemp_wn0_mon = [] + ktemp_fit_wn1_mon = [] + ktemp_wn1_mon = [] + ktemp_fit_wn2_mon = [] + ktemp_wn2_mon = [] + ktemp_fit_wn3_mon = [] + ktemp_wn3_mon = [] + ktemp_fit_wn4_mon = [] + ktemp_wn4_mon = [] + ktemp_fit_wn5_mon = [] + ktemp_wn5_mon = [] + ktemp_fft_mon = [] + ktemp_fft_lvbo_mon = [] + ktemp_ifft_mon = [] + ktemp_Nz_mon = [] + ktemp_Ptz_mon = [] + + for day_read in date_time: + print(f"读取日期 {day_read}") + (ktemp_cycles0, altitude_cycles0, + ktemp_wn00, + ktemp_fit_wn10, ktemp_wn10, + ktemp_fit_wn20, ktemp_wn20, + ktemp_fit_wn30, ktemp_wn30, + ktemp_fit_wn40, ktemp_wn40, + ktemp_fit_wn50, ktemp_wn50, + ktemp_fft0, ktemp_fft_lvbo0, ktemp_ifft0, + ktemp_Nz0, ktemp_Ptz0) = day_process_maing( + dataset, tplatitude, tplongitude, tpaltitude, ktemp, time, date, date_time, + day_read, latitude_min, latitude_max, altitude_min, altitude_max, lamda_low, lamda_high, lvboin) + + ktemp_cycles_mon.append(ktemp_cycles0) + altitude_cycles_mon.append(altitude_cycles0) + ktemp_wn0_mon.append(ktemp_wn00) + ktemp_fit_wn1_mon.append(ktemp_fit_wn50) + ktemp_wn1_mon.append(ktemp_wn50) + ktemp_fit_wn2_mon.append(ktemp_fit_wn50) + ktemp_wn2_mon.append(ktemp_wn50) + ktemp_fit_wn3_mon.append(ktemp_fit_wn50) + ktemp_wn3_mon.append(ktemp_wn50) + ktemp_fit_wn4_mon.append(ktemp_fit_wn50) + ktemp_wn4_mon.append(ktemp_wn50) + ktemp_fit_wn5_mon.append(ktemp_fit_wn50) + ktemp_wn5_mon.append(ktemp_wn50) + ktemp_fft_mon.append(ktemp_fft0) + ktemp_fft_lvbo_mon.append(ktemp_fft_lvbo0) + ktemp_ifft_mon.append(ktemp_ifft0) + ktemp_Nz_mon.append(ktemp_Nz0) + ktemp_Ptz_mon.append(ktemp_Ptz0) + + return [date_time, ktemp_cycles_mon, altitude_cycles_mon, # 某月份 逐日时间 每日不同循环周期温度 不同循环周期高度数据 + # 距平扰动温度 + ktemp_wn0_mon, + # 波数为1的拟合温度 滤波后的扰动温度 + ktemp_fit_wn1_mon, ktemp_wn1_mon, + # 波数为2的拟合温度 滤波后的扰动温度 + ktemp_fit_wn2_mon, ktemp_wn2_mon, + # 波数为3的拟合温度 滤波后的扰动温度 + ktemp_fit_wn3_mon, ktemp_wn3_mon, + # 波数为4的拟合温度 滤波后的扰动温度 + ktemp_fit_wn4_mon, ktemp_wn4_mon, + # 波数为5的拟合温度 滤波后的扰动温度 + ktemp_fit_wn5_mon, ktemp_wn5_mon, + # 滤波0-5后扰动温度傅里叶频谱分析 滤除波长为2km-15km内后频谱分析 滤波后的扰动温度 + ktemp_fft_mon, ktemp_fft_lvbo_mon, ktemp_ifft_mon, + ktemp_Nz_mon, ktemp_Ptz_mon] # 滤波后NZ、PTZ重力波势能指标计算 + +# ----------------------------------------------------------------------------------------------------------------------------- +# 6-主要统计分析图绘制 - +# ----------------------------------------------------------------------------------------------------------------------------- +# 6-1 示例的逐层滤波效果图---不同波数 曲线图 + + +day_fit_wave_modes = ["k=1", "k=2", "k=3", "k=4", "k=5", "滤波"] + + +def day_fit_wave_plot(height_no, ktemp_wn0, ktemp_fit_wn1, ktemp_wn1, ktemp_fit_wn2, ktemp_wn2, ktemp_fit_wn3, ktemp_wn3, ktemp_fit_wn4, ktemp_wn4, ktemp_fit_wn5, ktemp_wn5, mode): + + N = len(ktemp_wn0[:, height_no]) + # 循环周期索引 + x = np.arange(N) + + y1_1 = ktemp_wn0[:, height_no] + y1_2 = ktemp_fit_wn1[:, height_no] + + y2_1 = ktemp_wn1[:, height_no] + y2_2 = ktemp_fit_wn2[:, height_no] + + y3_1 = ktemp_wn2[:, height_no] + y3_2 = ktemp_fit_wn3[:, height_no] + + y4_1 = ktemp_wn3[:, height_no] + y4_2 = ktemp_fit_wn4[:, height_no] + + y5_1 = ktemp_wn4[:, height_no] + y5_2 = ktemp_fit_wn5[:, height_no] + + y6 = ktemp_wn5[:, height_no] + + if mode not in ["k=1", "k=2", "k=3", "k=4", "k=5", "滤波"]: + raise ValueError( + "mode 参数应为 'k=1', 'k=2', 'k=3', 'k=4', 'k=5', 'lvbo' 中的一个") + + plt.figure(figsize=(16, 10)) # 调整图形大小 + # 原始信号的时间序列 + if mode == "k=1": + plt.plot(x, y1_1, label='原始信号') + plt.plot(x, y1_2, label='拟合信号', linestyle='--') + plt.title('(a)波数k=1') + plt.xlabel('Cycles', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (°C)', labelpad=10) # 增加标签间距 + plt.legend() + plt.tight_layout() # 调整子图参数以适应图形区域 + + if mode == "k=2": + plt.plot(x, y2_1, label='原始信号') + plt.plot(x, y2_2, label='拟合信号', linestyle='--') + plt.title('(b)波数k=2') + plt.xlabel('Cycles', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (°C)', labelpad=10) # 增加标签间距 + plt.legend() + plt.tight_layout() # 调整子图参数以适应图形区域 + + if mode == "k=3": + plt.plot(x, y3_1, label='原始信号') + plt.plot(x, y3_2, label='拟合信号', linestyle='--') + plt.title('(c)波数k=3') + plt.xlabel('Cycles', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (°C)', labelpad=10) # 增加标签间距 + plt.legend() + plt.tight_layout() # 调整子图参数以适应图形区域 + + if mode == "k=4": + plt.plot(x, y4_1, label='原始信号') + plt.plot(x, y4_2, label='拟合信号', linestyle='--') + plt.title('(d)波数k=4') + plt.xlabel('Cycles', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (°C)', labelpad=10) # 增加标签间距 + plt.legend() + plt.tight_layout() # 调整子图参数以适应图形区域 + + if mode == "k=5": + plt.plot(x, y5_1, label='原始信号') + plt.plot(x, y5_2, label='拟合信号', linestyle='--') + plt.title('(e)波数k=5') + plt.xlabel('Cycles', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (°C)', labelpad=10) # 增加标签间距 + plt.legend() + plt.tight_layout() # 调整子图参数以适应图形区域 + + if mode == "滤波": + plt.plot(x, y6, label='滤波信号') + plt.title('(f)滤波1-5后信号') + plt.xlabel('Cycles', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (°C)', labelpad=10) # 增加标签间距 + plt.tight_layout() # 调整子图参数以适应图形区域 + + +def day_fit_wave_plotg(height_no, ktemp_wn0, ktemp_fit_wn1, ktemp_wn1, ktemp_fit_wn2, + ktemp_wn2, ktemp_fit_wn3, ktemp_wn3, ktemp_fit_wn4, + ktemp_wn4, ktemp_fit_wn5, ktemp_wn5): + + N = len(ktemp_wn0[:, height_no]) + x = np.arange(N) + + y1_1, y1_2 = ktemp_wn0[:, height_no], ktemp_fit_wn1[:, height_no] + y2_1, y2_2 = ktemp_wn1[:, height_no], ktemp_fit_wn2[:, height_no] + y3_1, y3_2 = ktemp_wn2[:, height_no], ktemp_fit_wn3[:, height_no] + y4_1, y4_2 = ktemp_wn3[:, height_no], ktemp_fit_wn4[:, height_no] + y5_1, y5_2 = ktemp_wn4[:, height_no], ktemp_fit_wn5[:, height_no] + y6 = ktemp_wn5[:, height_no] + + plt.figure(figsize=(16, 10)) + + y_limits = (min(min(y1_1), min(y2_1), min(y3_1), min(y4_1), min(y5_1), min(y6)), + max(max(y1_1), max(y2_1), max(y3_1), max(y4_1), max(y5_1), max(y6))) + + for i, (y1, y2) in enumerate([(y1_1, y1_2), (y2_1, y2_2), (y3_1, y3_2), + (y4_1, y4_2), (y5_1, y5_2), (y6, None)]): + plt.subplot(2, 3, i + 1) + plt.plot(x, y1, label='原始信号') + if y2 is not None: + plt.plot(x, y2, label='拟合信号', linestyle='--') + plt.title(f'({"abcdef"[i]})波数k={i + 1 if i < 5 else "滤波"}') + plt.xlabel('Cycles', labelpad=10) + plt.ylabel('温度 (°C)', labelpad=10) + plt.legend() + plt.xticks(x) # 设置横坐标为整数 + plt.ylim(y_limits) # 设置统一纵坐标范围 + plt.tight_layout() + + plt.subplots_adjust(top=0.8, bottom=0.2, left=0.1, + right=0.8, hspace=0.3, wspace=0.2) + plt.show() + + +# ----------------------------------------------------------------------------------------------------------------------------- +# 6-2 示例的高度滤波处理--不同循环周期 曲线图 +day_fft_ifft_modes = ["原始信号", "fft", "ifft", "滤波", "比较"] + + +def day_fft_ifft_plot(cycle_no, ktemp_wn5, ktemp_fft, ktemp_ifft, altitude_min, altitude_max, lamda_low, lamda_high, ktemp_fft_lvbo, mode): + + N = len(ktemp_wn5[cycle_no, :]) + # 采样时间间隔,其倒数等于采用频率,以1km为标准尺度等同于1s,假设波的速度为1km/s + dt = (altitude_max-altitude_min)/(N-1) + # 时间序列索引 + n = np.arange(N) + f = n / (N * dt) + t = np.round(np.linspace(altitude_min, altitude_max, N), 2) + + # 原始扰动温度 + x = ktemp_wn5[cycle_no, :] + # 傅里叶变换频谱分析 + y = ktemp_fft[cycle_no, :] + # 滤波后的傅里叶变换频谱分析 + yy = ktemp_fft_lvbo[cycle_no, :] + # 傅里叶逆变换后的扰动温度 + yyy = ktemp_ifft[cycle_no, :] + + if mode not in ["原始信号", "fft", "ifft", "滤波", "比较"]: + raise ValueError( + "mode 参数应为 '原始信号', 'fft', 'ifft', 'lv bo' 中的一个") + + plt.figure(figsize=(15, 10)) # 调整图形大小 + # 原始信号的时间序列 + if mode == "原始信号": + plt.plot(t, x) + plt.title('(a)原始信号') + plt.xlabel('高度 (km)', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (°C)', labelpad=10) # 增加标签间距 + # 原始振幅谱 + if mode == "fft": + plt.plot(f, np.abs(y) * 2 / N) + plt.title('(b))原始振幅谱') + plt.xlabel('频率/Hz', labelpad=10) # 增加标签间距 + plt.ylabel('振幅', labelpad=10) # 增加标签间距 + + # 通过IFFT回到时间域 + if mode == "ifft": + plt.plot(t, yyy) + plt.title('(c))傅里叶逆变换') + plt.xlabel('高度 (km)', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (°C)', labelpad=10) # 增加标签间距 + + if mode == "滤波": + plt.plot(f, np.abs(yy) * 2 / N) + plt.title(f'(d)滤除波长 < {lamda_low} km, > {lamda_high} km的波') + plt.xlabel('频率/Hz', labelpad=10) # 增加标签间距 + plt.ylabel('振幅', labelpad=10) # 增加标签间距 + + # 调整子图之间的边距 + + # 绘制原始信号与滤波后信号 + if mode == '比较': + plt.plot(t, x, label='原始信号') + plt.plot(t, yyy, label='滤波后信号', linestyle='--') + plt.title('信号比较') + plt.xlabel('高度 (km)', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (°C)', labelpad=10) # 增加标签间距 + plt.legend() + plt.tight_layout() # 调整子图参数以适应图形区域 + + +day_fft_ifft_modes = ["原始信号", "原始振幅谱", "傅里叶逆变换", "滤波后振幅谱", "信号比较"] + + +def day_fft_ifft_plotg(cycle_no, ktemp_wn5, ktemp_fft, ktemp_ifft, altitude_min, altitude_max, lamda_low, lamda_high, mode): + + N = len(ktemp_wn5[cycle_no, :]) + dt = (altitude_max - altitude_min) / (N - 1) # 采样时间间隔 + n = np.arange(N) # 时间序列索引 + f = n / (N * dt) + t = np.round(np.linspace(altitude_min, altitude_max, N), 2) + + x = ktemp_wn5[cycle_no, :] # 原始扰动温度 + y = ktemp_fft[cycle_no, :] # 傅里叶变换频谱分析 + yy = ktemp_fft_lvbo[cycle_no, :] # 滤波后的傅里叶变换频谱分析 + yyy = ktemp_ifft[cycle_no, :] # 傅里叶逆变换后的扰动温度 + + plt.figure(figsize=(15, 10)) # 调整图形大小 + + # 计算纵坐标范围 + temp_limits = (min(min(x), min(yyy)), max(max(x), max(yyy))) + amp_limits = (0, max(np.max(np.abs(y) * 2 / N), + np.max(np.abs(yy) * 2 / N))) + + if mode not in ["原始信号", "原始振幅谱", "傅里叶逆变换", "滤波后振幅谱", "信号比较"]: + raise ValueError( + "mode 参数应为 '原始信号', '原始振幅谱', '傅里叶逆变换', '滤波后振幅谱', '信号比较' 中的一个") + + # 原始信号的时间序列 + if mode == "原始信号": + plt.plot(t, x) + plt.title('(a)原始信号') + plt.xlabel('高度 (km)', labelpad=10) + plt.ylabel('温度 (°C)', labelpad=10) + plt.ylim(temp_limits) # 设置统一纵坐标范围 + + # 原始振幅谱 + if mode == "原始振幅谱": + plt.plot(f, np.abs(y) * 2 / N) + plt.title('(b)原始振幅谱') + plt.xlabel('频率/Hz', labelpad=10) + plt.ylabel('振幅', labelpad=10) + plt.ylim(amp_limits) # 设置统一纵坐标范围 + + # 通过IFFT回到时间域 + if mode == "傅里叶逆变换": + plt.plot(t, yyy) + plt.title('(c)傅里叶逆变换') + plt.xlabel('高度 (km)', labelpad=10) + plt.ylabel('温度 (°C)', labelpad=10) + plt.ylim(temp_limits) # 设置统一纵坐标范围 + + # 滤波后的振幅谱 + if mode == "滤波后振幅谱": + plt.plot(f, np.abs(yy) * 2 / N) + plt.title(f'(d)滤除波长 < {lamda_low} km, > {lamda_high} km的波') + plt.xlabel('频率/Hz', labelpad=10) + plt.ylabel('振幅', labelpad=10) + plt.ylim(amp_limits) # 设置统一纵坐标范围 + + if mode == "信号比较": + # 绘制原始信号与滤波后信号 + plt.figure(figsize=(6, 8)) # 调整图形大小 + plt.plot(x, t, label='原始信号') + plt.plot(yyy, t, label='滤波后信号', linestyle='--') + plt.title('信号比较') + plt.ylabel('高度 (km)', labelpad=10) # 增加标签间距 + plt.xlabel('温度 (°C)', labelpad=10) # 增加标签间距 + plt.legend() + plt.xlim(temp_limits) # 设置统一纵坐标范围 + plt.tight_layout() +# ----------------------------------------------------------------------------------------------------------------------------- +# 6-3 示例的按高度的重力波势能变化曲线图 + + +def day_cycle_power_wave_plot(cycle_no, altitude_min, altitude_max, ktemp_Nz, ktemp_Ptz): + + N = len(ktemp_Nz[cycle_no, :]) + y = np.round(np.linspace(altitude_min, altitude_max, N), 2) + x1 = ktemp_Nz[cycle_no, :] + x2 = ktemp_Ptz[cycle_no, :] + + plt.figure(figsize=(12, 10)) # 调整图形大小 + # 原始信号的时间序列 + plt.subplot(1, 2, 1) + plt.plot(x1, y, label='原始信号') + plt.title('(a)Nz') + plt.xlabel('Nz', labelpad=10) # 增加标签间距 + plt.ylabel('高度 (km)', labelpad=10) # 增加标签间距 + + # 原始信号的时间序列 + plt.subplot(1, 2, 2) + plt.plot(x2, y, label='原始信号') + plt.title('(b)Ptz') + plt.xlabel('Ptz', labelpad=10) # 增加标签间距 + plt.ylabel('高度 (km)', labelpad=10) # 增加标签间距 + + # 调整子图之间的边距 + plt.subplots_adjust(top=0.8, bottom=0.2, left=0.1, + right=0.8, hspace=0.3, wspace=0.2) + plt.tight_layout() # 调整子图参数以适应图形区域 + + plt.show() +# ----------------------------------------------------------------------------------------------------------------------------- +# 6-4 按日统计的按周期计算的不同高度的重力波势能 平面图 + + +def day_power_wave_plot(altitude_min, altitude_max, ktemp_Nz, ktemp_Ptz): + # 假设 ktemp_Nz 和 ktemp_Ptz 以及 altitude_min, altitude_max 已经定义好 + x = np.arange(ktemp_Nz.shape[0]) + y = np.round(np.linspace(altitude_min, altitude_max, ktemp_Nz.shape[1]), 2) + + # 创建一个图形,并指定两个子图 + fig, axs = plt.subplots(1, 2, figsize=(15, 10)) + + # 第一幅图 (a) NZ + cax1 = axs[0].imshow(ktemp_Nz.T, aspect='auto', cmap='viridis', + origin='lower', extent=[x[0], x[-1], y[0], y[-1]]) + fig.colorbar(cax1, ax=axs[0]) # 为第一幅图添加颜色条 + axs[0].set_title('(a) NZ') + axs[0].set_ylabel('Height (km)') + axs[0].set_xlabel('Cycles') + axs[0].set_yticks(np.linspace(30, 90, 7)) + axs[0].set_yticklabels(np.round(np.linspace(30, 90, 7), 1)) + axs[0].set_xticks(np.arange(15)) + axs[0].set_xticklabels(np.arange(15)) + + # 第二幅图 (b) PTZ + cax2 = axs[1].imshow(ktemp_Ptz.T, aspect='auto', cmap='viridis', + origin='lower', extent=[x[0], x[-1], y[0], y[-1]]) + fig.colorbar(cax2, ax=axs[1]) # 为第二幅图添加颜色条 + axs[1].set_title('(b) PTZ') + axs[1].set_ylabel('Height (km)') + axs[1].set_xlabel('Cycles') + axs[1].set_yticks(np.linspace(30, 90, 7)) + axs[1].set_yticklabels(np.round(np.linspace(30, 90, 7), 1)) + axs[1].set_xticks(np.arange(15)) + axs[1].set_xticklabels(np.arange(15)) + + # 调整子图之间的边距 + plt.subplots_adjust(top=0.9, bottom=0.1, left=0.05, + right=0.95, hspace=0.3, wspace=0.3) + plt.tight_layout() # 调整布局以避免重叠 + plt.show() +# ----------------------------------------------------------------------------------------------------------------------------- +# 6-5 按月统计的每日重力波势能随天变化的图 + + +# ----------------------------------------------------------------------------------------------------------------------------- +# 6-6 按年统计的每月重力波势能随月变化的图 + + +# ----------------------------------------------------------------------------------------------------------------------------- +# 7 主程序,运行数据,并输出主要统计分析图 - +# ----------------------------------------------------------------------------------------------------------------------------- +# 7-1 挑选某一天进行运行主要程序 +def render_saber(file_path, mode, mode_args=None): + # day_read=2018113, + day_read = 2018094 + # 初始化某一天、某个纬度、高度范围等参数 + latitude_min = 30.0 + latitude_max = 50.0 + altitude_min = 30.0 + altitude_max = 90.0 + lamda_low = 2 + lamda_high = 15 + lvboin = True + + (ktemp_cycles, altitude_cycles, + ktemp_wn0, + ktemp_fit_wn1, ktemp_wn1, + ktemp_fit_wn2, ktemp_wn2, + ktemp_fit_wn3, ktemp_wn3, + ktemp_fit_wn4, ktemp_wn4, + ktemp_fit_wn5, ktemp_wn5, + ktemp_fft, ktemp_fft_lvbo, ktemp_ifft, + ktemp_Nz, ktemp_Ptz) = day_process_main( + file_path, + # day_read=2018113, + day_read, + # 初始化某一天、某个纬度、高度范围等参数 + latitude_min, + latitude_max, + altitude_min, + altitude_max, + lamda_low, lamda_high, lvboin) + # ----------------------------------------------------------------------------------------------------------------------------- + # 7-2 挑选某一个月进行运行主要程序 + (date_time, ktemp_cycles_mon, altitude_cycles_mon, # 某月份 逐日时间 每日不同循环周期温度 不同循环周期高度数据 + # 距平扰动温度 + ktemp_wn0_mon, + # 波数为1的拟合温度 滤波后的扰动温度 + ktemp_fit_wn1_mon, ktemp_wn1_mon, + # 波数为2的拟合温度 滤波后的扰动温度 + ktemp_fit_wn2_mon, ktemp_wn2_mon, + # 波数为3的拟合温度 滤波后的扰动温度 + ktemp_fit_wn3_mon, ktemp_wn3_mon, + # 波数为4的拟合温度 滤波后的扰动温度 + ktemp_fit_wn4_mon, ktemp_wn4_mon, + # 波数为5的拟合温度 滤波后的扰动温度 + ktemp_fit_wn5_mon, ktemp_wn5_mon, + # 滤波0-5后扰动温度傅里叶频谱分析 滤除波长为2km-15km内后频谱分析 滤波后的扰动温度 + ktemp_fft_mon, ktemp_fft_lvbo_mon, ktemp_ifft_mon, + # 滤波后NZ、PTZ重力波势能指标计算 + ktemp_Nz_mon, ktemp_Ptz_mon) = mon_process_main( + file_path, + # 初始化某一天、某个纬度、高度范围等参数 + latitude_min, + latitude_max, + altitude_min, + altitude_max, + lamda_low, lamda_high, lvboin) + + # ----------------------------------------------------------------------------------------------------------------------------- + # 7-3 绘制不同截过图 + + if mode == "day_fit_wave_plot": + + height_no = 1 + # day_fit_wave_plotg(height_no, ktemp_wn0, ktemp_fit_wn1, ktemp_wn1, ktemp_fit_wn2, ktemp_wn2, + # ktemp_fit_wn3, ktemp_wn3, ktemp_fit_wn4, ktemp_wn4, ktemp_fit_wn5, ktemp_wn5) + day_fit_wave_plot(height_no, ktemp_wn0, ktemp_fit_wn1, ktemp_wn1, ktemp_fit_wn2, ktemp_wn2, + ktemp_fit_wn3, ktemp_wn3, ktemp_fit_wn4, ktemp_wn4, ktemp_fit_wn5, ktemp_wn5, mode_args) + if mode == "day_fft_ifft_plot": + cycle_no = 1 + day_fft_ifft_plotg(cycle_no, ktemp_wn5, ktemp_fft, ktemp_ifft, + altitude_min, altitude_max, lamda_low, lamda_high, mode_args) + if mode == "day_cycle_power_wave_plot": + + day_cycle_power_wave_plot(cycle_no, altitude_min, + altitude_max, ktemp_Nz, ktemp_Ptz) + if mode == "day_power_wave_plot": + day_power_wave_plot(altitude_min, altitude_max, ktemp_Nz, ktemp_Ptz) + + buffer = BytesIO() + plt.savefig(buffer, format='png') + buffer.seek(0) + return buffer + + +ALL_RENDER_MODES = ["day_fit_wave_plot", "day_fft_ifft_plot", + "day_cycle_power_wave_plot", "day_power_wave_plot"] diff --git a/saber/process.py b/saber/process.py new file mode 100644 index 0000000..7cbe68b --- /dev/null +++ b/saber/process.py @@ -0,0 +1,245 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import Dict, List, Optional, Tuple +import numpy as np +from saber.utils import * + +# lat_range=(latitude_min, latitude_max), +# alt_range=(altitude_min, altitude_max), +# lambda_range=(lamda_low, lamda_high), + + +@dataclass +class WaveData: + cycles: Optional[np.ndarray] = None + wn0: Optional[np.ndarray] = None + fit_wn: List[Optional[np.ndarray]] = None # 存储wn1-wn5的拟合结果 + wn: List[Optional[np.ndarray]] = None # 存储wn1-wn5 + fft: Optional[np.ndarray] = None + fft_filtered: Optional[np.ndarray] = None + ifft: Optional[np.ndarray] = None + Nz: Optional[float] = None + Ptz: Optional[float] = None + lat_range: Tuple[float, float] = None + alt_range: Tuple[float, float] = None + lambda_range: Tuple[float, float] = None + + # dump to dict + def to_dict(self) -> Dict: + """将数据转换为字典格式,与原代码保持兼容""" + result = { + "ktemp_cycles_mon_list": self.cycles, + "ktemp_wn0_mon_list": self.wn0, + "ktemp_fit_wn1_mon_list": self.fit_wn[0], + "ktemp_wn1_mon_list": self.wn[0], + "ktemp_fit_wn2_mon_list": self.fit_wn[1], + "ktemp_wn2_mon_list": self.wn[1], + "ktemp_fit_wn3_mon_list": self.fit_wn[2], + "ktemp_wn3_mon_list": self.wn[2], + "ktemp_fit_wn4_mon_list": self.fit_wn[3], + "ktemp_wn4_mon_list": self.wn[3], + "ktemp_fit_wn5_mon_list": self.fit_wn[4], + "ktemp_wn5_mon_list": self.wn[4], + "ktemp_fft_mon_list": self.fft, + "ktemp_fft_lvbo_mon_list": self.fft_filtered, + "ktemp_ifft_mon_list": self.ifft, + "ktemp_Nz_mon_list": self.Nz, + "ktemp_Ptz_mon_list": self.Ptz, + "lat_range": self.lat_range, + "alt_range": self.alt_range, + "lambda_range": self.lambda_range + } + return result + + # load from dict + @classmethod + def from_dict(cls, data: Dict): + wave_data = cls() + wave_data.cycles = data["ktemp_cycles_mon_list"] + wave_data.wn0 = data["ktemp_wn0_mon_list"] + wave_data.fit_wn = [ + data[f"ktemp_fit_wn{i+1}_mon_list"] for i in range(5)] + wave_data.wn = [data[f"ktemp_wn{i+1}_mon_list"] for i in range(5)] + wave_data.fft = data["ktemp_fft_mon_list"] + wave_data.fft_filtered = data["ktemp_fft_lvbo_mon_list"] + wave_data.ifft = data["ktemp_ifft_mon_list"] + wave_data.Nz = data["ktemp_Nz_mon_list"] + wave_data.Ptz = data["ktemp_Ptz_mon_list"] + wave_data.lat_range = data["lat_range"] + wave_data.alt_range = data["alt_range"] + wave_data.lambda_range = data["lambda_range"] + return wave_data + + +@dataclass +class YearlyData: + date_times: List[datetime] + wave_data: List[WaveData] + + def to_dict(self) -> Dict: + """将数据转换为字典格式,与原代码保持兼容""" + result = { + "date_time_list": self.date_times, + "ktemp_cycles_mon_list": [], + "altitude_cycles_mon_list": [], + "ktemp_wn0_mon_list": [], + "ktemp_fit_wn1_mon_list": [], + "ktemp_wn1_mon_list": [], + "ktemp_fit_wn2_mon_list": [], + "ktemp_wn2_mon_list": [], + "ktemp_fit_wn3_mon_list": [], + "ktemp_wn3_mon_list": [], + "ktemp_fit_wn4_mon_list": [], + "ktemp_wn4_mon_list": [], + "ktemp_fit_wn5_mon_list": [], + "ktemp_wn5_mon_list": [], + "ktemp_fft_mon_list": [], + "ktemp_fft_lvbo_mon_list": [], + "ktemp_ifft_mon_list": [], + "ktemp_Nz_mon_list": [], + "ktemp_Ptz_mon_list": [], + "alt_range": [], + } + + for data in self.wave_data: + result["ktemp_cycles_mon_list"].append(data.cycles) + result["ktemp_wn0_mon_list"].append(data.wn0) + for i in range(5): + result[f"ktemp_fit_wn{i+1}_mon_list"].append(data.fit_wn[i]) + result[f"ktemp_wn{i+1}_mon_list"].append(data.wn[i]) + result["ktemp_fft_mon_list"].append(data.fft) + result["ktemp_fft_lvbo_mon_list"].append(data.fft_filtered) + result["ktemp_ifft_mon_list"].append(data.ifft) + result["ktemp_Nz_mon_list"].append(data.Nz) + result["ktemp_Ptz_mon_list"].append(data.Ptz) + result["alt_range"].append(data.alt_range) + + return result + + # load from dict + @classmethod + def from_dict(cls, data: Dict): + wave_data = [] + for i in range(len(data["date_time_list"])): + wave_data.append(WaveData()) + wave_data[-1].cycles = data["ktemp_cycles_mon_list"][i] + wave_data[-1].wn0 = data["ktemp_wn0_mon_list"][i] + wave_data[-1].fit_wn = [ + data[f"ktemp_fit_wn{i+1}_mon_list"][i] for i in range(5)] + wave_data[-1].wn = [data[f"ktemp_wn{i+1}_mon_list"][i] + for i in range(5)] + wave_data[-1].fft = data["ktemp_fft_mon_list"][i] + wave_data[-1].fft_filtered = data["ktemp_fft_lvbo_mon_list"][i] + wave_data[-1].ifft = data["ktemp_ifft_mon_list"][i] + wave_data[-1].Nz = data["ktemp_Nz_mon_list"][i] + wave_data[-1].Ptz = data["ktemp_Ptz_mon_list"][i] + + return cls(date_times=data["date_time_list"], wave_data=wave_data) + + +class DataProcessor: + def __init__(self, lat_range: Tuple[float, float], + alt_range: Tuple[float, float], + lambda_range: Tuple[float, float], + lvboin: float): + self.lat_min, self.lat_max = lat_range + self.alt_min, self.alt_max = alt_range + self.lambda_low, self.lambda_high = lambda_range + self.lvboin = lvboin + + def process_day(self, ncdata, day_read) -> Optional[WaveData]: + # 读取数据 + dataset, tplatitude, tplongitude, tpaltitude, ktemp, time, date, date_time = ncdata + df = day_data_read(date, day_read, tplatitude) + cycles = data_cycle_identify(df, self.lat_min, self.lat_max) + + if not cycles: + return None + + # 生成周期数据 + ktemp_cycles, alt_cycles = data_cycle_generate( + cycles, ktemp, tpaltitude, self.alt_min, self.alt_max) + + if ktemp_cycles is None or alt_cycles is None: + return None + + wave_data = WaveData() + wave_data.cycles = ktemp_cycles + + # 计算波数分析 + wave_data.wn0 = ktemp_cycles - np.mean(ktemp_cycles, axis=0) + + wave_data.fit_wn = [] + wave_data.wn = [] + temp = wave_data.wn0 + + # 循环计算波数1-5 + for i in range(1, 6): + fit, residual = fit_wave(temp, i) + wave_data.fit_wn.append(fit) + wave_data.wn.append(residual) + temp = residual + + # FFT分析 + wave_data.fft, wave_data.fft_filtered, wave_data.ifft = fft_ifft_wave( + wave_data.wn[-1], self.lambda_low, self.lambda_high, + self.alt_min, self.alt_max, self.lvboin) + + # 计算指数 + wave_data.Nz, wave_data.Ptz = power_indices( + wave_data.cycles, wave_data.wn[-1], wave_data.ifft, + self.alt_min, self.alt_max) + + # ranges for + wave_data.lat_range = (self.lat_min, self.lat_max) + wave_data.alt_range = (self.alt_min, self.alt_max) + wave_data.lambda_range = (self.lambda_low, self.lambda_high) + + return wave_data + + def process_month(self, ncdata: NcData) -> List[Optional[WaveData]]: + + monthly_data = [] + for day_read in ncdata.date_time: + print(f"Processing date: {day_read}") + day_result = self.process_day( + ncdata, day_read) + + if day_result is not None: + monthly_data.append(day_result) + + return monthly_data + + def process_year(self, ncdata: List[NcData]) -> YearlyData: + date_times = [] + wave_data = [] + for ncfile in ncdata: + print(f"Processing file: {ncfile.path}") + monthly_results = self.process_month(ncfile) + date_times.extend(ncfile.date_time) + wave_data.extend(monthly_results) + + return YearlyData(date_times=date_times, wave_data=wave_data) + + +if __name__ == "__main__": + year = 2018 + path = "./saber/data/2012/SABER_Temp_O3_April2012_v2.0.nc" + # 初始化某一天、某个纬度、高度范围等参数 + latitude_min = 30.0 + latitude_max = 40.0 + altitude_min = 20.0 + altitude_max = 105.0 + lamda_low = 2 + lamda_high = 15 + lvboin = True + processor = DataProcessor( + lat_range=(latitude_min, latitude_max), + alt_range=(altitude_min, altitude_max), + lambda_range=(lamda_low, lamda_high), + lvboin=lvboin + ) + + # 处理月度数据 + monthly_results = processor.process_month(path) + print(monthly_results) diff --git a/saber/render.py b/saber/render.py new file mode 100644 index 0000000..4ebe66e --- /dev/null +++ b/saber/render.py @@ -0,0 +1,296 @@ +from dataclasses import dataclass +from typing import Optional, Tuple, List +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.figure import Figure +from matplotlib.axes import Axes +import matplotlib.dates as mdates + +from saber.process import DataProcessor, WaveData, YearlyData + + +@dataclass +class PlotConfig: + """绘图配置类""" + figsize: Tuple[int, int] = (16, 10) + dpi: int = 100 + cmap: str = 'viridis' + title_fontsize: int = 12 + label_fontsize: int = 10 + legend_fontsize: int = 8 + tick_fontsize: int = 8 + + +class Renderer: + def __init__(self, config: Optional[PlotConfig] = None): + self.config = config or PlotConfig() + plt.rcParams['font.family'] = 'SimHei' # 设置为黑体(需要你的环境中有该字体) + plt.rcParams['axes.unicode_minus'] = False # 解决负号'-'显示为方块的问题 + + def _create_figure(self, rows: int, cols: int) -> Tuple[Figure, List[Axes]]: + """创建图形和轴对象""" + fig, axes = plt.subplots( + rows, cols, figsize=self.config.figsize, dpi=self.config.dpi) + return fig, axes.flatten() if isinstance(axes, np.ndarray) else [axes] + + def _setup_subplot(self, ax: Axes, x: np.ndarray, y1: np.ndarray, + y2: Optional[np.ndarray], title: str, y_limits: Tuple[float, float]): + """设置单个子图的样式和数据""" + ax.plot(x, y1, label='原始信号', linewidth=1.5) + if y2 is not None: + ax.plot(x, y2, label='拟合信号', linestyle='--', linewidth=1.5) + + ax.set_title(title, fontsize=self.config.title_fontsize) + ax.set_xlabel('Cycles', labelpad=10, + fontsize=self.config.label_fontsize) + ax.set_ylabel('温度 (K)', labelpad=10, + fontsize=self.config.label_fontsize) + ax.legend(fontsize=self.config.legend_fontsize) + ax.tick_params(axis='both', labelsize=self.config.tick_fontsize) + ax.set_ylim(y_limits) + ax.grid(True, linestyle='--', alpha=0.7) + + def plot_wave_fitting(self, wave_data: WaveData, height_no: int): + """绘制波数拟合结果""" + fig, axes = self._create_figure(2, 3) + + # 准备数据 + N = len(wave_data.wn0[:, height_no]) + x = np.arange(N) + + data_pairs = [ + (wave_data.wn0[:, height_no], wave_data.fit_wn[0][:, height_no]), + (wave_data.wn[0][:, height_no], wave_data.fit_wn[1][:, height_no]), + (wave_data.wn[1][:, height_no], wave_data.fit_wn[2][:, height_no]), + (wave_data.wn[2][:, height_no], wave_data.fit_wn[3][:, height_no]), + (wave_data.wn[3][:, height_no], wave_data.fit_wn[4][:, height_no]), + (wave_data.wn[4][:, height_no], None) + ] + + # 计算统一的y轴范围 + all_values = [ + val for pair in data_pairs for val in pair if val is not None] + y_limits = (np.min(all_values), np.max(all_values)) + + # 绘制子图 + for i, (y1, y2) in enumerate(data_pairs): + title = f'({"abcdef"[i]})波数k={i + 1 if i < 5 else "滤波1-5后信号"}' + self._setup_subplot(axes[i], x, y1, y2, title, y_limits) + + # 调整布局 + plt.tight_layout() + plt.subplots_adjust(top=0.9, bottom=0.1, left=0.1, right=0.9, + hspace=0.3, wspace=0.3) + + def day_fft_ifft_plot(self, cycle_no, wave_data: WaveData): + + ktemp_wn5 = wave_data.wn[4] + ktemp_fft = wave_data.fft + ktemp_fft_lvbo = wave_data.fft_filtered + ktemp_ifft = wave_data.ifft + + altitude_min, altitude_max = wave_data.alt_range + lamda_low, lamda_high = wave_data.lambda_range + + N = len(ktemp_wn5[cycle_no, :]) + # 采样时间间隔,其倒数等于采用频率,以1km为标准尺度等同于1s,假设波的速度为1km/s + dt = (altitude_max-altitude_min)/(N-1) + # 时间序列索引 + n = np.arange(N) + f = n / (N * dt) + t = np.round(np.linspace(altitude_min, altitude_max, N), 2) + + # 原始扰动温度 + x = ktemp_wn5[cycle_no, :] + # 傅里叶变换频谱分析 + y = ktemp_fft[cycle_no, :] + # 滤波后的傅里叶变换频谱分析 + yy = ktemp_fft_lvbo[cycle_no, :] + # 傅里叶逆变换后的扰动温度 + yyy = ktemp_ifft[cycle_no, :] + + plt.figure(figsize=(15, 10)) # 调整图形大小 + # 原始信号的时间序列 + plt.subplot(2, 2, 1) + plt.plot(t, x) + plt.title('(a)原始信号') + plt.xlabel('高度 (km)', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距 + # 原始振幅谱 + plt.subplot(2, 2, 2) + plt.plot(f, np.abs(y) * 2 / N) + plt.title('(b))原始振幅谱') + plt.xlabel('频率/Hz', labelpad=10) # 增加标签间距 + plt.ylabel('振幅', labelpad=10) # 增加标签间距 + + # 通过IFFT回到时间域 + plt.subplot(2, 2, 3) + plt.plot(t, yyy) + plt.title('(c))傅里叶逆变换') + plt.xlabel('高度 (km)', labelpad=10) # 增加标签间距 + plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距 + + # 滤波后的振幅谱 + plt.subplot(2, 2, 4) + plt.plot(f, np.abs(yy) * 2 / N) + plt.title(f'(d)滤除波长 < {lamda_low} km, > {lamda_high} km的波') + plt.xlabel('频率/Hz', labelpad=10) # 增加标签间距 + plt.ylabel('振幅', labelpad=10) # 增加标签间距 + + # 调整子图之间的边距 + plt.subplots_adjust(top=0.8, bottom=0.2, left=0.2, + right=0.8, hspace=0.3, wspace=0.2) + + def day_cycle_power_wave_plot(self, cycle_no, wave_data: WaveData): + ktemp_Nz = wave_data.Nz + ktemp_Ptz = wave_data.Ptz + + altitude_min, altitude_max = wave_data.alt_range + + N = len(ktemp_Nz[cycle_no, :]) + y = np.round(np.linspace(altitude_min, altitude_max, N), 2) + x1 = ktemp_Nz[cycle_no, :] + x2 = ktemp_Ptz[cycle_no, :] + + plt.figure(figsize=(12, 10)) # 调整图形大小 + # 原始信号的时间序列 + plt.subplot(1, 2, 1) + plt.plot(x1[::-1], y, label='原始信号') + plt.title('(a)Nz') + plt.xlabel('Nz', labelpad=10) # 增加标签间距 + plt.ylabel('高度 (km)', labelpad=10) # 增加标签间距 + + # 原始信号的时间序列 + plt.subplot(1, 2, 2) + plt.plot(x2[::-1], y, label='原始信号') + plt.title('(b)Ptz') + plt.xlabel('Ptz', labelpad=10) # 增加标签间距 + plt.ylabel('高度 (km)', labelpad=10) # 增加标签间距 + + # 调整子图之间的边距 + plt.subplots_adjust(top=0.8, bottom=0.2, left=0.1, + right=0.8, hspace=0.3, wspace=0.2) + plt.tight_layout() # 调整子图参数以适应图形区域 + + def month_power_wave_plot(self, wave_data: List[WaveData], date_time): + ktemp_Nz_mon = [data.Nz for data in wave_data] + ktemp_Ptz_mon = [data.Ptz for data in wave_data] + + altitude_min, altitude_max = wave_data[0].alt_range + + if ktemp_Nz_mon and ktemp_Nz_mon[0] is not None: + nz_shape = np.array(ktemp_Nz_mon[0]).shape + else: + nz_shape = (15, 157) + if ktemp_Ptz_mon and ktemp_Ptz_mon[0] is not None: + ptz_shape = np.array(ktemp_Ptz_mon[0]).shape + else: + ptz_shape = (15, 157) + y = np.round(np.linspace(altitude_min, altitude_max, nz_shape[1]), 2) + x = np.arange(len(date_time.data)) + # 处理 ktemp_Nz_mon + ktemp_Nz_plot = np.array([np.mean(day_data if day_data is not None else np.zeros( + nz_shape), axis=0) for day_data in ktemp_Nz_mon]) + ktemp_Ptz_plot = np.array( + [np.mean(day_data if day_data is not None else np.zeros(nz_shape), axis=0) for day_data in ktemp_Ptz_mon]) + # 处理 ktemp_Ptz_mon(以100为界剔除异常值) + # ktemp_Ptz_plot = np.array([np.mean(day_data if day_data is not None and np.all(day_data <= 100) else np.zeros(ptz_shape), axis=0) for day_data in ktemp_Ptz_mon]) + # 创建一个图形,并指定两个子图 + fig, axs = plt.subplots(1, 2, figsize=(15, 10)) + + # 第一幅图 (a) NZ + cax1 = axs[0].imshow(ktemp_Nz_plot.T[::-1], aspect='auto', cmap='rainbow', origin='lower', + extent=[x[0], x[-1], y[0], y[-1]]) + fig.colorbar(cax1, ax=axs[0]) # 为第一幅图添加颜色条 + axs[0].set_title('(a) NZ') + axs[0].set_xlabel('Time') + axs[0].set_ylabel('Height') + axs[0].set_yticks(np.linspace(30, 100, 8)) + axs[0].set_yticklabels(np.round(np.linspace(30, 100, 8), 1)) + axs[0].set_xticks(x) + axs[0].set_xticklabels(x) + + # 第二幅图 (b) PTZ + cax2 = axs[1].imshow(np.log(ktemp_Ptz_plot.T[::-1]), aspect='auto', + cmap='rainbow', origin='lower', extent=[x[0], x[-1], y[0], y[-1]]) + fig.colorbar(cax2, ax=axs[1]) # 为第二幅图添加颜色条 + axs[1].set_title('(b) PTZ') + axs[1].set_xlabel('Time') + axs[1].set_ylabel('Height') + axs[1].set_yticks(np.linspace(30, 100, 8)) + axs[1].set_yticklabels(np.round(np.linspace(30, 100, 8), 1)) + axs[1].set_xticks(x) + axs[1].set_xticklabels(x) + + # 调整子图之间的边距 + plt.subplots_adjust(top=0.9, bottom=0.1, left=0.05, + right=0.95, hspace=0.3, wspace=0.3) + plt.tight_layout() # 调整布局以避免重叠 + + def year_power_wave_plot(self, year_wave: YearlyData): + # 假设我们已经从process_yearly_data函数中获取了一年的Nz和Ptz数据 + results = year_wave.to_dict() + altitude_min, altitude_max = results["alt_range"][0] + + ktemp_Nz_mon_list = results["ktemp_Nz_mon_list"] + ktemp_Ptz_mon_list = results["ktemp_Ptz_mon_list"] + ktemp_Ptz_mon_list.pop(0) + ktemp_Nz_mon_list.pop(0) + + # 准备日期数据,这里假设date_time_list是一年中的所有日期 + date_time_list = results["date_time_list"] + date_time_list.pop(0) + # 将日期转换为matplotlib可以理解的数字格式 + date_nums = mdates.date2num(date_time_list) + + # 获取date_time_list长度作为横坐标新的依据 + x_ticks_length = len(date_time_list) + x_ticks = np.arange(0, x_ticks_length, 30) + x_labels = [date_time_list[i] if i < len( + date_time_list) else "" for i in x_ticks] + + # 准备高度数据 + # 假设高度数据有157个点 + y = np.round(np.linspace(altitude_min, altitude_max, 157), 2) + + # 创建一个图形,并指定两个子图 + fig, axs = plt.subplots(1, 2, figsize=(20, 10)) + + # 处理 ktemp_Nz_mon + ktemp_Nz_plot = np.array( + [np.mean(day_data if day_data is not None else np.zeros((15, 157)), axis=0) for day_data in ktemp_Nz_mon_list]) + # 处理 ktemp_Ptz_mon + ktemp_Ptz_plot = np.array( + [np.mean(day_data if day_data is not None else np.zeros((15, 157)), axis=0) for day_data in ktemp_Ptz_mon_list]) + # ktemp_Ptz_plot = np.array( + # [np.mean(day_data if day_data is not None and np.all(day_data <= 100) else np.zeros((15, 157)), axis=0) for + # day_data in ktemp_Ptz_mon_list]) + + # 第一幅图 (a) NZ + cax1 = axs[0].imshow(ktemp_Nz_plot.T[::-1], aspect='auto', cmap='rainbow', origin='lower', + extent=[0, x_ticks_length - 1, y[0], y[-1]]) + fig.colorbar(cax1, ax=axs[0]) # 为第一幅图添加颜色条 + axs[0].set_title('(a) NZ') + axs[0].set_xlabel('Time') + axs[0].set_ylabel('Height') + axs[0].set_yticks(np.linspace(30, 100, 8)) + axs[0].set_yticklabels(np.round(np.linspace(30, 100, 8), 1)) + axs[0].set_xticks(x_ticks) # 设置新的横坐标刻度 + axs[0].set_xticklabels(x_labels, rotation=45) + + # 第二幅图 (b) PTZ + cax2 = axs[1].imshow(np.log(ktemp_Ptz_plot.T[::-1]), aspect='auto', cmap='rainbow', origin='lower', + extent=[0, x_ticks_length - 1, y[0], y[-1]]) + fig.colorbar(cax2, ax=axs[1]) # 为第二幅图添加颜色条 + axs[1].set_title('(b) PTZ') + axs[1].set_xlabel('Time') + axs[1].set_ylabel('Height') + axs[1].set_yticks(np.linspace(30, 100, 8)) + axs[1].set_yticklabels(np.round(np.linspace(30, 100, 8), 1)) + axs[1].set_xticks(x_ticks) # 设置新的横坐标刻度 + axs[1].set_xticklabels(x_labels, rotation=45) + + # 调整子图之间的边距 + plt.subplots_adjust(top=0.9, bottom=0.1, left=0.05, + right=0.95, hspace=0.3, wspace=0.3) + plt.tight_layout() # 调整布局以避免重叠 diff --git a/saber/utils.py b/saber/utils.py new file mode 100644 index 0000000..c307f81 --- /dev/null +++ b/saber/utils.py @@ -0,0 +1,278 @@ +from dataclasses import dataclass +from os import path +import numpy as np +import netCDF4 as nc +import pandas as pd +from scipy.optimize import curve_fit +# ---------------------------------------------------------------------------------------------------------------------------- +# ---------------------------------------------------------------------------------------------------------------------------- +# 5---同周期下不同高度数据的BT_z背景位等指标计算 - +# ---------------------------------------------------------------------------------------------------------------------------- +# ---------------------------------------------------------------------------------- + + +def power_indices(ktemp_cycles, ktemp_wn5, ktemp_ifft, altitude_min, altitude_max): + + # 定义Brunt-Väisälä频率计算函数 + def brunt_vaisala_frequency(g, BT_z, c_p, height): + # 计算位温随高度的变化率 + dBT_z_dz = np.gradient(BT_z, height) + # 计算 Brunt-Väisälä 频率 + return np.sqrt((g / BT_z) * ((g / c_p) + dBT_z_dz)) + + # 定义势能计算函数 + def calculate_gravitational_potential_energy(g, BT_z, N_z, PT_z): + return 0.5 * ((g / N_z) ** 2) * ((PT_z / BT_z) ** 2) + + # 重力加速度 + g = 9.81 + # 空气比热容 + c_p = 1004.5 + + height = np.linspace(altitude_min, altitude_max, + ktemp_cycles.shape[1]) * 1000 # 高度 + background = ktemp_cycles - ktemp_wn5 + + # 初始化结果矩阵 + N_z_matrix = [] + # 初始化结果矩阵 + PT_z_matrix = [] + + # 循环处理background和filtered_perturbation所有行 + for i in range(background.shape[0]): + BT_z = np.array(background[i]) + # 滤波后的扰动 + PT_z = np.array(ktemp_ifft[i]) + + # 调用Brunt-Väisälä频率函数 + N_z = brunt_vaisala_frequency(g, BT_z, c_p, height) + PT_z = calculate_gravitational_potential_energy( + g, BT_z, N_z, PT_z) # 调用势能函数 + N_z_matrix.append(N_z) + PT_z_matrix.append(PT_z) + + ktemp_Nz = np.vstack(N_z_matrix) + ktemp_Ptz = np.vstack(PT_z_matrix) + return ktemp_Nz, ktemp_Ptz + +# ---------------------------------------------------------------------------------------------------------------------------- +# 1---打开文件并读取不同变量数据 - +# ---------------------------------------------------------------------------------------------------------------------------- + + +@dataclass +class NcData: + dataset: nc.Dataset + tplatitude: np.ndarray + tplongitude: np.ndarray + tpaltitude: np.ndarray + ktemp: np.ndarray + time: np.ndarray + date: np.ndarray + date_time: np.ndarray + + path: str = None + + # 兼容旧代码,老的解构方式也能用 + def __iter__(self): + return iter([self.dataset, self.tplatitude, self.tplongitude, self.tpaltitude, self.ktemp, self.time, self.date, self.date_time]) + + +def data_nc_load(file_path): + + dataset = nc.Dataset(file_path, 'r') + + # 纬度数据,二维数组形状为(42820,379) 42820为事件,379则为不同高度 + tplatitude = dataset.variables['tplatitude'][:, :] + tplongitude = dataset.variables['tplongitude'][:, :] # 经度数据 + tpaltitude = dataset.variables['tpaltitude'][:, :] # 高度,二维数组形状为(42820,379) + time = dataset.variables['time'][:, :] # 二维数组形状为(42820,379) + date = dataset.variables['date'][:] + date_time = np.unique(date) # 输出数据时间信息 + ktemp = dataset.variables['ktemp'][:, :] # 温度数据,二维数组形状为(42820,379) + + return NcData(dataset, tplatitude, tplongitude, tpaltitude, ktemp, time, date, date_time, path=file_path) + # return dataset, tplatitude, tplongitude, tpaltitude, ktemp, time, date, date_time +# ---------------------------------------------------------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------------------------------------------------------- +# 2---筛选某一天、某个纬度和高度范围15个不同cycle的温度数据 - +# ---------------------------------------------------------------------------------------------------------------------------- +# 2-1 读取某一天的所有事件及其对应的纬度数据 + + +def day_data_read(date, day_read, tplatitude): + + events = np.where(date == day_read)[0] # 读取筛选天的事件编号 4294-5714位置,从0开始编号 + time_events = date[date == day_read] # 读取筛选天的事件编号 4294-5714位置,从0开始编号 + latitudes = tplatitude[events, 189] # 输出每个事件中间位置 即第189个经纬度 + df = pd.DataFrame([ # 创建一个包含事件编号、纬度的列表,共1421个事件 + {'time': time, 'event': event, 'latitude': lat} + for time, event, lat in zip(time_events, events, latitudes)]) + + # print(df.head()) # 打印前几行数据以检查 + + return df +# ---------------------------------------------------------------------------------------------------------------------------- +# 2-2 将事件按照卫星轨迹周期进行输出处理,并输出落在纬度范围内的每个周期的事件的行号 + + +def data_cycle_identify(df, latitude_min, latitude_max): + + cycles = [] # 存储每个周期的事件编号列表 + # 存储当前周期的事件编号 + current_cycle_events = [] + prev_latitude = None + + # 遍历DataFrame中的每一行以识别周期和筛选事件 + for index, row in df.iterrows(): + current_event = int(row['event']) + current_latitude = row['latitude'] + + if prev_latitude is not None and prev_latitude < 0 and current_latitude >= 0: # 检查是否是新周期的开始(纬度从负变正,且首次变正) + # 重置当前周期的事件编号列表 + current_cycle_events = [] + + if latitude_min <= current_latitude <= latitude_max: # 如果事件的纬度在指定范围内,添加到当前周期的事件编号列表 + current_cycle_events.append(current_event) + + if prev_latitude is not None and prev_latitude >= 0 and current_latitude < 0: # 检查是否是周期的结束(纬度从正变负) + + # 添加当前周期的事件编号列表到周期列表 + if current_cycle_events: # 确保当前周期有事件编号 + cycles.append(current_cycle_events) + current_cycle_events = [] # 重置当前周期的事件编号列表 + prev_latitude = current_latitude + + if current_cycle_events: # 处理最后一个周期,如果存在的话 + cycles.append(current_cycle_events) + + print(f"一天周期为 {len(cycles)}") + for cycle_index, cycle in enumerate(cycles, start=0): + # 屏幕显示每个循环周期的事件 + print(f"周期 {cycle_index} 包含事件个数: {len(cycle)} 具体事件为: {cycle} ") + + return cycles +# ---------------------------------------------------------------------------------------------------------------------------- +# 2-3---按照循环周期合并同周期数据,并输出处理后的温度数据、对应的高度数据 + + +def data_cycle_generate(cycles, ktemp, tpaltitude, altitude_min, altitude_max): + if not cycles: # 如果周期列表为空,跳过当前迭代 + return None, None + + ktemp_cycles = [] # 初始化列表存储每个周期的温度 + altitude_cycles = [] # 初始化每个循环周期的高度数据 + for event in cycles: + ktemp_cycles_events = np.array(ktemp[event, :]) # 获取每个周期各个事件的ktemp数据 + ktemp_cycles_events[ + np.logical_or(ktemp_cycles_events == -999, ktemp_cycles_events > 999)] = np.nan # 缺失值处理,避免影响结果 + ktemp_cycles_mean = np.nanmean( + ktemp_cycles_events, axis=0) # 对所有周期的 ktemp 数据取均值 + + altitude_cycles_mean = tpaltitude[event[0], :] # 使用第一个的高度来表征所有的 + altitude_indices = np.where((altitude_cycles_mean >= altitude_min) & ( + altitude_cycles_mean <= altitude_max))[0] + + ktemp_cycles.append(np.array(ktemp_cycles_mean[altitude_indices])) + altitude_cycles.append( + np.array(altitude_cycles_mean[altitude_indices])) + + min_length = 157 # min(len(arr) for arr in ktemp_cycles) # 找到最短列表的长度 + ktemp_cycles = np.vstack([arr[:min_length] + for arr in ktemp_cycles]) # 创建新的列表,将每个子列表截断为最短长度 + altitude_cycles = np.vstack([arr[:min_length] for arr in altitude_cycles]) + + return ktemp_cycles, altitude_cycles + +# ---------------------------------------------------------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------------------------------------------------------- +# 4---高度相同下不同循环周期数据的波拟合和滤波处理 - +# ---------------------------------------------------------------------------------------------------------------------------- + + +# 对输入数据按照行(即纬向)进行波数为k的滤波,数据为15*157 +def fit_wave(ktemp_wn0, k): + + wave_fit = [] + + def single_harmonic(x, A, phi, C, k): + return A * np.sin(2 * np.pi / (15/k) * x + phi) + C + + # 数据转置并对每行进行操作,按照同高度数据进行处理 + for rtemp in ktemp_wn0.T: + # 为当前高度层创建索引数组 + indices = np.arange(rtemp.size) + def fit_temp(x, A, phi, C): return single_harmonic( + x, A, phi, C, k) # 定义只拟合 A, phi, C 的 lambda 函数,k 固定 + params, params_covariance = curve_fit( + fit_temp, indices, rtemp) # 使用 curve_fit 进行拟合 + # 提取拟合参数 A, phi, C + A, phi, C = params + # 使用拟合参数计算 wn3 + rtemp1 = single_harmonic(indices, A, phi, C, k) + # 存储拟合参数和拟合曲线 + wave_fit.append(np.array(rtemp1)) + wave_fit = np.vstack(wave_fit) + + wave_fit = wave_fit.T + wave_wn = ktemp_wn0-wave_fit + + return wave_fit, wave_wn +# ---------------------------------------------------------------------------------------------------------------------------- + + +# ---------------------------------------------------------------------------------------------------------------------------- +# 4---同周期下不同高度数据的波拟合和滤波处理 - +# ---------------------------------------------------------------------------------------------------------------------------- +# 对输入数据按照列(即纬向)进行滤波,滤除波长2和15km以内的波, 数据为15*157 +def fft_ifft_wave(ktemp_wn5, lamda_low, lamda_high, altitude_min, altitude_max, lvboin): + + ktemp_fft = [] + ktemp_ifft = [] + ktemp_fft_lvbo = [] + + for rtemp in ktemp_wn5: + # 采样点数或长度 + N = len(rtemp) + # 采样时间间隔,其倒数等于采用频率,以1km为标准尺度等同于1s,假设波的速度为1km/s + dt = (altitude_max-altitude_min)/(N-1) + # 时间序列索引 + n = np.arange(N) + # # t = altitude_min + n * dt # 时间向量 + # t = np.round(np.linspace(altitude_min, altitude_max, N),2) + # 频率索引向量 + f = n / (N * dt) + # 对输入信号进行傅里叶变换 + y = np.fft.fft(rtemp) + + # 定义波长滤波范围(以频率计算) # 高频截止频率 + f_low = 2*np.pi / lamda_high + f_high = 2*np.pi / lamda_low + + # f_low = 1 / lamda_high # 定义波长滤波范围(以频率计算) # 高频截止频率 + # f_high = 1 / lamda_low # 低频截止频率 + + # 创建滤波后的频域信号 + yy = y.copy() + + # 使用逻辑索引过滤特定频段(未确定) + if lvboin: + freq_filter = (f > f_low) & (f < f_high) # 创建逻辑掩码 + else: + freq_filter = (f < f_low) | (f > f_high) # 创建逻辑掩码 + + yy[freq_filter] = 0 # 过滤掉指定频段 + yy_ifft = np.real(np.fft.ifft(yy)) + + ktemp_fft.append(y) + # 存储拟合参数和拟合曲线 + ktemp_ifft.append(np.array(yy_ifft)) + ktemp_fft_lvbo.append(yy) + + ktemp_fft = np.vstack(ktemp_fft) + ktemp_ifft = np.vstack(ktemp_ifft) + ktemp_fft_lvbo = np.vstack(ktemp_fft_lvbo) + + return ktemp_fft, ktemp_fft_lvbo, ktemp_ifft diff --git a/tidi/plot.py b/tidi/plot.py new file mode 100644 index 0000000..633264b --- /dev/null +++ b/tidi/plot.py @@ -0,0 +1,878 @@ +import pandas as pd +import numpy as np +from scipy.io import loadmat +from scipy.optimize import curve_fit +import matplotlib.pyplot as plt +import seaborn as sns +# --------------------------------------------------------------------------------------- +# -----vzonal---------------------------------------------------------------------------- + + +def process_vzonal_day(day, year, ): + try: + # 读取数据 + height_data = loadmat(rf"./tidi/data\{year}\{day:03d}_Height.mat") + lat_data = loadmat(rf"./tidi/data\{year}\{day:03d}_Lat.mat") + lon_data = loadmat(rf"./tidi/data\{year}\{day:03d}_Lon.mat") + vmeridional_data = loadmat( + rf"./tidi/data\{year}\{day:03d}_VMerdional.mat") + vzonal_data = loadmat(rf"./tidi/data\{year}\{day:03d}_Vzonal.mat") + + # 将数据转换为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']) + # 将经纬度拼接为两列并添加到对应的DataFrame中 + lon_lat_df = pd.concat([lon_df, lat_df], axis=1) + lon_lat_df.columns = ['Longitude', 'Latitude'] + # 筛选出10到30度纬度范围的数据 + lat_filter = (lat_df.values >= 0) & (lat_df.values <= 20) + # 使用纬度范围过滤数据 + vmeridional_filtered = vmeridional_df.iloc[:, lat_filter.flatten()] + vzonal_filtered = vzonal_df.iloc[:, lat_filter.flatten()] + lon_lat_filtered = lon_lat_df.iloc[lat_filter.flatten(), :] + # 接着对lon_lat_filtered的经度进行分组,0到360度每30度一个区间 + bins = range(0, 361, 30) + group_labels = [f"{i}-{i + 29}" for i in range(0, 360, 30)] + + lon_lat_filtered['Longitude_Group'] = pd.cut( + lon_lat_filtered['Longitude'], bins=bins, labels=group_labels) + + # 获取所有唯一的经度分组标签并按照数值顺序排序 + unique_groups = sorted(lon_lat_filtered['Longitude_Group'].unique( + ), key=lambda x: int(x.split('-')[0])) + # 按照经度分组获取每个区间对应的vzonal_filtered、vmeridional_filtered数据 + grouped_data = {} + insufficient_data_count = 0 # 用于计数数据不足的组数 + + for group in unique_groups: + mask = lon_lat_filtered['Longitude_Group'] == group + grouped_data[group] = { + 'vzonal_filtered': vzonal_filtered.loc[:, mask], + 'vmeridional_filtered': vmeridional_filtered.loc[:, mask], + 'lon_lat_filtered': lon_lat_filtered.loc[mask] + } + + # 计算有效值数量 + vzonal_count = grouped_data[group]['vzonal_filtered'].notna( + ).sum().sum() + vmeridional_count = grouped_data[group]['vmeridional_filtered'].notna( + ).sum().sum() + + if vzonal_count <= 20 or vmeridional_count <= 20: + insufficient_data_count += 1 + + # 如果超过6组数据不足,则抛出错误 + if insufficient_data_count > 6: + expected_length = 21 + return pd.Series(np.nan, index=range(expected_length)) + + # 如果代码运行到这里,说明所有分组的数据量都足够或者不足的组数不超过6 + print("所有分组的数据量都足够") + # -----------计算w0------------------------------------------------------------------------------------------ + # 定义期望的12个区间的分组标签 + expected_groups = [f"{i}-{i + 29}" for i in range(0, 360, 30)] + # 初始化一个空DataFrame来存储所有区间的均值廓线,列名设置为期望的分组标签 + W0_profiles_df = pd.DataFrame(columns=expected_groups) + + # 遍历grouped_data字典中的每个组 + for group, data in grouped_data.items(): + # 提取当前组的vzonal_filtered数据 + vzonal_filtered = data['vzonal_filtered'] + # 计算有效数据的均值廓线,跳过NaN值 + mean_profile = vzonal_filtered.mean(axis=1, skipna=True) + # 将当前组的均值廓线作为一列添加到W0_profiles_df DataFrame中 + W0_profiles_df[group] = mean_profile + + # 检查并填充缺失的区间列,将缺失的列添加并填充为NaN + for group in expected_groups: + if group not in W0_profiles_df.columns: + W0_profiles_df[group] = pd.Series( + [float('NaN')] * len(W0_profiles_df)) + + # 打印拼接后的DataFrame以验证 + print("Concatenated mean profiles for all longitude groups:\n", W0_profiles_df) + # 计算每个高度的均值 + height_mean_profiles = W0_profiles_df.mean(axis=1) + # 将每个高度的均值作为新的一行添加到DataFrame中,All_Heights_Mean就是wn0 + W0_profiles_df['All_Heights_Mean'] = height_mean_profiles + wn0_df = W0_profiles_df['All_Heights_Mean'] + # -------计算残余量-------------------------------------------------------------------------------------- + # 计算每个经度区间的残余值 (即每个区间的值减去该高度的All_Heights_Mean) + residuals_df = W0_profiles_df.drop(columns='All_Heights_Mean').subtract( + W0_profiles_df['All_Heights_Mean'], axis=0) + + # --------wn1------------------------- + def single_harmonic(x, A, phi, C): + return A * np.sin(2 * np.pi / 12 * x + phi) + C + # 用于存储每个高度拟合后的参数结果 + fit_results = [] + for index, row in residuals_df.iterrows(): + # 检查该行是否存在NaN值,如果有则跳过拟合,直接将参数设为0 + if row.isnull().any(): + fit_results.append((0, 0, 0)) + continue + x = np.arange(12) # 对应12个位置作为自变量 + y = row.values + try: + # 进行曲线拟合 + popt, _ = curve_fit(single_harmonic, x, y) + fit_results.append(popt) + except RuntimeError: + # 如果拟合过程出现问题(例如无法收敛等),也将参数设为0 + fit_results.append((0, 0, 0)) + # 将拟合结果转换为DataFrame + fit_results_df = pd.DataFrame(fit_results, columns=['A', 'phi', 'C']) + print(fit_results_df) + # 用于存储每个高度的拟合值 + wn1_values = [] + for index, row in fit_results_df.iterrows(): + A, phi, C = row + x = np.arange(12) # 同样对应12个位置作为自变量 + wn1 = single_harmonic(x, A, phi, C) + wn1_values.append(wn1) + # 将拟合值转换为DataFrame + wn1_df = pd.DataFrame(wn1_values, columns=[ + f'wn1_{i}' for i in range(12)]) + print(wn1_df) + # 如果wn1_df全为0,则跳过下面的计算,直接令该天的day_log_gwresult全部为NaN + if (wn1_df == 0).all().all(): + return pd.Series(np.nan, index=range(21)) + # ------------计算temp-wn0-wn1--------------------------------------------------------- + temp_wn0_wn1 = residuals_df.values - wn1_df.values + # 将结果转为 DataFrame + temp_wn0_wn1_df = pd.DataFrame( + temp_wn0_wn1, columns=residuals_df.columns, index=residuals_df.index) + + # -------wn2-------------------------------------------------------------------------- + def single_harmonic(x, A, phi, C): + return A * np.sin(2 * np.pi / 6 * x + phi) + C + + # 用于存储每个高度拟合后的参数结果 + fit_results2 = [] + for index, row in temp_wn0_wn1_df.iterrows(): + # 检查该行是否存在NaN值,如果有则跳过拟合,直接将参数设为0 + if row.isnull().any(): + fit_results2.append((0, 0, 0)) + continue + x = np.arange(12) # 对应12个位置作为自变量 + y = row.values + try: + # 进行曲线拟合 + popt, _ = curve_fit(single_harmonic, x, y) + fit_results2.append(popt) + except RuntimeError: + # 如果拟合过程出现问题(例如无法收敛等),也将参数设为0 + fit_results2.append((0, 0, 0)) + # 将拟合结果转换为DataFrame + fit_results2_df = pd.DataFrame(fit_results2, columns=['A', 'phi', 'C']) + print(fit_results2_df) + # 用于存储每个高度的拟合值 + wn2_values = [] + for index, row in fit_results2_df.iterrows(): + A, phi, C = row + x = np.arange(12) # 同样对应12个位置作为自变量 + wn2 = single_harmonic(x, A, phi, C) + wn2_values.append(wn2) + # 将拟合值转换为DataFrame + wn2_df = pd.DataFrame(wn2_values, columns=[ + f'wn2_{i}' for i in range(12)]) + print(wn2_df) + # ---------计算temp-wn0-wn1-wn2------------------------------------------------------ + temp_wn0_wn1_wn2 = temp_wn0_wn1_df.values - wn2_df.values + # 转换为 DataFrame + temp_wn0_wn1_wn2_df = pd.DataFrame( + temp_wn0_wn1_wn2, columns=temp_wn0_wn1_df.columns) + + # -------wn3----------------------------------------------------------------------- + def single_harmonic(x, A, phi, C): + return A * np.sin(2 * np.pi / 4 * x + phi) + C + + # 用于存储每个高度拟合后的参数结果 + fit_results3 = [] + for index, row in temp_wn0_wn1_wn2_df.iterrows(): + # 检查该行是否存在NaN值,如果有则跳过拟合,直接将参数设为0 + if row.isnull().any(): + fit_results3.append((0, 0, 0)) + continue + x = np.arange(12) # 对应12个位置作为自变量 + y = row.values + try: + # 进行曲线拟合 + popt, _ = curve_fit(single_harmonic, x, y) + fit_results3.append(popt) + except RuntimeError: + # 如果拟合过程出现问题(例如无法收敛等),也将参数设为0 + fit_results3.append((0, 0, 0)) + # 将拟合结果转换为DataFrame + fit_results3_df = pd.DataFrame(fit_results3, columns=['A', 'phi', 'C']) + print(fit_results3_df) + # 用于存储每个高度的拟合值 + wn3_values = [] + for index, row in fit_results3_df.iterrows(): + A, phi, C = row + x = np.arange(12) # 同样对应12个位置作为自变量 + wn3 = single_harmonic(x, A, phi, C) + wn3_values.append(wn3) + # 将拟合值转换为DataFrame + wn3_df = pd.DataFrame(wn3_values, columns=[ + f'wn3_{i}' for i in range(12)]) + print(wn3_df) + # ---------计算temp-wn0-wn1-wn2-wn3------------------------------------------------------ + temp_wn0_wn1_wn2_wn3 = temp_wn0_wn1_wn2_df.values - wn3_df.values + # 转换为 DataFrame + temp_wn0_wn1_wn2_wn3_df = pd.DataFrame( + temp_wn0_wn1_wn2_wn3, columns=temp_wn0_wn1_wn2_df.columns) + # -------wn4 - ---------------------------------------------------------------------- + + def single_harmonic(x, A, phi, C): + return A * np.sin(2 * np.pi / 3 * x + phi) + C + + # 用于存储每个高度拟合后的参数结果 + fit_results4 = [] + for index, row in temp_wn0_wn1_wn2_wn3_df.iterrows(): + # 检查该行是否存在NaN值,如果有则跳过拟合,直接将参数设为0 + if row.isnull().any(): + fit_results4.append((0, 0, 0)) + continue + x = np.arange(12) # 对应12个位置作为自变量 + y = row.values + try: + # 进行曲线拟合 + popt, _ = curve_fit(single_harmonic, x, y) + fit_results4.append(popt) + except RuntimeError: + # 如果拟合过程出现问题(例如无法收敛等),也将参数设为0 + fit_results4.append((0, 0, 0)) + + fit_results4_df = pd.DataFrame(fit_results4, columns=['A', 'phi', 'C']) + print(fit_results4_df) + # 用于存储每个高度的拟合值 + wn4_values = [] + for index, row in fit_results4_df.iterrows(): + A, phi, C = row + x = np.arange(12) # 同样对应12个位置作为自变量 + wn4 = single_harmonic(x, A, phi, C) + wn4_values.append(wn4) + # 将拟合值转换为DataFrame + wn4_df = pd.DataFrame(wn4_values, columns=[ + f'wn4_{i}' for i in range(12)]) + print(wn4_df) + # ---------计算temp-wn0-wn1-wn2-wn3------------------------------------------------------ + temp_wn0_wn1_wn2_wn3_wn4 = temp_wn0_wn1_wn2_wn3_df.values - wn4_df.values + # 转换为 DataFrame + temp_wn0_wn1_wn2_wn3_wn4_df = pd.DataFrame( + temp_wn0_wn1_wn2_wn3_wn4, columns=temp_wn0_wn1_wn2_wn3_df.columns) + + # -------wn5----------------------------------------------------------------------- + def single_harmonic(x, A, phi, C): + return A * np.sin(2 * np.pi / 2.4 * x + phi) + C + + # 用于存储每个高度拟合后的参数结果 + fit_results5 = [] + for index, row in temp_wn0_wn1_wn2_wn3_wn4_df.iterrows(): + # 检查该行是否存在NaN值,如果有则跳过拟合,直接将参数设为0 + if row.isnull().any(): + fit_results5.append((0, 0, 0)) + continue + x = np.arange(12) # 对应12个位置作为自变量 + y = row.values + try: + # 进行曲线拟合 + popt, _ = curve_fit(single_harmonic, x, y) + fit_results5.append(popt) + except RuntimeError: + # 如果拟合过程出现问题(例如无法收敛等),也将参数设为0 + fit_results5.append((0, 0, 0)) + # 将拟合结果转换为DataFrame + fit_results5_df = pd.DataFrame(fit_results5, columns=['A', 'phi', 'C']) + print(fit_results5_df) + # 用于存储每个高度的拟合值 + wn5_values = [] + for index, row in fit_results5_df.iterrows(): + A, phi, C = row + x = np.arange(12) # 同样对应12个位置作为自变量 + wn5 = single_harmonic(x, A, phi, C) + wn5_values.append(wn5) + # 将拟合值转换为DataFrame + wn5_df = pd.DataFrame(wn5_values, columns=[ + f'wn5_{i}' for i in range(12)]) + print(wn5_df) + # ---------计算temp-wn0-wn1-wn2-wn3------------------------------------------------------ + temp_wn0_wn1_wn2_wn3_wn4_wn5 = temp_wn0_wn1_wn2_wn3_wn4_df.values - wn5_df.values + # 转换为 DataFrame + temp_wn0_wn1_wn2_wn3_wn4_wn5_df = pd.DataFrame(temp_wn0_wn1_wn2_wn3_wn4_wn5, + columns=temp_wn0_wn1_wn2_wn3_wn4_df.columns) + + # ------计算背景温度=wn0+wn1+wn2+wn3+wn4+wn5--------------------------------------------------- + background = wn5_df.values + wn4_df.values + \ + wn3_df.values + wn2_df.values + wn1_df.values + # wn0只有一列单独处理相加 + # 使用 np.isnan 和 np.where 来判断是否为 NaN 或 0,避免这些值参与相加 + for i in range(21): + wn0_value = wn0_df.iloc[i] + # 只有当 wn0_value 既不是 NaN 也不是 0 时才加到 background 上 + if not np.isnan(wn0_value) and wn0_value != 0: + background[i, :] += wn0_value + # 扰动 + perturbation = temp_wn0_wn1_wn2_wn3_wn4_wn5_df + # ---------傅里叶变换---------------------------------------------------------------------- + # 初始化一个新的DataFrame来保存处理结果 + result = pd.DataFrame( + np.nan, index=perturbation.index, columns=perturbation.columns) + # 定义滤波范围 + lambda_low = 2 # 2 km + lambda_high = 15 # 15 km + f_low = 2 * np.pi / lambda_high + f_high = 2 * np.pi / lambda_low + + # 循环处理perturbation中的每一列 + for col in perturbation.columns: + x = perturbation[col] + # 提取有效值 + valid_values = x.dropna() + N = len(valid_values) # 有效值的数量 + + # 找到第一个有效值的索引 + first_valid_index = valid_values.index[0] if not valid_values.index.empty else None + height_value = height_df.loc[first_valid_index] if first_valid_index is not None else None + + # 如果有效值为空,则跳过该列 + if N == 0 or height_value is None: + continue + + # 时间序列和频率 + dt = 0.25 + n = np.arange(N) + t = height_value.values + n * dt + f = n / (N * dt) + + # 傅里叶变换 + y = np.fft.fft(valid_values.values) + + # 频率滤波 + yy = y.copy() + freq_filter = (f < f_low) | (f > f_high) + yy[freq_filter] = 0 # 过滤掉指定频段 + + # 逆傅里叶变换 + perturbation_after = np.real(np.fft.ifft(yy)) + + # 将处理结果插回到result矩阵中 + result.loc[valid_values.index, col] = perturbation_after + u2 = result ** 2 + u2 = u2.mean(axis=1) + return u2 + except FileNotFoundError: + # 如果文件不存在,返回全NaN的Series + expected_length = 21 + return pd.Series(np.nan, index=range(expected_length)) + + +# ------------------------------------------------------------------------------------------- +# --------meridional------------------------------------------------------------------------- +def process_vmeridional_day(day, year): + try: + # 读取数据 + height_data = loadmat(rf"./tidi/data\{year}\{day:03d}_Height.mat") + lat_data = loadmat(rf"./tidi/data\{year}\{day:03d}_Lat.mat") + lon_data = loadmat(rf"./tidi/data\{year}\{day:03d}_Lon.mat") + vmeridional_data = loadmat( + rf"./tidi/data\{year}\{day:03d}_VMerdional.mat") + vzonal_data = loadmat(rf"./tidi/data\{year}\{day:03d}_Vzonal.mat") + + # 将数据转换为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']) + # 将经纬度拼接为两列并添加到对应的DataFrame中 + lon_lat_df = pd.concat([lon_df, lat_df], axis=1) + lon_lat_df.columns = ['Longitude', 'Latitude'] + # 筛选出10到30度纬度范围的数据 + lat_filter = (lat_df.values >= 0) & (lat_df.values <= 20) + # 使用纬度范围过滤数据 + vmeridional_filtered = vmeridional_df.iloc[:, lat_filter.flatten()] + vzonal_filtered = vzonal_df.iloc[:, lat_filter.flatten()] + lon_lat_filtered = lon_lat_df.iloc[lat_filter.flatten(), :] + # 接着对lon_lat_filtered的经度进行分组,0到360度每30度一个区间 + bins = range(0, 361, 30) + group_labels = [f"{i}-{i + 29}" for i in range(0, 360, 30)] + + lon_lat_filtered['Longitude_Group'] = pd.cut( + lon_lat_filtered['Longitude'], bins=bins, labels=group_labels) + + # 获取所有唯一的经度分组标签并按照数值顺序排序 + unique_groups = sorted(lon_lat_filtered['Longitude_Group'].unique( + ), key=lambda x: int(x.split('-')[0])) + # 按照经度分组获取每个区间对应的vzonal_filtered、vmeridional_filtered数据 + grouped_data = {} + insufficient_data_count = 0 # 用于计数数据不足的组数 + + for group in unique_groups: + mask = lon_lat_filtered['Longitude_Group'] == group + grouped_data[group] = { + 'vzonal_filtered': vzonal_filtered.loc[:, mask], + 'vmeridional_filtered': vmeridional_filtered.loc[:, mask], + 'lon_lat_filtered': lon_lat_filtered.loc[mask] + } + + # 计算有效值数量 + vzonal_count = grouped_data[group]['vzonal_filtered'].notna( + ).sum().sum() + vmeridional_count = grouped_data[group]['vmeridional_filtered'].notna( + ).sum().sum() + + if vzonal_count <= 20 or vmeridional_count <= 20: + insufficient_data_count += 1 + + # 如果超过6组数据不足,则抛出错误 + if insufficient_data_count > 6: + expected_length = 21 + return pd.Series(np.nan, index=range(expected_length)) + + # 如果代码运行到这里,说明所有分组的数据量都足够或者不足的组数不超过6 + print("所有分组的数据量都足够") + # -----------计算w0------------------------------------------------------------------------------------------ + # 定义期望的12个区间的分组标签 + expected_groups = [f"{i}-{i + 29}" for i in range(0, 360, 30)] + # 初始化一个空DataFrame来存储所有区间的均值廓线,列名设置为期望的分组标签 + W0_profiles_df = pd.DataFrame(columns=expected_groups) + + # 遍历grouped_data字典中的每个组 + for group, data in grouped_data.items(): + # 提取当前组的vzonal_filtered数据 + vmeridional_filtered = data['vmeridional_filtered'] + # 计算有效数据的均值廓线,跳过NaN值 + mean_profile = vmeridional_filtered.mean(axis=1, skipna=True) + # 将当前组的均值廓线作为一列添加到W0_profiles_df DataFrame中 + W0_profiles_df[group] = mean_profile + + # 检查并填充缺失的区间列,将缺失的列添加并填充为NaN + for group in expected_groups: + if group not in W0_profiles_df.columns: + W0_profiles_df[group] = pd.Series( + [float('NaN')] * len(W0_profiles_df)) + + # 打印拼接后的DataFrame以验证 + print("Concatenated mean profiles for all longitude groups:\n", W0_profiles_df) + # 计算每个高度的均值 + height_mean_profiles = W0_profiles_df.mean(axis=1) + # 将每个高度的均值作为新的一行添加到DataFrame中,All_Heights_Mean就是wn0 + W0_profiles_df['All_Heights_Mean'] = height_mean_profiles + wn0_df = W0_profiles_df['All_Heights_Mean'] + # -------计算残余量-------------------------------------------------------------------------------------- + # 计算每个经度区间的残余值 (即每个区间的值减去该高度的All_Heights_Mean) + residuals_df = W0_profiles_df.drop(columns='All_Heights_Mean').subtract( + W0_profiles_df['All_Heights_Mean'], axis=0) + + # --------wn1------------------------- + def single_harmonic(x, A, phi, C): + return A * np.sin(2 * np.pi / 12 * x + phi) + C + # 用于存储每个高度拟合后的参数结果 + fit_results = [] + for index, row in residuals_df.iterrows(): + # 检查该行是否存在NaN值,如果有则跳过拟合,直接将参数设为0 + if row.isnull().any(): + fit_results.append((0, 0, 0)) + continue + x = np.arange(12) # 对应12个位置作为自变量 + y = row.values + try: + # 进行曲线拟合 + popt, _ = curve_fit(single_harmonic, x, y) + fit_results.append(popt) + except RuntimeError: + # 如果拟合过程出现问题(例如无法收敛等),也将参数设为0 + fit_results.append((0, 0, 0)) + # 将拟合结果转换为DataFrame + fit_results_df = pd.DataFrame(fit_results, columns=['A', 'phi', 'C']) + print(fit_results_df) + # 用于存储每个高度的拟合值 + wn1_values = [] + for index, row in fit_results_df.iterrows(): + A, phi, C = row + x = np.arange(12) # 同样对应12个位置作为自变量 + wn1 = single_harmonic(x, A, phi, C) + wn1_values.append(wn1) + # 将拟合值转换为DataFrame + wn1_df = pd.DataFrame(wn1_values, columns=[ + f'wn1_{i}' for i in range(12)]) + print(wn1_df) + # 如果wn1_df全为0,则跳过下面的计算,直接令该天的day_log_gwresult全部为NaN + if (wn1_df == 0).all().all(): + return pd.Series(np.nan, index=range(21)) + # ------------计算temp-wn0-wn1--------------------------------------------------------- + temp_wn0_wn1 = residuals_df.values - wn1_df.values + # 将结果转为 DataFrame + temp_wn0_wn1_df = pd.DataFrame( + temp_wn0_wn1, columns=residuals_df.columns, index=residuals_df.index) + + # -------wn2-------------------------------------------------------------------------- + def single_harmonic(x, A, phi, C): + return A * np.sin(2 * np.pi / 6 * x + phi) + C + + # 用于存储每个高度拟合后的参数结果 + fit_results2 = [] + for index, row in temp_wn0_wn1_df.iterrows(): + # 检查该行是否存在NaN值,如果有则跳过拟合,直接将参数设为0 + if row.isnull().any(): + fit_results2.append((0, 0, 0)) + continue + x = np.arange(12) # 对应12个位置作为自变量 + y = row.values + try: + # 进行曲线拟合 + popt, _ = curve_fit(single_harmonic, x, y) + fit_results2.append(popt) + except RuntimeError: + # 如果拟合过程出现问题(例如无法收敛等),也将参数设为0 + fit_results2.append((0, 0, 0)) + # 将拟合结果转换为DataFrame + fit_results2_df = pd.DataFrame(fit_results2, columns=['A', 'phi', 'C']) + print(fit_results2_df) + # 用于存储每个高度的拟合值 + wn2_values = [] + for index, row in fit_results2_df.iterrows(): + A, phi, C = row + x = np.arange(12) # 同样对应12个位置作为自变量 + wn2 = single_harmonic(x, A, phi, C) + wn2_values.append(wn2) + # 将拟合值转换为DataFrame + wn2_df = pd.DataFrame(wn2_values, columns=[ + f'wn2_{i}' for i in range(12)]) + print(wn2_df) + # ---------计算temp-wn0-wn1-wn2------------------------------------------------------ + temp_wn0_wn1_wn2 = temp_wn0_wn1_df.values - wn2_df.values + # 转换为 DataFrame + temp_wn0_wn1_wn2_df = pd.DataFrame( + temp_wn0_wn1_wn2, columns=temp_wn0_wn1_df.columns) + + # -------wn3----------------------------------------------------------------------- + def single_harmonic(x, A, phi, C): + return A * np.sin(2 * np.pi / 4 * x + phi) + C + + # 用于存储每个高度拟合后的参数结果 + fit_results3 = [] + for index, row in temp_wn0_wn1_wn2_df.iterrows(): + # 检查该行是否存在NaN值,如果有则跳过拟合,直接将参数设为0 + if row.isnull().any(): + fit_results3.append((0, 0, 0)) + continue + x = np.arange(12) # 对应12个位置作为自变量 + y = row.values + try: + # 进行曲线拟合 + popt, _ = curve_fit(single_harmonic, x, y) + fit_results3.append(popt) + except RuntimeError: + # 如果拟合过程出现问题(例如无法收敛等),也将参数设为0 + fit_results3.append((0, 0, 0)) + # 将拟合结果转换为DataFrame + fit_results3_df = pd.DataFrame(fit_results3, columns=['A', 'phi', 'C']) + print(fit_results3_df) + # 用于存储每个高度的拟合值 + wn3_values = [] + for index, row in fit_results3_df.iterrows(): + A, phi, C = row + x = np.arange(12) # 同样对应12个位置作为自变量 + wn3 = single_harmonic(x, A, phi, C) + wn3_values.append(wn3) + # 将拟合值转换为DataFrame + wn3_df = pd.DataFrame(wn3_values, columns=[ + f'wn3_{i}' for i in range(12)]) + print(wn3_df) + # ---------计算temp-wn0-wn1-wn2-wn3------------------------------------------------------ + temp_wn0_wn1_wn2_wn3 = temp_wn0_wn1_wn2_df.values - wn3_df.values + # 转换为 DataFrame + temp_wn0_wn1_wn2_wn3_df = pd.DataFrame( + temp_wn0_wn1_wn2_wn3, columns=temp_wn0_wn1_wn2_df.columns) + # -------wn4 - ---------------------------------------------------------------------- + + def single_harmonic(x, A, phi, C): + return A * np.sin(2 * np.pi / 3 * x + phi) + C + + # 用于存储每个高度拟合后的参数结果 + fit_results4 = [] + for index, row in temp_wn0_wn1_wn2_wn3_df.iterrows(): + # 检查该行是否存在NaN值,如果有则跳过拟合,直接将参数设为0 + if row.isnull().any(): + fit_results4.append((0, 0, 0)) + continue + x = np.arange(12) # 对应12个位置作为自变量 + y = row.values + try: + # 进行曲线拟合 + popt, _ = curve_fit(single_harmonic, x, y) + fit_results4.append(popt) + except RuntimeError: + # 如果拟合过程出现问题(例如无法收敛等),也将参数设为0 + fit_results4.append((0, 0, 0)) + + fit_results4_df = pd.DataFrame(fit_results4, columns=['A', 'phi', 'C']) + print(fit_results4_df) + # 用于存储每个高度的拟合值 + wn4_values = [] + for index, row in fit_results4_df.iterrows(): + A, phi, C = row + x = np.arange(12) # 同样对应12个位置作为自变量 + wn4 = single_harmonic(x, A, phi, C) + wn4_values.append(wn4) + # 将拟合值转换为DataFrame + wn4_df = pd.DataFrame(wn4_values, columns=[ + f'wn4_{i}' for i in range(12)]) + print(wn4_df) + # ---------计算temp-wn0-wn1-wn2-wn3------------------------------------------------------ + temp_wn0_wn1_wn2_wn3_wn4 = temp_wn0_wn1_wn2_wn3_df.values - wn4_df.values + # 转换为 DataFrame + temp_wn0_wn1_wn2_wn3_wn4_df = pd.DataFrame( + temp_wn0_wn1_wn2_wn3_wn4, columns=temp_wn0_wn1_wn2_wn3_df.columns) + + # -------wn5----------------------------------------------------------------------- + def single_harmonic(x, A, phi, C): + return A * np.sin(2 * np.pi / 2.4 * x + phi) + C + + # 用于存储每个高度拟合后的参数结果 + fit_results5 = [] + for index, row in temp_wn0_wn1_wn2_wn3_wn4_df.iterrows(): + # 检查该行是否存在NaN值,如果有则跳过拟合,直接将参数设为0 + if row.isnull().any(): + fit_results5.append((0, 0, 0)) + continue + x = np.arange(12) # 对应12个位置作为自变量 + y = row.values + try: + # 进行曲线拟合 + popt, _ = curve_fit(single_harmonic, x, y) + fit_results5.append(popt) + except RuntimeError: + # 如果拟合过程出现问题(例如无法收敛等),也将参数设为0 + fit_results5.append((0, 0, 0)) + # 将拟合结果转换为DataFrame + fit_results5_df = pd.DataFrame(fit_results5, columns=['A', 'phi', 'C']) + print(fit_results5_df) + # 用于存储每个高度的拟合值 + wn5_values = [] + for index, row in fit_results5_df.iterrows(): + A, phi, C = row + x = np.arange(12) # 同样对应12个位置作为自变量 + wn5 = single_harmonic(x, A, phi, C) + wn5_values.append(wn5) + # 将拟合值转换为DataFrame + wn5_df = pd.DataFrame(wn5_values, columns=[ + f'wn5_{i}' for i in range(12)]) + print(wn5_df) + # ---------计算temp-wn0-wn1-wn2-wn3------------------------------------------------------ + temp_wn0_wn1_wn2_wn3_wn4_wn5 = temp_wn0_wn1_wn2_wn3_wn4_df.values - wn5_df.values + # 转换为 DataFrame + temp_wn0_wn1_wn2_wn3_wn4_wn5_df = pd.DataFrame(temp_wn0_wn1_wn2_wn3_wn4_wn5, + columns=temp_wn0_wn1_wn2_wn3_wn4_df.columns) + + # ------计算背景温度=wn0+wn1+wn2+wn3+wn4+wn5--------------------------------------------------- + background = wn5_df.values + wn4_df.values + \ + wn3_df.values + wn2_df.values + wn1_df.values + # wn0只有一列单独处理相加 + # 使用 np.isnan 和 np.where 来判断是否为 NaN 或 0,避免这些值参与相加 + for i in range(21): + wn0_value = wn0_df.iloc[i] + # 只有当 wn0_value 既不是 NaN 也不是 0 时才加到 background 上 + if not np.isnan(wn0_value) and wn0_value != 0: + background[i, :] += wn0_value + # 扰动 + perturbation = temp_wn0_wn1_wn2_wn3_wn4_wn5_df + # ---------傅里叶变换---------------------------------------------------------------------- + # 初始化一个新的DataFrame来保存处理结果 + result = pd.DataFrame( + np.nan, index=perturbation.index, columns=perturbation.columns) + # 定义滤波范围 + lambda_low = 2 # 2 km + lambda_high = 15 # 15 km + f_low = 2 * np.pi / lambda_high + f_high = 2 * np.pi / lambda_low + + # 循环处理perturbation中的每一列 + for col in perturbation.columns: + x = perturbation[col] + # 提取有效值 + valid_values = x.dropna() + N = len(valid_values) # 有效值的数量 + + # 找到第一个有效值的索引 + first_valid_index = valid_values.index[0] if not valid_values.index.empty else None + height_value = height_df.loc[first_valid_index] if first_valid_index is not None else None + + # 如果有效值为空,则跳过该列 + if N == 0 or height_value is None: + continue + + # 时间序列和频率 + dt = 0.25 + n = np.arange(N) + t = height_value.values + n * dt + f = n / (N * dt) + + # 傅里叶变换 + y = np.fft.fft(valid_values.values) + + # 频率滤波 + yy = y.copy() + freq_filter = (f < f_low) | (f > f_high) + yy[freq_filter] = 0 # 过滤掉指定频段 + + # 逆傅里叶变换 + perturbation_after = np.real(np.fft.ifft(yy)) + + # 将处理结果插回到result矩阵中 + result.loc[valid_values.index, col] = perturbation_after + v2 = result ** 2 + v2 = v2.mean(axis=1) + return v2 + except FileNotFoundError: + # 如果文件不存在,返回全NaN的Series + expected_length = 21 + return pd.Series(np.nan, index=range(expected_length)) +# 初始化一个空的DataFrame来存储所有天的结果 + + +# 初始化一个空的DataFrame来存储所有天的结果 +all_days_vzonal_results = pd.DataFrame() + +# 循环处理每一天的数据 +for day in range(1, 365): + u2 = process_vzonal_day(day, 2019) + if u2 is not None: + all_days_vzonal_results[rf"{day:02d}"] = u2 + +# 将结果按列拼接 +# all_days_vzonal_results.columns = [f"{day:02d}" for day in range(1, 365)] + +all_days_vmeridional_results = pd.DataFrame() + +# 循环处理每一天的数据 +for day in range(1, 365): + v2 = process_vmeridional_day(day, 2019) + if v2 is not None: + all_days_vmeridional_results[rf"{day:02d}"] = v2 + +# 将结果按列拼接 +# all_days_vmeridional_results.columns = [f"{day:02d}" for day in range(1, 365)] + +# --------------------------------------------------------------------------------------------------- +# --------经纬向风平方和计算动能-------------------------------------------------------------------------------- + +# 使用numpy.where来检查两个表格中的对应元素是否都不是NaN +sum_df = np.where( + pd.notna(all_days_vmeridional_results) & pd.notna(all_days_vzonal_results), + all_days_vmeridional_results + all_days_vzonal_results, + np.nan +) +HP = 1/2*all_days_vmeridional_results+1/2*all_days_vzonal_results +heights = [70.0, 72.5, 75.0, 77.5, 80.0, 82.5, 85.0, 87.5, 90.0, 92.5, + 95.0, 97.5, 100.0, 102.5, 105.0, 107.5, 110.0, 112.5, 115.0, 117.5, 120.0] +HP.index = heights +# # 将 DataFrame 保存为 Excel 文件 +# HP.to_excel('HP_data.xlsx') +# ----------绘年统计图------------------------------------------------------------------------------------------------------------ +data = HP +# 使用 reset_index() 方法将索引变为第一列 +data = data.reset_index() +h = data.iloc[:, 0].copy() # 高度,保留作为纵坐标 +dates = list(range(1, data.shape[1])) # 日期,作为横坐标 +data0 = data.iloc[:, 1:].copy() # 绘图数据 +'''数据处理''' +# 反转 h 以确保高度从下往上递增 +h_reversed = h[::-1].reset_index(drop=True) +data0_reversed = data0[::-1].reset_index(drop=True) +# 将数值大于20的数据点替换为nan +data0_reversed[data0_reversed > 20] = float('nan') +# 转换成月份,365天 +days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] +# 将日期转换为英文月份 + + +def day_to_month(day): + # 累积每个月的天数,找到对应的月份 + cumulative_days = 0 + for i, days in enumerate(days_in_month): + cumulative_days += days + if day <= cumulative_days: + return f'{["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"][i]}' + + +months = [day_to_month(day) for day in dates] + + +'''绘图''' +plt.rcParams['font.family'] = 'SimSun' # 宋体 +plt.rcParams['font.size'] = 12 # 中文字号 +plt.rcParams['axes.unicode_minus'] = False # 正确显示负号 +plt.rcParams['font.sans-serif'] = 'Times New Roman' # 新罗马 +plt.rcParams['axes.labelsize'] = 14 # 坐标轴标签字号 +plt.rcParams['xtick.labelsize'] = 12 # x轴刻度字号 +plt.rcParams['ytick.labelsize'] = 12 # y轴刻度字号 +plt.rcParams['legend.fontsize'] = 16 # 图例字号 +plt.rcParams['axes.unicode_minus'] = False # 正确显示负号 +plt.figure(figsize=(10, 6)) # 设置图像大小 +# 绘制热力图,设置 x 和 y 轴的标签 +sns.heatmap(data0_reversed, annot=False, cmap='YlGnBu', linewidths=0.5, + yticklabels=h_reversed, xticklabels=months, cbar_kws={'label': ' Gravitational potential energy'}) + +# 横坐标过长,设置等间隔展示 +interval = 34 # 横坐标显示间隔 +plt.xticks(ticks=range(0, len(dates), interval), + labels=months[::interval], rotation=45) # rotation旋转可不加 + +# 添加轴标签 +plt.xlabel('Month') # X轴标签 +plt.ylabel('Height') # Y轴标签 + +# 显示图形 +plt.show() +# --------------绘制月统计图------------------------------------------------------------------- +# 获取HP的列数 +num_cols = HP.shape[1] +# 用于存储按要求计算出的均值列数据 +mean_cols = [] +start = 0 +while start < num_cols: + end = start + 30 + if end > num_cols: + end = num_cols + # 提取每30列(或不满30列的剩余部分)的数据 + subset = HP.iloc[:, start:end] + # 计算该部分数据每一行的均值,得到一个Series,作为新的均值列 + mean_series = subset.mean(axis=1) + mean_cols.append(mean_series) + start = end +# 将所有的均值列合并成一个新的DataFrame +result_df = pd.concat(mean_cols, axis=1) +# 对result_df中的每一个元素取自然对数 +result_df_log = result_df.applymap(lambda x: np.log(x)) +# 通过drop方法删除第一行,axis=0表示按行操作,inplace=True表示直接在原DataFrame上修改(若不想修改原DataFrame可设置为False) +result_df_log.drop(70, axis=0, inplace=True) +# 计算每个月的平均值 +monthly_average = result_df_log.mean(axis=0) +# 将结果转换为 (1, 12) 形状 +monthly_average = monthly_average.values.reshape(1, 12) +monthly_average = monthly_average.ravel() + +# 生成x轴的月份标签 +months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + +# 绘制折线图 +plt.plot(months, monthly_average, marker='o', linestyle='-', color='b') + +# 添加标题和标签 +plt.title("Monthly Average ENERGY(log)") +plt.xlabel("Month") +plt.ylabel("Average Energy") +# 显示图表 +plt.xticks(rotation=45) # 让月份标签更清晰可读 +plt.grid(True) +plt.tight_layout() +# 显示图形 +plt.show() diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..d2fb7ac --- /dev/null +++ b/utils.py @@ -0,0 +1,163 @@ +import glob +import re + +import pandas as pd + +import balloon +import time +import logging + +filter_columns = [ + "file_name", + "c", + "a", + "b", + "omega_upper", + "w_f", + "ver_wave_len", + "hori_wave_len", + "c_x", + "c_y", + "c_z", + "Ek", + "E_p", + "MFu", + "MFv", + "u1", + "v1", + "T1", + "zhou_qi", +] + +lat = 52.21 +g = 9.76 + +combos = {} +comboType = [ + "探空气球", + "流星雷达", + "Saber", + "TIDI", + "COSMIC", +] + +comboMode = [ + ["重力波单次", "重力波统计"], + ["重力波月统计", "潮汐波单次", "潮汐波月统计"], + ["行星波月统计", "重力波单次", "重力波月统计"], + ["行星波月统计"], + ["行星波月统计"], +] + +comboDate = [ + [["年", "时间"], ["起始年", "终止年"]], + [["年", "月"], ["年", "日期"], ["年", "月"]], + [["起始月", "-"], ["月", "日"], ["月", "-"]], + [["起始月", "-"]], + [["起始月", "-"]], +] + + +def get_ballon_files(): + try: + data = glob.glob("data/探空气球/**/*.nc", recursive=True) + except FileNotFoundError: + return [] + return data + + +all_ballon_files = get_ballon_files() + + +def get_ballon_path_by_year(start_year, end_year): + return list(filter( + lambda x: any(f"LIN-{year}" in x for year in range( + start_year, end_year + 1)), + all_ballon_files + )) + + +def get_ballon_full_df_by_year(start_year, end_year): + + # Set up logging + logging.basicConfig(level=logging.DEBUG, + format='%(asctime)s %(levelname)s: %(message)s') + + start_time = time.time() + logging.debug( + f"Starting get_ballon_full_df_by_year with start_year={start_year}, end_year={end_year}") + + # Timing the path retrieval + t0 = time.time() + paths = get_ballon_path_by_year(start_year, end_year) + t1 = time.time() + logging.debug(f"Retrieved {len(paths)} paths in {t1 - t0:.2f} seconds") + + # optimization: add cache. only select need to be reprocessed + with open("./cache/ballon_lin_has_wave", "r") as f: + cache_has_waves = f.readlines() + cache_has_waves = [x.strip() for x in cache_has_waves] + + year_df = pd.DataFrame() + for idx, file in enumerate(paths, 1): + if len(cache_has_waves) > 0 and file not in cache_has_waves: + logging.debug(f"Skipping {file} as it has no wave data") + continue + file_start_time = time.time() + logging.debug(f"Processing file {idx}/{len(paths)}: {file}") + + # Read data + data = balloon.read_data(file) + read_time = time.time() + logging.debug( + f"Read data in {read_time - file_start_time:.2f} seconds") + + # Extract wave + try: + wave = balloon.extract_wave(data, lat, g) + extract_time = time.time() + logging.debug( + f"Extracted wave in {extract_time - read_time:.2f} seconds") + except Exception as e: + logging.error(f"Error extracting wave from {file}: {e}") + wave = [] + extract_time = time.time() + + if len(wave) == 0: + logging.debug(f"No wave data in {file}, skipping") + continue + + # Determine terrain wave + c = balloon.is_terrain_wave(data, lat, g) + terrain_time = time.time() + + year_pattern = r"products-RS92-GDP.2-LIN-(\d{4})" + year = int(re.search(year_pattern, file).group(1)) + + logging.debug( + f"Determined terrain wave in {terrain_time - extract_time:.2f} seconds") + + # Build DataFrame line + wave.insert(0, c) + wave.insert(0, file) + + line = pd.DataFrame([wave], columns=filter_columns) + concat_start_time = time.time() + + # Concatenate DataFrame + year_df = pd.concat([year_df, line], ignore_index=True) + concat_time = time.time() + logging.debug( + f"Concatenated DataFrame in {concat_time - concat_start_time:.2f} seconds") + logging.debug( + f"Total time for {file}: {concat_time - file_start_time:.2f} seconds") + + total_time = time.time() - start_time + logging.debug( + f"Completed get_ballon_full_df_by_year in {total_time:.2f} seconds") + return year_df + + +def get_has_wave_data_by_year(start_year, end_year): + df = get_ballon_full_df_by_year(start_year, end_year) + return df[df["b"] == 1]