增加web_api
This commit is contained in:
@ -29,13 +29,14 @@ from datetime import datetime
|
||||
import sys
|
||||
import os
|
||||
from collections import Counter
|
||||
import yaml
|
||||
|
||||
try:
|
||||
from tqdm import tqdm
|
||||
HAS_TQDM = True
|
||||
except ImportError:
|
||||
HAS_TQDM = False
|
||||
print("⚠️ 未安装tqdm库,将不显示进度条。如需进度条,请运行: pip install tqdm")
|
||||
print("WARNING: 未安装tqdm库,将不显示进度条。如需进度条,请运行: pip install tqdm")
|
||||
|
||||
|
||||
def create_height_bins(heights, bin_size=2.0):
|
||||
@ -82,7 +83,7 @@ def create_height_bins(heights, bin_size=2.0):
|
||||
# 导入qiya模块
|
||||
try:
|
||||
from .qiya import get_pressure_at_location
|
||||
print("✅ 成功导入qiya模块")
|
||||
|
||||
except ImportError as e:
|
||||
print(f"❌ 导入qiya模块失败: {e}")
|
||||
print("请确保GasFlux包结构完整")
|
||||
@ -104,13 +105,13 @@ def load_excel_data(file_path):
|
||||
|
||||
# 读取Excel文件
|
||||
df = pd.read_excel(file_path)
|
||||
print(f"✅ 成功读取数据:{len(df)} 行,{len(df.columns)} 列")
|
||||
print(f"SUCCESS: Successfully loaded data: {len(df)} rows, {len(df.columns)} columns")
|
||||
print(f"列名:{list(df.columns)}")
|
||||
|
||||
return df
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 读取文件失败: {e}")
|
||||
print(f"ERROR: Failed to read file: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@ -132,11 +133,11 @@ def remove_columns(df, columns_to_remove):
|
||||
missing_columns = [col for col in columns_to_remove if col not in df.columns]
|
||||
|
||||
if missing_columns:
|
||||
print(f"⚠️ 以下列不存在(跳过): {missing_columns}")
|
||||
print(f"以下列不存在(跳过): {missing_columns}")
|
||||
|
||||
if existing_columns:
|
||||
df = df.drop(columns=existing_columns)
|
||||
print(f"✅ 已删除 {len(existing_columns)} 列")
|
||||
print(f"已删除 {len(existing_columns)} 列")
|
||||
|
||||
return df
|
||||
|
||||
@ -158,7 +159,7 @@ def extract_hour_from_filename(filename):
|
||||
if match:
|
||||
return match.group(1)
|
||||
else:
|
||||
print(f"⚠️ 无法从文件名 '{filename}' 中提取小时信息,使用默认值 '00'")
|
||||
print(f"无法从文件名 '{filename}' 中提取小时信息,使用默认值 '00'")
|
||||
return "00"
|
||||
|
||||
|
||||
@ -181,7 +182,7 @@ def fix_time_column(df, filename):
|
||||
|
||||
# 检查时间列是否存在
|
||||
if '时间' not in df.columns:
|
||||
print("❌ 未找到 '时间' 列")
|
||||
print("未找到 '时间' 列")
|
||||
return df
|
||||
|
||||
# 修正时间格式
|
||||
@ -231,18 +232,18 @@ def convert_coordinates(df):
|
||||
"""
|
||||
print("转换经纬度坐标...")
|
||||
|
||||
if '经度' in df.columns:
|
||||
original_lon = df['经度'].head(3).tolist()
|
||||
df['经度'] = df['经度'] / 1e7
|
||||
converted_lon = df['经度'].head(3).tolist()
|
||||
if 'stGPSPositionX' in df.columns:
|
||||
original_lon = df['stGPSPositionX'].head(3).tolist()
|
||||
df['stGPSPositionX'] = df['stGPSPositionX'] / 1e7
|
||||
converted_lon = df['stGPSPositionX'].head(3).tolist()
|
||||
print("经度转换示例:")
|
||||
for orig, conv in zip(original_lon, converted_lon):
|
||||
print(".6f")
|
||||
|
||||
if '纬度' in df.columns:
|
||||
original_lat = df['纬度'].head(3).tolist()
|
||||
df['纬度'] = df['纬度'] / 1e7
|
||||
converted_lat = df['纬度'].head(3).tolist()
|
||||
if 'stGPSPositionY' in df.columns:
|
||||
original_lat = df['stGPSPositionY'].head(3).tolist()
|
||||
df['stGPSPositionY'] = df['stGPSPositionY'] / 1e7
|
||||
converted_lat = df['stGPSPositionY'].head(3).tolist()
|
||||
print("纬度转换示例:")
|
||||
for orig, conv in zip(original_lat, converted_lat):
|
||||
print(".6f")
|
||||
@ -265,42 +266,54 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
|
||||
"""
|
||||
print("计算气压数据...")
|
||||
|
||||
# 检查必要列是否存在
|
||||
required_cols = ['日期', '时间', '经度', '纬度', '融合高程']
|
||||
# 检查必要列是否存在(支持原始列名和新列名)
|
||||
# 原始数据中的列名
|
||||
original_cols = ['qStrDate', 'qStrTime', 'stGPSPositionX', 'stGPSPositionY', 'fAltitudeFused']
|
||||
# 坐标转换后的列名(经纬度已被除以1e7)
|
||||
converted_cols = ['qStrDate', 'qStrTime', 'stGPSPositionX', 'stGPSPositionY', 'fAltitudeFused']
|
||||
|
||||
# 优先使用转换后的列名(如果存在),否则使用原始列名
|
||||
date_col = 'qStrDate' if 'qStrDate' in df.columns else '日期'
|
||||
time_col = 'qStrTime' if 'qStrTime' in df.columns else '时间'
|
||||
lon_col = 'stGPSPositionX' if 'stGPSPositionX' in df.columns else '经度'
|
||||
lat_col = 'stGPSPositionY' if 'stGPSPositionY' in df.columns else '纬度'
|
||||
height_col = 'fAltitudeFused' if 'fAltitudeFused' in df.columns else '融合高程'
|
||||
|
||||
required_cols = [date_col, time_col, lon_col, lat_col, height_col]
|
||||
missing_cols = [col for col in required_cols if col not in df.columns]
|
||||
|
||||
if missing_cols:
|
||||
print(f"❌ 缺少必要列: {missing_cols}")
|
||||
print(f"缺少必要列: {missing_cols}")
|
||||
return df
|
||||
|
||||
# 检查高度变化范围
|
||||
height_min = df['融合高程'].min()
|
||||
height_max = df['融合高程'].max()
|
||||
height_min = df[height_col].min()
|
||||
height_max = df[height_col].max()
|
||||
height_range = height_max - height_min
|
||||
|
||||
print(f"🏔️ 高度范围: {height_min:.1f} - {height_max:.1f} 米 (变化: {height_range:.1f} 米)")
|
||||
print(f"高度范围: {height_min:.1f} - {height_max:.1f} 米 (变化: {height_range:.1f} 米)")
|
||||
# 创建高度分档
|
||||
height_bins = create_height_bins(df['融合高程'], height_bin_size)
|
||||
print(f"📏 高度分档: {len(height_bins)} 个档位 (间隔: {height_bin_size:.1f} 米)")
|
||||
height_bins = create_height_bins(df[height_col], height_bin_size)
|
||||
print(f"高度分档: {len(height_bins)} 个档位 (间隔: {height_bin_size:.1f} 米)")
|
||||
|
||||
for i, (bin_min, bin_max, bin_center, count) in enumerate(height_bins):
|
||||
print(f" 档位{i+1}: {bin_min:.1f}-{bin_max:.1f}m (中心: {bin_center:.1f}m, 数据: {count}行)")
|
||||
# 决定计算策略
|
||||
if height_range <= height_tolerance:
|
||||
# 高度变化小,只计算一次气压
|
||||
print("🎯 高度变化小,将使用平均高度计算一次气压")
|
||||
print("高度变化小,将使用平均高度计算一次气压")
|
||||
use_single_calculation = True
|
||||
mean_height = df['融合高程'].mean()
|
||||
print(f"📍 使用平均高度: {mean_height:.1f} 米")
|
||||
mean_height = df[height_col].mean()
|
||||
print(f"使用平均高度: {mean_height:.1f} 米")
|
||||
elif len(height_bins) == 1:
|
||||
# 只有一个高度档位,使用档位中心高度
|
||||
print("📦 只有一个高度档位,使用档位中心高度")
|
||||
print("只有一个高度档位,使用档位中心高度")
|
||||
use_single_calculation = True
|
||||
mean_height = height_bins[0][2] # bin_center
|
||||
print(f"📍 使用档位中心高度: {mean_height:.1f} 米")
|
||||
print(f"使用档位中心高度: {mean_height:.1f} 米")
|
||||
else:
|
||||
# 高度变化大,使用分档计算
|
||||
print("🏗️ 使用高度分档策略,减少API调用")
|
||||
print("使用高度分档策略,减少API调用")
|
||||
use_single_calculation = False
|
||||
|
||||
# 确定要处理的行数
|
||||
@ -309,10 +322,10 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
|
||||
sample_df = df.copy()
|
||||
actual_samples = len(df)
|
||||
if not use_single_calculation:
|
||||
print(f"📊 将计算所有 {len(df)} 行的气压数据")
|
||||
print(f"将计算所有 {len(df)} 行的气压数据")
|
||||
else:
|
||||
# 限制采样数量
|
||||
print(f"⚠️ 数据量较大 ({len(df)} 行),只对前 {max_samples} 行计算气压")
|
||||
print(f"数据量较大 ({len(df)} 行),只对前 {max_samples} 行计算气压")
|
||||
sample_df = df.head(max_samples).copy()
|
||||
actual_samples = max_samples
|
||||
|
||||
@ -325,11 +338,11 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
|
||||
first_row = sample_df.iloc[0]
|
||||
|
||||
# 转换日期格式 - 只提取日期部分,移除任何时间信息
|
||||
date_str = str(first_row['日期'])
|
||||
date_str = str(first_row[date_col])
|
||||
if ' ' in date_str:
|
||||
date_str = date_str.split(' ')[0]
|
||||
date_str = date_str.split(' ')[0] # 处理 "2026-01-15 00:00:00" 格式
|
||||
elif 'T' in date_str:
|
||||
date_str = date_str.split('T')[0]
|
||||
date_str = date_str.split('T')[0] # 处理ISO格式
|
||||
|
||||
if '/' in date_str:
|
||||
date_str = date_str.replace('/', '-')
|
||||
@ -343,8 +356,15 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
|
||||
|
||||
# 使用数据的代表性时间(整点小时,众数)
|
||||
time_strings = []
|
||||
for time_val in sample_df['时间']:
|
||||
for time_val in sample_df[time_col]:
|
||||
time_str = str(time_val).strip()
|
||||
|
||||
# 处理时间字符串,提取正确的部分
|
||||
# 如果时间字符串包含多个时间部分(如 "00:00:00 08:37:12"),取最后一个
|
||||
if ' ' in time_str:
|
||||
time_parts = time_str.split()
|
||||
time_str = time_parts[-1] # 取最后一个有效的时间部分
|
||||
|
||||
if ':' in time_str:
|
||||
# 确保是有效的 HH:MM 格式,然后取整点小时
|
||||
parts = time_str.split(':')
|
||||
@ -368,8 +388,8 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
|
||||
|
||||
print("正在计算平均气压...")
|
||||
pressure = get_pressure_at_location(
|
||||
lat=sample_df['纬度'].mean(),
|
||||
lon=sample_df['经度'].mean(),
|
||||
lat=sample_df[lat_col].mean(),
|
||||
lon=sample_df[lon_col].mean(),
|
||||
altitude=mean_height,
|
||||
date=formatted_date,
|
||||
time=formatted_time
|
||||
@ -377,18 +397,18 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
|
||||
|
||||
if pressure is not None:
|
||||
pressures = [pressure] * len(sample_df)
|
||||
print("✅ 平均气压计算成功,将应用到所有行")
|
||||
print("平均气压计算成功,将应用到所有行")
|
||||
else:
|
||||
print("❌ 平均气压计算失败")
|
||||
print("平均气压计算失败")
|
||||
pressures = [None] * len(sample_df)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 平均气压计算失败: {e}")
|
||||
print(f"平均气压计算失败: {e}")
|
||||
pressures = [None] * len(sample_df)
|
||||
|
||||
else:
|
||||
# 使用高度分档策略
|
||||
print("🏗️ 开始分档计算气压...")
|
||||
print("开始分档计算气压...")
|
||||
|
||||
# 为每个高度档位计算气压
|
||||
bin_pressures = {} # bin_center -> pressure
|
||||
@ -402,19 +422,19 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
|
||||
try:
|
||||
# 使用第一行数据作为代表来获取日期和时间
|
||||
# 找到这个档位中的一行数据
|
||||
bin_rows = sample_df[(sample_df['融合高程'] >= bin_min) &
|
||||
(sample_df['融合高程'] <= bin_max)]
|
||||
bin_rows = sample_df[(sample_df[height_col] >= bin_min) &
|
||||
(sample_df[height_col] <= bin_max)]
|
||||
if len(bin_rows) == 0:
|
||||
continue
|
||||
|
||||
first_row = bin_rows.iloc[0]
|
||||
|
||||
# 转换日期格式 - 只提取日期部分,移除任何时间信息
|
||||
date_str = str(first_row['日期'])
|
||||
date_str = str(first_row[date_col])
|
||||
if ' ' in date_str:
|
||||
date_str = date_str.split(' ')[0]
|
||||
date_str = date_str.split(' ')[0] # 处理 "2026-01-15 00:00:00" 格式
|
||||
elif 'T' in date_str:
|
||||
date_str = date_str.split('T')[0]
|
||||
date_str = date_str.split('T')[0] # 处理ISO格式
|
||||
|
||||
if '/' in date_str:
|
||||
date_str = date_str.replace('/', '-')
|
||||
@ -424,14 +444,21 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
|
||||
year, month, day = date_parts
|
||||
formatted_date = f"{year}-{month.zfill(2)}-{day.zfill(2)}"
|
||||
else:
|
||||
print(f"⚠️ 档位高度 {bin_center:.1f}m 日期格式异常: {date_str}")
|
||||
print(f"档位高度 {bin_center:.1f}m 日期格式异常: {date_str}")
|
||||
bin_pressures[bin_center] = None
|
||||
continue
|
||||
|
||||
# 使用该档位数据的代表性时间(整点小时)
|
||||
time_strings = []
|
||||
for time_val in bin_rows['时间']:
|
||||
for time_val in bin_rows[time_col]:
|
||||
time_str = str(time_val).strip()
|
||||
|
||||
# 处理时间字符串,提取正确的部分
|
||||
# 如果时间字符串包含多个时间部分(如 "00:00:00 08:37:12"),取最后一个
|
||||
if ' ' in time_str:
|
||||
time_parts = time_str.split()
|
||||
time_str = time_parts[-1] # 取最后一个有效的时间部分
|
||||
|
||||
if ':' in time_str:
|
||||
# 确保是有效的 HH:MM 格式,然后取整点小时
|
||||
parts = time_str.split(':')
|
||||
@ -456,8 +483,8 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
|
||||
print(" 无有效时间数据,使用默认中午12:00")
|
||||
|
||||
# 计算这个档位的气压(使用平均位置和档位中心高度)
|
||||
avg_lat = bin_rows['纬度'].mean()
|
||||
avg_lon = bin_rows['经度'].mean()
|
||||
avg_lat = bin_rows[lat_col].mean()
|
||||
avg_lon = bin_rows[lon_col].mean()
|
||||
|
||||
pressure = get_pressure_at_location(
|
||||
lat=avg_lat,
|
||||
@ -475,14 +502,14 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
|
||||
iterator.set_description(f"计算档位 (成功: {success_count}/{len(bin_pressures)})")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 计算高度档位 {bin_center:.1f}m 气压失败: {e}")
|
||||
print(f"计算高度档位 {bin_center:.1f}m 气压失败: {e}")
|
||||
bin_pressures[bin_center] = None
|
||||
|
||||
# 为每一行分配对应档位的气压
|
||||
pressures = []
|
||||
for idx, row in sample_df.iterrows():
|
||||
# 找到这个高度对应的档位
|
||||
height = row['融合高程']
|
||||
height = row[height_col]
|
||||
assigned_pressure = None
|
||||
|
||||
for bin_min, bin_max, bin_center, count in height_bins:
|
||||
@ -492,7 +519,7 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
|
||||
|
||||
pressures.append(assigned_pressure)
|
||||
|
||||
print(f"✅ 完成分档气压计算,共 {len(bin_pressures)} 个档位,{len([p for p in bin_pressures.values() if p is not None])} 个成功")
|
||||
print(f"完成分档气压计算,共 {len(bin_pressures)} 个档位,{len([p for p in bin_pressures.values() if p is not None])} 个成功")
|
||||
|
||||
# 添加气压列
|
||||
df['pressure'] = None # 初始化
|
||||
@ -513,7 +540,7 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
|
||||
avg_pressure = sum(valid_pressures) / len(valid_pressures)
|
||||
print(f"成功计算 {len(valid_pressures)}/{actual_samples} 个气压值,平均值: {avg_pressure:.1f} hPa")
|
||||
else:
|
||||
print("⚠️ 未能计算出任何气压值")
|
||||
print("未能计算出任何气压值")
|
||||
return df
|
||||
|
||||
|
||||
@ -529,13 +556,13 @@ def adjust_altitude(df):
|
||||
"""
|
||||
print("调整融合高程...")
|
||||
|
||||
if '融合高程' in df.columns:
|
||||
min_altitude = df['融合高程'].min()
|
||||
if 'fAltitudeFused' in df.columns:
|
||||
min_altitude = df['fAltitudeFused'].min()
|
||||
print(".2f")
|
||||
|
||||
original_alt = df['融合高程'].head(3).tolist()
|
||||
df['融合高程'] = df['融合高程'] - min_altitude
|
||||
adjusted_alt = df['融合高程'].head(3).tolist()
|
||||
original_alt = df['fAltitudeFused'].head(3).tolist()
|
||||
df['fAltitudeFused'] = df['fAltitudeFused'] - min_altitude
|
||||
adjusted_alt = df['fAltitudeFused'].head(3).tolist()
|
||||
|
||||
print("高度调整示例:")
|
||||
for orig, adj in zip(original_alt, adjusted_alt):
|
||||
@ -546,7 +573,7 @@ def adjust_altitude(df):
|
||||
|
||||
def merge_timestamp(df):
|
||||
"""
|
||||
融合日期和时间列为时间戳
|
||||
融合日期和时间列为时间戳(修正时间格式)
|
||||
|
||||
Args:
|
||||
df: 输入DataFrame
|
||||
@ -554,57 +581,48 @@ def merge_timestamp(df):
|
||||
Returns:
|
||||
pd.DataFrame: 融合后的DataFrame
|
||||
"""
|
||||
print("融合日期和时间...")
|
||||
print("融合日期和时间(修正时间格式)...")
|
||||
|
||||
if '日期' in df.columns and '时间' in df.columns:
|
||||
if 'qStrDate' in df.columns and 'qStrTime' in df.columns:
|
||||
timestamps = []
|
||||
|
||||
for idx, row in df.iterrows():
|
||||
try:
|
||||
date_str = str(row['日期'])
|
||||
time_str = str(row['时间'])
|
||||
date_str = str(row['qStrDate']).strip()
|
||||
time_str = str(row['qStrTime']).strip()
|
||||
|
||||
# 清理日期字符串 - 移除任何时间部分
|
||||
date_str = date_str.strip()
|
||||
# 处理日期字符串,提取纯日期部分
|
||||
# 如果日期字符串包含时间部分(如 "2026-01-15 00:00:00"),取日期部分
|
||||
if ' ' in date_str:
|
||||
date_str = date_str.split(' ')[0] # 只取日期部分
|
||||
if 'T' in date_str:
|
||||
date_str = date_str.split('T')[0] # 处理ISO格式
|
||||
date_parts = date_str.split()
|
||||
date_str = date_parts[0] # 取第一个部分作为日期
|
||||
|
||||
# 标准化日期格式
|
||||
if '/' in date_str:
|
||||
date_str = date_str.replace('/', '-')
|
||||
# 处理时间字符串,提取正确的部分
|
||||
# 如果时间字符串包含多个时间部分(如 "00:00:00 08:37:12"),取最后一个
|
||||
if ' ' in time_str:
|
||||
time_parts = time_str.split()
|
||||
# 取最后一个有效的时间部分
|
||||
time_str = time_parts[-1]
|
||||
|
||||
# 确保日期格式正确
|
||||
date_parts = date_str.split('-')
|
||||
if len(date_parts) == 3:
|
||||
year, month, day = date_parts
|
||||
date_formatted = f"{year.zfill(4)}-{month.zfill(2)}-{day.zfill(2)}"
|
||||
else:
|
||||
print(f"⚠️ 日期格式异常: '{date_str}',使用当前日期")
|
||||
date_formatted = datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
# 时间字符串已经是修正后的格式(如 "08:34:01"),直接使用
|
||||
time_str = time_str.strip()
|
||||
# 确保时间格式正确
|
||||
if ':' in time_str and len(time_str.split(':')) >= 2:
|
||||
time_formatted = time_str
|
||||
# 组合日期和修正后的时间
|
||||
timestamp = f"{date_str} {time_str}"
|
||||
else:
|
||||
print(f"⚠️ 时间格式异常: '{time_str}',使用默认时间")
|
||||
time_formatted = "12:00:00"
|
||||
timestamp = f"{date_str} 12:00:00" # 默认中午时间
|
||||
print(f" 时间格式异常 '{row['qStrTime']}',使用默认时间")
|
||||
|
||||
# 组合时间戳 - 直接连接日期和时间
|
||||
timestamp = f"{date_formatted} {time_formatted}"
|
||||
timestamps.append(timestamp)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 处理第 {idx+1} 行时间戳失败: {e}")
|
||||
print(f"处理第 {idx+1} 行时间戳失败: {e}")
|
||||
timestamps.append(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
|
||||
df['timestamp'] = timestamps
|
||||
|
||||
print("时间戳融合示例:")
|
||||
for i in range(min(3, len(timestamps))):
|
||||
print(f" {df.loc[i, '日期']} + {df.loc[i, '时间']} → {timestamps[i]}")
|
||||
print(f" {df.loc[i, 'qStrDate']} + {df.loc[i, 'qStrTime']} → {timestamps[i]}")
|
||||
|
||||
return df
|
||||
|
||||
@ -623,83 +641,104 @@ def rename_columns(df):
|
||||
|
||||
# 定义字段映射
|
||||
column_mapping = {
|
||||
'timestamp': 'timestamp', # 时间戳(已创建)
|
||||
'经度': 'longitude', # 经度 → latitude
|
||||
'纬度': 'latitude', # 纬度 → longitude
|
||||
'融合高程': 'height_ato', # 融合高程 → height_ato
|
||||
'修正风向': 'winddir', # 修正风向 → winddir
|
||||
'修正风速': 'windspeed', # 修正风速 → windspeed
|
||||
'风温': 'temperature', # 风温 → temperature
|
||||
'pressure': 'pressure', # 气压(已计算)
|
||||
'CH4': 'ch4', # CH4保持不变
|
||||
'pitch': 'course_elevation', # pitch → course_elevation
|
||||
'yaw': 'course_azimuth' # yaw → course_azimuth
|
||||
'timestamp': 'timestamp', # 时间戳(已创建)
|
||||
'stGPSPositionX': 'longitude', # 经度 → longitude
|
||||
'stGPSPositionY': 'latitude', # 纬度 → latitude
|
||||
'fAltitudeFused': 'height_ato', # 融合高程 → height_ato
|
||||
'fFixedWindDirection': 'winddir', # 修正风向 → winddir
|
||||
'fFixedWindSpeed': 'windspeed', # 修正风速 → windspeed
|
||||
'fWindTemperature': 'temperature', # 风温 → temperature
|
||||
'CO2': 'CO2', # CO2浓度 → co2
|
||||
'pitch': 'course_elevation', # pitch → course_elevation
|
||||
'yaw': 'course_azimuth' # yaw → course_azimuth
|
||||
}
|
||||
|
||||
# 重命名存在的列
|
||||
columns_to_rename = {}
|
||||
# 先复制字段,再对复制的字段重命名
|
||||
columns_renamed = []
|
||||
for old_name, new_name in column_mapping.items():
|
||||
if old_name in df.columns:
|
||||
columns_to_rename[old_name] = new_name
|
||||
# 复制字段到新名称
|
||||
df[new_name] = df[old_name].copy()
|
||||
columns_renamed.append((old_name, new_name))
|
||||
print(f" 复制并重命名: {old_name} → {new_name}")
|
||||
|
||||
if columns_to_rename:
|
||||
df = df.rename(columns=columns_to_rename)
|
||||
print("字段重命名:")
|
||||
for old, new in columns_to_rename.items():
|
||||
print(f" {old} → {new}")
|
||||
|
||||
# 只保留GasFlux需要的列
|
||||
required_columns = ['timestamp', 'latitude', 'longitude', 'height_ato', 'windspeed', 'winddir', 'temperature', 'pressure', 'ch4', 'course_elevation', 'course_azimuth']
|
||||
existing_required_columns = [col for col in required_columns if col in df.columns]
|
||||
|
||||
if len(existing_required_columns) != len(required_columns):
|
||||
missing = [col for col in required_columns if col not in df.columns]
|
||||
print(f"⚠️ 缺少必需列: {missing}")
|
||||
|
||||
# 移除不需要的列,只保留必需的列
|
||||
df = df[existing_required_columns]
|
||||
print(f"最终保留列: {existing_required_columns}")
|
||||
if columns_renamed:
|
||||
print(f"共处理了 {len(columns_renamed)} 个字段")
|
||||
|
||||
return df
|
||||
|
||||
|
||||
def process_excel_file(file_path):
|
||||
def ensure_float64_types(df, config=None):
|
||||
"""
|
||||
确保数值字段为float64类型,以满足GasFlux处理要求
|
||||
|
||||
Args:
|
||||
df: 输入DataFrame
|
||||
config: 配置字典,包含gases字段
|
||||
|
||||
Returns:
|
||||
pd.DataFrame: 数据类型转换后的DataFrame
|
||||
"""
|
||||
print("确保数值字段类型为float64...")
|
||||
|
||||
# 定义基础需要转换为float64的字段
|
||||
float64_columns = [
|
||||
'longitude', 'latitude', 'height_ato', # 位置和高度
|
||||
'winddir', 'windspeed', 'temperature', # 风和温度
|
||||
'pressure', # 气压
|
||||
'course_elevation', 'course_azimuth' # 姿态角
|
||||
]
|
||||
|
||||
# 如果提供了配置,添加gases中的气体列
|
||||
if config and 'gases' in config:
|
||||
gas_columns = list(config['gases'].keys())
|
||||
float64_columns.extend(gas_columns)
|
||||
print(f"从配置中添加气体列: {gas_columns}")
|
||||
|
||||
converted_count = 0
|
||||
for col in float64_columns:
|
||||
if col in df.columns:
|
||||
try:
|
||||
original_dtype = df[col].dtype
|
||||
df[col] = df[col].astype('float64')
|
||||
new_dtype = df[col].dtype
|
||||
if original_dtype != new_dtype:
|
||||
print(f" 转换: {col} ({original_dtype} → {new_dtype})")
|
||||
converted_count += 1
|
||||
except Exception as e:
|
||||
print(f" 转换失败: {col} - {e}")
|
||||
|
||||
if converted_count > 0:
|
||||
print(f"共转换了 {converted_count} 个字段的数据类型为float64")
|
||||
else:
|
||||
print("所有数值字段已经是float64类型")
|
||||
|
||||
return df
|
||||
|
||||
|
||||
def process_excel_file(file_path, config_path=None):
|
||||
"""
|
||||
处理单个Excel文件的主函数
|
||||
|
||||
Args:
|
||||
file_path: Excel文件路径
|
||||
config_path: 配置文件路径,用于读取gases配置
|
||||
"""
|
||||
print(f"=== 开始处理文件: {file_path} ===\n")
|
||||
|
||||
# 获取文件名(用于时间修正)
|
||||
filename = Path(file_path).name
|
||||
|
||||
# 1. 读取数据
|
||||
df = load_excel_data(file_path)
|
||||
|
||||
# 2. 删除不需要的列
|
||||
columns_to_remove = [
|
||||
'高程', '速度x', '速度y', '速度z',
|
||||
'四元数_q0', '四元数_q1', '四元数_q2', '四元数_q3',
|
||||
'roll', 'H2O', # 保留pitch和yaw,将重命名为course_elevation和course_azimuth
|
||||
'原始风向', '原始风速'
|
||||
]
|
||||
df = remove_columns(df, columns_to_remove)
|
||||
|
||||
# 3. 修正时间格式
|
||||
df = fix_time_column(df, filename)
|
||||
|
||||
# 4. 坐标转换
|
||||
# 2. 坐标转换
|
||||
df = convert_coordinates(df)
|
||||
|
||||
# 5. 计算气压
|
||||
# 4. 计算气压数据
|
||||
df = calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_size=2.0) # 计算所有行,高度容差10米,分档2米
|
||||
|
||||
# 6. 高度调整
|
||||
# 5. 高度调整
|
||||
df = adjust_altitude(df)
|
||||
|
||||
# 7. 时间戳融合
|
||||
# 6. 时间戳融合(保持原有时间格式)
|
||||
df = merge_timestamp(df)
|
||||
|
||||
# 调试:检查当前列
|
||||
@ -707,28 +746,41 @@ def process_excel_file(file_path):
|
||||
if 'timestamp' in df.columns:
|
||||
print(f"timestamp列示例: {df['timestamp'].head(3).tolist()}")
|
||||
|
||||
# 8. 字段重命名
|
||||
# 7. 字段重命名
|
||||
df = rename_columns(df)
|
||||
|
||||
# 8. 确保数值字段类型为float64
|
||||
# 读取配置以获取gases字段
|
||||
config = None
|
||||
if config_path:
|
||||
try:
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
config = yaml.safe_load(f)
|
||||
except Exception as e:
|
||||
print(f"读取配置文件失败: {e}")
|
||||
|
||||
df = ensure_float64_types(df, config)
|
||||
|
||||
# 保存处理结果
|
||||
output_path = Path(file_path).with_suffix('.processed.csv')
|
||||
df.to_csv(output_path, index=False)
|
||||
df.to_csv(output_path, index=False, encoding='utf-8-sig')
|
||||
|
||||
print(f"\n✅ 处理完成!")
|
||||
print(f"📁 输出文件: {output_path}")
|
||||
print(f"📊 最终数据形状: {df.shape[0]} 行 × {df.shape[1]} 列")
|
||||
print(f"📋 最终列名: {list(df.columns)}")
|
||||
print(f"\n处理完成!")
|
||||
print(f"输出文件: {output_path}")
|
||||
print(f"最终数据形状: {df.shape[0]} 行 × {df.shape[1]} 列")
|
||||
print(f"最终列名: {list(df.columns)}")
|
||||
|
||||
return df
|
||||
|
||||
|
||||
def process_file(input_file, output_file=None):
|
||||
def process_file(input_file, output_file=None, config_file=None):
|
||||
"""
|
||||
直接处理Excel文件的函数(不使用命令行参数)
|
||||
|
||||
Args:
|
||||
input_file: 输入Excel文件路径(字符串或Path对象)
|
||||
output_file: 输出CSV文件路径(可选,字符串或Path对象)
|
||||
config_file: 配置文件路径(可选,用于读取gases配置)
|
||||
|
||||
Returns:
|
||||
pd.DataFrame: 处理后的DataFrame
|
||||
@ -744,13 +796,13 @@ def process_file(input_file, output_file=None):
|
||||
raise ValueError(f"输入文件必须是Excel格式 (.xlsx 或 .xls),当前文件: {input_path}")
|
||||
|
||||
# 处理文件
|
||||
df = process_excel_file(str(input_path))
|
||||
df = process_excel_file(str(input_path), config_file)
|
||||
|
||||
# 如果指定了输出路径,额外保存一份
|
||||
if output_file:
|
||||
output_path = Path(output_file)
|
||||
df.to_csv(output_path, index=False)
|
||||
print(f"📁 额外保存到: {output_path}")
|
||||
df.to_csv(output_path, index=False, encoding='utf-8-sig')
|
||||
print(f"额外保存到: {output_path}")
|
||||
|
||||
return df
|
||||
|
||||
@ -769,17 +821,17 @@ def interactive_input():
|
||||
while True:
|
||||
input_file = input("请输入Excel文件路径 (例如: data.xlsx): ").strip()
|
||||
if not input_file:
|
||||
print("❌ 文件路径不能为空,请重新输入")
|
||||
print("文件路径不能为空,请重新输入")
|
||||
continue
|
||||
|
||||
input_path = Path(input_file)
|
||||
if not input_path.exists():
|
||||
print(f"❌ 文件不存在: {input_path}")
|
||||
print(f"文件不存在: {input_path}")
|
||||
print("提示: 请确保文件路径正确,或者将文件放在当前目录下")
|
||||
continue
|
||||
|
||||
if input_path.suffix.lower() not in ['.xlsx', '.xls']:
|
||||
print(f"❌ 文件格式错误: {input_path.suffix}")
|
||||
print(f"文件格式错误: {input_path.suffix}")
|
||||
print("只支持 .xlsx 和 .xls 格式的Excel文件")
|
||||
continue
|
||||
|
||||
@ -791,13 +843,13 @@ def interactive_input():
|
||||
output_file = None
|
||||
print("使用默认输出文件名")
|
||||
|
||||
print(f"\n✅ 输入确认:")
|
||||
print(f"\n输入确认:")
|
||||
print(f" 输入文件: {input_file}")
|
||||
print(f" 输出文件: {output_file or '自动生成'}")
|
||||
|
||||
confirm = input("\n确认开始处理? (y/N): ").strip().lower()
|
||||
if confirm not in ['y', 'yes', '是', '确认']:
|
||||
print("❌ 用户取消操作")
|
||||
print("用户取消操作")
|
||||
return None, None
|
||||
|
||||
return input_file, output_file
|
||||
@ -823,7 +875,7 @@ def main(input_file=None, output_file=None, interactive=False):
|
||||
try:
|
||||
return process_file(input_file, output_file)
|
||||
except Exception as e:
|
||||
print(f"❌ 处理失败: {e}")
|
||||
print(f"处理失败: {e}")
|
||||
raise
|
||||
|
||||
# 否则使用命令行参数
|
||||
@ -881,7 +933,7 @@ def main(input_file=None, output_file=None, interactive=False):
|
||||
if not input_file:
|
||||
parser.print_help()
|
||||
print("\n" + "="*60)
|
||||
print("📖 使用示例:")
|
||||
print("使用示例:")
|
||||
print("="*60)
|
||||
print("1. 命令行模式:")
|
||||
print(" python data_processor.py your_file.xlsx")
|
||||
@ -900,11 +952,11 @@ def main(input_file=None, output_file=None, interactive=False):
|
||||
# 检查输入文件
|
||||
input_path = Path(input_file)
|
||||
if not input_path.exists():
|
||||
print(f"❌ 错误:输入文件不存在: {input_path}")
|
||||
print(f"错误:输入文件不存在: {input_path}")
|
||||
sys.exit(1)
|
||||
|
||||
if input_path.suffix.lower() not in ['.xlsx', '.xls']:
|
||||
print(f"❌ 错误:输入文件必须是Excel格式 (.xlsx 或 .xls)")
|
||||
print(f"错误:输入文件必须是Excel格式 (.xlsx 或 .xls)")
|
||||
sys.exit(1)
|
||||
|
||||
# 处理文件
|
||||
@ -914,16 +966,16 @@ def main(input_file=None, output_file=None, interactive=False):
|
||||
# 如果指定了输出路径,额外保存一份
|
||||
if output_file:
|
||||
output_path = Path(output_file)
|
||||
df.to_csv(output_path, index=False)
|
||||
print(f"📁 额外保存到: {output_path}")
|
||||
df.to_csv(output_path, index=False, encoding='utf-8-sig')
|
||||
print(f"额外保存到: {output_path}")
|
||||
|
||||
return df
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n⚠️ 用户中断处理")
|
||||
print("\n用户中断处理")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n❌ 处理失败: {e}")
|
||||
print(f"\n处理失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
Reference in New Issue
Block a user