#!/usr/bin/env python3
"""
3D可视化脚本 - 显示经纬度高程数据
读取CSV文件中的经度、纬度、融合高程数据,
生成可交互的三维点图,输出为HTML格式。
数据格式要求:
经度 纬度 高程 融合高程
经纬度坐标需要除以10^-7进行单位转换。
"""
import pandas as pd
import plotly.graph_objects as go
import plotly.io as pio
import argparse
import sys
import numpy as np
from pathlib import Path
def load_and_process_data(file_path, lon_col=0, lat_col=1, alt_col=3, delimiter=None, sheet_name=0):
"""
加载并处理CSV或Excel数据
Args:
file_path: 文件路径 (支持.csv, .xlsx, .xls)
lon_col: 经度列索引 (默认0)
lat_col: 纬度列索引 (默认1)
alt_col: 高程列索引 (默认3,为融合高程)
delimiter: CSV分隔符,None表示自动检测
sheet_name: Excel工作表名称或索引 (默认0)
Returns:
pandas.DataFrame: 处理后的数据
"""
try:
file_path_obj = Path(file_path)
# 根据文件扩展名选择读取方式
if file_path_obj.suffix.lower() in ['.xlsx', '.xls']:
# 读取Excel文件
print(f"检测到Excel文件: {file_path}")
df = pd.read_excel(file_path, sheet_name=sheet_name, header=None)
else:
# 读取CSV文件
print(f"检测到CSV文件: {file_path}")
df = pd.read_csv(file_path, delimiter=delimiter, header=None)
# 验证数据列数
if df.shape[1] < 4:
raise ValueError(f"数据文件至少需要4列,经度、纬度、高程、融合高程。当前只有{df.shape[1]}列。")
# 显示前几行数据以便调试
print(f"数据预览 (前3行):")
print(df.head(3))
print()
# 提取所需列
lon_raw = df.iloc[:, lon_col] # 经度(原始格式)
lat_raw = df.iloc[:, lat_col] # 纬度(原始格式)
altitude = df.iloc[:, alt_col] # 融合高程
# 数据类型转换和清理
try:
lon_raw = pd.to_numeric(lon_raw, errors='coerce')
lat_raw = pd.to_numeric(lat_raw, errors='coerce')
altitude = pd.to_numeric(altitude, errors='coerce')
except Exception as e:
print(f"数据类型转换失败: {e}")
print("尝试手动转换...")
# 手动转换函数
def safe_convert(value):
try:
return float(value)
except (ValueError, TypeError):
return np.nan
lon_raw = lon_raw.apply(safe_convert)
lat_raw = lat_raw.apply(safe_convert)
altitude = altitude.apply(safe_convert)
# 检查转换结果
nan_count_lon = lon_raw.isna().sum()
nan_count_lat = lat_raw.isna().sum()
nan_count_alt = altitude.isna().sum()
if nan_count_lon > 0 or nan_count_lat > 0 or nan_count_alt > 0:
print(f"警告:发现无效数据点 - 经度: {nan_count_lon}, 纬度: {nan_count_lat}, 高程: {nan_count_alt}")
print("无效数据点将被移除")
# 移除NaN值
valid_mask = ~(lon_raw.isna() | lat_raw.isna() | altitude.isna())
lon_raw = lon_raw[valid_mask]
lat_raw = lat_raw[valid_mask]
altitude = altitude[valid_mask]
if len(lon_raw) == 0:
raise ValueError("没有有效的坐标数据")
# 经纬度单位转换 (除以10^7)
longitude = lon_raw / 1e7
latitude = lat_raw / 1e7
# 创建处理后的DataFrame
processed_df = pd.DataFrame({
'longitude': longitude,
'latitude': latitude,
'altitude': altitude
})
print(f"成功加载数据:{len(processed_df)} 个数据点")
print(f"经度范围: {longitude.min():.6f} - {longitude.max():.6f}")
print(f"纬度范围: {latitude.min():.6f} - {latitude.max():.6f}")
print(f"高程范围: {altitude.min():.2f} - {altitude.max():.2f} 米")
return processed_df
except Exception as e:
print(f"数据加载失败: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
def create_3d_plot(df, title="3D轨迹可视化", output_file="3d_view.html"):
"""
创建三维散点图
Args:
df: 包含longitude, latitude, altitude列的DataFrame
title: 图表标题
output_file: 输出HTML文件路径
"""
# 创建3D散点图
fig = go.Figure(data=[go.Scatter3d(
x=df['longitude'],
y=df['latitude'],
z=df['altitude'],
mode='markers',
marker=dict(
size=3,
color=df['altitude'], # 颜色映射到高程
colorscale='Viridis', # 颜色方案
colorbar=dict(title="高程 (m)"),
opacity=0.8
),
hovertemplate=(
'经度: %{x:.6f}
' +
'纬度: %{y:.6f}
' +
'高程: %{z:.2f} m
' +
''
)
)])
# 设置布局
fig.update_layout(
title=title,
scene=dict(
xaxis_title='经度',
yaxis_title='纬度',
zaxis_title='高程 (m)',
xaxis=dict(showgrid=True, gridcolor='lightgray'),
yaxis=dict(showgrid=True, gridcolor='lightgray'),
zaxis=dict(showgrid=True, gridcolor='lightgray'),
# 设置视角
camera=dict(
eye=dict(x=1.5, y=1.5, z=1.5) # 初始视角位置
)
),
margin=dict(l=0, r=0, b=0, t=40),
paper_bgcolor='white',
plot_bgcolor='white'
)
# 添加交互说明
fig.add_annotation(
text="鼠标拖拽旋转视角,滚轮缩放,双击重置",
xref="paper", yref="paper",
x=0.02, y=0.98,
showarrow=False,
bgcolor="rgba(255,255,255,0.8)",
bordercolor="black",
borderwidth=1
)
# 保存为HTML文件
try:
pio.write_html(fig, file=output_file, auto_open=False)
print(f"3D可视化已保存到: {output_file}")
print("在浏览器中打开该文件即可查看可交互的3D图表")
except Exception as e:
print(f"保存HTML文件失败: {e}")
sys.exit(1)
return fig
def main(input_file, output_file='3d_view.html', lon_col=2, lat_col=3, alt_col=5,
delimiter=None, title='3D轨迹可视化', sheet_name=0):
"""
直接调用函数生成3D可视化
Args:
input_file: 输入文件路径 (支持.csv, .xlsx, .xls)
output_file: 输出HTML文件路径
lon_col: 经度列索引 (默认: 0)
lat_col: 纬度列索引 (默认: 1)
alt_col: 融合高程列索引 (默认: 3)
delimiter: CSV分隔符 (默认: 自动检测)
title: 图表标题 (默认: '3D轨迹可视化')
sheet_name: Excel工作表名称或索引 (默认: 0)
"""
# 检查输入文件
input_path = Path(input_file)
if not input_path.exists():
print(f"错误:输入文件不存在: {input_path}")
return False
print(f"正在处理文件: {input_path}")
print(f"输出文件: {output_file}")
print("-" * 50)
try:
# 加载和处理数据
df = load_and_process_data(
file_path=input_path,
lon_col=lon_col,
lat_col=lat_col,
alt_col=alt_col,
delimiter=delimiter,
sheet_name=sheet_name
)
# 创建3D图表
fig = create_3d_plot(
df=df,
title=title,
output_file=output_file
)
print("-" * 50)
print("处理完成!")
print(f"\n✅ 3D可视化已保存到: {output_file}")
print("\n使用说明:")
print("- 在浏览器中打开生成的HTML文件")
print("- 鼠标左键拖拽:旋转视角")
print("- 鼠标滚轮:缩放视图")
print("- 双击:重置到初始视角")
print("- 右键拖拽:平移视图")
return True
except Exception as e:
print(f"❌ 处理失败: {e}")
import traceback
traceback.print_exc()
return False
# 命令行接口(保持兼容性)
def cli_main():
"""命令行接口"""
parser = argparse.ArgumentParser(
description="3D轨迹可视化工具 - 将CSV数据转换为可交互的3D点图",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
使用示例:
# CSV文件
python 3Dview.py data.csv
python 3Dview.py data.csv -o custom_output.html
python 3Dview.py data.csv --lon-col 1 --lat-col 2 --alt-col 4
# Excel文件
python 3Dview.py trajectory.xlsx
python 3Dview.py data.xlsx --sheet-name "Sheet1"
数据格式:
文件应包含至少4列:经度 纬度 高程 融合高程
支持格式: CSV (.csv), Excel (.xlsx, .xls)
经纬度将自动除以10^7进行单位转换
直接调用示例:
from 3Dview import main
main('data.xlsx', sheet_name='Sheet1') # Excel文件
main('data.csv', lon_col=0, lat_col=1, alt_col=3) # CSV文件
"""
)
parser.add_argument('input_file', help='输入CSV文件路径')
parser.add_argument('-o', '--output', default='3d_view.html',
help='输出HTML文件路径 (默认: 3d_view.html)')
parser.add_argument('--lon-col', type=int, default=0,
help='经度列索引 (默认: 0)')
parser.add_argument('--lat-col', type=int, default=1,
help='纬度列索引 (默认: 1)')
parser.add_argument('--alt-col', type=int, default=3,
help='融合高程列索引 (默认: 3)')
parser.add_argument('--delimiter', default=None,
help='CSV分隔符 (默认: 自动检测)')
parser.add_argument('--title', default='3D轨迹可视化',
help='图表标题 (默认: 3D轨迹可视化)')
parser.add_argument('--sheet-name', default=0,
help='Excel工作表名称或索引 (默认: 0)')
args = parser.parse_args()
success = main(
input_file=args.input_file,
output_file=args.output,
lon_col=args.lon_col,
lat_col=args.lat_col,
alt_col=args.alt_col,
delimiter=args.delimiter,
title=args.title,
sheet_name=args.sheet_name
)
if not success:
sys.exit(1)
# 示例用法
if __name__ == "__main__":
# 检查是否有命令行参数
if len(sys.argv) > 1:
# 使用命令行接口
cli_main()
else:
# 直接调用示例
print("3D可视化工具使用示例")
print("=" * 50)
# 示例1: 处理Excel文件
print("\n示例1: 处理Excel数据文件")
excel_file = r"C:\Users\HL\Documents\xwechat_files\wxid_rdrswaol1qsr21_737e\msg\file\2025-12\08_51_15_间隔高度10m.xlsx"
if Path(excel_file).exists():
success = main(
input_file=excel_file,
output_file='./10m_3d_view.html',
title='无人机轨迹3D可视化示例'
)
else:
print(f"示例文件不存在: {excel_file}")
print("请提供正确的Excel文件路径")
success = False
if success:
print("\n示例代码:")
print("""
# 直接调用示例
from 3Dview import main
# 处理Excel文件
main('data.xlsx', sheet_name=0) # 指定工作表
# 处理CSV文件
main('data.csv')
# 自定义参数
main(
input_file='trajectory.xlsx',
output_file='flight_path.html',
lon_col=0, # 经度列索引
lat_col=1, # 纬度列索引
alt_col=3, # 融合高程列索引
sheet_name=0, # Excel工作表
title='飞行轨迹可视化'
)
""")