backup: version 1

This commit is contained in:
Dustella 2025-01-08 12:53:55 +08:00
commit eb7f4420ff
Signed by: Dustella
GPG Key ID: 07C9FA0DB5373B8D
27 changed files with 9834 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Ignore everything
*
# But not .py files
!*.py
# Include .gitignore itself
!.gitignore
# Recursively allow .py files in subdirectories
!*/

35
backend.py Normal file
View File

@ -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)

77
balloon/__init__.py Normal file
View File

@ -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')

413
balloon/extract_wave.py Normal file
View File

@ -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_z0
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_h0
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

167
balloon/plot_once.py Normal file
View File

@ -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),
]

View File

@ -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

359
balloon/plot_year.py Normal file
View File

@ -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

View File

@ -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",
"每月上传/下传重力波占比",
"动能和势能分布情况",
]

41
balloon/read_data.py Normal file
View File

@ -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

538
cosmic/multiple.py Normal file
View File

@ -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_Nz10^(-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()

393
cosmic/single.py Normal file
View File

@ -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)

11
file_stuff.py Normal file
View File

@ -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

506
main.py Normal file
View File

@ -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())

616
qt.py Normal file
View File

@ -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

61
radar/__init__.py Normal file
View File

@ -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')

998
radar/plot2.py Normal file
View File

@ -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))

262
radar/plot_original.py Normal file
View File

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

213
radar/plot_prod.py Normal file
View File

@ -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

103
saber/__init__.py Normal file
View File

@ -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()

1077
saber/archive/legacy.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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('aNz')
plt.xlabel('Nz', labelpad=10) # 增加标签间距
plt.ylabel('高度 (km)', labelpad=10) # 增加标签间距
# 原始信号的时间序列
plt.subplot(1, 2, 2)
plt.plot(x2[::-1], y, label='原始信号')
plt.title('bPtz')
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)

View File

@ -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('aNz')
plt.xlabel('Nz', labelpad=10) # 增加标签间距
plt.ylabel('高度 (km)', labelpad=10) # 增加标签间距
# 原始信号的时间序列
plt.subplot(1, 2, 2)
plt.plot(x2, y, label='原始信号')
plt.title('bPtz')
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"]

245
saber/process.py Normal file
View File

@ -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)

296
saber/render.py Normal file
View File

@ -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('aNz')
plt.xlabel('Nz', labelpad=10) # 增加标签间距
plt.ylabel('高度 (km)', labelpad=10) # 增加标签间距
# 原始信号的时间序列
plt.subplot(1, 2, 2)
plt.plot(x2[::-1], y, label='原始信号')
plt.title('bPtz')
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() # 调整布局以避免重叠

278
saber/utils.py Normal file
View File

@ -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

878
tidi/plot.py Normal file
View File

@ -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()

163
utils.py Normal file
View File

@ -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]