with tidi
This commit is contained in:
parent
eb7f4420ff
commit
104e253e2b
14
.gitignore
vendored
14
.gitignore
vendored
@ -1,8 +1,8 @@
|
||||
# Ignore everything
|
||||
*
|
||||
# But not .py files
|
||||
!*.py
|
||||
# Include .gitignore itself
|
||||
!.gitignore
|
||||
# Recursively allow .py files in subdirectories
|
||||
# Ignore everything
|
||||
*
|
||||
# But not .py files
|
||||
!*.py
|
||||
# Include .gitignore itself
|
||||
!.gitignore
|
||||
# Recursively allow .py files in subdirectories
|
||||
!*/
|
||||
74
backend.py
74
backend.py
@ -1,35 +1,39 @@
|
||||
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)
|
||||
from gevent import pywsgi, monkey
|
||||
monkey.patch_all()
|
||||
|
||||
import tidi
|
||||
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
|
||||
import matplotlib.font_manager as fm
|
||||
|
||||
app = Flask(__name__)
|
||||
fm.fontManager.addfont("./SimHei.ttf")
|
||||
|
||||
|
||||
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")
|
||||
app.register_blueprint(tidi.tidi_module, url_prefix="/tidi")
|
||||
|
||||
# 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("0.0.0.0",debug=True)
|
||||
|
||||
@ -1,77 +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')
|
||||
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')
|
||||
|
||||
@ -1,413 +1,413 @@
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from scipy.optimize import curve_fit
|
||||
from scipy.signal import lombscargle, hilbert
|
||||
from sklearn.decomposition import PCA
|
||||
import sympy as sp
|
||||
|
||||
|
||||
def calculate_n(Av, Au, Bu, Bv):
|
||||
# 计算 Fuv
|
||||
Fuv = Av * Au * np.cos(Bu - Bv)
|
||||
# 根据条件计算 n
|
||||
if Au > Av:
|
||||
n = 1
|
||||
elif Au < Av:
|
||||
if Fuv > 0:
|
||||
n = 0
|
||||
else: # Fuv <= 0
|
||||
n = 2
|
||||
else:
|
||||
raise ValueError("Au 和 Av 不应相等")
|
||||
return n
|
||||
|
||||
|
||||
def calculate_seta(Av, Au, Bu, Bv):
|
||||
# 计算 n
|
||||
n = calculate_n(Av, Au, Bu, Bv)
|
||||
# 计算 Fuv
|
||||
Fuv = Av * Au * np.cos(Bu - Bv)
|
||||
# 计算 arctan 部分
|
||||
arctan_value = np.atan2(2 * Fuv, Av**2 - Au**2) # 更通用,返回的角度范围是 ([-π, π])
|
||||
# arctan_value = np.atan(2 * Fuv / (Av ** 2 - Au ** 2)) # 返回的角度范围是 ([-π/2, π/2])
|
||||
# 计算 seta
|
||||
seta = round((1 / 2) * (n * np.pi + arctan_value), 2)
|
||||
return seta # 返回结果为弧度
|
||||
|
||||
|
||||
# 判断椭圆旋转方向
|
||||
def analyze_direction(df1, df2, seta_degrees):
|
||||
# 初始值
|
||||
# a = 0
|
||||
# b = 0
|
||||
|
||||
# 计算元素
|
||||
element1 = df1.iloc[0, 1] * df1.iloc[1, 2] - \
|
||||
df1.iloc[0, 2] * df1.iloc[1, 1]
|
||||
element2 = df2.iloc[0, 1] * df2.iloc[1, 2] - \
|
||||
df2.iloc[0, 2] * df2.iloc[1, 1]
|
||||
|
||||
# 判断 element1
|
||||
if element1 < 0:
|
||||
a = 1 # 表示顺时针,重力波向上传播
|
||||
else:
|
||||
a = -1
|
||||
|
||||
# 判断 element2
|
||||
if element2 < 0:
|
||||
b = seta_degrees # 表示顺时针,水平传播方向与假设方向同,seta
|
||||
else:
|
||||
b = seta_degrees + 180 # 逆时针,seta+180
|
||||
return a, b
|
||||
|
||||
|
||||
# 计算u_v椭圆长短轴之比(先标准化椭圆,再计算最远距离最近距离之比)
|
||||
def calculate_axial_ratio(u_fit, v_fit):
|
||||
# 将 ucmp 和 vcmp 组成一个数据矩阵
|
||||
data_matrix = np.vstack((u_fit, v_fit)).T
|
||||
|
||||
# 使用 PCA 进行主成分分析
|
||||
pca = PCA(n_components=2)
|
||||
transformed_data = pca.fit_transform(data_matrix)
|
||||
|
||||
# 提取主成分
|
||||
principal_components = pca.components_
|
||||
|
||||
# 计算每个数据点在标准椭圆上的坐标
|
||||
scaling_factors = np.linalg.norm(principal_components, axis=1)
|
||||
standardized_data = transformed_data / scaling_factors
|
||||
|
||||
# 创建 DataFrame
|
||||
standardized_df = pd.DataFrame(standardized_data, columns=["PC1", "PC2"])
|
||||
# 获取第一列和第二列的最大值和最小值
|
||||
col1_max = standardized_df.iloc[:, 0].max()
|
||||
col1_min = standardized_df.iloc[:, 0].min()
|
||||
col2_max = standardized_df.iloc[:, 1].max()
|
||||
col2_min = standardized_df.iloc[:, 1].min()
|
||||
|
||||
# 计算长短轴比值
|
||||
axial_ratio = (col1_max - col1_min) / (col2_max - col2_min)
|
||||
|
||||
return axial_ratio
|
||||
|
||||
|
||||
def calculate_wave(data: pd.DataFrame, lat: float, g: float):
|
||||
height = data["alt"].values
|
||||
temp = data["temp"].values
|
||||
ucmp = data["u"].values
|
||||
vcmp = data["v"].values
|
||||
wcmp = data["wspeed"].values
|
||||
press = (data["press"].values) * 100 # 单位Pa
|
||||
|
||||
coef_temp = np.polyfit(height, temp, 2)
|
||||
poly_temp = np.poly1d(coef_temp)
|
||||
coef_ucmp = np.polyfit(height, ucmp, 2)
|
||||
poly_ucmp = np.poly1d(coef_ucmp)
|
||||
coef_vcmp = np.polyfit(height, vcmp, 2)
|
||||
poly_vcmp = np.poly1d(coef_vcmp)
|
||||
coef_wcmp = np.polyfit(height, wcmp, 2)
|
||||
poly_wcmp = np.poly1d(coef_wcmp)
|
||||
|
||||
residual_temp = temp - poly_temp(height)
|
||||
residual_ucmp = ucmp - poly_ucmp(height)
|
||||
residual_vcmp = vcmp - poly_vcmp(height)
|
||||
residual_wcmp = wcmp - poly_wcmp(height)
|
||||
|
||||
ff = np.linspace(0.01, 25, 1000) # 横坐标范围
|
||||
pgram_temp = lombscargle(height, residual_temp, ff)
|
||||
pgram_ucmp = lombscargle(height, residual_ucmp, ff)
|
||||
pgram_vcmp = lombscargle(height, residual_vcmp, ff)
|
||||
|
||||
# todo:我的频率等于垂直波数(单位10-3rad/m)?
|
||||
main_frequency_temp = round(ff[np.argmax(pgram_temp)], 2)
|
||||
main_frequency_ucmp = round(ff[np.argmax(pgram_ucmp)], 2)
|
||||
main_frequency_vcmp = round(ff[np.argmax(pgram_vcmp)], 2)
|
||||
|
||||
# 由波数计算波长
|
||||
λ1 = round(1 / ((main_frequency_temp / 2.5) * 0.4), 2)
|
||||
λ2 = round(1 / ((main_frequency_ucmp / 2.5) * 0.4), 2)
|
||||
λ3 = round(1 / ((main_frequency_vcmp / 2.5) * 0.4), 2)
|
||||
|
||||
mean = round(sum([λ1, λ2, λ3]) / 3, 2)
|
||||
rsd_temp = abs(round(((λ1 - mean) / λ1) * 100, 2))
|
||||
rsd_ucmp = abs(round(((λ2 - mean) / λ2) * 100, 2))
|
||||
rsd_vcmp = abs(round(((λ3 - mean) / λ3) * 100, 2))
|
||||
|
||||
if not (rsd_temp < 20 and rsd_ucmp < 20 and rsd_vcmp < 20):
|
||||
return []
|
||||
|
||||
# 定义正弦波模型函数
|
||||
def sine_wave(height, A, B, omega):
|
||||
omega = 2 * np.pi / mean
|
||||
return A * np.sin(omega * height + B)
|
||||
|
||||
def fit_sine_wave(height, data, mean):
|
||||
# 提供初始猜测值,例如 A=1, B=0, omega=2 * np.pi / mean
|
||||
initial_guess = [1, 0, 2 * np.pi / mean]
|
||||
# 设置参数的边界:A的下限为0(确保A为正值),B和omega没有边界限制
|
||||
bounds = ([0, -np.inf, -np.inf], [np.inf, np.inf, np.inf])
|
||||
# curve_fit 函数通过调整 sine_wave 函数的参数(A、B 和 omega)来最小化模型预测值与实际数据 data 之间的残差平方和。
|
||||
params, covariance = curve_fit(
|
||||
sine_wave, height, data, p0=initial_guess, bounds=bounds)
|
||||
return params
|
||||
|
||||
# 对 u 风、v 风、温度扰动量进行拟合
|
||||
params_u = fit_sine_wave(height, residual_ucmp, mean) # 输出3个参数分别是振幅,相位,角频率
|
||||
u_fit = sine_wave(height, *params_u)
|
||||
|
||||
params_v = fit_sine_wave(height, residual_vcmp, mean)
|
||||
v_fit = sine_wave(height, *params_v)
|
||||
|
||||
params_T = fit_sine_wave(height, residual_temp, mean)
|
||||
T_fit = sine_wave(height, *params_T)
|
||||
|
||||
params_w = fit_sine_wave(height, residual_wcmp, mean)
|
||||
w_fit = sine_wave(height, *params_w)
|
||||
|
||||
# 将高度、u_fit、v_fit创建一个数据框df1
|
||||
df1 = pd.DataFrame({"Height": height, "u_fit": u_fit, "v_fit": v_fit})
|
||||
|
||||
# 计算假设的水平传播方向
|
||||
rad_seta = calculate_seta(
|
||||
params_v[0], params_u[0], params_u[1], params_v[1]) # 输入、输出都为弧度
|
||||
# 转换为角度并保留2位小数
|
||||
seta_degrees = round(np.degrees(rad_seta), 2) # 38.41°
|
||||
|
||||
# 计算水平风
|
||||
uh = u_fit * np.sin(seta_degrees * np.pi / 180) + \
|
||||
v_fit * np.cos(seta_degrees * np.pi / 180) # 水平风
|
||||
# temp = T_fit
|
||||
# 将高度、水平风、温度创建一个数据框df2
|
||||
df2 = pd.DataFrame({"Height": height, "uh": uh, "temp": T_fit})
|
||||
a, b = analyze_direction(df1, df2, seta_degrees) # 计算垂直、水平传播方向
|
||||
|
||||
Axial_ratio = round(calculate_axial_ratio(u_fit, v_fit),
|
||||
2) # 计算长短轴之比并保留2位小数,后续计算本征频率会用到
|
||||
|
||||
# 计算剩余几个参数
|
||||
# 常量
|
||||
# f = 1.32e-4 # rad/s
|
||||
# N = 2.13e-2 # 浮力频数,单位 rad/s
|
||||
|
||||
# 根据站点纬度,计算惯性频率f
|
||||
ou = 7.27e-5 # 单位rad/s,地球自转频率
|
||||
# lat = 64.5
|
||||
f = 2 * ou * np.sin(lat * np.pi / 180)
|
||||
# print(f"f = {f:.3e} rad/s")
|
||||
|
||||
# 求浮力频率N
|
||||
df3 = pd.DataFrame({"height": height, "temp": temp,
|
||||
"temp_bar": poly_temp(height)})
|
||||
|
||||
Cp = 1005 # 单位:J/kgK
|
||||
df3["temp_bar_derivative"] = np.gradient(
|
||||
df3["temp_bar"], (df3["height"]) * 1000) # 求背景温度对高度的偏导
|
||||
gen = (g / temp) * (df3["temp_bar_derivative"] + (g / Cp))
|
||||
sqrt_gen = np.sqrt(np.abs(gen)) # np.sqrt()对列表求根号,np.sqrt()对数字求根号
|
||||
N = sqrt_gen.mean() # 计算平均值,得到浮力频率的均值
|
||||
|
||||
# 1. 本征频率omega_upper
|
||||
omega_upper = sp.symbols("omega_upper")
|
||||
eq1 = sp.Eq(Axial_ratio, omega_upper / f)
|
||||
omega_upper = (sp.solve(eq1, omega_upper))[0]
|
||||
omega_upper = sp.N(omega_upper)
|
||||
|
||||
# 求本征频率与惯性频率的比值
|
||||
w_f = omega_upper / f # todo:总是大于1
|
||||
|
||||
# 本征周期
|
||||
zhou_qi = 2 * np.pi / omega_upper / 3600
|
||||
|
||||
# 2. 垂直波长λ_z、水平波长λ_h
|
||||
|
||||
# 垂直波数
|
||||
λ_z = mean # 保留两位小数
|
||||
# 在北半球,重力波向上传播,垂直波数k_z<0
|
||||
if a == 1:
|
||||
k_z = -(2 * np.pi / λ_z / 1000)
|
||||
else:
|
||||
k_z = 2 * np.pi / λ_z / 1000
|
||||
|
||||
# 水平波数
|
||||
k_h = sp.symbols("k_h")
|
||||
eq2 = sp.Eq(k_h**2, (omega_upper**2 - f**2) * (k_z**2) / N**2)
|
||||
k_h = sp.solve(eq2, k_h)
|
||||
k_h = [sol.evalf() for sol in k_h if sol.evalf() > 0][0]
|
||||
k_h = sp.N(k_h) # 正值
|
||||
|
||||
# 在北半球,重力波向上传播,垂直波数k_h<0
|
||||
if b > 180: # 说明水平传播方向与假定方向相反
|
||||
k_h = -k_h
|
||||
else:
|
||||
k_h = k_h
|
||||
|
||||
λ_h = abs(round((2 * np.pi / k_h / 1000), 2)) # 水平波长,取绝对值,因为波数可能为负数
|
||||
|
||||
# 纬向、经向水平波长λ_x, λ_y
|
||||
# λ_x = abs(λ_h * np.sin(b * np.pi / 180))
|
||||
# λ_y = abs(λ_h * np.cos(b * np.pi / 180))
|
||||
|
||||
# 本征相速度
|
||||
c_x = omega_upper / (abs(k_h) * np.sin(b * np.pi / 180))
|
||||
c_y = omega_upper / (abs(k_h) * np.cos(b * np.pi / 180))
|
||||
c_z = omega_upper / k_z
|
||||
|
||||
"""
|
||||
# 群速度
|
||||
C_gz = -((omega_upper ** 2 - f ** 2)) / (omega_upper * k_z)
|
||||
C_gh = sp.symbols('C_gh')
|
||||
eq3 = sp.Eq(C_gz / C_gh, sp.sqrt(omega_upper ** 2 - f ** 2) / N)
|
||||
C_gh = (sp.solve(eq3, C_gh))[0]
|
||||
C_gh = sp.N(C_gh) # 转成数值
|
||||
"""
|
||||
|
||||
# 波动能E_k
|
||||
u2_mean = np.mean(np.square(u_fit))
|
||||
v2_mean = np.mean(np.square(v_fit))
|
||||
Ek = (1 / 2) * (u2_mean + v2_mean) # 3.11 J/kg
|
||||
|
||||
# 势能E_p
|
||||
T_normalized = T_fit / poly_temp(height) # 归一化处理
|
||||
T2_mean = np.mean(np.square(T_normalized))
|
||||
E_p = ((g**2) * T2_mean) / (2 * (N**2)) # 1.02J/kg
|
||||
|
||||
# 动量通量
|
||||
# 经、纬向风动量通量
|
||||
P = press # 单位Pa
|
||||
R = 287 # 单位:J/(kgK),气体常量
|
||||
T = temp # 温度已经是开尔文
|
||||
densi = P / (R * T) # 向量
|
||||
c = (omega_upper * g) / (N**2)
|
||||
S = 1 - (f**2) / (omega_upper**2) # 常数
|
||||
|
||||
# 归一化温度90°相位变换
|
||||
|
||||
hilb = hilbert(T_normalized)
|
||||
hilb1 = np.imag(hilb) # 获取虚部作为90°相位变换的结果
|
||||
|
||||
uT = u_fit * hilb1 # 向量
|
||||
uT1 = np.mean(uT) # 均值
|
||||
MFu = (np.mean(-densi * c * uT1 * S)) * 1000 # 单位mPa
|
||||
|
||||
vT = v_fit * hilb1
|
||||
vT1 = np.mean(vT)
|
||||
MFv = (np.mean(-densi * c * vT1 * S)) * 1000
|
||||
|
||||
# 调整动量通量符号与本征相速度符号一致
|
||||
# 调整b的符号与a一致
|
||||
MFu1 = abs(MFu) if c_x >= 0 else -abs(MFu)
|
||||
MFv1 = abs(MFv) if c_y >= 0 else -abs(MFv)
|
||||
|
||||
omega_upper = float(omega_upper)
|
||||
# make wf, c_x, c_y, c_z, MFu, MFv, zhouqi float
|
||||
w_f = float(w_f)
|
||||
c_x = float(c_x)
|
||||
c_y = float(c_y)
|
||||
c_z = float(c_z)
|
||||
MFu1 = float(MFu1)
|
||||
MFv1 = float(MFv1)
|
||||
zhou_qi = float(zhou_qi)
|
||||
|
||||
return [
|
||||
a,
|
||||
b,
|
||||
omega_upper,
|
||||
w_f,
|
||||
λ_z,
|
||||
λ_h,
|
||||
c_x,
|
||||
c_y,
|
||||
c_z,
|
||||
Ek,
|
||||
E_p,
|
||||
MFu1,
|
||||
MFv1,
|
||||
params_u[0],
|
||||
params_v[0],
|
||||
params_T[0],
|
||||
ucmp,
|
||||
vcmp,
|
||||
temp,
|
||||
height,
|
||||
poly_ucmp,
|
||||
poly_vcmp,
|
||||
poly_wcmp,
|
||||
poly_temp,
|
||||
residual_ucmp,
|
||||
residual_vcmp,
|
||||
residual_temp,
|
||||
u_fit,
|
||||
v_fit,
|
||||
T_fit,
|
||||
uh,
|
||||
zhou_qi,
|
||||
]
|
||||
|
||||
|
||||
def extract_wave(data: pd.DataFrame, lat: float, g: float):
|
||||
wave = calculate_wave(
|
||||
data[(data["alt"] >= 15) & (data["alt"] <= 25)], lat, g)
|
||||
if len(wave) == 0:
|
||||
return []
|
||||
return [
|
||||
wave[0],
|
||||
wave[1],
|
||||
round(wave[2], 6),
|
||||
round(wave[3], 2),
|
||||
round(wave[4], 2),
|
||||
round(wave[5], 2),
|
||||
round(wave[6], 2),
|
||||
round(wave[7], 2),
|
||||
round(wave[8], 3),
|
||||
round(wave[9], 4),
|
||||
round(wave[10], 4),
|
||||
round(wave[11], 4),
|
||||
round(wave[12], 4),
|
||||
round(wave[13], 2),
|
||||
round(wave[14], 2),
|
||||
round(wave[15], 2),
|
||||
round(wave[31], 2),
|
||||
]
|
||||
|
||||
|
||||
def is_terrain_wave(data: pd.DataFrame, lat: float, g: float):
|
||||
wave = calculate_wave(
|
||||
data[(data["alt"] >= 2) & (data["alt"] <= 10)], lat, g)
|
||||
if len(wave) == 0:
|
||||
return False
|
||||
|
||||
# 计算观测u风v风的均值,为后边合成背景速度
|
||||
mean_ucmp = np.mean(wave[16]) # 11.34
|
||||
mean_vcmp = np.mean(wave[17]) # 0.38
|
||||
|
||||
x1, y1 = mean_ucmp, mean_vcmp # 背景风的分量
|
||||
x2, y2 = float(wave[6]), float(wave[7]) # 相速度的分量
|
||||
# wave[18](wave[19])
|
||||
|
||||
"""
|
||||
# 二阶拟合的u风v风
|
||||
mean_ucmp = np.mean(poly_ucmp(height))
|
||||
mean_vcmp = np.mean(poly_vcmp(height))
|
||||
|
||||
x1, y1 = mean_ucmp, mean_vcmp # 背景风的分量
|
||||
x2, y2 = float(c_x), float(c_y) # 相速度的分量
|
||||
"""
|
||||
# 计算两个风的方向
|
||||
direction1 = np.arctan2(y1, x1)
|
||||
direction2 = np.arctan2(y2, x2)
|
||||
# 转化为角度(该角度是以x轴逆时针旋转),取特殊值用于验证。
|
||||
# direction1_degrees = np.degrees(direction1)
|
||||
# direction2_degrees = np.degrees(direction2)
|
||||
|
||||
# 计算夹角(弧度)
|
||||
angle_radians = np.abs(direction1 - direction2)
|
||||
# 确保夹角在0到π之间
|
||||
if angle_radians > np.pi:
|
||||
angle_radians = 2 * np.pi - angle_radians
|
||||
# 将夹角转换为度
|
||||
angle_degrees = np.degrees(angle_radians)
|
||||
|
||||
# 判断是否超过150,或者165
|
||||
if round(angle_degrees, 2) > 150:
|
||||
return True
|
||||
return False
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from scipy.optimize import curve_fit
|
||||
from scipy.signal import lombscargle, hilbert
|
||||
from sklearn.decomposition import PCA
|
||||
import sympy as sp
|
||||
|
||||
|
||||
def calculate_n(Av, Au, Bu, Bv):
|
||||
# 计算 Fuv
|
||||
Fuv = Av * Au * np.cos(Bu - Bv)
|
||||
# 根据条件计算 n
|
||||
if Au > Av:
|
||||
n = 1
|
||||
elif Au < Av:
|
||||
if Fuv > 0:
|
||||
n = 0
|
||||
else: # Fuv <= 0
|
||||
n = 2
|
||||
else:
|
||||
raise ValueError("Au 和 Av 不应相等")
|
||||
return n
|
||||
|
||||
|
||||
def calculate_seta(Av, Au, Bu, Bv):
|
||||
# 计算 n
|
||||
n = calculate_n(Av, Au, Bu, Bv)
|
||||
# 计算 Fuv
|
||||
Fuv = Av * Au * np.cos(Bu - Bv)
|
||||
# 计算 arctan 部分
|
||||
arctan_value = np.atan2(2 * Fuv, Av**2 - Au**2) # 更通用,返回的角度范围是 ([-π, π])
|
||||
# arctan_value = np.atan(2 * Fuv / (Av ** 2 - Au ** 2)) # 返回的角度范围是 ([-π/2, π/2])
|
||||
# 计算 seta
|
||||
seta = round((1 / 2) * (n * np.pi + arctan_value), 2)
|
||||
return seta # 返回结果为弧度
|
||||
|
||||
|
||||
# 判断椭圆旋转方向
|
||||
def analyze_direction(df1, df2, seta_degrees):
|
||||
# 初始值
|
||||
# a = 0
|
||||
# b = 0
|
||||
|
||||
# 计算元素
|
||||
element1 = df1.iloc[0, 1] * df1.iloc[1, 2] - \
|
||||
df1.iloc[0, 2] * df1.iloc[1, 1]
|
||||
element2 = df2.iloc[0, 1] * df2.iloc[1, 2] - \
|
||||
df2.iloc[0, 2] * df2.iloc[1, 1]
|
||||
|
||||
# 判断 element1
|
||||
if element1 < 0:
|
||||
a = 1 # 表示顺时针,重力波向上传播
|
||||
else:
|
||||
a = -1
|
||||
|
||||
# 判断 element2
|
||||
if element2 < 0:
|
||||
b = seta_degrees # 表示顺时针,水平传播方向与假设方向同,seta
|
||||
else:
|
||||
b = seta_degrees + 180 # 逆时针,seta+180
|
||||
return a, b
|
||||
|
||||
|
||||
# 计算u_v椭圆长短轴之比(先标准化椭圆,再计算最远距离最近距离之比)
|
||||
def calculate_axial_ratio(u_fit, v_fit):
|
||||
# 将 ucmp 和 vcmp 组成一个数据矩阵
|
||||
data_matrix = np.vstack((u_fit, v_fit)).T
|
||||
|
||||
# 使用 PCA 进行主成分分析
|
||||
pca = PCA(n_components=2)
|
||||
transformed_data = pca.fit_transform(data_matrix)
|
||||
|
||||
# 提取主成分
|
||||
principal_components = pca.components_
|
||||
|
||||
# 计算每个数据点在标准椭圆上的坐标
|
||||
scaling_factors = np.linalg.norm(principal_components, axis=1)
|
||||
standardized_data = transformed_data / scaling_factors
|
||||
|
||||
# 创建 DataFrame
|
||||
standardized_df = pd.DataFrame(standardized_data, columns=["PC1", "PC2"])
|
||||
# 获取第一列和第二列的最大值和最小值
|
||||
col1_max = standardized_df.iloc[:, 0].max()
|
||||
col1_min = standardized_df.iloc[:, 0].min()
|
||||
col2_max = standardized_df.iloc[:, 1].max()
|
||||
col2_min = standardized_df.iloc[:, 1].min()
|
||||
|
||||
# 计算长短轴比值
|
||||
axial_ratio = (col1_max - col1_min) / (col2_max - col2_min)
|
||||
|
||||
return axial_ratio
|
||||
|
||||
|
||||
def calculate_wave(data: pd.DataFrame, lat: float, g: float):
|
||||
height = data["alt"].values
|
||||
temp = data["temp"].values
|
||||
ucmp = data["u"].values
|
||||
vcmp = data["v"].values
|
||||
wcmp = data["wspeed"].values
|
||||
press = (data["press"].values) * 100 # 单位Pa
|
||||
|
||||
coef_temp = np.polyfit(height, temp, 2)
|
||||
poly_temp = np.poly1d(coef_temp)
|
||||
coef_ucmp = np.polyfit(height, ucmp, 2)
|
||||
poly_ucmp = np.poly1d(coef_ucmp)
|
||||
coef_vcmp = np.polyfit(height, vcmp, 2)
|
||||
poly_vcmp = np.poly1d(coef_vcmp)
|
||||
coef_wcmp = np.polyfit(height, wcmp, 2)
|
||||
poly_wcmp = np.poly1d(coef_wcmp)
|
||||
|
||||
residual_temp = temp - poly_temp(height)
|
||||
residual_ucmp = ucmp - poly_ucmp(height)
|
||||
residual_vcmp = vcmp - poly_vcmp(height)
|
||||
residual_wcmp = wcmp - poly_wcmp(height)
|
||||
|
||||
ff = np.linspace(0.01, 25, 1000) # 横坐标范围
|
||||
pgram_temp = lombscargle(height, residual_temp, ff)
|
||||
pgram_ucmp = lombscargle(height, residual_ucmp, ff)
|
||||
pgram_vcmp = lombscargle(height, residual_vcmp, ff)
|
||||
|
||||
# todo:我的频率等于垂直波数(单位10-3rad/m)?
|
||||
main_frequency_temp = round(ff[np.argmax(pgram_temp)], 2)
|
||||
main_frequency_ucmp = round(ff[np.argmax(pgram_ucmp)], 2)
|
||||
main_frequency_vcmp = round(ff[np.argmax(pgram_vcmp)], 2)
|
||||
|
||||
# 由波数计算波长
|
||||
λ1 = round(1 / ((main_frequency_temp / 2.5) * 0.4), 2)
|
||||
λ2 = round(1 / ((main_frequency_ucmp / 2.5) * 0.4), 2)
|
||||
λ3 = round(1 / ((main_frequency_vcmp / 2.5) * 0.4), 2)
|
||||
|
||||
mean = round(sum([λ1, λ2, λ3]) / 3, 2)
|
||||
rsd_temp = abs(round(((λ1 - mean) / λ1) * 100, 2))
|
||||
rsd_ucmp = abs(round(((λ2 - mean) / λ2) * 100, 2))
|
||||
rsd_vcmp = abs(round(((λ3 - mean) / λ3) * 100, 2))
|
||||
|
||||
if not (rsd_temp < 20 and rsd_ucmp < 20 and rsd_vcmp < 20):
|
||||
return []
|
||||
|
||||
# 定义正弦波模型函数
|
||||
def sine_wave(height, A, B, omega):
|
||||
omega = 2 * np.pi / mean
|
||||
return A * np.sin(omega * height + B)
|
||||
|
||||
def fit_sine_wave(height, data, mean):
|
||||
# 提供初始猜测值,例如 A=1, B=0, omega=2 * np.pi / mean
|
||||
initial_guess = [1, 0, 2 * np.pi / mean]
|
||||
# 设置参数的边界:A的下限为0(确保A为正值),B和omega没有边界限制
|
||||
bounds = ([0, -np.inf, -np.inf], [np.inf, np.inf, np.inf])
|
||||
# curve_fit 函数通过调整 sine_wave 函数的参数(A、B 和 omega)来最小化模型预测值与实际数据 data 之间的残差平方和。
|
||||
params, covariance = curve_fit(
|
||||
sine_wave, height, data, p0=initial_guess, bounds=bounds)
|
||||
return params
|
||||
|
||||
# 对 u 风、v 风、温度扰动量进行拟合
|
||||
params_u = fit_sine_wave(height, residual_ucmp, mean) # 输出3个参数分别是振幅,相位,角频率
|
||||
u_fit = sine_wave(height, *params_u)
|
||||
|
||||
params_v = fit_sine_wave(height, residual_vcmp, mean)
|
||||
v_fit = sine_wave(height, *params_v)
|
||||
|
||||
params_T = fit_sine_wave(height, residual_temp, mean)
|
||||
T_fit = sine_wave(height, *params_T)
|
||||
|
||||
params_w = fit_sine_wave(height, residual_wcmp, mean)
|
||||
w_fit = sine_wave(height, *params_w)
|
||||
|
||||
# 将高度、u_fit、v_fit创建一个数据框df1
|
||||
df1 = pd.DataFrame({"Height": height, "u_fit": u_fit, "v_fit": v_fit})
|
||||
|
||||
# 计算假设的水平传播方向
|
||||
rad_seta = calculate_seta(
|
||||
params_v[0], params_u[0], params_u[1], params_v[1]) # 输入、输出都为弧度
|
||||
# 转换为角度并保留2位小数
|
||||
seta_degrees = round(np.degrees(rad_seta), 2) # 38.41°
|
||||
|
||||
# 计算水平风
|
||||
uh = u_fit * np.sin(seta_degrees * np.pi / 180) + \
|
||||
v_fit * np.cos(seta_degrees * np.pi / 180) # 水平风
|
||||
# temp = T_fit
|
||||
# 将高度、水平风、温度创建一个数据框df2
|
||||
df2 = pd.DataFrame({"Height": height, "uh": uh, "temp": T_fit})
|
||||
a, b = analyze_direction(df1, df2, seta_degrees) # 计算垂直、水平传播方向
|
||||
|
||||
Axial_ratio = round(calculate_axial_ratio(u_fit, v_fit),
|
||||
2) # 计算长短轴之比并保留2位小数,后续计算本征频率会用到
|
||||
|
||||
# 计算剩余几个参数
|
||||
# 常量
|
||||
# f = 1.32e-4 # rad/s
|
||||
# N = 2.13e-2 # 浮力频数,单位 rad/s
|
||||
|
||||
# 根据站点纬度,计算惯性频率f
|
||||
ou = 7.27e-5 # 单位rad/s,地球自转频率
|
||||
# lat = 64.5
|
||||
f = 2 * ou * np.sin(lat * np.pi / 180)
|
||||
# print(f"f = {f:.3e} rad/s")
|
||||
|
||||
# 求浮力频率N
|
||||
df3 = pd.DataFrame({"height": height, "temp": temp,
|
||||
"temp_bar": poly_temp(height)})
|
||||
|
||||
Cp = 1005 # 单位:J/kgK
|
||||
df3["temp_bar_derivative"] = np.gradient(
|
||||
df3["temp_bar"], (df3["height"]) * 1000) # 求背景温度对高度的偏导
|
||||
gen = (g / temp) * (df3["temp_bar_derivative"] + (g / Cp))
|
||||
sqrt_gen = np.sqrt(np.abs(gen)) # np.sqrt()对列表求根号,np.sqrt()对数字求根号
|
||||
N = sqrt_gen.mean() # 计算平均值,得到浮力频率的均值
|
||||
|
||||
# 1. 本征频率omega_upper
|
||||
omega_upper = sp.symbols("omega_upper")
|
||||
eq1 = sp.Eq(Axial_ratio, omega_upper / f)
|
||||
omega_upper = (sp.solve(eq1, omega_upper))[0]
|
||||
omega_upper = sp.N(omega_upper)
|
||||
|
||||
# 求本征频率与惯性频率的比值
|
||||
w_f = omega_upper / f # todo:总是大于1
|
||||
|
||||
# 本征周期
|
||||
zhou_qi = 2 * np.pi / omega_upper / 3600
|
||||
|
||||
# 2. 垂直波长λ_z、水平波长λ_h
|
||||
|
||||
# 垂直波数
|
||||
λ_z = mean # 保留两位小数
|
||||
# 在北半球,重力波向上传播,垂直波数k_z<0
|
||||
if a == 1:
|
||||
k_z = -(2 * np.pi / λ_z / 1000)
|
||||
else:
|
||||
k_z = 2 * np.pi / λ_z / 1000
|
||||
|
||||
# 水平波数
|
||||
k_h = sp.symbols("k_h")
|
||||
eq2 = sp.Eq(k_h**2, (omega_upper**2 - f**2) * (k_z**2) / N**2)
|
||||
k_h = sp.solve(eq2, k_h)
|
||||
k_h = [sol.evalf() for sol in k_h if sol.evalf() > 0][0]
|
||||
k_h = sp.N(k_h) # 正值
|
||||
|
||||
# 在北半球,重力波向上传播,垂直波数k_h<0
|
||||
if b > 180: # 说明水平传播方向与假定方向相反
|
||||
k_h = -k_h
|
||||
else:
|
||||
k_h = k_h
|
||||
|
||||
λ_h = abs(round((2 * np.pi / k_h / 1000), 2)) # 水平波长,取绝对值,因为波数可能为负数
|
||||
|
||||
# 纬向、经向水平波长λ_x, λ_y
|
||||
# λ_x = abs(λ_h * np.sin(b * np.pi / 180))
|
||||
# λ_y = abs(λ_h * np.cos(b * np.pi / 180))
|
||||
|
||||
# 本征相速度
|
||||
c_x = omega_upper / (abs(k_h) * np.sin(b * np.pi / 180))
|
||||
c_y = omega_upper / (abs(k_h) * np.cos(b * np.pi / 180))
|
||||
c_z = omega_upper / k_z
|
||||
|
||||
"""
|
||||
# 群速度
|
||||
C_gz = -((omega_upper ** 2 - f ** 2)) / (omega_upper * k_z)
|
||||
C_gh = sp.symbols('C_gh')
|
||||
eq3 = sp.Eq(C_gz / C_gh, sp.sqrt(omega_upper ** 2 - f ** 2) / N)
|
||||
C_gh = (sp.solve(eq3, C_gh))[0]
|
||||
C_gh = sp.N(C_gh) # 转成数值
|
||||
"""
|
||||
|
||||
# 波动能E_k
|
||||
u2_mean = np.mean(np.square(u_fit))
|
||||
v2_mean = np.mean(np.square(v_fit))
|
||||
Ek = (1 / 2) * (u2_mean + v2_mean) # 3.11 J/kg
|
||||
|
||||
# 势能E_p
|
||||
T_normalized = T_fit / poly_temp(height) # 归一化处理
|
||||
T2_mean = np.mean(np.square(T_normalized))
|
||||
E_p = ((g**2) * T2_mean) / (2 * (N**2)) # 1.02J/kg
|
||||
|
||||
# 动量通量
|
||||
# 经、纬向风动量通量
|
||||
P = press # 单位Pa
|
||||
R = 287 # 单位:J/(kgK),气体常量
|
||||
T = temp # 温度已经是开尔文
|
||||
densi = P / (R * T) # 向量
|
||||
c = (omega_upper * g) / (N**2)
|
||||
S = 1 - (f**2) / (omega_upper**2) # 常数
|
||||
|
||||
# 归一化温度90°相位变换
|
||||
|
||||
hilb = hilbert(T_normalized)
|
||||
hilb1 = np.imag(hilb) # 获取虚部作为90°相位变换的结果
|
||||
|
||||
uT = u_fit * hilb1 # 向量
|
||||
uT1 = np.mean(uT) # 均值
|
||||
MFu = (np.mean(-densi * c * uT1 * S)) * 1000 # 单位mPa
|
||||
|
||||
vT = v_fit * hilb1
|
||||
vT1 = np.mean(vT)
|
||||
MFv = (np.mean(-densi * c * vT1 * S)) * 1000
|
||||
|
||||
# 调整动量通量符号与本征相速度符号一致
|
||||
# 调整b的符号与a一致
|
||||
MFu1 = abs(MFu) if c_x >= 0 else -abs(MFu)
|
||||
MFv1 = abs(MFv) if c_y >= 0 else -abs(MFv)
|
||||
|
||||
omega_upper = float(omega_upper)
|
||||
# make wf, c_x, c_y, c_z, MFu, MFv, zhouqi float
|
||||
w_f = float(w_f)
|
||||
c_x = float(c_x)
|
||||
c_y = float(c_y)
|
||||
c_z = float(c_z)
|
||||
MFu1 = float(MFu1)
|
||||
MFv1 = float(MFv1)
|
||||
zhou_qi = float(zhou_qi)
|
||||
|
||||
return [
|
||||
a,
|
||||
b,
|
||||
omega_upper,
|
||||
w_f,
|
||||
λ_z,
|
||||
λ_h,
|
||||
c_x,
|
||||
c_y,
|
||||
c_z,
|
||||
Ek,
|
||||
E_p,
|
||||
MFu1,
|
||||
MFv1,
|
||||
params_u[0],
|
||||
params_v[0],
|
||||
params_T[0],
|
||||
ucmp,
|
||||
vcmp,
|
||||
temp,
|
||||
height,
|
||||
poly_ucmp,
|
||||
poly_vcmp,
|
||||
poly_wcmp,
|
||||
poly_temp,
|
||||
residual_ucmp,
|
||||
residual_vcmp,
|
||||
residual_temp,
|
||||
u_fit,
|
||||
v_fit,
|
||||
T_fit,
|
||||
uh,
|
||||
zhou_qi,
|
||||
]
|
||||
|
||||
|
||||
def extract_wave(data: pd.DataFrame, lat: float, g: float):
|
||||
wave = calculate_wave(
|
||||
data[(data["alt"] >= 15) & (data["alt"] <= 25)], lat, g)
|
||||
if len(wave) == 0:
|
||||
return []
|
||||
return [
|
||||
wave[0],
|
||||
wave[1],
|
||||
round(wave[2], 6),
|
||||
round(wave[3], 2),
|
||||
round(wave[4], 2),
|
||||
round(wave[5], 2),
|
||||
round(wave[6], 2),
|
||||
round(wave[7], 2),
|
||||
round(wave[8], 3),
|
||||
round(wave[9], 4),
|
||||
round(wave[10], 4),
|
||||
round(wave[11], 4),
|
||||
round(wave[12], 4),
|
||||
round(wave[13], 2),
|
||||
round(wave[14], 2),
|
||||
round(wave[15], 2),
|
||||
round(wave[31], 2),
|
||||
]
|
||||
|
||||
|
||||
def is_terrain_wave(data: pd.DataFrame, lat: float, g: float):
|
||||
wave = calculate_wave(
|
||||
data[(data["alt"] >= 2) & (data["alt"] <= 10)], lat, g)
|
||||
if len(wave) == 0:
|
||||
return False
|
||||
|
||||
# 计算观测u风v风的均值,为后边合成背景速度
|
||||
mean_ucmp = np.mean(wave[16]) # 11.34
|
||||
mean_vcmp = np.mean(wave[17]) # 0.38
|
||||
|
||||
x1, y1 = mean_ucmp, mean_vcmp # 背景风的分量
|
||||
x2, y2 = float(wave[6]), float(wave[7]) # 相速度的分量
|
||||
# wave[18](wave[19])
|
||||
|
||||
"""
|
||||
# 二阶拟合的u风v风
|
||||
mean_ucmp = np.mean(poly_ucmp(height))
|
||||
mean_vcmp = np.mean(poly_vcmp(height))
|
||||
|
||||
x1, y1 = mean_ucmp, mean_vcmp # 背景风的分量
|
||||
x2, y2 = float(c_x), float(c_y) # 相速度的分量
|
||||
"""
|
||||
# 计算两个风的方向
|
||||
direction1 = np.arctan2(y1, x1)
|
||||
direction2 = np.arctan2(y2, x2)
|
||||
# 转化为角度(该角度是以x轴逆时针旋转),取特殊值用于验证。
|
||||
# direction1_degrees = np.degrees(direction1)
|
||||
# direction2_degrees = np.degrees(direction2)
|
||||
|
||||
# 计算夹角(弧度)
|
||||
angle_radians = np.abs(direction1 - direction2)
|
||||
# 确保夹角在0到π之间
|
||||
if angle_radians > np.pi:
|
||||
angle_radians = 2 * np.pi - angle_radians
|
||||
# 将夹角转换为度
|
||||
angle_degrees = np.degrees(angle_radians)
|
||||
|
||||
# 判断是否超过150,或者165
|
||||
if round(angle_degrees, 2) > 150:
|
||||
return True
|
||||
return False
|
||||
|
||||
@ -1,167 +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),
|
||||
]
|
||||
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),
|
||||
]
|
||||
|
||||
@ -1,145 +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
|
||||
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
|
||||
|
||||
@ -1,359 +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
|
||||
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
|
||||
|
||||
@ -1,286 +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",
|
||||
"每月上传/下传重力波占比",
|
||||
"动能和势能分布情况",
|
||||
]
|
||||
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",
|
||||
"每月上传/下传重力波占比",
|
||||
"动能和势能分布情况",
|
||||
]
|
||||
|
||||
@ -1,41 +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
|
||||
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
|
||||
|
||||
1076
cosmic/multiple.py
1076
cosmic/multiple.py
File diff suppressed because it is too large
Load Diff
786
cosmic/single.py
786
cosmic/single.py
@ -1,393 +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)
|
||||
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)
|
||||
|
||||
@ -1,11 +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
|
||||
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
|
||||
|
||||
@ -1,61 +1,84 @@
|
||||
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')
|
||||
import asyncio
|
||||
import glob
|
||||
from io import BytesIO
|
||||
from flask import Blueprint, request, send_file
|
||||
from matplotlib import pyplot as plt
|
||||
from radar.plot_original import final_render_v2
|
||||
from radar.plot_prod import final_plot_v2
|
||||
|
||||
|
||||
globed_all_files = glob.glob("./radar/data/**/**.txt", recursive=True)
|
||||
|
||||
|
||||
radar_module = Blueprint("Radar", __name__)
|
||||
|
||||
|
||||
@radar_module.route("/metadata")
|
||||
def get_all_files():
|
||||
return final_render_v2.get_all_pathes()
|
||||
|
||||
|
||||
@radar_module.route("/metadata/models")
|
||||
def get_all_models():
|
||||
return final_render_v2.get_all_models()
|
||||
|
||||
|
||||
@radar_module.route('/render/heatmap')
|
||||
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')
|
||||
station = request.args.get('station')
|
||||
|
||||
model_name = request.args.get('model_name')
|
||||
mode = request.args.get('mode')
|
||||
|
||||
renderer = final_render_v2(int(H), int(year), station, wind_type)
|
||||
if mode=="day":
|
||||
day = request.args.get('day')
|
||||
renderer.render_day(day,model_name)
|
||||
elif mode=="month":
|
||||
month = request.args.get('month')
|
||||
renderer.render_month(int(month),model_name)
|
||||
elif mode=="year":
|
||||
renderer.render_year(model_name)
|
||||
else:
|
||||
raise ValueError("mode not supported")
|
||||
buf = BytesIO()
|
||||
plt.savefig(buf, format='png')
|
||||
buf.seek(0)
|
||||
# close the plot
|
||||
plt.close()
|
||||
return send_file(buf, mimetype='image/png')
|
||||
|
||||
|
||||
@radar_module.route('/render/changes')
|
||||
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')
|
||||
start_month = request.args.get('start_month')
|
||||
end_month = request.args.get('end_month')
|
||||
if start_month is not None and end_month is not None:
|
||||
start_month = int(start_month)
|
||||
end_month = int(end_month)
|
||||
month_range = (start_month, end_month)
|
||||
else:
|
||||
month_range = (1, 12)
|
||||
|
||||
|
||||
buffer = final_plot_v2(int(year), station, model_name, month_range)
|
||||
buffer.seek(0)
|
||||
|
||||
return send_file(buffer, mimetype='image/png')
|
||||
|
||||
998
radar/plot2.py
998
radar/plot2.py
@ -1,998 +0,0 @@
|
||||
import os
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from scipy.optimize import curve_fit
|
||||
import seaborn as sns
|
||||
|
||||
# 解决绘图中中文不能显示的问题
|
||||
import matplotlib
|
||||
# 设置中文显示和负号正常显示
|
||||
matplotlib.rcParams['font.sans-serif'] = ['SimHei'] # 显示中文
|
||||
matplotlib.rcParams['axes.unicode_minus'] = False # 正常显示负号
|
||||
|
||||
# 定义所有模型
|
||||
def tidal_model(t, v0, a1, b1, a2, b2, a3, b3, a4, b4):
|
||||
"""潮汐波模型"""
|
||||
T1, T2, T3, T4 = 6, 8, 12, 24 # 周期以小时为单位
|
||||
return (v0 + a1 * np.sin((2 * np.pi / T1) * t + b1)
|
||||
+ a2 * np.sin((2 * np.pi / T2) * t + b2)
|
||||
+ a3 * np.sin((2 * np.pi / T3) * t + b3)
|
||||
+ a4 * np.sin((2 * np.pi / T4) * t + b4))
|
||||
|
||||
def planetary_model_2day(t, v0, a, b):
|
||||
"""2日行星波模型"""
|
||||
T = 48 # 周期为2天(48小时)
|
||||
return v0 + a * np.sin((2 * np.pi / T) * t + b)
|
||||
|
||||
def planetary_model_5day(t, v0, a, b):
|
||||
"""5日行星波模型"""
|
||||
T = 120 # 周期为5天(120小时)
|
||||
return v0 + a * np.sin((2 * np.pi / T) * t + b)
|
||||
|
||||
def planetary_model_10day(t, v0, a, b):
|
||||
"""10日行星波模型"""
|
||||
T = 240 # 周期为10天(240小时)
|
||||
return v0 + a * np.sin((2 * np.pi / T) * t + b)
|
||||
|
||||
def planetary_model_16day(t, v0, a, b):
|
||||
"""16日行星波模型"""
|
||||
T = 384 # 周期为16天(384小时)
|
||||
return v0 + a * np.sin((2 * np.pi / T) * t + b)
|
||||
|
||||
# ----------------------------------------------------------第二大部分————————————————————————————————————————————————————
|
||||
# # 根据纬向风(u)、经向风(v),得到武汉左岭镇站/黑龙江漠河站
|
||||
# 大气波动(6、8、12、24小时潮汐波,2日、5日、10日、16日行星波)随时空变化热力图
|
||||
|
||||
# 设置文件路径
|
||||
year = 2022
|
||||
station = '武汉左岭镇站'
|
||||
# station = '黑龙江漠河站'
|
||||
|
||||
# 文件路径设置
|
||||
file_dir = rf'D:\wu\gravity wave\Meteor\流星雷达分析数据\{station}\{year}' # 数据文件路径
|
||||
output_dir = rf'D:\wu\gravity wave\Meteor\输出\{station}\{year}\时空' # 参数txt、图片输出路径
|
||||
os.makedirs(output_dir, exist_ok=True) # 确保输出路径存在
|
||||
|
||||
|
||||
# 获取目录下所有txt文件
|
||||
file_list = [f for f in os.listdir(file_dir) if f.endswith('.txt')]
|
||||
|
||||
# 初始化空的DataFrame,用于合并所有文件的数据
|
||||
final_df = pd.DataFrame()
|
||||
|
||||
# 批量处理每个txt文件
|
||||
for file_name in file_list:
|
||||
file_path = os.path.join(file_dir, file_name)
|
||||
df = pd.read_csv(file_path, delim_whitespace=True)
|
||||
|
||||
date_part = os.path.splitext(file_name)[0][-8:] # 假设文件名最后8个字符是日期
|
||||
|
||||
# 替换异常值为 NaN
|
||||
df.loc[df['uwind'] == 1000000, 'uwind'] = np.nan
|
||||
df.loc[df['vwind'] == 1000000, 'vwind'] = np.nan
|
||||
|
||||
|
||||
# 筛选出所需的列
|
||||
df = df[['height', 'time', 'uwind', 'vwind']]
|
||||
df = df.rename(columns={
|
||||
'uwind': f'{date_part}_uwind',
|
||||
'vwind': f'{date_part}_vwind'
|
||||
})
|
||||
|
||||
if final_df.empty:
|
||||
final_df = df
|
||||
else:
|
||||
final_df = pd.merge(final_df, df, on=['height', 'time'], how='outer')
|
||||
|
||||
# 生成完整日期范围的列名,补全缺失的天,使得平年365,闰年366
|
||||
all_dates = pd.date_range(f'{year}-01-01', f'{year}-12-31').strftime('%Y%m%d')
|
||||
expected_columns = [f'{date}_uwind' for date in all_dates] + [f'{date}_vwind' for date in all_dates]
|
||||
|
||||
# 确保 final_df 包含所有日期的列,缺失列补为 NaN
|
||||
for col in expected_columns:
|
||||
if col not in final_df.columns:
|
||||
final_df[col] = np.nan
|
||||
|
||||
# 按列名排序(确保日期顺序)
|
||||
final_df = final_df[['height', 'time'] + sorted(expected_columns)]
|
||||
# 此时,final_df已经是全高度包含完整天的分析数据---------------------------------------------------------
|
||||
|
||||
|
||||
# -----------------------------------------得到潮汐波拟合参数-------------------------------------------
|
||||
# 定义单高度的拟合函数
|
||||
def fit_wind_speed(data, window=5):
|
||||
# 动态调整最小有效数据点数量:参数数量 * 6
|
||||
num_params = 9 # 模型参数数量
|
||||
|
||||
# 转换为 pandas DataFrame 并设置时间为索引
|
||||
data["date"] = pd.to_datetime(data["date"])
|
||||
data.set_index("date", inplace=True)
|
||||
|
||||
# 获取唯一日期
|
||||
unique_dates = np.unique(data.index.date)
|
||||
|
||||
# 存储拟合参数
|
||||
params = []
|
||||
|
||||
for date in unique_dates:
|
||||
start_date = pd.to_datetime(date) - pd.Timedelta(days=(window - 1) / 2)
|
||||
end_date = pd.to_datetime(date) + pd.Timedelta(days=(window - 1) / 2)
|
||||
|
||||
# 获取这几天的数据
|
||||
window_data = data[start_date:end_date]
|
||||
|
||||
t = np.arange(len(window_data)) + 1 # 时间点
|
||||
y = window_data['wind_speed'].values # 风速数据
|
||||
|
||||
# 将 y 转换为浮点类型,以防有非数值类型数据
|
||||
y = pd.to_numeric(y, errors='coerce') # 将非数值转换为 NaN
|
||||
|
||||
# 去掉包含nan的那一列,筛选有效数据
|
||||
valid_mask = ~np.isnan(y) # 创建布尔掩码,标记非 NaN 的位置
|
||||
t = t[valid_mask]
|
||||
y = y[valid_mask]
|
||||
|
||||
# 检查筛选后的数据是否足够
|
||||
if len(y) < num_params*6: # 确保数据足够
|
||||
# print(f"Not enough valid data after filtering for date: {date}")
|
||||
params.append([None] * 9) # 如果数据不足,记录None
|
||||
continue
|
||||
|
||||
# 设置初始参数
|
||||
initial_guess = [0, 1, 0, 1, 0, 1, 0, 1, 0] # v0, a1, b1, a2, b2, a3, b3, a4, b4
|
||||
|
||||
# 设置参数界限
|
||||
bounds = ([-np.inf, 0, -np.inf, 0, -np.inf, 0, -np.inf, 0, -np.inf], # 下界
|
||||
[np.inf, np.inf, np.inf, np.inf, np.inf, np.inf, np.inf, np.inf, np.inf]) # 上界
|
||||
|
||||
|
||||
# 拟合模型
|
||||
try:
|
||||
popt, _ = curve_fit(tidal_model, t, y, p0=initial_guess, bounds=bounds)
|
||||
params.append(popt) # 存储拟合参数
|
||||
except RuntimeError:
|
||||
# print(f"Fitting failed for date: {date}")
|
||||
params.append([None] *9) # 如果拟合失败,记录None
|
||||
|
||||
# 转换拟合参数为 DataFrame
|
||||
params_df = pd.DataFrame(
|
||||
params,
|
||||
columns=["v0", "a1", "b1", "a2", "b2", "a3", "b3", "a4", "b4"],
|
||||
index=unique_dates,
|
||||
)
|
||||
return params_df
|
||||
|
||||
# 1-拟合纬向风------------------------------------------
|
||||
# 提取高度、时间和纬向风数据
|
||||
uwind_data = final_df[["height", "time"] + [col for col in final_df.columns if "_uwind" in col]]
|
||||
uwind_data.columns = [col[:8] for col in uwind_data.columns]
|
||||
|
||||
# 遍历所有高度
|
||||
heights = uwind_data["height"].unique()
|
||||
u_result_dict = {}
|
||||
|
||||
for height in heights:
|
||||
print(f"Processing height: {height} m")
|
||||
# 获取当前高度的纬向风数据
|
||||
height_data = uwind_data[uwind_data["height"] == height]
|
||||
|
||||
# 转换为长格式(适配拟合函数)
|
||||
long_data = pd.melt(
|
||||
height_data,
|
||||
id_vars=["time", "height"],
|
||||
var_name="date",
|
||||
value_name="wind_speed",
|
||||
)
|
||||
long_data["date"] = pd.to_datetime(long_data["date"], format="%Y%m%d")
|
||||
|
||||
# 调用拟合函数
|
||||
params_df = fit_wind_speed(long_data)
|
||||
u_result_dict[height] = params_df
|
||||
|
||||
# 汇总结果为 DataFrame
|
||||
u_final_result = pd.concat(u_result_dict, names=["height", "date"])
|
||||
# 将结果保存为 TXT 文档
|
||||
file_name1 = f'{year}年_纬向风_潮汐波振幅参数.txt'
|
||||
u_final_result.to_csv(
|
||||
os.path.join(output_dir, file_name),
|
||||
sep='\t',
|
||||
index=True,
|
||||
float_format='%.2f', # 控制数值列保留 2 位小数
|
||||
header=True)
|
||||
|
||||
# 2-拟合经向风------------------------------------------
|
||||
# 提取高度、时间和经向风数据
|
||||
vwind_data = final_df[["height", "time"] + [col for col in final_df.columns if "_vwind" in col]]
|
||||
vwind_data.columns = [col[:8] for col in vwind_data.columns]
|
||||
|
||||
# 遍历所有高度
|
||||
heights = vwind_data["height"].unique()
|
||||
v_result_dict = {}
|
||||
|
||||
for height in heights:
|
||||
print(f"Processing height: {height} m")
|
||||
height_data = vwind_data[vwind_data["height"] == height]
|
||||
|
||||
long_data = pd.melt(
|
||||
height_data,
|
||||
id_vars=["time", "height"],
|
||||
var_name="date",
|
||||
value_name="wind_speed",
|
||||
)
|
||||
long_data["date"] = pd.to_datetime(long_data["date"], format="%Y%m%d")
|
||||
|
||||
params_df = fit_wind_speed(long_data)
|
||||
v_result_dict[height] = params_df
|
||||
|
||||
# 汇总结果为 DataFrame
|
||||
v_final_result = pd.concat(v_result_dict, names=["height", "date"])
|
||||
|
||||
# 将结果保存为 TXT 文档
|
||||
# 保存参数
|
||||
file_name2 = f'{year}年_经向风_潮汐波振幅参数.txt'
|
||||
v_final_result.to_csv(
|
||||
os.path.join(output_dir, file_name),
|
||||
sep='\t',
|
||||
index=True,
|
||||
float_format='%.2f', # 控制数值列保留 2 位小数
|
||||
header=True)
|
||||
|
||||
|
||||
# 画热力图-----------------------------------------
|
||||
'''
|
||||
# 读取数据,设置第一列和第二列为多重索引
|
||||
u_final_result = pd.read_csv(os.path.join(output_dir, file_name1), sep='\t')
|
||||
u_final_result = u_final_result.set_index([u_final_result.columns[0], u_final_result.columns[1]])
|
||||
|
||||
v_final_result = pd.read_csv(os.path.join(output_dir, file_name2), sep='\t')
|
||||
v_final_result = v_final_result.set_index([v_final_result.columns[0], v_final_result.columns[1]])
|
||||
'''
|
||||
# 定义振幅参数
|
||||
amplitude_param = 'a4' # 代表 24 小时周期振幅 # todo: 选择a4为周日潮,a3半日潮
|
||||
|
||||
# 提取纬向风数据,重置索引为平坦结构
|
||||
u_heatmap_data = u_final_result[amplitude_param].unstack(level=0) # 将高度作为列,日期作为行
|
||||
u_heatmap_data.index = pd.to_datetime(u_heatmap_data.index) # 确保日期为时间格式
|
||||
u_heatmap_data = u_heatmap_data.iloc[:, ::-1] # 按高度降序排列
|
||||
|
||||
# 提取经向风数据,重置索引为平坦结构
|
||||
v_heatmap_data = v_final_result[amplitude_param].unstack(level=0) # 将高度作为列,日期作为行
|
||||
v_heatmap_data.index = pd.to_datetime(v_heatmap_data.index) # 确保日期为时间格式
|
||||
v_heatmap_data = v_heatmap_data.iloc[:, ::-1] # 按高度降序排列
|
||||
|
||||
# 创建画布和子图
|
||||
fig, axes = plt.subplots(1, 2, figsize=(18, 8)) # 1行2列的布局
|
||||
fig.suptitle('2022年周日潮汐波振幅时空变化', fontsize=20) # 添加总标题
|
||||
|
||||
# 绘制第一幅热力图(纬向风)
|
||||
sns.heatmap(
|
||||
u_heatmap_data.T, # 转置,横轴为时间,纵轴为高度
|
||||
cmap="viridis", # 配色方案
|
||||
vmax=100, # 设置颜色范围的最大值
|
||||
cbar_kws={'label': '振幅 (m/s)'}, # 设置颜色条标签
|
||||
xticklabels=True, # 保留真实的横轴刻度标签
|
||||
#yticklabels=u_heatmap_data.columns.astype(str), # 显示纵轴的高度
|
||||
yticklabels=[f'{height / 1000}' for height in u_heatmap_data.columns], # 转换高度为千米并格式化
|
||||
ax=axes[0] # 指定子图
|
||||
)
|
||||
axes[0].set_title('纬向风振幅变化', fontsize=14)
|
||||
axes[0].set_xlabel('日期', fontsize=12)
|
||||
axes[0].set_ylabel('高度 (km)', fontsize=12)
|
||||
|
||||
# 绘制第二幅热力图(经向风)
|
||||
sns.heatmap(
|
||||
v_heatmap_data.T, # 转置,横轴为时间,纵轴为高度
|
||||
cmap="viridis", # 配色方案
|
||||
vmax=100, # 设置颜色范围的最大值
|
||||
cbar_kws={'label': '振幅 (m/s)'}, # 设置颜色条标签
|
||||
xticklabels=True, # 保留真实的横轴刻度标签
|
||||
#yticklabels=v_heatmap_data.columns.astype(str), # 显示纵轴的高度
|
||||
yticklabels=[f'{height / 1000}' for height in v_heatmap_data.columns], # 转换高度为千米并格式化
|
||||
ax=axes[1] # 指定子图
|
||||
)
|
||||
axes[1].set_title('经向风振幅变化', fontsize=14)
|
||||
axes[1].set_xlabel('日期', fontsize=12)
|
||||
axes[1].set_ylabel('高度 (km)', fontsize=12)
|
||||
|
||||
# 调整布局
|
||||
plt.tight_layout(rect=[0, 0, 1, 0.95]) # 为总标题留出空间
|
||||
# 显示图像
|
||||
plt.show()
|
||||
|
||||
# 输出路径和文件名
|
||||
picture_file_name = f'{year}年_周日_潮汐波振幅时空变化.png'
|
||||
plt.savefig(os.path.join(output_dir, picture_file_name))
|
||||
|
||||
|
||||
# -----------------------------------------得到2日行星波拟合参数-------------------------------------------
|
||||
# 定义新的拟合函数
|
||||
def fit_wind_speed_v1(data, window=5):
|
||||
# 动态调整最小有效数据点数量:参数数量 * 6
|
||||
num_params = 3 # 模型参数数量
|
||||
# 转换为 pandas DataFrame 并设置时间为索引
|
||||
data["date"] = pd.to_datetime(data["date"])
|
||||
data.set_index("date", inplace=True)
|
||||
|
||||
# 获取唯一日期
|
||||
unique_dates = np.unique(data.index.date)
|
||||
|
||||
# 存储拟合参数
|
||||
params = []
|
||||
|
||||
for date in unique_dates:
|
||||
start_date = pd.to_datetime(date) - pd.Timedelta(days=(window - 1) / 2)
|
||||
end_date = pd.to_datetime(date) + pd.Timedelta(days=(window - 1) / 2)
|
||||
|
||||
# 获取这几天的数据
|
||||
window_data = data[start_date:end_date]
|
||||
|
||||
t = np.arange(len(window_data)) + 1 # 时间点
|
||||
y = window_data['wind_speed'].values # 风速数据
|
||||
|
||||
# 将 y 转换为浮点类型,以防有非数值类型数据
|
||||
y = pd.to_numeric(y, errors='coerce') # 将非数值转换为 NaN
|
||||
|
||||
# 去掉包含nan的那一列,筛选有效数据
|
||||
valid_mask = ~np.isnan(y) # 创建布尔掩码,标记非 NaN 的位置
|
||||
t = t[valid_mask]
|
||||
y = y[valid_mask]
|
||||
|
||||
# 检查筛选后的数据是否足够
|
||||
if len(y) < num_params * 6: # 确保数据足够
|
||||
# print(f"Not enough valid data after filtering for date: {date}")
|
||||
params.append([None] * 3) # 如果数据不足,记录None
|
||||
continue
|
||||
|
||||
# 设置初始参数
|
||||
initial_guess = [0, 1, 0] # 新模型的初始猜测参数
|
||||
bounds = (
|
||||
[-np.inf, 0, -np.inf], # 下界
|
||||
[np.inf, np.inf, np.inf], # 上界
|
||||
)
|
||||
|
||||
# 拟合模型
|
||||
try:
|
||||
popt, _ = curve_fit(planetary_model_2day, t, y, p0=initial_guess, bounds=bounds)
|
||||
params.append(popt) # 存储拟合参数
|
||||
except RuntimeError:
|
||||
params.append([None] * 3) # 如果拟合失败,记录None
|
||||
|
||||
# 转换拟合参数为 DataFrame
|
||||
params_df = pd.DataFrame(
|
||||
params,
|
||||
columns=["v0", "a", "b"],
|
||||
index=unique_dates,
|
||||
)
|
||||
return params_df
|
||||
|
||||
# 1-拟合纬向风------------------------------------------
|
||||
u_result_dict = {}
|
||||
|
||||
for height in heights:
|
||||
print(f"Processing height: {height} m")
|
||||
# 获取当前高度的纬向风数据
|
||||
height_data = uwind_data[uwind_data["height"] == height]
|
||||
|
||||
# 转换为长格式(适配拟合函数)
|
||||
long_data = pd.melt(
|
||||
height_data,
|
||||
id_vars=["time", "height"],
|
||||
var_name="date",
|
||||
value_name="wind_speed",
|
||||
)
|
||||
long_data["date"] = pd.to_datetime(long_data["date"], format="%Y%m%d")
|
||||
|
||||
# 调用修改后的拟合函数 fit_wind_speed_v2
|
||||
params_df = fit_wind_speed_v1(long_data)
|
||||
u_result_dict[height] = params_df
|
||||
|
||||
# 汇总结果为 DataFrame
|
||||
u_final_result = pd.concat(u_result_dict, names=["height", "date"])
|
||||
|
||||
|
||||
# 2-拟合经向风------------------------------------------
|
||||
v_result_dict = {}
|
||||
|
||||
for height in heights:
|
||||
print(f"Processing height: {height} m")
|
||||
height_data = vwind_data[vwind_data["height"] == height]
|
||||
|
||||
long_data = pd.melt(
|
||||
height_data,
|
||||
id_vars=["time", "height"],
|
||||
var_name="date",
|
||||
value_name="wind_speed",
|
||||
)
|
||||
long_data["date"] = pd.to_datetime(long_data["date"], format="%Y%m%d")
|
||||
|
||||
|
||||
# 调用修改后的拟合函数 fit_wind_speed_v2
|
||||
params_df = fit_wind_speed_v1(long_data)
|
||||
v_result_dict[height] = params_df
|
||||
|
||||
# 汇总结果为 DataFrame
|
||||
v_final_result = pd.concat(v_result_dict, names=["height", "date"])
|
||||
|
||||
|
||||
# 定义振幅参数
|
||||
amplitude_param = 'a'
|
||||
# 提取纬向风数据,重置索引为平坦结构
|
||||
u_heatmap_data = u_final_result[amplitude_param].unstack(level=0) # 将高度作为列,日期作为行
|
||||
u_heatmap_data.index = pd.to_datetime(u_heatmap_data.index) # 确保日期为时间格式
|
||||
u_heatmap_data = u_heatmap_data.iloc[:, ::-1] # 按高度降序排列
|
||||
|
||||
# 提取经向风数据,重置索引为平坦结构
|
||||
v_heatmap_data = v_final_result[amplitude_param].unstack(level=0) # 将高度作为列,日期作为行
|
||||
v_heatmap_data.index = pd.to_datetime(v_heatmap_data.index) # 确保日期为时间格式
|
||||
v_heatmap_data = v_heatmap_data.iloc[:, ::-1] # 按高度降序排列
|
||||
|
||||
# 创建画布和子图
|
||||
fig, axes = plt.subplots(1, 2, figsize=(18, 8)) # 1行2列的布局
|
||||
fig.suptitle('2022年2日_行星波振幅时空变化', fontsize=20) # 添加总标题
|
||||
|
||||
# 绘制第一幅热力图(纬向风)
|
||||
sns.heatmap(
|
||||
u_heatmap_data.T, # 转置,横轴为时间,纵轴为高度
|
||||
cmap="viridis", # 配色方案
|
||||
vmax=100, # 设置颜色范围的最大值
|
||||
cbar_kws={'label': '振幅 (m/s)'}, # 设置颜色条标签
|
||||
xticklabels=False, # 禁用默认的横轴刻度标签
|
||||
#yticklabels=u_heatmap_data.columns.astype(str), # 显示纵轴的高度
|
||||
yticklabels=[f'{height // 1000}' for height in u_heatmap_data.columns], # 转换高度为千米并格式化
|
||||
ax=axes[0] # 指定子图
|
||||
)
|
||||
axes[0].set_title('纬向风振幅变化', fontsize=14)
|
||||
axes[0].set_xlabel('日期', fontsize=12)
|
||||
axes[0].set_ylabel('高度 (km)', fontsize=12)
|
||||
|
||||
|
||||
# 绘制第二幅热力图(经向风)
|
||||
sns.heatmap(
|
||||
v_heatmap_data.T, # 转置,横轴为时间,纵轴为高度
|
||||
cmap="viridis", # 配色方案
|
||||
vmax=100, # 设置颜色范围的最大值
|
||||
cbar_kws={'label': '振幅 (m/s)'}, # 设置颜色条标签
|
||||
xticklabels=False, # 禁用默认的横轴刻度标签
|
||||
#yticklabels=v_heatmap_data.columns.astype(str), # 显示纵轴的高度
|
||||
yticklabels=[f'{height // 1000}' for height in v_heatmap_data.columns], # 转换高度为千米并格式化
|
||||
ax=axes[1] # 指定子图
|
||||
)
|
||||
axes[1].set_title('经向风振幅变化', fontsize=14)
|
||||
axes[1].set_xlabel('日期', fontsize=12)
|
||||
axes[1].set_ylabel('高度 (km)', fontsize=12)
|
||||
|
||||
# 调整布局
|
||||
plt.tight_layout(rect=[0, 0, 1, 0.95]) # 为总标题留出空间
|
||||
plt.show()
|
||||
|
||||
# 输出路径和文件名
|
||||
picture_file_name = f'{year}年_2日_行星波振幅时空变化.png'
|
||||
plt.savefig(os.path.join(output_dir, picture_file_name))
|
||||
# -----------------------------------------得到5日行星波拟合参数-------------------------------------------
|
||||
# 定义新的拟合函数
|
||||
def fit_wind_speed_v2(data, window=15):
|
||||
# 动态调整最小有效数据点数量:参数数量 * 6
|
||||
num_params = 3 # 模型参数数量
|
||||
# 转换为 pandas DataFrame 并设置时间为索引
|
||||
data["date"] = pd.to_datetime(data["date"])
|
||||
data.set_index("date", inplace=True)
|
||||
|
||||
# 获取唯一日期
|
||||
unique_dates = np.unique(data.index.date)
|
||||
|
||||
# 存储拟合参数
|
||||
params = []
|
||||
|
||||
for date in unique_dates:
|
||||
start_date = pd.to_datetime(date) - pd.Timedelta(days=(window - 1) / 2)
|
||||
end_date = pd.to_datetime(date) + pd.Timedelta(days=(window - 1) / 2)
|
||||
|
||||
# 获取这几天的数据
|
||||
window_data = data[start_date:end_date]
|
||||
|
||||
t = np.arange(len(window_data)) + 1 # 时间点
|
||||
y = window_data['wind_speed'].values # 风速数据
|
||||
|
||||
# 将 y 转换为浮点类型,以防有非数值类型数据
|
||||
y = pd.to_numeric(y, errors='coerce') # 将非数值转换为 NaN
|
||||
|
||||
# 去掉包含nan的那一列,筛选有效数据
|
||||
valid_mask = ~np.isnan(y) # 创建布尔掩码,标记非 NaN 的位置
|
||||
t = t[valid_mask]
|
||||
y = y[valid_mask]
|
||||
|
||||
# 检查筛选后的数据是否足够
|
||||
if len(y) < num_params * 6: # 确保数据足够
|
||||
# print(f"Not enough valid data after filtering for date: {date}")
|
||||
params.append([None] * 3) # 如果数据不足,记录None
|
||||
continue
|
||||
|
||||
# 设置初始参数
|
||||
initial_guess = [0, 1, 0] # 新模型的初始猜测参数
|
||||
bounds = (
|
||||
[-np.inf, 0, -np.inf], # 下界
|
||||
[np.inf, np.inf, np.inf], # 上界
|
||||
)
|
||||
|
||||
# 拟合模型
|
||||
try:
|
||||
popt, _ = curve_fit(planetary_model_5day, t, y, p0=initial_guess, bounds=bounds)
|
||||
params.append(popt) # 存储拟合参数
|
||||
except RuntimeError:
|
||||
params.append([None] * 3) # 如果拟合失败,记录None
|
||||
|
||||
# 转换拟合参数为 DataFrame
|
||||
params_df = pd.DataFrame(
|
||||
params,
|
||||
columns=["v0", "a", "b"],
|
||||
index=unique_dates,
|
||||
)
|
||||
return params_df
|
||||
|
||||
# 1-拟合纬向风------------------------------------------
|
||||
u_result_dict = {}
|
||||
|
||||
for height in heights:
|
||||
print(f"Processing height: {height} m")
|
||||
# 获取当前高度的纬向风数据
|
||||
height_data = uwind_data[uwind_data["height"] == height]
|
||||
|
||||
# 转换为长格式(适配拟合函数)
|
||||
long_data = pd.melt(
|
||||
height_data,
|
||||
id_vars=["time", "height"],
|
||||
var_name="date",
|
||||
value_name="wind_speed",
|
||||
)
|
||||
long_data["date"] = pd.to_datetime(long_data["date"], format="%Y%m%d")
|
||||
|
||||
# 调用修改后的拟合函数 fit_wind_speed_v2
|
||||
params_df = fit_wind_speed_v2(long_data)
|
||||
u_result_dict[height] = params_df
|
||||
|
||||
# 汇总结果为 DataFrame
|
||||
u_final_result = pd.concat(u_result_dict, names=["height", "date"])
|
||||
'''
|
||||
# 将结果保存为 TXT 文档
|
||||
file_name = f'{year}年_纬向风_5日行星波振幅参数.txt'
|
||||
u_final_result.to_csv(
|
||||
os.path.join(output_dir, file_name),
|
||||
sep='\t',
|
||||
index=True,
|
||||
float_format='%.2f', # 控制数值列保留 2 位小数
|
||||
header=True)
|
||||
'''
|
||||
|
||||
# 2-拟合经向风------------------------------------------
|
||||
v_result_dict = {}
|
||||
|
||||
for height in heights:
|
||||
print(f"Processing height: {height} m")
|
||||
height_data = vwind_data[vwind_data["height"] == height]
|
||||
|
||||
long_data = pd.melt(
|
||||
height_data,
|
||||
id_vars=["time", "height"],
|
||||
var_name="date",
|
||||
value_name="wind_speed",
|
||||
)
|
||||
long_data["date"] = pd.to_datetime(long_data["date"], format="%Y%m%d")
|
||||
|
||||
|
||||
# 调用修改后的拟合函数 fit_wind_speed_v2
|
||||
params_df = fit_wind_speed_v2(long_data)
|
||||
v_result_dict[height] = params_df
|
||||
|
||||
# 汇总结果为 DataFrame
|
||||
v_final_result = pd.concat(v_result_dict, names=["height", "date"])
|
||||
'''
|
||||
# 保存参数
|
||||
file_name = f'{year}年_经向风_5日行星波振幅参数.txt'
|
||||
v_final_result.to_csv(
|
||||
os.path.join(output_dir, file_name),
|
||||
sep='\t',
|
||||
index=True,
|
||||
float_format='%.2f', # 控制数值列保留 2 位小数
|
||||
header=True)
|
||||
'''
|
||||
|
||||
# 定义振幅参数
|
||||
amplitude_param = 'a'
|
||||
# 提取纬向风数据,重置索引为平坦结构
|
||||
u_heatmap_data = u_final_result[amplitude_param].unstack(level=0) # 将高度作为列,日期作为行
|
||||
u_heatmap_data.index = pd.to_datetime(u_heatmap_data.index) # 确保日期为时间格式
|
||||
u_heatmap_data = u_heatmap_data.iloc[:, ::-1] # 按高度降序排列
|
||||
|
||||
# 提取经向风数据,重置索引为平坦结构
|
||||
v_heatmap_data = v_final_result[amplitude_param].unstack(level=0) # 将高度作为列,日期作为行
|
||||
v_heatmap_data.index = pd.to_datetime(v_heatmap_data.index) # 确保日期为时间格式
|
||||
v_heatmap_data = v_heatmap_data.iloc[:, ::-1] # 按高度降序排列
|
||||
|
||||
# 创建画布和子图
|
||||
fig, axes = plt.subplots(1, 2, figsize=(18, 8)) # 1行2列的布局
|
||||
fig.suptitle('2022年5日_行星波振幅时空变化', fontsize=20) # 添加总标题
|
||||
|
||||
# 绘制第一幅热力图(纬向风)
|
||||
sns.heatmap(
|
||||
u_heatmap_data.T, # 转置,横轴为时间,纵轴为高度
|
||||
cmap="viridis", # 配色方案
|
||||
vmax=100, # 设置颜色范围的最大值
|
||||
cbar_kws={'label': '振幅 (m/s)'}, # 设置颜色条标签
|
||||
xticklabels=False, # 禁用默认的横轴刻度标签
|
||||
#yticklabels=u_heatmap_data.columns.astype(str), # 显示纵轴的高度
|
||||
yticklabels=[f'{height // 1000}' for height in u_heatmap_data.columns], # 转换高度为千米并格式化
|
||||
ax=axes[0] # 指定子图
|
||||
)
|
||||
axes[0].set_title('纬向风振幅变化', fontsize=14)
|
||||
axes[0].set_xlabel('日期', fontsize=12)
|
||||
axes[0].set_ylabel('高度 (km)', fontsize=12)
|
||||
|
||||
|
||||
# 绘制第二幅热力图(经向风)
|
||||
sns.heatmap(
|
||||
v_heatmap_data.T, # 转置,横轴为时间,纵轴为高度
|
||||
cmap="viridis", # 配色方案
|
||||
vmax=100, # 设置颜色范围的最大值
|
||||
cbar_kws={'label': '振幅 (m/s)'}, # 设置颜色条标签
|
||||
xticklabels=False, # 禁用默认的横轴刻度标签
|
||||
#yticklabels=v_heatmap_data.columns.astype(str), # 显示纵轴的高度
|
||||
yticklabels=[f'{height // 1000}' for height in v_heatmap_data.columns], # 转换高度为千米并格式化
|
||||
ax=axes[1] # 指定子图
|
||||
)
|
||||
axes[1].set_title('经向风振幅变化', fontsize=14)
|
||||
axes[1].set_xlabel('日期', fontsize=12)
|
||||
axes[1].set_ylabel('高度 (km)', fontsize=12)
|
||||
|
||||
# 调整布局
|
||||
plt.tight_layout(rect=[0, 0, 1, 0.95]) # 为总标题留出空间
|
||||
plt.show()
|
||||
|
||||
# 输出路径和文件名
|
||||
picture_file_name = f'{year}年_5日_行星波振幅时空变化.png'
|
||||
plt.savefig(os.path.join(output_dir, picture_file_name))
|
||||
|
||||
|
||||
# -----------------------------------------得到10日行星波拟合参数-------------------------------------------
|
||||
|
||||
# 定义新的拟合函数
|
||||
def fit_wind_speed_v3(data, window=29):
|
||||
|
||||
# 动态调整最小有效数据点数量:参数数量 * 6
|
||||
num_params = 3 # 模型参数数量
|
||||
# 转换为 pandas DataFrame 并设置时间为索引
|
||||
data["date"] = pd.to_datetime(data["date"])
|
||||
data.set_index("date", inplace=True)
|
||||
|
||||
# 获取唯一日期
|
||||
unique_dates = np.unique(data.index.date)
|
||||
|
||||
# 存储拟合参数
|
||||
params = []
|
||||
|
||||
for date in unique_dates:
|
||||
start_date = pd.to_datetime(date) - pd.Timedelta(days=(window - 1) / 2)
|
||||
end_date = pd.to_datetime(date) + pd.Timedelta(days=(window - 1) / 2)
|
||||
|
||||
# 获取这几天的数据
|
||||
window_data = data[start_date:end_date]
|
||||
|
||||
t = np.arange(len(window_data)) + 1 # 时间点
|
||||
y = window_data['wind_speed'].values # 风速数据
|
||||
|
||||
# 将 y 转换为浮点类型,以防有非数值类型数据
|
||||
y = pd.to_numeric(y, errors='coerce') # 将非数值转换为 NaN
|
||||
|
||||
# 去掉包含nan的那一列,筛选有效数据
|
||||
valid_mask = ~np.isnan(y) # 创建布尔掩码,标记非 NaN 的位置
|
||||
t = t[valid_mask]
|
||||
y = y[valid_mask]
|
||||
|
||||
# 检查筛选后的数据是否足够
|
||||
if len(y) < num_params * 6: # 确保数据足够
|
||||
# print(f"Not enough valid data after filtering for date: {date}")
|
||||
params.append([None] * 3) # 如果数据不足,记录None
|
||||
continue
|
||||
|
||||
# 设置初始参数
|
||||
initial_guess = [0, 1, 0] # 新模型的初始猜测参数
|
||||
bounds = (
|
||||
[-np.inf, 0, -np.inf], # 下界
|
||||
[np.inf, np.inf, np.inf], # 上界
|
||||
)
|
||||
|
||||
# 拟合模型
|
||||
try:
|
||||
popt, _ = curve_fit(planetary_model_10day, t, y, p0=initial_guess, bounds=bounds)
|
||||
params.append(popt) # 存储拟合参数
|
||||
except RuntimeError:
|
||||
params.append([None] * 3) # 如果拟合失败,记录None
|
||||
|
||||
# 转换拟合参数为 DataFrame
|
||||
params_df = pd.DataFrame(
|
||||
params,
|
||||
columns=["v0", "a", "b"],
|
||||
index=unique_dates,
|
||||
)
|
||||
return params_df
|
||||
|
||||
# 1-拟合纬向风------------------------------------------
|
||||
|
||||
u_result_dict = {}
|
||||
|
||||
for height in heights:
|
||||
print(f"Processing height: {height} m")
|
||||
# 获取当前高度的纬向风数据
|
||||
height_data = uwind_data[uwind_data["height"] == height]
|
||||
|
||||
# 转换为长格式(适配拟合函数)
|
||||
long_data = pd.melt(
|
||||
height_data,
|
||||
id_vars=["time", "height"],
|
||||
var_name="date",
|
||||
value_name="wind_speed",
|
||||
)
|
||||
long_data["date"] = pd.to_datetime(long_data["date"], format="%Y%m%d")
|
||||
|
||||
# 调用修改后的拟合函数 fit_wind_speed_v3
|
||||
params_df = fit_wind_speed_v3(long_data)
|
||||
u_result_dict[height] = params_df
|
||||
|
||||
# 汇总结果为 DataFrame
|
||||
u_final_result = pd.concat(u_result_dict, names=["height", "date"])
|
||||
|
||||
# 2-拟合经向风------------------------------------------
|
||||
|
||||
v_result_dict = {}
|
||||
|
||||
for height in heights:
|
||||
print(f"Processing height: {height} m")
|
||||
height_data = vwind_data[vwind_data["height"] == height]
|
||||
|
||||
long_data = pd.melt(
|
||||
height_data,
|
||||
id_vars=["time", "height"],
|
||||
var_name="date",
|
||||
value_name="wind_speed",
|
||||
)
|
||||
long_data["date"] = pd.to_datetime(long_data["date"], format="%Y%m%d")
|
||||
|
||||
|
||||
# 调用修改后的拟合函数 fit_wind_speed_v3
|
||||
params_df = fit_wind_speed_v3(long_data)
|
||||
v_result_dict[height] = params_df
|
||||
|
||||
# 汇总结果为 DataFrame
|
||||
v_final_result = pd.concat(v_result_dict, names=["height", "date"])
|
||||
|
||||
|
||||
# 定义振幅参数
|
||||
amplitude_param = 'a'
|
||||
|
||||
# 提取纬向风数据,重置索引为平坦结构
|
||||
u_heatmap_data = u_final_result[amplitude_param].unstack(level=0) # 将高度作为列,日期作为行
|
||||
u_heatmap_data.index = pd.to_datetime(u_heatmap_data.index) # 确保日期为时间格式
|
||||
u_heatmap_data = u_heatmap_data.iloc[:, ::-1] # 按高度降序排列
|
||||
|
||||
# 提取经向风数据,重置索引为平坦结构
|
||||
v_heatmap_data = v_final_result[amplitude_param].unstack(level=0) # 将高度作为列,日期作为行
|
||||
v_heatmap_data.index = pd.to_datetime(v_heatmap_data.index) # 确保日期为时间格式
|
||||
v_heatmap_data = v_heatmap_data.iloc[:, ::-1] # 按高度降序排列
|
||||
|
||||
# 创建画布和子图
|
||||
fig, axes = plt.subplots(1, 2, figsize=(18, 8)) # 1行2列的布局
|
||||
fig.suptitle('2022年10日_行星波振幅时空变化', fontsize=20) # 添加总标题
|
||||
|
||||
# 绘制第一幅热力图(纬向风)
|
||||
sns.heatmap(
|
||||
u_heatmap_data.T, # 转置,横轴为时间,纵轴为高度
|
||||
cmap="viridis", # 配色方案
|
||||
vmax=100, # 设置颜色范围的最大值
|
||||
cbar_kws={'label': '振幅 (m/s)'}, # 设置颜色条标签
|
||||
xticklabels=False, # 禁用默认的横轴刻度标签
|
||||
#yticklabels=u_heatmap_data.columns.astype(str), # 显示纵轴的高度
|
||||
yticklabels=[f'{height // 1000}' for height in u_heatmap_data.columns], # 转换高度为千米并格式化
|
||||
ax=axes[0] # 指定子图
|
||||
)
|
||||
axes[0].set_title('纬向风振幅变化', fontsize=14)
|
||||
axes[0].set_xlabel('日期', fontsize=12)
|
||||
axes[0].set_ylabel('高度 (m)', fontsize=12)
|
||||
|
||||
# 绘制第二幅热力图(经向风)
|
||||
sns.heatmap(
|
||||
v_heatmap_data.T, # 转置,横轴为时间,纵轴为高度
|
||||
cmap="viridis", # 配色方案
|
||||
vmax=100, # 设置颜色范围的最大值
|
||||
cbar_kws={'label': '振幅 (m/s)'}, # 设置颜色条标签
|
||||
xticklabels=False, # 禁用默认的横轴刻度标签
|
||||
#yticklabels=v_heatmap_data.columns.astype(str), # 显示纵轴的高度
|
||||
yticklabels=[f'{height // 1000}' for height in u_heatmap_data.columns], # 转换高度为千米并格式化
|
||||
ax=axes[1] # 指定子图
|
||||
)
|
||||
axes[1].set_title('经向风振幅变化', fontsize=14)
|
||||
axes[1].set_xlabel('日期', fontsize=12)
|
||||
axes[1].set_ylabel('高度 (m)', fontsize=12)
|
||||
|
||||
# 调整布局
|
||||
plt.tight_layout(rect=[0, 0, 1, 0.95]) # 为总标题留出空间
|
||||
# 显示或保存图像
|
||||
plt.show()
|
||||
|
||||
# 输出路径和文件名
|
||||
picture_file_name = f'{year}年_10日_行星波振幅时空变化.png'
|
||||
plt.savefig(os.path.join(output_dir, picture_file_name))
|
||||
|
||||
|
||||
# -----------------------------------------得到16日行星波拟合参数-------------------------------------------
|
||||
|
||||
# 定义新的拟合函数
|
||||
def fit_wind_speed_v4(data, window=49):
|
||||
# 动态调整最小有效数据点数量:参数数量 * 6
|
||||
num_params = 3 # 模型参数数量
|
||||
# 转换为 pandas DataFrame 并设置时间为索引
|
||||
data["date"] = pd.to_datetime(data["date"])
|
||||
data.set_index("date", inplace=True)
|
||||
|
||||
# 获取唯一日期
|
||||
unique_dates = np.unique(data.index.date)
|
||||
|
||||
# 存储拟合参数
|
||||
params = []
|
||||
|
||||
for date in unique_dates:
|
||||
start_date = pd.to_datetime(date) - pd.Timedelta(days=(window - 1) / 2)
|
||||
end_date = pd.to_datetime(date) + pd.Timedelta(days=(window - 1) / 2)
|
||||
|
||||
# 获取这几天的数据
|
||||
window_data = data[start_date:end_date]
|
||||
|
||||
t = np.arange(len(window_data)) + 1 # 时间点
|
||||
y = window_data['wind_speed'].values # 风速数据
|
||||
|
||||
# 将 y 转换为浮点类型,以防有非数值类型数据
|
||||
y = pd.to_numeric(y, errors='coerce') # 将非数值转换为 NaN
|
||||
|
||||
# 去掉包含nan的那一列,筛选有效数据
|
||||
valid_mask = ~np.isnan(y) # 创建布尔掩码,标记非 NaN 的位置
|
||||
t = t[valid_mask]
|
||||
y = y[valid_mask]
|
||||
|
||||
# 检查筛选后的数据是否足够
|
||||
if len(y) < num_params * 6: # 确保数据足够
|
||||
# print(f"Not enough valid data after filtering for date: {date}")
|
||||
params.append([None] * 3) # 如果数据不足,记录None
|
||||
continue
|
||||
|
||||
# 设置初始参数
|
||||
initial_guess = [0, 1, 0] # 新模型的初始猜测参数
|
||||
bounds = (
|
||||
[-np.inf, 0, -np.inf], # 下界
|
||||
[np.inf, np.inf, np.inf], # 上界
|
||||
)
|
||||
|
||||
# 拟合模型
|
||||
try:
|
||||
popt, _ = curve_fit(planetary_model_16day, t, y, p0=initial_guess, bounds=bounds)
|
||||
params.append(popt) # 存储拟合参数
|
||||
except RuntimeError:
|
||||
params.append([None] * 3) # 如果拟合失败,记录None
|
||||
|
||||
# 转换拟合参数为 DataFrame
|
||||
params_df = pd.DataFrame(
|
||||
params,
|
||||
columns=["v0", "a", "b"],
|
||||
index=unique_dates,
|
||||
)
|
||||
return params_df
|
||||
|
||||
# 1-拟合纬向风------------------------------------------
|
||||
|
||||
u_result_dict = {}
|
||||
|
||||
for height in heights:
|
||||
print(f"Processing height: {height} m")
|
||||
# 获取当前高度的纬向风数据
|
||||
height_data = uwind_data[uwind_data["height"] == height]
|
||||
|
||||
# 转换为长格式(适配拟合函数)
|
||||
long_data = pd.melt(
|
||||
height_data,
|
||||
id_vars=["time", "height"],
|
||||
var_name="date",
|
||||
value_name="wind_speed",
|
||||
)
|
||||
long_data["date"] = pd.to_datetime(long_data["date"], format="%Y%m%d")
|
||||
|
||||
# 调用修改后的拟合函数 fit_wind_speed_v4
|
||||
params_df = fit_wind_speed_v4(long_data)
|
||||
u_result_dict[height] = params_df
|
||||
|
||||
# 汇总结果为 DataFrame
|
||||
u_final_result = pd.concat(u_result_dict, names=["height", "date"])
|
||||
|
||||
# 2-拟合经向风------------------------------------------
|
||||
|
||||
v_result_dict = {}
|
||||
|
||||
for height in heights:
|
||||
print(f"Processing height: {height} m")
|
||||
height_data = vwind_data[vwind_data["height"] == height]
|
||||
|
||||
long_data = pd.melt(
|
||||
height_data,
|
||||
id_vars=["time", "height"],
|
||||
var_name="date",
|
||||
value_name="wind_speed",
|
||||
)
|
||||
long_data["date"] = pd.to_datetime(long_data["date"], format="%Y%m%d")
|
||||
|
||||
|
||||
# 调用修改后的拟合函数 fit_wind_speed_v4
|
||||
params_df = fit_wind_speed_v4(long_data)
|
||||
v_result_dict[height] = params_df
|
||||
|
||||
# 汇总结果为 DataFrame
|
||||
v_final_result = pd.concat(v_result_dict, names=["height", "date"])
|
||||
|
||||
|
||||
# 定义振幅参数
|
||||
amplitude_param = 'a'
|
||||
|
||||
# 提取纬向风数据,重置索引为平坦结构
|
||||
u_heatmap_data = u_final_result[amplitude_param].unstack(level=0) # 将高度作为列,日期作为行
|
||||
u_heatmap_data.index = pd.to_datetime(u_heatmap_data.index) # 确保日期为时间格式
|
||||
u_heatmap_data = u_heatmap_data.iloc[:, ::-1] # 按高度降序排列
|
||||
|
||||
# 提取经向风数据,重置索引为平坦结构
|
||||
v_heatmap_data = v_final_result[amplitude_param].unstack(level=0) # 将高度作为列,日期作为行
|
||||
v_heatmap_data.index = pd.to_datetime(v_heatmap_data.index) # 确保日期为时间格式
|
||||
v_heatmap_data = v_heatmap_data.iloc[:, ::-1] # 按高度降序排列
|
||||
|
||||
# 创建画布和子图
|
||||
fig, axes = plt.subplots(1, 2, figsize=(18, 8)) # 1行2列的布局
|
||||
fig.suptitle('2022年16日_行星波振幅时空变化', fontsize=20) # 添加总标题
|
||||
|
||||
# 绘制第一幅热力图(纬向风)
|
||||
sns.heatmap(
|
||||
u_heatmap_data.T, # 转置,横轴为时间,纵轴为高度
|
||||
cmap="viridis", # 配色方案
|
||||
vmax=100, # 设置颜色范围的最大值
|
||||
cbar_kws={'label': '振幅 (m/s)'}, # 设置颜色条标签
|
||||
xticklabels=False, # 禁用默认的横轴刻度标签
|
||||
#yticklabels=u_heatmap_data.columns.astype(str), # 显示纵轴的高度
|
||||
yticklabels=[f'{height // 1000}' for height in u_heatmap_data.columns], # 转换高度为千米并格式化
|
||||
|
||||
ax=axes[0] # 指定子图
|
||||
)
|
||||
axes[0].set_title('纬向风振幅变化', fontsize=14)
|
||||
axes[0].set_xlabel('日期', fontsize=12)
|
||||
axes[0].set_ylabel('高度 (m)', fontsize=12)
|
||||
|
||||
# 绘制第二幅热力图(经向风)
|
||||
sns.heatmap(
|
||||
v_heatmap_data.T, # 转置,横轴为时间,纵轴为高度
|
||||
cmap="viridis", # 配色方案
|
||||
vmax=100, # 设置颜色范围的最大值
|
||||
cbar_kws={'label': '振幅 (m/s)'}, # 设置颜色条标签
|
||||
xticklabels=False, # 禁用默认的横轴刻度标签
|
||||
#yticklabels=v_heatmap_data.columns.astype(str), # 显示纵轴的高度
|
||||
yticklabels=[f'{height // 1000}' for height in v_heatmap_data.columns], # 转换高度为千米并格式化
|
||||
|
||||
ax=axes[1] # 指定子图
|
||||
)
|
||||
axes[1].set_title('经向风振幅变化', fontsize=14)
|
||||
axes[1].set_xlabel('日期', fontsize=12)
|
||||
axes[1].set_ylabel('高度 (m)', fontsize=12)
|
||||
|
||||
# 调整布局
|
||||
plt.tight_layout(rect=[0, 0, 1, 0.95]) # 为总标题留出空间
|
||||
|
||||
# 显示或保存图像
|
||||
plt.show()
|
||||
|
||||
# 输出路径和文件名
|
||||
picture_file_name = f'{year}年_16日_行星波振幅时空变化.png'
|
||||
plt.savefig(os.path.join(output_dir, picture_file_name))
|
||||
|
||||
|
||||
|
||||
@ -1,262 +1,487 @@
|
||||
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, "")
|
||||
# 此代码可以绘制某年、某高度的纬向风/经向风的,潮汐波和行星波的
|
||||
# 年振幅图、月振幅图、日拟合正弦波图
|
||||
|
||||
# 基本思想是,1-先处理某高度全年观测数据,得到final_df
|
||||
# 2-再选择风的类型,得到此高度全年每一天的纬向风/经向风大气长波拟合参数,函数process_wind_data_with_models
|
||||
# 3-下一步就是根据参数绘制图形,可选择年振幅图、月振幅图、日拟合正弦波图(#todo:上一步风的类型已确定)
|
||||
# plot_yearly_params、plot_month_params、plot_sine_wave_for_day
|
||||
|
||||
|
||||
import glob
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import os
|
||||
from scipy.optimize import curve_fit
|
||||
import matplotlib.pyplot as plt
|
||||
from datetime import datetime
|
||||
|
||||
# 解决绘图中中文不能显示的问题
|
||||
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天
|
||||
},
|
||||
}
|
||||
|
||||
################################ 选定风的类型,得到整年的每一天拟合参数
|
||||
def process_wind_data_with_models(final_df, wind_type, year, H, selected_models=None):
|
||||
"""
|
||||
处理风速数据并针对多种模型进行拟合和绘图。
|
||||
|
||||
:param selected_models: 可选,指定使用的模型类型列表。如果为 None,则处理所有模型。
|
||||
:return: 整年所有模型的拟合参数字典
|
||||
"""
|
||||
wind_data = final_df[[col for col in final_df.columns if wind_type in col]]
|
||||
wind_data.columns = [col[:8] for col in wind_data.columns]
|
||||
result = wind_data.to_numpy().T.flatten()
|
||||
column_names = np.repeat(wind_data.columns.to_numpy(), 24)
|
||||
combined = pd.DataFrame(np.vstack((column_names, result)))
|
||||
|
||||
dates = combined.iloc[0].values
|
||||
wind_speed = combined.iloc[1].values
|
||||
date_series = pd.to_datetime(dates)
|
||||
data = pd.DataFrame({'date': date_series, 'wind_speed': wind_speed})
|
||||
data.set_index('date', inplace=True)
|
||||
unique_dates = np.unique(data.index.date)
|
||||
|
||||
# 如果指定了模型,则只处理指定的模型
|
||||
if selected_models is None:
|
||||
models_to_process = MODELS.keys() # 默认处理所有模型
|
||||
else:
|
||||
models_to_process = selected_models # 使用指定的模型列表
|
||||
|
||||
all_params = {} # 存储所有模型的参数数据框
|
||||
|
||||
for model_name in models_to_process:
|
||||
if model_name not in MODELS:
|
||||
print(f"Model {model_name} is not valid.")
|
||||
continue
|
||||
|
||||
model_info = MODELS[model_name]
|
||||
params = []
|
||||
model_func = model_info['function']
|
||||
param_names = model_info['param_names']
|
||||
initial_guess = model_info['initial_guess']
|
||||
bounds = model_info['bounds']
|
||||
window_days = model_info['window'] # 获取窗口长度
|
||||
|
||||
for date in unique_dates:
|
||||
# 根据模型窗口调整时间范围
|
||||
start_date = pd.to_datetime(date) - pd.Timedelta(days=(window_days - 1) / 2)
|
||||
end_date = pd.to_datetime(date) + pd.Timedelta(days=(window_days - 1) / 2)
|
||||
window_data = data[start_date:end_date]
|
||||
t = np.arange(len(window_data)) + 1
|
||||
y = pd.to_numeric(window_data['wind_speed'], errors='coerce')
|
||||
valid_mask = ~np.isnan(y)
|
||||
t = t[valid_mask]
|
||||
y = y[valid_mask]
|
||||
|
||||
if len(y) < len(initial_guess) * 2: # todo:认为如果数据点个数少于参数的2倍,则认为数据不够拟合,之前是6倍,在这里放宽了
|
||||
print(f"Not enough valid data after filtering for date: {date}")
|
||||
params.append([None] * len(param_names))
|
||||
continue
|
||||
|
||||
try:
|
||||
popt, _ = curve_fit(model_func, t, y, p0=initial_guess, bounds=bounds)
|
||||
params.append(popt)
|
||||
except RuntimeError:
|
||||
print(f"Fitting failed for date: {date}")
|
||||
params.append([None] * len(param_names))
|
||||
|
||||
# 保存整年参数
|
||||
params_df = pd.DataFrame(params, columns=param_names, index=unique_dates)
|
||||
# file_name = f'{year}年_{wind_type}_{H // 1000}km_{model_name}_强度.txt'
|
||||
# params_df.to_csv(os.path.join(output_dir, file_name), sep='\t', index=True, header=True)
|
||||
|
||||
all_params[model_name] = params_df # 将每个模型的参数数据框存入字典
|
||||
|
||||
return all_params # 返回包含所有模型参数的数据字典
|
||||
|
||||
# 外部绘制全年图形
|
||||
def plot_yearly_params(params_dict, model_name,year, wind_type, H):
|
||||
"""
|
||||
绘制全年的参数图形。
|
||||
|
||||
:param params_dict: 存储所有模型的参数数据框字典
|
||||
:param model_name: 模型名称
|
||||
"""
|
||||
params_df = params_dict[model_name]
|
||||
|
||||
# 绘图
|
||||
if 'a1' in params_df.columns: # 处理潮汐波多分量
|
||||
plt.figure(figsize=(12, 6))
|
||||
for i, T in enumerate([6, 8, 12, 24], start=1):
|
||||
plt.plot(params_df[f'a{i}'], label=f'{T}h', linewidth=2)
|
||||
else: # 处理行星波单分量
|
||||
plt.figure(figsize=(12, 6))
|
||||
plt.plot(params_df['a'], label=f'{model_name}', color='orange', linewidth=2)
|
||||
|
||||
plt.title(f'{year}年{wind_type}_{H // 1000}km_{model_name}_强度', fontsize=16)
|
||||
plt.xlabel('日期', fontsize=14)
|
||||
plt.ylabel('幅度 (m/s)', fontsize=14)
|
||||
plt.xticks(rotation=45)
|
||||
plt.legend()
|
||||
plt.tick_params(axis='both', labelsize=12) # 调整刻度字体大小
|
||||
plt.tight_layout()
|
||||
|
||||
# 绘制月振幅图
|
||||
def plot_month_params(params_dict, model_name, wind_type, year, month, H):
|
||||
"""
|
||||
绘制指定月份的参数图形。
|
||||
:param params_dict: 存储所有模型的参数数据框字典
|
||||
:param model_name: 模型名称
|
||||
:param wind_type: 风速类型
|
||||
:param year: 需要绘制的年份
|
||||
:param month: 需要绘制的月份
|
||||
:param output_dir: 输出目录
|
||||
:param H: 水平尺度
|
||||
"""
|
||||
# 获取该年份的数据框
|
||||
params_df = params_dict[model_name]
|
||||
|
||||
# 确保索引是 datetime 类型
|
||||
if not pd.api.types.is_datetime64_any_dtype(params_df.index):
|
||||
# print("Index is not datetime. Converting to datetime...")
|
||||
params_df.index = pd.to_datetime(params_df.index)
|
||||
|
||||
# 筛选出指定年份和月份的数据
|
||||
params_df_month = params_df[(params_df.index.year == year) & (params_df.index.month == month)]
|
||||
|
||||
# 检查筛选后的数据是否为空
|
||||
if params_df_month.empty:
|
||||
print(f"No data available for {year}-{month}.")
|
||||
return
|
||||
|
||||
# 绘图
|
||||
plt.figure(figsize=(12, 6))
|
||||
|
||||
# 判断是潮汐波还是行星波
|
||||
if model_name == '潮汐波': # 处理潮汐波多分量
|
||||
for i, T in enumerate([6, 8, 12, 24], start=1):
|
||||
col_name = f'a{i}'
|
||||
if col_name in params_df_month.columns:
|
||||
plt.plot(params_df_month[col_name], label=f'{T}h', linewidth=2)
|
||||
else:
|
||||
print(f"Warning: Column '{col_name}' not found in the dataframe.")
|
||||
else: # 处理行星波单分量
|
||||
if 'a' in params_df_month.columns:
|
||||
plt.plot(params_df_month['a'], label=f'{model_name}', color='orange', linewidth=2)
|
||||
else:
|
||||
print(f"Warning: Column 'a' not found in the dataframe.")
|
||||
|
||||
# 设置标题和标签
|
||||
plt.title(f'{year}年{month}月{wind_type}_{H // 1000}km_{model_name}_强度', fontsize=16)
|
||||
plt.xlabel('日期', fontsize=14)
|
||||
plt.ylabel('幅度 (m/s)', fontsize=14)
|
||||
plt.xticks(rotation=45)
|
||||
plt.legend()
|
||||
plt.tick_params(axis='both', labelsize=12) # 调整刻度字体大小
|
||||
plt.tight_layout()
|
||||
|
||||
|
||||
|
||||
# 绘制日拟合正弦波图
|
||||
def plot_sine_wave_for_day(params_df, date, wind_type, H, model_name):
|
||||
"""
|
||||
根据指定日期的参数,绘制标准的正弦曲线图。
|
||||
:param params_df: 存储所有模型的参数数据框
|
||||
:param date: 指定日期(例如 '2024-03-17')
|
||||
:param wind_type: 风速类型(例如 'uwind')
|
||||
:param H: 水平尺度
|
||||
:param model_name: 模型名称(例如 '5日行星波', '10日行星波', '潮汐波')
|
||||
"""
|
||||
# 将字符串日期转换为 datetime 格式
|
||||
try:
|
||||
date = datetime.strptime(date, '%Y-%m-%d')
|
||||
except ValueError:
|
||||
print(f"Error: Invalid date format '{date}', expected 'YYYY-MM-DD'.")
|
||||
return
|
||||
|
||||
# 检查 params_df.index 是否为 datetime 类型,若不是,则将其转换
|
||||
if not isinstance(params_df.index, pd.DatetimeIndex):
|
||||
print("Warning: params_df index is not of type datetime. Converting index to datetime...")
|
||||
params_df.index = pd.to_datetime(params_df.index)
|
||||
|
||||
# 获取指定日期的参数
|
||||
if date not in params_df.index:
|
||||
print(f"Warning: {date.strftime('%Y-%m-%d')} not found in the parameter dataframe.")
|
||||
return
|
||||
|
||||
params = params_df.loc[date]
|
||||
|
||||
# 检查参数是否为 NaN 或 Inf
|
||||
if params.isnull().any() or np.isinf(params).any():
|
||||
print(f"Warning: Parameters for {date.strftime('%Y-%m-%d')} contain NaN or Inf values. Skipping the plot.")
|
||||
return
|
||||
|
||||
# 判断是否是潮汐波模型
|
||||
if 'a1' in params: # 如果是潮汐波模型
|
||||
T1, T2, T3, T4 = 6, 8, 12, 24 # 潮汐波的周期
|
||||
|
||||
# 生成时间数据
|
||||
t = np.linspace(0, 48, 1000) # 生成0到48小时的1000个点
|
||||
|
||||
# 从参数中提取值
|
||||
v0 = params['v0']
|
||||
a1 = params['a1']
|
||||
b1 = params['b1']
|
||||
a2 = params['a2']
|
||||
b2 = params['b2']
|
||||
a3 = params['a3']
|
||||
b3 = params['b3']
|
||||
a4 = params['a4']
|
||||
b4 = params['b4']
|
||||
|
||||
# 计算正弦波的值
|
||||
y1 = v0 * np.ones_like(t) # 常量项
|
||||
y2 = a1 * np.sin((2 * np.pi / T1) * t + b1)
|
||||
y3 = a2 * np.sin((2 * np.pi / T2) * t + b2)
|
||||
y4 = a3 * np.sin((2 * np.pi / T3) * t + b3)
|
||||
y5 = a4 * np.sin((2 * np.pi / T4) * t + b4)
|
||||
|
||||
# 绘制图形
|
||||
plt.figure(figsize=(12, 8))
|
||||
plt.plot(t, y1, label='常量', color='purple')
|
||||
plt.plot(t, y2, label='6h', color='blue')
|
||||
plt.plot(t, y3, label='8h', color='orange')
|
||||
plt.plot(t, y4, label='12h', color='green')
|
||||
plt.plot(t, y5, label='24h', color='red')
|
||||
|
||||
# 添加图例和标签
|
||||
plt.title(f'{date.strftime("%Y-%m-%d")} {wind_type}风速拟合潮汐波', fontsize=16)
|
||||
plt.xlabel('时间 (小时)', fontsize=14)
|
||||
plt.ylabel('幅度 (m/s)', fontsize=14)
|
||||
plt.legend()
|
||||
plt.grid(True)
|
||||
plt.xlim(0, 48) # 0到48小时
|
||||
plt.ylim(min(y1.min(), y2.min(), y3.min(), y4.min(), y5.min()-1),
|
||||
max(y1.max(), y2.max(), y3.max(), y4.max(), y5.max())+1)
|
||||
|
||||
plt.tight_layout()
|
||||
|
||||
else: # 处理行星波模型
|
||||
# 根据传入的模型名称选择周期
|
||||
if model_name == '2日行星波':
|
||||
T = 48 # 2日行星波的周期为48小时
|
||||
elif model_name == '5日行星波':
|
||||
T = 120 # 5日行星波的周期为120小时
|
||||
elif model_name == '10日行星波':
|
||||
T = 240 # 10日行星波的周期为240小时
|
||||
elif model_name == '16日行星波':
|
||||
T = 384 # 16日行星波的周期为384小时
|
||||
else:
|
||||
print(f"Error: Unsupported planetary wave model '{model_name}'.")
|
||||
return
|
||||
|
||||
# 生成时间数据
|
||||
t = np.linspace(0, T*2, 1000) # 根据周期生成对应的时间范围
|
||||
|
||||
# 从参数中提取值
|
||||
v0 = params['v0']
|
||||
a = params['a']
|
||||
b = params['b']
|
||||
|
||||
# 计算正弦波的值
|
||||
y1 = v0 * np.ones_like(t) # 常量项
|
||||
y2 = a * np.sin((2 * np.pi / T) * t + b)
|
||||
|
||||
# 绘制图形
|
||||
plt.figure(figsize=(12, 8))
|
||||
plt.plot(t, y1, label='常量', color='purple')
|
||||
plt.plot(t, y2, label=f'{model_name} ({T}小时)', color='orange')
|
||||
|
||||
# 添加图例和标签
|
||||
plt.title(f'{date.strftime("%Y-%m-%d")} {wind_type}风速拟合 {model_name}', fontsize=16)
|
||||
plt.xlabel('时间 (小时)', fontsize=14)
|
||||
plt.ylabel('幅度 (m/s)', fontsize=14)
|
||||
plt.legend()
|
||||
plt.grid(True)
|
||||
plt.xlim(0, T*2) # 根据周期调整时间范围
|
||||
plt.ylim(min(y1.min(), y2.min()-1), max(y1.max(), y2.max())+1)
|
||||
|
||||
plt.tight_layout()
|
||||
|
||||
|
||||
def get_final_df(H, year, station):
|
||||
|
||||
# 下边处理,绘图
|
||||
# 参数设置,选择高度、时间、站点
|
||||
|
||||
# # station = '黑龙江漠河站'
|
||||
|
||||
# 文件路径设置
|
||||
file_dir = rf'./radar/data/{station}/{year}' # 数据文件路径
|
||||
output_dir = rf'./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, delim_whitespace=True) # 读取文件
|
||||
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包含完整天的观测数据---------------------------------------------------------
|
||||
|
||||
# 2---------------------------整年纬向风/经向风拟合参数
|
||||
# params_dict = process_wind_data_with_models(final_df,'uwind', output_dir, year, H,selected_models=['潮汐波','2日行星波'])
|
||||
|
||||
|
||||
# params_dict = process_wind_data_with_models(final_df,
|
||||
# 'uwind', output_dir, year, H,
|
||||
# selected_models=None)
|
||||
|
||||
|
||||
class final_render_v2():
|
||||
def __init__(self, H, year, station, wind_type):
|
||||
self.H , self.year, self.station, self.wind_type = H, year, station, wind_type
|
||||
final_df = get_final_df(H, year, station)
|
||||
self.params_dict = process_wind_data_with_models(final_df, wind_type, year, H, selected_models=None)
|
||||
|
||||
def render_day(self, date, model_name):
|
||||
plot_sine_wave_for_day(self.params_dict[model_name], date, self.wind_type, self.H, model_name)
|
||||
|
||||
def render_month(self, month, model_name):
|
||||
plot_month_params(self.params_dict, model_name, self.wind_type, self.year, month, self.H)
|
||||
|
||||
def render_year(self, model_name):
|
||||
plot_yearly_params(self.params_dict, model_name, self.year, self.wind_type, self.H)
|
||||
|
||||
@staticmethod
|
||||
def get_all_models():
|
||||
return list(MODELS.keys())
|
||||
|
||||
@staticmethod
|
||||
def get_all_pathes():
|
||||
result = glob.glob(f"./radar/data/**/*.txt", recursive=True)
|
||||
# normalize path
|
||||
result = [os.path.normpath(path).replace("\\", "/") for path in result]
|
||||
return result
|
||||
|
||||
|
||||
# todo:此时得到的参数,年份、高度、风的类型已经固定
|
||||
|
||||
# 3---------------------------绘图
|
||||
# 使用示例:绘制全年图形
|
||||
if __name__ == '__main__':
|
||||
# H = 94000 # 高度为 90km
|
||||
# year = 2022
|
||||
import matplotlib.font_manager as fm
|
||||
fm.fontManager.addfont("./SimHei.ttf")
|
||||
|
||||
# station = '武汉左岭镇站'
|
||||
renderer = final_render_v2(94000, 2022, '武汉左岭镇站', 'uwind')
|
||||
renderer.render_year('潮汐波')
|
||||
plt.show()
|
||||
renderer.render_month(3, '潮汐波')
|
||||
plt.show()
|
||||
renderer.render_day('2022-03-17', '潮汐波')
|
||||
plt.show()
|
||||
@ -1,213 +1,234 @@
|
||||
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
|
||||
import glob
|
||||
from io import BytesIO
|
||||
import os
|
||||
import re
|
||||
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_list, year, month_range=(1,12)):
|
||||
def filter_given_month(month_range, file_list):
|
||||
begin_month, end_month = month_range
|
||||
get_date_pattern = r'_(\d{8})\.txt'
|
||||
filtered_file_list = []
|
||||
for file in file_list:
|
||||
date = re.search(get_date_pattern, file).group(1)
|
||||
month = int(date[4:6])
|
||||
if month >= begin_month and month <= end_month:
|
||||
filtered_file_list.append(file)
|
||||
return filtered_file_list
|
||||
if file_list == []:
|
||||
raise ValueError("No data files found.")
|
||||
|
||||
file_list = filter_given_month(month_range, file_list)
|
||||
"""读取数据文件并合并成完整的 DataFrame"""
|
||||
# file_list = [f for f in os.listdir(file_dir) if f.endswith('.txt')]
|
||||
final_df = pd.DataFrame()
|
||||
|
||||
for file_path in file_list:
|
||||
file_name = file_path.split('/')[-1]
|
||||
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')
|
||||
|
||||
# 补全缺失列并排序
|
||||
begin_month, end_month = month_range
|
||||
# calculate begin and end date
|
||||
begin_date = f'{year}-{begin_month:02d}-01'
|
||||
end_date = f'{year}-{end_month+1:02d}-01' if end_month < 12 else f'{year}-12-31'
|
||||
all_dates = pd.date_range(
|
||||
begin_date, end_date).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 * 2:
|
||||
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]
|
||||
heatmap_data.index = heatmap_data.index.strftime('%Y-%m-%d')
|
||||
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, month_range=(1,12)):
|
||||
"""
|
||||
主逻辑函数,用于处理数据并绘制选定模型的热力图。
|
||||
|
||||
参数:
|
||||
- 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)
|
||||
|
||||
# 数据预处理
|
||||
file_list = glob.glob(file_dir + '/**/*.txt', recursive=True)
|
||||
final_df = preprocess_data(file_list, year, month_range)
|
||||
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}年{month_range[0]}-{month_range[1]}月{model_name}振幅时空变化',
|
||||
vmax=100,
|
||||
)
|
||||
|
||||
buffer = BytesIO()
|
||||
plt.savefig(buffer, format='png')
|
||||
buffer.seek(0)
|
||||
plt.close()
|
||||
return buffer
|
||||
|
||||
@ -1,103 +1,105 @@
|
||||
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()
|
||||
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():
|
||||
# normalizing the path, and replace \\ with /
|
||||
all_saber_files = [path.replace("\\", "/") for path in all_saber_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()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
490
saber/process.py
490
saber/process.py
@ -1,245 +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)
|
||||
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)
|
||||
|
||||
592
saber/render.py
592
saber/render.py
@ -1,296 +1,296 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Tuple, List
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from matplotlib.figure import Figure
|
||||
from matplotlib.axes import Axes
|
||||
import matplotlib.dates as mdates
|
||||
|
||||
from saber.process import DataProcessor, WaveData, YearlyData
|
||||
|
||||
|
||||
@dataclass
|
||||
class PlotConfig:
|
||||
"""绘图配置类"""
|
||||
figsize: Tuple[int, int] = (16, 10)
|
||||
dpi: int = 100
|
||||
cmap: str = 'viridis'
|
||||
title_fontsize: int = 12
|
||||
label_fontsize: int = 10
|
||||
legend_fontsize: int = 8
|
||||
tick_fontsize: int = 8
|
||||
|
||||
|
||||
class Renderer:
|
||||
def __init__(self, config: Optional[PlotConfig] = None):
|
||||
self.config = config or PlotConfig()
|
||||
plt.rcParams['font.family'] = 'SimHei' # 设置为黑体(需要你的环境中有该字体)
|
||||
plt.rcParams['axes.unicode_minus'] = False # 解决负号'-'显示为方块的问题
|
||||
|
||||
def _create_figure(self, rows: int, cols: int) -> Tuple[Figure, List[Axes]]:
|
||||
"""创建图形和轴对象"""
|
||||
fig, axes = plt.subplots(
|
||||
rows, cols, figsize=self.config.figsize, dpi=self.config.dpi)
|
||||
return fig, axes.flatten() if isinstance(axes, np.ndarray) else [axes]
|
||||
|
||||
def _setup_subplot(self, ax: Axes, x: np.ndarray, y1: np.ndarray,
|
||||
y2: Optional[np.ndarray], title: str, y_limits: Tuple[float, float]):
|
||||
"""设置单个子图的样式和数据"""
|
||||
ax.plot(x, y1, label='原始信号', linewidth=1.5)
|
||||
if y2 is not None:
|
||||
ax.plot(x, y2, label='拟合信号', linestyle='--', linewidth=1.5)
|
||||
|
||||
ax.set_title(title, fontsize=self.config.title_fontsize)
|
||||
ax.set_xlabel('Cycles', labelpad=10,
|
||||
fontsize=self.config.label_fontsize)
|
||||
ax.set_ylabel('温度 (K)', labelpad=10,
|
||||
fontsize=self.config.label_fontsize)
|
||||
ax.legend(fontsize=self.config.legend_fontsize)
|
||||
ax.tick_params(axis='both', labelsize=self.config.tick_fontsize)
|
||||
ax.set_ylim(y_limits)
|
||||
ax.grid(True, linestyle='--', alpha=0.7)
|
||||
|
||||
def plot_wave_fitting(self, wave_data: WaveData, height_no: int):
|
||||
"""绘制波数拟合结果"""
|
||||
fig, axes = self._create_figure(2, 3)
|
||||
|
||||
# 准备数据
|
||||
N = len(wave_data.wn0[:, height_no])
|
||||
x = np.arange(N)
|
||||
|
||||
data_pairs = [
|
||||
(wave_data.wn0[:, height_no], wave_data.fit_wn[0][:, height_no]),
|
||||
(wave_data.wn[0][:, height_no], wave_data.fit_wn[1][:, height_no]),
|
||||
(wave_data.wn[1][:, height_no], wave_data.fit_wn[2][:, height_no]),
|
||||
(wave_data.wn[2][:, height_no], wave_data.fit_wn[3][:, height_no]),
|
||||
(wave_data.wn[3][:, height_no], wave_data.fit_wn[4][:, height_no]),
|
||||
(wave_data.wn[4][:, height_no], None)
|
||||
]
|
||||
|
||||
# 计算统一的y轴范围
|
||||
all_values = [
|
||||
val for pair in data_pairs for val in pair if val is not None]
|
||||
y_limits = (np.min(all_values), np.max(all_values))
|
||||
|
||||
# 绘制子图
|
||||
for i, (y1, y2) in enumerate(data_pairs):
|
||||
title = f'({"abcdef"[i]})波数k={i + 1 if i < 5 else "滤波1-5后信号"}'
|
||||
self._setup_subplot(axes[i], x, y1, y2, title, y_limits)
|
||||
|
||||
# 调整布局
|
||||
plt.tight_layout()
|
||||
plt.subplots_adjust(top=0.9, bottom=0.1, left=0.1, right=0.9,
|
||||
hspace=0.3, wspace=0.3)
|
||||
|
||||
def day_fft_ifft_plot(self, cycle_no, wave_data: WaveData):
|
||||
|
||||
ktemp_wn5 = wave_data.wn[4]
|
||||
ktemp_fft = wave_data.fft
|
||||
ktemp_fft_lvbo = wave_data.fft_filtered
|
||||
ktemp_ifft = wave_data.ifft
|
||||
|
||||
altitude_min, altitude_max = wave_data.alt_range
|
||||
lamda_low, lamda_high = wave_data.lambda_range
|
||||
|
||||
N = len(ktemp_wn5[cycle_no, :])
|
||||
# 采样时间间隔,其倒数等于采用频率,以1km为标准尺度等同于1s,假设波的速度为1km/s
|
||||
dt = (altitude_max-altitude_min)/(N-1)
|
||||
# 时间序列索引
|
||||
n = np.arange(N)
|
||||
f = n / (N * dt)
|
||||
t = np.round(np.linspace(altitude_min, altitude_max, N), 2)
|
||||
|
||||
# 原始扰动温度
|
||||
x = ktemp_wn5[cycle_no, :]
|
||||
# 傅里叶变换频谱分析
|
||||
y = ktemp_fft[cycle_no, :]
|
||||
# 滤波后的傅里叶变换频谱分析
|
||||
yy = ktemp_fft_lvbo[cycle_no, :]
|
||||
# 傅里叶逆变换后的扰动温度
|
||||
yyy = ktemp_ifft[cycle_no, :]
|
||||
|
||||
plt.figure(figsize=(15, 10)) # 调整图形大小
|
||||
# 原始信号的时间序列
|
||||
plt.subplot(2, 2, 1)
|
||||
plt.plot(t, x)
|
||||
plt.title('(a)原始信号')
|
||||
plt.xlabel('高度 (km)', labelpad=10) # 增加标签间距
|
||||
plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距
|
||||
# 原始振幅谱
|
||||
plt.subplot(2, 2, 2)
|
||||
plt.plot(f, np.abs(y) * 2 / N)
|
||||
plt.title('(b))原始振幅谱')
|
||||
plt.xlabel('频率/Hz', labelpad=10) # 增加标签间距
|
||||
plt.ylabel('振幅', labelpad=10) # 增加标签间距
|
||||
|
||||
# 通过IFFT回到时间域
|
||||
plt.subplot(2, 2, 3)
|
||||
plt.plot(t, yyy)
|
||||
plt.title('(c))傅里叶逆变换')
|
||||
plt.xlabel('高度 (km)', labelpad=10) # 增加标签间距
|
||||
plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距
|
||||
|
||||
# 滤波后的振幅谱
|
||||
plt.subplot(2, 2, 4)
|
||||
plt.plot(f, np.abs(yy) * 2 / N)
|
||||
plt.title(f'(d)滤除波长 < {lamda_low} km, > {lamda_high} km的波')
|
||||
plt.xlabel('频率/Hz', labelpad=10) # 增加标签间距
|
||||
plt.ylabel('振幅', labelpad=10) # 增加标签间距
|
||||
|
||||
# 调整子图之间的边距
|
||||
plt.subplots_adjust(top=0.8, bottom=0.2, left=0.2,
|
||||
right=0.8, hspace=0.3, wspace=0.2)
|
||||
|
||||
def day_cycle_power_wave_plot(self, cycle_no, wave_data: WaveData):
|
||||
ktemp_Nz = wave_data.Nz
|
||||
ktemp_Ptz = wave_data.Ptz
|
||||
|
||||
altitude_min, altitude_max = wave_data.alt_range
|
||||
|
||||
N = len(ktemp_Nz[cycle_no, :])
|
||||
y = np.round(np.linspace(altitude_min, altitude_max, N), 2)
|
||||
x1 = ktemp_Nz[cycle_no, :]
|
||||
x2 = ktemp_Ptz[cycle_no, :]
|
||||
|
||||
plt.figure(figsize=(12, 10)) # 调整图形大小
|
||||
# 原始信号的时间序列
|
||||
plt.subplot(1, 2, 1)
|
||||
plt.plot(x1[::-1], y, label='原始信号')
|
||||
plt.title('(a)Nz')
|
||||
plt.xlabel('Nz', labelpad=10) # 增加标签间距
|
||||
plt.ylabel('高度 (km)', labelpad=10) # 增加标签间距
|
||||
|
||||
# 原始信号的时间序列
|
||||
plt.subplot(1, 2, 2)
|
||||
plt.plot(x2[::-1], y, label='原始信号')
|
||||
plt.title('(b)Ptz')
|
||||
plt.xlabel('Ptz', labelpad=10) # 增加标签间距
|
||||
plt.ylabel('高度 (km)', labelpad=10) # 增加标签间距
|
||||
|
||||
# 调整子图之间的边距
|
||||
plt.subplots_adjust(top=0.8, bottom=0.2, left=0.1,
|
||||
right=0.8, hspace=0.3, wspace=0.2)
|
||||
plt.tight_layout() # 调整子图参数以适应图形区域
|
||||
|
||||
def month_power_wave_plot(self, wave_data: List[WaveData], date_time):
|
||||
ktemp_Nz_mon = [data.Nz for data in wave_data]
|
||||
ktemp_Ptz_mon = [data.Ptz for data in wave_data]
|
||||
|
||||
altitude_min, altitude_max = wave_data[0].alt_range
|
||||
|
||||
if ktemp_Nz_mon and ktemp_Nz_mon[0] is not None:
|
||||
nz_shape = np.array(ktemp_Nz_mon[0]).shape
|
||||
else:
|
||||
nz_shape = (15, 157)
|
||||
if ktemp_Ptz_mon and ktemp_Ptz_mon[0] is not None:
|
||||
ptz_shape = np.array(ktemp_Ptz_mon[0]).shape
|
||||
else:
|
||||
ptz_shape = (15, 157)
|
||||
y = np.round(np.linspace(altitude_min, altitude_max, nz_shape[1]), 2)
|
||||
x = np.arange(len(date_time.data))
|
||||
# 处理 ktemp_Nz_mon
|
||||
ktemp_Nz_plot = np.array([np.mean(day_data if day_data is not None else np.zeros(
|
||||
nz_shape), axis=0) for day_data in ktemp_Nz_mon])
|
||||
ktemp_Ptz_plot = np.array(
|
||||
[np.mean(day_data if day_data is not None else np.zeros(nz_shape), axis=0) for day_data in ktemp_Ptz_mon])
|
||||
# 处理 ktemp_Ptz_mon(以100为界剔除异常值)
|
||||
# ktemp_Ptz_plot = np.array([np.mean(day_data if day_data is not None and np.all(day_data <= 100) else np.zeros(ptz_shape), axis=0) for day_data in ktemp_Ptz_mon])
|
||||
# 创建一个图形,并指定两个子图
|
||||
fig, axs = plt.subplots(1, 2, figsize=(15, 10))
|
||||
|
||||
# 第一幅图 (a) NZ
|
||||
cax1 = axs[0].imshow(ktemp_Nz_plot.T[::-1], aspect='auto', cmap='rainbow', origin='lower',
|
||||
extent=[x[0], x[-1], y[0], y[-1]])
|
||||
fig.colorbar(cax1, ax=axs[0]) # 为第一幅图添加颜色条
|
||||
axs[0].set_title('(a) NZ')
|
||||
axs[0].set_xlabel('Time')
|
||||
axs[0].set_ylabel('Height')
|
||||
axs[0].set_yticks(np.linspace(30, 100, 8))
|
||||
axs[0].set_yticklabels(np.round(np.linspace(30, 100, 8), 1))
|
||||
axs[0].set_xticks(x)
|
||||
axs[0].set_xticklabels(x)
|
||||
|
||||
# 第二幅图 (b) PTZ
|
||||
cax2 = axs[1].imshow(np.log(ktemp_Ptz_plot.T[::-1]), aspect='auto',
|
||||
cmap='rainbow', origin='lower', extent=[x[0], x[-1], y[0], y[-1]])
|
||||
fig.colorbar(cax2, ax=axs[1]) # 为第二幅图添加颜色条
|
||||
axs[1].set_title('(b) PTZ')
|
||||
axs[1].set_xlabel('Time')
|
||||
axs[1].set_ylabel('Height')
|
||||
axs[1].set_yticks(np.linspace(30, 100, 8))
|
||||
axs[1].set_yticklabels(np.round(np.linspace(30, 100, 8), 1))
|
||||
axs[1].set_xticks(x)
|
||||
axs[1].set_xticklabels(x)
|
||||
|
||||
# 调整子图之间的边距
|
||||
plt.subplots_adjust(top=0.9, bottom=0.1, left=0.05,
|
||||
right=0.95, hspace=0.3, wspace=0.3)
|
||||
plt.tight_layout() # 调整布局以避免重叠
|
||||
|
||||
def year_power_wave_plot(self, year_wave: YearlyData):
|
||||
# 假设我们已经从process_yearly_data函数中获取了一年的Nz和Ptz数据
|
||||
results = year_wave.to_dict()
|
||||
altitude_min, altitude_max = results["alt_range"][0]
|
||||
|
||||
ktemp_Nz_mon_list = results["ktemp_Nz_mon_list"]
|
||||
ktemp_Ptz_mon_list = results["ktemp_Ptz_mon_list"]
|
||||
ktemp_Ptz_mon_list.pop(0)
|
||||
ktemp_Nz_mon_list.pop(0)
|
||||
|
||||
# 准备日期数据,这里假设date_time_list是一年中的所有日期
|
||||
date_time_list = results["date_time_list"]
|
||||
date_time_list.pop(0)
|
||||
# 将日期转换为matplotlib可以理解的数字格式
|
||||
date_nums = mdates.date2num(date_time_list)
|
||||
|
||||
# 获取date_time_list长度作为横坐标新的依据
|
||||
x_ticks_length = len(date_time_list)
|
||||
x_ticks = np.arange(0, x_ticks_length, 30)
|
||||
x_labels = [date_time_list[i] if i < len(
|
||||
date_time_list) else "" for i in x_ticks]
|
||||
|
||||
# 准备高度数据
|
||||
# 假设高度数据有157个点
|
||||
y = np.round(np.linspace(altitude_min, altitude_max, 157), 2)
|
||||
|
||||
# 创建一个图形,并指定两个子图
|
||||
fig, axs = plt.subplots(1, 2, figsize=(20, 10))
|
||||
|
||||
# 处理 ktemp_Nz_mon
|
||||
ktemp_Nz_plot = np.array(
|
||||
[np.mean(day_data if day_data is not None else np.zeros((15, 157)), axis=0) for day_data in ktemp_Nz_mon_list])
|
||||
# 处理 ktemp_Ptz_mon
|
||||
ktemp_Ptz_plot = np.array(
|
||||
[np.mean(day_data if day_data is not None else np.zeros((15, 157)), axis=0) for day_data in ktemp_Ptz_mon_list])
|
||||
# ktemp_Ptz_plot = np.array(
|
||||
# [np.mean(day_data if day_data is not None and np.all(day_data <= 100) else np.zeros((15, 157)), axis=0) for
|
||||
# day_data in ktemp_Ptz_mon_list])
|
||||
|
||||
# 第一幅图 (a) NZ
|
||||
cax1 = axs[0].imshow(ktemp_Nz_plot.T[::-1], aspect='auto', cmap='rainbow', origin='lower',
|
||||
extent=[0, x_ticks_length - 1, y[0], y[-1]])
|
||||
fig.colorbar(cax1, ax=axs[0]) # 为第一幅图添加颜色条
|
||||
axs[0].set_title('(a) NZ')
|
||||
axs[0].set_xlabel('Time')
|
||||
axs[0].set_ylabel('Height')
|
||||
axs[0].set_yticks(np.linspace(30, 100, 8))
|
||||
axs[0].set_yticklabels(np.round(np.linspace(30, 100, 8), 1))
|
||||
axs[0].set_xticks(x_ticks) # 设置新的横坐标刻度
|
||||
axs[0].set_xticklabels(x_labels, rotation=45)
|
||||
|
||||
# 第二幅图 (b) PTZ
|
||||
cax2 = axs[1].imshow(np.log(ktemp_Ptz_plot.T[::-1]), aspect='auto', cmap='rainbow', origin='lower',
|
||||
extent=[0, x_ticks_length - 1, y[0], y[-1]])
|
||||
fig.colorbar(cax2, ax=axs[1]) # 为第二幅图添加颜色条
|
||||
axs[1].set_title('(b) PTZ')
|
||||
axs[1].set_xlabel('Time')
|
||||
axs[1].set_ylabel('Height')
|
||||
axs[1].set_yticks(np.linspace(30, 100, 8))
|
||||
axs[1].set_yticklabels(np.round(np.linspace(30, 100, 8), 1))
|
||||
axs[1].set_xticks(x_ticks) # 设置新的横坐标刻度
|
||||
axs[1].set_xticklabels(x_labels, rotation=45)
|
||||
|
||||
# 调整子图之间的边距
|
||||
plt.subplots_adjust(top=0.9, bottom=0.1, left=0.05,
|
||||
right=0.95, hspace=0.3, wspace=0.3)
|
||||
plt.tight_layout() # 调整布局以避免重叠
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Tuple, List
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from matplotlib.figure import Figure
|
||||
from matplotlib.axes import Axes
|
||||
import matplotlib.dates as mdates
|
||||
|
||||
from saber.process import DataProcessor, WaveData, YearlyData
|
||||
|
||||
|
||||
@dataclass
|
||||
class PlotConfig:
|
||||
"""绘图配置类"""
|
||||
figsize: Tuple[int, int] = (16, 10)
|
||||
dpi: int = 100
|
||||
cmap: str = 'viridis'
|
||||
title_fontsize: int = 12
|
||||
label_fontsize: int = 10
|
||||
legend_fontsize: int = 8
|
||||
tick_fontsize: int = 8
|
||||
|
||||
|
||||
class Renderer:
|
||||
def __init__(self, config: Optional[PlotConfig] = None):
|
||||
self.config = config or PlotConfig()
|
||||
plt.rcParams['font.family'] = 'SimHei' # 设置为黑体(需要你的环境中有该字体)
|
||||
plt.rcParams['axes.unicode_minus'] = False # 解决负号'-'显示为方块的问题
|
||||
|
||||
def _create_figure(self, rows: int, cols: int) -> Tuple[Figure, List[Axes]]:
|
||||
"""创建图形和轴对象"""
|
||||
fig, axes = plt.subplots(
|
||||
rows, cols, figsize=self.config.figsize, dpi=self.config.dpi)
|
||||
return fig, axes.flatten() if isinstance(axes, np.ndarray) else [axes]
|
||||
|
||||
def _setup_subplot(self, ax: Axes, x: np.ndarray, y1: np.ndarray,
|
||||
y2: Optional[np.ndarray], title: str, y_limits: Tuple[float, float]):
|
||||
"""设置单个子图的样式和数据"""
|
||||
ax.plot(x, y1, label='原始信号', linewidth=1.5)
|
||||
if y2 is not None:
|
||||
ax.plot(x, y2, label='拟合信号', linestyle='--', linewidth=1.5)
|
||||
|
||||
ax.set_title(title, fontsize=self.config.title_fontsize)
|
||||
ax.set_xlabel('Cycles', labelpad=10,
|
||||
fontsize=self.config.label_fontsize)
|
||||
ax.set_ylabel('温度 (K)', labelpad=10,
|
||||
fontsize=self.config.label_fontsize)
|
||||
ax.legend(fontsize=self.config.legend_fontsize)
|
||||
ax.tick_params(axis='both', labelsize=self.config.tick_fontsize)
|
||||
ax.set_ylim(y_limits)
|
||||
ax.grid(True, linestyle='--', alpha=0.7)
|
||||
|
||||
def plot_wave_fitting(self, wave_data: WaveData, height_no: int):
|
||||
"""绘制波数拟合结果"""
|
||||
fig, axes = self._create_figure(2, 3)
|
||||
|
||||
# 准备数据
|
||||
N = len(wave_data.wn0[:, height_no])
|
||||
x = np.arange(N)
|
||||
|
||||
data_pairs = [
|
||||
(wave_data.wn0[:, height_no], wave_data.fit_wn[0][:, height_no]),
|
||||
(wave_data.wn[0][:, height_no], wave_data.fit_wn[1][:, height_no]),
|
||||
(wave_data.wn[1][:, height_no], wave_data.fit_wn[2][:, height_no]),
|
||||
(wave_data.wn[2][:, height_no], wave_data.fit_wn[3][:, height_no]),
|
||||
(wave_data.wn[3][:, height_no], wave_data.fit_wn[4][:, height_no]),
|
||||
(wave_data.wn[4][:, height_no], None)
|
||||
]
|
||||
|
||||
# 计算统一的y轴范围
|
||||
all_values = [
|
||||
val for pair in data_pairs for val in pair if val is not None]
|
||||
y_limits = (np.min(all_values), np.max(all_values))
|
||||
|
||||
# 绘制子图
|
||||
for i, (y1, y2) in enumerate(data_pairs):
|
||||
title = f'({"abcdef"[i]})波数k={i + 1 if i < 5 else "滤波1-5后信号"}'
|
||||
self._setup_subplot(axes[i], x, y1, y2, title, y_limits)
|
||||
|
||||
# 调整布局
|
||||
plt.tight_layout()
|
||||
plt.subplots_adjust(top=0.9, bottom=0.1, left=0.1, right=0.9,
|
||||
hspace=0.3, wspace=0.3)
|
||||
|
||||
def day_fft_ifft_plot(self, cycle_no, wave_data: WaveData):
|
||||
|
||||
ktemp_wn5 = wave_data.wn[4]
|
||||
ktemp_fft = wave_data.fft
|
||||
ktemp_fft_lvbo = wave_data.fft_filtered
|
||||
ktemp_ifft = wave_data.ifft
|
||||
|
||||
altitude_min, altitude_max = wave_data.alt_range
|
||||
lamda_low, lamda_high = wave_data.lambda_range
|
||||
|
||||
N = len(ktemp_wn5[cycle_no, :])
|
||||
# 采样时间间隔,其倒数等于采用频率,以1km为标准尺度等同于1s,假设波的速度为1km/s
|
||||
dt = (altitude_max-altitude_min)/(N-1)
|
||||
# 时间序列索引
|
||||
n = np.arange(N)
|
||||
f = n / (N * dt)
|
||||
t = np.round(np.linspace(altitude_min, altitude_max, N), 2)
|
||||
|
||||
# 原始扰动温度
|
||||
x = ktemp_wn5[cycle_no, :]
|
||||
# 傅里叶变换频谱分析
|
||||
y = ktemp_fft[cycle_no, :]
|
||||
# 滤波后的傅里叶变换频谱分析
|
||||
yy = ktemp_fft_lvbo[cycle_no, :]
|
||||
# 傅里叶逆变换后的扰动温度
|
||||
yyy = ktemp_ifft[cycle_no, :]
|
||||
|
||||
plt.figure(figsize=(15, 10)) # 调整图形大小
|
||||
# 原始信号的时间序列
|
||||
plt.subplot(2, 2, 1)
|
||||
plt.plot(t, x)
|
||||
plt.title('(a)原始信号')
|
||||
plt.xlabel('高度 (km)', labelpad=10) # 增加标签间距
|
||||
plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距
|
||||
# 原始振幅谱
|
||||
plt.subplot(2, 2, 2)
|
||||
plt.plot(f, np.abs(y) * 2 / N)
|
||||
plt.title('(b))原始振幅谱')
|
||||
plt.xlabel('频率/Hz', labelpad=10) # 增加标签间距
|
||||
plt.ylabel('振幅', labelpad=10) # 增加标签间距
|
||||
|
||||
# 通过IFFT回到时间域
|
||||
plt.subplot(2, 2, 3)
|
||||
plt.plot(t, yyy)
|
||||
plt.title('(c))傅里叶逆变换')
|
||||
plt.xlabel('高度 (km)', labelpad=10) # 增加标签间距
|
||||
plt.ylabel('温度 (K)', labelpad=10) # 增加标签间距
|
||||
|
||||
# 滤波后的振幅谱
|
||||
plt.subplot(2, 2, 4)
|
||||
plt.plot(f, np.abs(yy) * 2 / N)
|
||||
plt.title(f'(d)滤除波长 < {lamda_low} km, > {lamda_high} km的波')
|
||||
plt.xlabel('频率/Hz', labelpad=10) # 增加标签间距
|
||||
plt.ylabel('振幅', labelpad=10) # 增加标签间距
|
||||
|
||||
# 调整子图之间的边距
|
||||
plt.subplots_adjust(top=0.8, bottom=0.2, left=0.2,
|
||||
right=0.8, hspace=0.3, wspace=0.2)
|
||||
|
||||
def day_cycle_power_wave_plot(self, cycle_no, wave_data: WaveData):
|
||||
ktemp_Nz = wave_data.Nz
|
||||
ktemp_Ptz = wave_data.Ptz
|
||||
|
||||
altitude_min, altitude_max = wave_data.alt_range
|
||||
|
||||
N = len(ktemp_Nz[cycle_no, :])
|
||||
y = np.round(np.linspace(altitude_min, altitude_max, N), 2)
|
||||
x1 = ktemp_Nz[cycle_no, :]
|
||||
x2 = ktemp_Ptz[cycle_no, :]
|
||||
|
||||
plt.figure(figsize=(12, 10)) # 调整图形大小
|
||||
# 原始信号的时间序列
|
||||
plt.subplot(1, 2, 1)
|
||||
plt.plot(x1[::-1], y, label='原始信号')
|
||||
plt.title('(a)Nz')
|
||||
plt.xlabel('Nz', labelpad=10) # 增加标签间距
|
||||
plt.ylabel('高度 (km)', labelpad=10) # 增加标签间距
|
||||
|
||||
# 原始信号的时间序列
|
||||
plt.subplot(1, 2, 2)
|
||||
plt.plot(x2[::-1], y, label='原始信号')
|
||||
plt.title('(b)Ptz')
|
||||
plt.xlabel('Ptz', labelpad=10) # 增加标签间距
|
||||
plt.ylabel('高度 (km)', labelpad=10) # 增加标签间距
|
||||
|
||||
# 调整子图之间的边距
|
||||
plt.subplots_adjust(top=0.8, bottom=0.2, left=0.1,
|
||||
right=0.8, hspace=0.3, wspace=0.2)
|
||||
plt.tight_layout() # 调整子图参数以适应图形区域
|
||||
|
||||
def month_power_wave_plot(self, wave_data: List[WaveData], date_time):
|
||||
ktemp_Nz_mon = [data.Nz for data in wave_data]
|
||||
ktemp_Ptz_mon = [data.Ptz for data in wave_data]
|
||||
|
||||
altitude_min, altitude_max = wave_data[0].alt_range
|
||||
|
||||
if ktemp_Nz_mon and ktemp_Nz_mon[0] is not None:
|
||||
nz_shape = np.array(ktemp_Nz_mon[0]).shape
|
||||
else:
|
||||
nz_shape = (15, 157)
|
||||
if ktemp_Ptz_mon and ktemp_Ptz_mon[0] is not None:
|
||||
ptz_shape = np.array(ktemp_Ptz_mon[0]).shape
|
||||
else:
|
||||
ptz_shape = (15, 157)
|
||||
y = np.round(np.linspace(altitude_min, altitude_max, nz_shape[1]), 2)
|
||||
x = np.arange(len(date_time.data))
|
||||
# 处理 ktemp_Nz_mon
|
||||
ktemp_Nz_plot = np.array([np.mean(day_data if day_data is not None else np.zeros(
|
||||
nz_shape), axis=0) for day_data in ktemp_Nz_mon])
|
||||
ktemp_Ptz_plot = np.array(
|
||||
[np.mean(day_data if day_data is not None else np.zeros(nz_shape), axis=0) for day_data in ktemp_Ptz_mon])
|
||||
# 处理 ktemp_Ptz_mon(以100为界剔除异常值)
|
||||
# ktemp_Ptz_plot = np.array([np.mean(day_data if day_data is not None and np.all(day_data <= 100) else np.zeros(ptz_shape), axis=0) for day_data in ktemp_Ptz_mon])
|
||||
# 创建一个图形,并指定两个子图
|
||||
fig, axs = plt.subplots(1, 2, figsize=(15, 10))
|
||||
|
||||
# 第一幅图 (a) NZ
|
||||
cax1 = axs[0].imshow(ktemp_Nz_plot.T[::-1], aspect='auto', cmap='rainbow', origin='lower',
|
||||
extent=[x[0], x[-1], y[0], y[-1]])
|
||||
fig.colorbar(cax1, ax=axs[0]) # 为第一幅图添加颜色条
|
||||
axs[0].set_title('(a) NZ')
|
||||
axs[0].set_xlabel('Time')
|
||||
axs[0].set_ylabel('Height')
|
||||
axs[0].set_yticks(np.linspace(30, 100, 8))
|
||||
axs[0].set_yticklabels(np.round(np.linspace(30, 100, 8), 1))
|
||||
axs[0].set_xticks(x)
|
||||
axs[0].set_xticklabels(x)
|
||||
|
||||
# 第二幅图 (b) PTZ
|
||||
cax2 = axs[1].imshow(np.log(ktemp_Ptz_plot.T[::-1]), aspect='auto',
|
||||
cmap='rainbow', origin='lower', extent=[x[0], x[-1], y[0], y[-1]])
|
||||
fig.colorbar(cax2, ax=axs[1]) # 为第二幅图添加颜色条
|
||||
axs[1].set_title('(b) PTZ')
|
||||
axs[1].set_xlabel('Time')
|
||||
axs[1].set_ylabel('Height')
|
||||
axs[1].set_yticks(np.linspace(30, 100, 8))
|
||||
axs[1].set_yticklabels(np.round(np.linspace(30, 100, 8), 1))
|
||||
axs[1].set_xticks(x)
|
||||
axs[1].set_xticklabels(x)
|
||||
|
||||
# 调整子图之间的边距
|
||||
plt.subplots_adjust(top=0.9, bottom=0.1, left=0.05,
|
||||
right=0.95, hspace=0.3, wspace=0.3)
|
||||
plt.tight_layout() # 调整布局以避免重叠
|
||||
|
||||
def year_power_wave_plot(self, year_wave: YearlyData):
|
||||
# 假设我们已经从process_yearly_data函数中获取了一年的Nz和Ptz数据
|
||||
results = year_wave.to_dict()
|
||||
altitude_min, altitude_max = results["alt_range"][0]
|
||||
|
||||
ktemp_Nz_mon_list = results["ktemp_Nz_mon_list"]
|
||||
ktemp_Ptz_mon_list = results["ktemp_Ptz_mon_list"]
|
||||
ktemp_Ptz_mon_list.pop(0)
|
||||
ktemp_Nz_mon_list.pop(0)
|
||||
|
||||
# 准备日期数据,这里假设date_time_list是一年中的所有日期
|
||||
date_time_list = results["date_time_list"]
|
||||
date_time_list.pop(0)
|
||||
# 将日期转换为matplotlib可以理解的数字格式
|
||||
date_nums = mdates.date2num(date_time_list)
|
||||
|
||||
# 获取date_time_list长度作为横坐标新的依据
|
||||
x_ticks_length = len(date_time_list)
|
||||
x_ticks = np.arange(0, x_ticks_length, 30)
|
||||
x_labels = [date_time_list[i] if i < len(
|
||||
date_time_list) else "" for i in x_ticks]
|
||||
|
||||
# 准备高度数据
|
||||
# 假设高度数据有157个点
|
||||
y = np.round(np.linspace(altitude_min, altitude_max, 157), 2)
|
||||
|
||||
# 创建一个图形,并指定两个子图
|
||||
fig, axs = plt.subplots(1, 2, figsize=(20, 10))
|
||||
|
||||
# 处理 ktemp_Nz_mon
|
||||
ktemp_Nz_plot = np.array(
|
||||
[np.mean(day_data if day_data is not None else np.zeros((15, 157)), axis=0) for day_data in ktemp_Nz_mon_list])
|
||||
# 处理 ktemp_Ptz_mon
|
||||
ktemp_Ptz_plot = np.array(
|
||||
[np.mean(day_data if day_data is not None else np.zeros((15, 157)), axis=0) for day_data in ktemp_Ptz_mon_list])
|
||||
# ktemp_Ptz_plot = np.array(
|
||||
# [np.mean(day_data if day_data is not None and np.all(day_data <= 100) else np.zeros((15, 157)), axis=0) for
|
||||
# day_data in ktemp_Ptz_mon_list])
|
||||
|
||||
# 第一幅图 (a) NZ
|
||||
cax1 = axs[0].imshow(ktemp_Nz_plot.T[::-1], aspect='auto', cmap='rainbow', origin='lower',
|
||||
extent=[0, x_ticks_length - 1, y[0], y[-1]])
|
||||
fig.colorbar(cax1, ax=axs[0]) # 为第一幅图添加颜色条
|
||||
axs[0].set_title('(a) NZ')
|
||||
axs[0].set_xlabel('Time')
|
||||
axs[0].set_ylabel('Height')
|
||||
axs[0].set_yticks(np.linspace(30, 100, 8))
|
||||
axs[0].set_yticklabels(np.round(np.linspace(30, 100, 8), 1))
|
||||
axs[0].set_xticks(x_ticks) # 设置新的横坐标刻度
|
||||
axs[0].set_xticklabels(x_labels, rotation=45)
|
||||
|
||||
# 第二幅图 (b) PTZ
|
||||
cax2 = axs[1].imshow(np.log(ktemp_Ptz_plot.T[::-1]), aspect='auto', cmap='rainbow', origin='lower',
|
||||
extent=[0, x_ticks_length - 1, y[0], y[-1]])
|
||||
fig.colorbar(cax2, ax=axs[1]) # 为第二幅图添加颜色条
|
||||
axs[1].set_title('(b) PTZ')
|
||||
axs[1].set_xlabel('Time')
|
||||
axs[1].set_ylabel('Height')
|
||||
axs[1].set_yticks(np.linspace(30, 100, 8))
|
||||
axs[1].set_yticklabels(np.round(np.linspace(30, 100, 8), 1))
|
||||
axs[1].set_xticks(x_ticks) # 设置新的横坐标刻度
|
||||
axs[1].set_xticklabels(x_labels, rotation=45)
|
||||
|
||||
# 调整子图之间的边距
|
||||
plt.subplots_adjust(top=0.9, bottom=0.1, left=0.05,
|
||||
right=0.95, hspace=0.3, wspace=0.3)
|
||||
plt.tight_layout() # 调整布局以避免重叠
|
||||
|
||||
556
saber/utils.py
556
saber/utils.py
@ -1,278 +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
|
||||
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
|
||||
|
||||
35
tidi/__init__.py
Normal file
35
tidi/__init__.py
Normal file
@ -0,0 +1,35 @@
|
||||
import glob
|
||||
from io import BytesIO
|
||||
from flask import Blueprint, request, send_file
|
||||
from matplotlib import pyplot as plt
|
||||
|
||||
from tidi.staged.plot import tidi_render
|
||||
|
||||
|
||||
tidi_module = Blueprint("Tidi", __name__)
|
||||
|
||||
@tidi_module.route('/metadata')
|
||||
def get_all_years():
|
||||
res = glob.glob("./tidi/data/**/**.txt", recursive=True)
|
||||
# search for the folder name that is year
|
||||
|
||||
return {
|
||||
"path": res
|
||||
}
|
||||
|
||||
@tidi_module.route('/render/wave')
|
||||
def render_wave():
|
||||
mode = request.args.get('mode')
|
||||
year = request.args.get('year')
|
||||
k = request.args.get('k')
|
||||
T = request.args.get('T')
|
||||
year = int(year)
|
||||
k = int(k)
|
||||
T = int(T)
|
||||
|
||||
tidi_render(mode, year, k, T)
|
||||
buffer = BytesIO()
|
||||
plt.savefig(buffer, format="png")
|
||||
buffer.seek(0)
|
||||
|
||||
return send_file(buffer, mimetype="image/png")
|
||||
1756
tidi/plot.py
1756
tidi/plot.py
File diff suppressed because it is too large
Load Diff
205
tidi/staged/plot.py
Normal file
205
tidi/staged/plot.py
Normal file
@ -0,0 +1,205 @@
|
||||
#此代码是对数据处理后的txt数据进行行星波参数提取绘图
|
||||
#2006_TIDI_V_Meridional_data.txt也可以做同样处理,一个是经向风提取的行星波,一个是纬向风的
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from scipy.optimize import curve_fit
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# 解决绘图中中文不能显示的问题
|
||||
import matplotlib
|
||||
# 设置中文显示和负号正常显示
|
||||
matplotlib.rcParams['font.sans-serif'] = ['SimHei'] # 显示中文
|
||||
matplotlib.rcParams['axes.unicode_minus'] = False # 正常显示负号
|
||||
# 读取一年的数据文件
|
||||
# 设置初始参数
|
||||
# initial_guess = [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0] # v0, a1, b1, a2, b2, a3, b3
|
||||
initial_guess = [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,0.5, 0.5, 0.5, 0.5,0.5, 0.5, 0.5, 0.5] # 9个 a 和 9个 b 参数
|
||||
|
||||
# 设置参数界限
|
||||
bounds = (
|
||||
[0, -np.inf, 0, -np.inf, 0, -np.inf, 0, -np.inf, 0, -np.inf, 0, -np.inf, 0, -np.inf, 0, -np.inf, 0, -np.inf], # 下界
|
||||
[np.inf, np.inf, np.inf, np.inf, np.inf, np.inf, np.inf, np.inf, np.inf, np.inf, np.inf, np.inf, np.inf, np.inf, np.inf,
|
||||
np.inf, np.inf, np.inf]) # 上界
|
||||
|
||||
|
||||
|
||||
# 定义拟合函数
|
||||
|
||||
def tidi_render(mode, year, k, T=10):
|
||||
# 用于存储拟合参数结果的列表
|
||||
all_fit_results = []
|
||||
|
||||
# 设置最小数据量的阈值
|
||||
min_data_points = 36
|
||||
if mode != 'V_Zonal' and mode != 'V_Meridional':
|
||||
raise ValueError('mode must be V_Zonal or V_Meridional')
|
||||
if k not in list(range(-4, 5)):
|
||||
raise ValueError('k must be in range(-4, 5)')
|
||||
df = pd.read_csv(f'./tidi/data/{year}/TIDI_{mode}_data.txt', sep='\s+')
|
||||
# 删除有 NaN 的行
|
||||
# V_Meridional
|
||||
df = df.dropna(subset=[mode])
|
||||
# 进行多个时间窗口的拟合
|
||||
#T应该取5、10、16
|
||||
# todo:对于5日波,滑动窗口选择15天
|
||||
def u_func(x, *params):
|
||||
a1, b1, a2, b2, a3, b3, a4, b4, a5, b5, a6, b6, a7, b7, a8, b8, a9, b9 = params
|
||||
return (
|
||||
a1 * np.sin((2 * np.pi / T) * t - 4 * x + b1)
|
||||
+ a2 * np.sin((2 * np.pi / T) * t - 3 * x + b2)
|
||||
+ a3 * np.sin((2 * np.pi / T) * t - 2 * x + b3)
|
||||
+ a4 * np.sin((2 * np.pi / T) * t - x + b4)
|
||||
+ a5 * np.sin((2 * np.pi / T) * t + b5)
|
||||
+ a6 * np.sin((2 * np.pi / T) * t + x + b6)
|
||||
+ a7 * np.sin((2 * np.pi / T) * t + 2 * x + b7)
|
||||
+ a8 * np.sin((2 * np.pi / T) * t + 3 * x + b8)
|
||||
+ a9 * np.sin((2 * np.pi / T) * t + 4 * x + b9)
|
||||
)
|
||||
for start_day in range(0, 365-3*T): # 最后一个窗口为[351, 366]
|
||||
end_day = start_day + 3 * T # 每个窗口的结束时间为 start_day + 3*T
|
||||
|
||||
# 选择当前窗口的数据
|
||||
df_8 = df[(df['UTime'] >= start_day) & (df['UTime'] <= end_day)]
|
||||
|
||||
# 检查当前窗口的数据量
|
||||
if len(df_8) < min_data_points:
|
||||
# 输出数据量不足的警告
|
||||
print(f"数据量不足,无法拟合:{start_day} 到 {end_day},数据点数量:{len(df_8)}")
|
||||
# 将拟合参数设置为 NaN
|
||||
all_fit_results.append([np.nan] * 18)
|
||||
continue # 跳过当前时间窗口,继续下一个窗口
|
||||
|
||||
# 提取时间、经度、温度数据
|
||||
t = np.array(df_8['UTime']) # 时间
|
||||
x = np.array(df_8['Longitude_Radians']) # 经度弧度制
|
||||
temperature = np.array(df_8[mode]) # 经向风速,因变量
|
||||
|
||||
# 用T进行拟合
|
||||
popt, pcov = curve_fit(u_func, x, temperature, p0=initial_guess, bounds=bounds, maxfev=50000)
|
||||
|
||||
# 将拟合结果添加到列表中
|
||||
all_fit_results.append(popt)
|
||||
|
||||
# 将结果转换为DataFrame
|
||||
columns = ['a1', 'b1', 'a2', 'b2', 'a3', 'b3', 'a4', 'b4', 'a5', 'b5', 'a6', 'b6', 'a7', 'b7', 'a8', 'b8', 'a9', 'b9']
|
||||
fit_df = pd.DataFrame(all_fit_results, columns=columns) # fit_df即为拟合的参数汇总
|
||||
|
||||
# -------------------------------画图----------------------------
|
||||
#a1-a9,对应波数-4、-3、-2、-1、0、1、2、3、4的行星波振幅
|
||||
a_columns = ['a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9']
|
||||
k_values = list(range(-4, 5)) # 从 -4 到 4
|
||||
|
||||
# 创建一个字典映射 k 值到 a_columns
|
||||
k_to_a = {f'k={k}': a for k, a in zip(k_values, a_columns)}
|
||||
|
||||
# 获取索引并转换为 numpy 数组
|
||||
x_values = fit_df.index.to_numpy()
|
||||
|
||||
# 对每一列生成独立的图
|
||||
col = k_to_a[f'k={k}']
|
||||
plt.figure(figsize=(8, 6)) # 创建新的图形
|
||||
plt.plot(x_values, fit_df[col].values)
|
||||
plt.title(f'k={k} 振幅图')
|
||||
plt.xlabel('Day')
|
||||
plt.ylabel('振幅')
|
||||
|
||||
# 设置横坐标的动态调整
|
||||
adjusted_x_values = x_values + (3 * T + 1) / 2
|
||||
if len(adjusted_x_values) > 50:
|
||||
step = 30
|
||||
tick_positions = adjusted_x_values[::step] # 选择每30个点
|
||||
tick_labels = [f'{int(val)}' for val in tick_positions]
|
||||
else:
|
||||
tick_positions = adjusted_x_values
|
||||
tick_labels = [f'{int(val)}' for val in tick_positions]
|
||||
|
||||
plt.xticks(ticks=tick_positions, labels=tick_labels)
|
||||
|
||||
# plt.show() # 显示图形
|
||||
|
||||
if __name__ == '__main__':
|
||||
tidi_render('V_Zonal', 2015, 1)
|
||||
tidi_render('V_Meridional', 2015, 1)
|
||||
|
||||
# # 用于存储拟合参数结果的列表
|
||||
# all_fit_results = []
|
||||
#
|
||||
# # 设置最小数据量的阈值
|
||||
# min_data_points = 36
|
||||
#
|
||||
# # 进行多个时间窗口的拟合
|
||||
# # todo:对于5日波,滑动窗口选择15天
|
||||
# for start_day in range(0, 351): # 最后一个窗口为[351, 366]
|
||||
# end_day = start_day + 15 # 设置每个窗口的结束时间
|
||||
#
|
||||
# # 选择当前窗口的数据
|
||||
# df_8 = df[(df['UTime'] >= start_day) & (df['UTime'] <= end_day)]
|
||||
#
|
||||
# # 检查当前窗口的数据量
|
||||
# if len(df_8) < min_data_points:
|
||||
# # 输出数据量不足的警告
|
||||
# print(f"数据量不足,无法拟合:{start_day} 到 {end_day},数据点数量:{len(df_8)}")
|
||||
# # 将拟合参数设置为 NaN
|
||||
# all_fit_results.append([np.nan] * 18)
|
||||
# continue # 跳过当前时间窗口,继续下一个窗口
|
||||
#
|
||||
#
|
||||
# # 提取时间、经度、温度数据
|
||||
# t = np.array(df_8['UTime']) # 时间
|
||||
# x = np.array(df_8['Longitude_Radians']) # 经度弧度制
|
||||
# temperature = np.array(df_8['V_Zonal']) # 经向风速,因变量
|
||||
#
|
||||
# # 用T=5进行拟合
|
||||
# for T in [5]:
|
||||
# popt, pcov = curve_fit(u_func, x, temperature, p0=initial_guess, bounds=bounds, maxfev=50000)
|
||||
#
|
||||
# # 将拟合结果添加到列表中
|
||||
# all_fit_results.append(popt)
|
||||
#
|
||||
# # 将结果转换为DataFrame
|
||||
# columns = ['a1', 'b1', 'a2', 'b2', 'a3', 'b3', 'a4', 'b4', 'a5', 'b5', 'a6', 'b6', 'a7', 'b7', 'a8', 'b8', 'a9', 'b9']
|
||||
# fit_df = pd.DataFrame(all_fit_results, columns=columns) # fit_df即为拟合的参数汇总
|
||||
#
|
||||
# # -------------------------------画图----------------------------
|
||||
# # 定义 a1 到 a9 的列名
|
||||
# a_columns = ['a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9']
|
||||
# a_values = fit_df[a_columns].values
|
||||
#
|
||||
# # 获取索引并转换为 numpy 数组
|
||||
# x_values = fit_df.index.to_numpy()
|
||||
#
|
||||
# # 对每一列生成独立的图
|
||||
# for i, col in enumerate(a_columns):
|
||||
# plt.figure(figsize=(8, 6)) # 创建新的图形
|
||||
# plt.plot(x_values, a_values[:, i])
|
||||
# plt.title(f'{col} 振幅图')
|
||||
# plt.xlabel('Day')
|
||||
# plt.ylabel('振幅')
|
||||
# plt.show() # 显示图形
|
||||
|
||||
# # 提取 a1 到 a9 列的数据
|
||||
# a_columns = ['a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9']
|
||||
# a_values = fit_df[a_columns].values
|
||||
# # 获取索引并转换为 numpy 数组
|
||||
# x_values = fit_df.index.to_numpy()
|
||||
#
|
||||
# # 创建一个图形和子图
|
||||
# plt.figure(figsize=(10, 6))
|
||||
#
|
||||
# # 绘制每一条线
|
||||
# for i, col in enumerate(a_columns):
|
||||
# plt.plot(x_values, a_values[:, i], label=col)
|
||||
#
|
||||
# # 添加标题、标签和图例
|
||||
# plt.title('a1 to a9 振幅图')
|
||||
# plt.xlabel('Day')
|
||||
# plt.ylabel('振幅')
|
||||
# plt.legend()
|
||||
# # 显示图形
|
||||
# plt.tight_layout()
|
||||
# plt.show()
|
||||
|
||||
|
||||
|
||||
|
||||
210
tidi/staged/process.py
Normal file
210
tidi/staged/process.py
Normal file
@ -0,0 +1,210 @@
|
||||
import os
|
||||
import numpy as np
|
||||
from scipy.io import loadmat
|
||||
import pandas as pd
|
||||
|
||||
#第156行代码,纬度可以自己选择,但是都是每5°做一个选择
|
||||
#70km-120km每2.5km一个,共21个,第六个就是82.5km高度的情况,高度也可以自己选择
|
||||
|
||||
fixed_height_index = 6
|
||||
# 初始化空列表来存储每天的数据
|
||||
height_list = []
|
||||
lat_list = []
|
||||
lon_list = []
|
||||
vmeridional_list = []
|
||||
vzonal_list = []
|
||||
|
||||
# 文件路径
|
||||
base_path = "./tidi/data/2022/"
|
||||
|
||||
# 循环读取从第1天到第365天的数据
|
||||
for day in range(1, 366): # 365天数据,确保循环次数为365
|
||||
# 构建文件名(使用三位数格式)
|
||||
day_str = f"{day:03d}" # 格式化数字为三位数,前面补0
|
||||
height_file = f"{day_str}_Height.mat"
|
||||
lat_file = f"{day_str}_Lat.mat"
|
||||
lon_file = f"{day_str}_Lon.mat"
|
||||
vmeridional_file = f"{day_str}_VMerdional.mat"
|
||||
vzonal_file = f"{day_str}_Vzonal.mat"
|
||||
|
||||
# 检查文件是否存在
|
||||
try:
|
||||
if not os.path.exists(os.path.join(base_path, height_file)):
|
||||
raise FileNotFoundError(f"{height_file} not found")
|
||||
if not os.path.exists(os.path.join(base_path, lat_file)):
|
||||
raise FileNotFoundError(f"{lat_file} not found")
|
||||
if not os.path.exists(os.path.join(base_path, lon_file)):
|
||||
raise FileNotFoundError(f"{lon_file} not found")
|
||||
if not os.path.exists(os.path.join(base_path, vmeridional_file)):
|
||||
raise FileNotFoundError(f"{vmeridional_file} not found")
|
||||
if not os.path.exists(os.path.join(base_path, vzonal_file)):
|
||||
raise FileNotFoundError(f"{vzonal_file} not found")
|
||||
|
||||
# 读取.mat文件
|
||||
height_data = loadmat(os.path.join(base_path, height_file))
|
||||
lat_data = loadmat(os.path.join(base_path, lat_file))
|
||||
lon_data = loadmat(os.path.join(base_path, lon_file))
|
||||
vmeridional_data = loadmat(os.path.join(base_path, vmeridional_file))
|
||||
vzonal_data = loadmat(os.path.join(base_path, vzonal_file))
|
||||
|
||||
# 将数据转换为DataFrame
|
||||
height_df = pd.DataFrame(height_data['Height'])
|
||||
lat_df = pd.DataFrame(lat_data['Lat'])
|
||||
lon_df = pd.DataFrame(lon_data['Lon'])
|
||||
vmeridional_df = pd.DataFrame(vmeridional_data['VMerdional'])
|
||||
vzonal_df = pd.DataFrame(vzonal_data['Vzonal'])
|
||||
|
||||
# 将每天的数据添加到列表中
|
||||
height_list.append(height_df)
|
||||
lat_list.append(lat_df)
|
||||
lon_list.append(lon_df)
|
||||
vmeridional_list.append(vmeridional_df)
|
||||
vzonal_list.append(vzonal_df)
|
||||
except FileNotFoundError as e:
|
||||
print(f"Error: {e}")
|
||||
continue # 跳过当前文件,继续处理其他文件
|
||||
|
||||
# 合并所有天的数据
|
||||
height_df = pd.concat(height_list, ignore_index=True)
|
||||
lat_df = pd.concat(lat_list, ignore_index=True)
|
||||
lon_df = pd.concat(lon_list, ignore_index=True)
|
||||
vmeridional_df = pd.concat(vmeridional_list, axis=1) # 按列合并
|
||||
vzonal_df = pd.concat(vzonal_list, axis=1) # 按列合并
|
||||
|
||||
# 初始化空列表来存储每天的数据
|
||||
UTime_list = []
|
||||
# 初始化累加的时间偏移量
|
||||
time_offset = 0
|
||||
|
||||
# 循环读取从第1天到第365天的数据
|
||||
for day in range(1, 366): # 365天数据
|
||||
# 构建文件名(使用三位数格式)
|
||||
day_str = f"{day:03d}" # 格式化数字为三位数,前面补0
|
||||
UTime_file = f"{day_str}_UTime.mat"
|
||||
|
||||
# 检查UTime文件是否存在
|
||||
try:
|
||||
if not os.path.exists(os.path.join(base_path, UTime_file)):
|
||||
raise FileNotFoundError(f"{UTime_file} not found")
|
||||
|
||||
# 读取.mat文件
|
||||
UTime_data = loadmat(os.path.join(base_path, UTime_file))
|
||||
|
||||
# 将数据转换为DataFrame
|
||||
UTime_df = pd.DataFrame(UTime_data['UTime'])
|
||||
|
||||
# 对UTime进行累加处理
|
||||
UTime_df += time_offset
|
||||
|
||||
# 将每天的数据添加到列表中
|
||||
UTime_list.append(UTime_df)
|
||||
|
||||
# 更新时间偏移量,为下一天增加24小时
|
||||
time_offset += 24
|
||||
except FileNotFoundError as e:
|
||||
print(f"Error: {e}")
|
||||
continue # 跳过当前文件,继续处理其他文件
|
||||
|
||||
# 合并所有天的数据
|
||||
UTime_df = pd.concat(UTime_list, ignore_index=True)
|
||||
# 将UTime_df的单位从小时转换为天(除以24)
|
||||
UTime_df = UTime_df / 24
|
||||
|
||||
|
||||
# 获取固定高度下的纬度数据
|
||||
fixed_height_lat = lat_df.iloc[:, 0].values
|
||||
# 获取固定高度下的经度数据
|
||||
fixed_height_lon = lon_df.iloc[:, 0].values
|
||||
# 获取固定高度下的经向风数据
|
||||
fixed_height_vmeridional = vmeridional_df.iloc[fixed_height_index, :].values
|
||||
# 获取固定高度下的纬向风数据
|
||||
fixed_height_vzonal = vzonal_df.iloc[fixed_height_index, :].values
|
||||
# 获取对应的世界时数据(这里直接使用整个UTime数据,你可以根据实际情况判断是否需要处理下格式等)
|
||||
fixed_height_utime = UTime_df.iloc[:, 0].values
|
||||
# 将这些数据整合到一个新的DataFrame(可选操作,方便后续查看等)
|
||||
combined_data = pd.DataFrame({
|
||||
'Latitude': fixed_height_lat,
|
||||
'Longitude': fixed_height_lon,
|
||||
'V_Meridional': fixed_height_vmeridional,
|
||||
'V_Zonal': fixed_height_vzonal,
|
||||
'UTime': fixed_height_utime
|
||||
})
|
||||
|
||||
# 简单打印下整合后的数据前几行查看下内容
|
||||
print(combined_data)
|
||||
|
||||
# 确定纬度和经度的范围
|
||||
lat_min, lat_max = np.min(fixed_height_lat), np.max(fixed_height_lat)
|
||||
lon_min, lon_max = np.min(fixed_height_lon), np.max(fixed_height_lon)
|
||||
|
||||
# 计算网格的边界
|
||||
lat_bins = np.linspace(lat_min, lat_max, 10) # 纬度分为6个网格,需要7个边界
|
||||
lon_bins = np.linspace(lon_min, lon_max, 13) # 经度分为20个网格,需要21个边界
|
||||
|
||||
# 将数据分配到网格中
|
||||
grid_data = []
|
||||
for i in range(len(fixed_height_lat)):
|
||||
lat_idx = np.digitize(fixed_height_lat[i], lat_bins) - 1 # 找到纬度所在的网格索引
|
||||
lon_idx = np.digitize(fixed_height_lon[i], lon_bins) - 1 # 找到经度所在的网格索引
|
||||
grid_idx = lat_idx * 12 + lon_idx # 计算网格的唯一索引(9x12=108)
|
||||
grid_data.append([fixed_height_lat[i], fixed_height_lon[i], fixed_height_vmeridional[i], fixed_height_vzonal[i], fixed_height_utime[i], grid_idx])
|
||||
# 将网格数据转换为DataFrame
|
||||
grid_df = pd.DataFrame(grid_data, columns=['Latitude', 'Longitude', 'V_Meridional', 'V_Zonal', 'UTime', 'Grid_Index'])
|
||||
# 打印网格数据的前几行查看
|
||||
print(grid_df.head())
|
||||
# 筛选纬度在 0 到 5° 范围内的数据
|
||||
filtered_df = grid_df[(grid_df['Latitude'] >= 0) & (grid_df['Latitude'] <= 5)]
|
||||
# 将经度从度转换为弧度
|
||||
filtered_df['Longitude_Radians'] = np.radians(filtered_df['Longitude'])
|
||||
# 选择需要输出的列
|
||||
output_columns = ['Longitude_Radians', 'UTime', 'V_Zonal']
|
||||
# 将数据输出为txt文件
|
||||
output_file = base_path+'TIDI_V_Zonal_data.txt'#经向风速分量
|
||||
filtered_df[output_columns].to_csv(output_file, sep='\t', index=False)
|
||||
# 打印结果确认
|
||||
print(f"数据已保存到 {output_file}")
|
||||
# 选择需要输出的列
|
||||
output_columns1 = ['Longitude_Radians', 'UTime', 'V_Meridional']
|
||||
# 将数据输出为txt文件
|
||||
output_file1 = base_path+'TIDI_V_Meridional_data.txt'#纬向风速分量
|
||||
filtered_df[output_columns1].to_csv(output_file1, sep='\t', index=False)
|
||||
# 打印结果确认
|
||||
print(f"数据已保存到 {output_file1}")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# # 对网格数据按照Grid_Index进行分组,并计算每个网格的统计数据
|
||||
# grid_stats = grid_df.groupby('Grid_Index').agg({
|
||||
# 'Latitude': 'mean', # 计算纬度的平均值
|
||||
# 'Longitude': 'mean', # 计算经度的平均值
|
||||
# 'V_Meridional': 'mean', # 计算经向风的平均值、最大值和最小值
|
||||
# 'V_Zonal': 'mean', # 计算纬向风的平均值、最大值和最小值
|
||||
# 'UTime': 'mean' # 计算UTime的平均值
|
||||
# }).reset_index()
|
||||
#
|
||||
# # 重命名列,使其更具可读性
|
||||
# grid_stats.columns = ['Grid_Index', 'Mean_Latitude', 'Mean_Longitude', 'Mean_VMeridional', 'Mean_VZonal', 'Mean_UTime']
|
||||
# # 将经度转换为2π范围
|
||||
# grid_stats['Mean_Longitude_2pi'] = (grid_stats['Mean_Longitude'] / 360) * (2 * np.pi)
|
||||
# # 定义纬度的最小值和最大值(起码要40°区间,否则数据太少无法拟合)
|
||||
# latmin =0
|
||||
# latmax=40
|
||||
# # 筛选出Mean_Latitude在lat_min到lat_max之间的数据
|
||||
# filtered_grid_stats = grid_stats[(grid_stats['Mean_Latitude'] >= latmin) & (grid_stats['Mean_Latitude'] <= latmax)]
|
||||
#
|
||||
# # 检查筛选后的数据行数是否小于18
|
||||
# if filtered_grid_stats.shape[0] < 18:
|
||||
# print("无法拟合行星波")
|
||||
# else:
|
||||
# # 打印筛选后的网格数据的前几行查看
|
||||
# print(filtered_grid_stats.head())
|
||||
326
utils.py
326
utils.py
@ -1,163 +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]
|
||||
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]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user