PyVista战场可视化实战(三):雷达与目标轨迹可视化

摘要

在军事仿真、空中交通管制、无人机监控等领域,轨迹数据的可视化是理解目标行为、分析运动规律、评估战术效果的关键。本文是"PyVista雷达电子对抗战场态势仿真"系列的第三篇,专注于雷达与目标轨迹的3D可视化技术。我们将深入探讨如何从各种数据源加载轨迹数据,在3D空间中创建直观的动态轨迹展示,以及如何通过颜色映射、时间标签、多维度信息叠加等方式增强轨迹的可读性和信息密度。通过三个完整的实战案例,读者将掌握轨迹可视化的核心技术,为战场态势分析、目标行为分析等应用打下坚实基础。

1. 引言:为什么需要轨迹可视化?

1.1 轨迹数据的重要性

在军事和民用领域,轨迹数据承载着丰富的信息:

  • 空间信息:目标的实时位置、运动路径

  • 时间信息:目标在不同时间点的状态

  • 运动学信息:速度、加速度、航向变化

  • 环境信息:高度、地形关联、气象影响

  • 战术信息:作战意图、威胁等级、运动模式

1.2 轨迹可视化的挑战

轨迹可视化面临多方面的技术挑战:

1.3 PyVista在轨迹可视化中的优势

PyVista提供了完整的3D轨迹可视化解决方案:

  1. 丰富的几何体支持:支持点、线、面、体等多种几何表示

  2. 灵活的颜色映射:支持连续、离散的颜色映射方案

  3. 高效的管线架构:支持大规模轨迹数据的快速渲染

  4. 完善的时间支持:支持时间序列数据的动态展示

  5. 交互式分析工具:支持轨迹的选取、测量、分析

2. 数据准备:轨迹数据的格式与处理

2.1 常见的轨迹数据格式

轨迹数据可以有多种格式,我们需要根据数据源的特点选择合适的处理方法:

python 复制代码
# 1. CSV格式示例
csv_data_example = """timestamp,object_id,latitude,longitude,altitude,speed,course
2024-01-01 12:00:00,001,39.9042,116.4074,10000,250,45
2024-01-01 12:00:10,001,39.9055,116.4101,10100,255,46
2024-01-01 12:00:20,001,39.9068,116.4128,10200,260,47"""

# 2. JSON格式示例
json_data_example = {
    "trajectory": {
        "id": "001",
        "points": [
            {"t": 0, "x": 100, "y": 200, "z": 3000, "v": 250},
            {"t": 10, "x": 150, "y": 220, "z": 3100, "v": 255}
        ]
    }
}

# 3. 二进制格式(适合大规模数据)
import struct
binary_format = struct.Struct('3f2f')  # x,y,z,v,heading

2.2 轨迹数据处理基类

创建一个通用的轨迹数据处理基类,支持多种数据格式:

python 复制代码
import pandas as pd
import numpy as np
import json
import csv
from datetime import datetime, timedelta
import math

class TrajectoryData:
    """轨迹数据处理基类"""
    
    def __init__(self):
        self.trajectories = {}  # 轨迹字典 {id: Trajectory}
        self.coordinate_system = 'cartesian'  # 坐标系类型
        self.time_zone = 'UTC'  # 时区
        
    class TrajectoryPoint:
        """轨迹点类"""
        
        def __init__(self, timestamp, position, **kwargs):
            self.timestamp = timestamp
            self.position = np.array(position, dtype=float)  # [x, y, z]
            
            # 运动学参数
            self.velocity = kwargs.get('velocity', 0.0)  # 速度
            self.heading = kwargs.get('heading', 0.0)  # 航向
            self.pitch = kwargs.get('pitch', 0.0)  # 俯仰
            self.acceleration = kwargs.get('acceleration', 0.0)  # 加速度
            
            # 其他参数
            self.attributes = kwargs  # 所有属性
            
        def __repr__(self):
            return f"Point(t={self.timestamp}, pos={self.position}, v={self.velocity})"
    
    class Trajectory:
        """轨迹类"""
        
        def __init__(self, trajectory_id, object_type="unknown", color=None):
            self.id = trajectory_id
            self.object_type = object_type
            self.color = color
            self.points = []  # TrajectoryPoint列表
            self.start_time = None
            self.end_time = None
            self.duration = 0.0
            self.total_distance = 0.0
            
        def add_point(self, point):
            """添加轨迹点"""
            if not self.points:
                self.start_time = point.timestamp
            self.points.append(point)
            self.end_time = point.timestamp
            
            # 计算累积距离
            if len(self.points) > 1:
                last_point = self.points[-2]
                distance = np.linalg.norm(point.position - last_point.position)
                self.total_distance += distance
                
            # 计算持续时间
            if self.start_time and self.end_time:
                if isinstance(self.start_time, (int, float)) and isinstance(self.end_time, (int, float)):
                    self.duration = self.end_time - self.start_time
                else:
                    # 如果是datetime对象
                    self.duration = (self.end_time - self.start_time).total_seconds()
                    
        def get_point_at_time(self, timestamp):
            """获取指定时间的轨迹点(插值)"""
            if not self.points:
                return None
                
            # 找到最接近的两个点
            for i in range(len(self.points) - 1):
                p1 = self.points[i]
                p2 = self.points[i + 1]
                
                if p1.timestamp <= timestamp <= p2.timestamp:
                    # 线性插值
                    ratio = (timestamp - p1.timestamp) / (p2.timestamp - p1.timestamp)
                    
                    # 位置插值
                    position = p1.position + (p2.position - p1.position) * ratio
                    
                    # 速度插值
                    velocity = p1.velocity + (p2.velocity - p1.velocity) * ratio
                    
                    # 创建插值点
                    interpolated_point = self.__class__.TrajectoryPoint(
                        timestamp=timestamp,
                        position=position,
                        velocity=velocity,
                        heading=p1.heading + (p2.heading - p1.heading) * ratio
                    )
                    
                    return interpolated_point
                    
            return None
            
        def resample(self, interval=1.0):
            """重新采样轨迹点"""
            if not self.points or len(self.points) < 2:
                return
                
            resampled_points = []
            current_time = self.start_time
            
            while current_time <= self.end_time:
                point = self.get_point_at_time(current_time)
                if point:
                    resampled_points.append(point)
                current_time += interval
                
            self.points = resampled_points
            
        def get_statistics(self):
            """获取轨迹统计信息"""
            if not self.points:
                return {}
                
            speeds = [p.velocity for p in self.points]
            altitudes = [p.position[2] for p in self.points]
            headings = [p.heading for p in self.points]
            
            return {
                'point_count': len(self.points),
                'duration': self.duration,
                'total_distance': self.total_distance,
                'avg_speed': np.mean(speeds) if speeds else 0,
                'max_speed': max(speeds) if speeds else 0,
                'min_altitude': min(altitudes) if altitudes else 0,
                'max_altitude': max(altitudes) if altitudes else 0,
                'avg_heading': np.mean(headings) if headings else 0
            }
    
    def load_from_csv(self, filepath, time_column='timestamp', id_column='object_id', 
                     x_column='x', y_column='y', z_column='z', **kwargs):
        """从CSV文件加载轨迹数据"""
        print(f"加载CSV文件: {filepath}")
        
        try:
            df = pd.read_csv(filepath)
            print(f"数据形状: {df.shape}")
            print(f"列名: {list(df.columns)}")
            
            # 按目标ID分组
            if id_column in df.columns:
                grouped = df.groupby(id_column)
                
                for obj_id, group in grouped:
                    print(f"处理目标 {obj_id},数据点: {len(group)}")
                    
                    # 创建轨迹
                    trajectory = self.Trajectory(str(obj_id))
                    
                    # 处理每个数据点
                    for _, row in group.iterrows():
                        # 解析时间
                        timestamp = row[time_column]
                        if isinstance(timestamp, str):
                            try:
                                timestamp = pd.to_datetime(timestamp)
                            except:
                                # 转换为时间戳
                                timestamp = float(timestamp)
                        
                        # 解析位置
                        x = float(row[x_column]) if x_column in row else 0.0
                        y = float(row[y_column]) if y_column in row else 0.0
                        z = float(row[z_column]) if z_column in row else 0.0
                        
                        # 获取其他属性
                        attributes = {}
                        for col, val in row.items():
                            if col not in [time_column, id_column, x_column, y_column, z_column]:
                                try:
                                    attributes[col] = float(val)
                                except:
                                    attributes[col] = val
                        
                        # 创建轨迹点
                        point = self.TrajectoryPoint(
                            timestamp=timestamp,
                            position=[x, y, z],
                            **attributes
                        )
                        
                        trajectory.add_point(point)
                    
                    # 添加到轨迹字典
                    self.trajectories[trajectory.id] = trajectory
                    
            else:
                print(f"警告: 未找到ID列 '{id_column}'")
                
        except Exception as e:
            print(f"加载CSV文件失败: {e}")
            import traceback
            traceback.print_exc()
            
    def load_from_json(self, filepath):
        """从JSON文件加载轨迹数据"""
        with open(filepath, 'r', encoding='utf-8') as f:
            data = json.load(f)
            
        if isinstance(data, list):
            for item in data:
                self._load_single_trajectory_from_dict(item)
        elif isinstance(data, dict):
            if 'trajectories' in data:
                for traj_data in data['trajectories']:
                    self._load_single_trajectory_from_dict(traj_data)
            else:
                self._load_single_trajectory_from_dict(data)
                
    def _load_single_trajectory_from_dict(self, data):
        """从字典加载单个轨迹"""
        traj_id = data.get('id', str(len(self.trajectories)))
        traj_type = data.get('type', 'unknown')
        
        trajectory = self.Trajectory(traj_id, traj_type)
        
        points = data.get('points', [])
        for point_data in points:
            timestamp = point_data.get('t', 0)
            x = point_data.get('x', 0.0)
            y = point_data.get('y', 0.0)
            z = point_data.get('z', 0.0)
            
            point = self.TrajectoryPoint(
                timestamp=timestamp,
                position=[x, y, z],
                velocity=point_data.get('v', 0.0),
                heading=point_data.get('heading', 0.0)
            )
            
            trajectory.add_point(point)
            
        self.trajectories[trajectory.id] = trajectory
        
    def convert_coordinates(self, from_system='wgs84', to_system='cartesian'):
        """坐标转换(简化版本)"""
        if from_system == 'wgs84' and to_system == 'cartesian':
            # 简化的WGS84到笛卡尔坐标转换
            for traj_id, trajectory in self.trajectories.items():
                for point in trajectory.points:
                    # 获取经纬高
                    lon, lat, alt = point.position
                    
                    # 转换为弧度
                    lat_rad = math.radians(lat)
                    lon_rad = math.radians(lon)
                    
                    # 简化的转换(适合小范围区域)
                    R = 6371000  # 地球半径(米)
                    x = (R + alt) * math.cos(lat_rad) * math.cos(lon_rad)
                    y = (R + alt) * math.cos(lat_rad) * math.sin(lon_rad)
                    z = (R + alt) * math.sin(lat_rad)
                    
                    point.position = np.array([x, y, z])
                    
    def get_trajectory(self, trajectory_id):
        """获取指定ID的轨迹"""
        return self.trajectories.get(trajectory_id)
        
    def get_all_trajectories(self):
        """获取所有轨迹"""
        return list(self.trajectories.values())
        
    def get_summary(self):
        """获取数据摘要"""
        return {
            'trajectory_count': len(self.trajectories),
            'total_points': sum(len(t.points) for t in self.trajectories.values()),
            'trajectory_ids': list(self.trajectories.keys())
        }

2.3 轨迹数据采样与优化

对于大规模轨迹数据,需要进行采样和优化以提高可视化性能:

python 复制代码
class TrajectoryOptimizer:
    """轨迹优化器"""
    
    @staticmethod
    def douglas_peucker(points, epsilon):
        """道格拉斯-普克算法简化轨迹"""
        if len(points) < 3:
            return points
            
        # 找到距离最远的点
        dmax = 0
        index = 0
        
        for i in range(1, len(points) - 1):
            d = TrajectoryOptimizer._perpendicular_distance(
                points[i], points[0], points[-1]
            )
            if d > dmax:
                dmax = d
                index = i
                
        # 递归简化
        if dmax > epsilon:
            left = TrajectoryOptimizer.douglas_peucker(points[:index+1], epsilon)
            right = TrajectoryOptimizer.douglas_peucker(points[index:], epsilon)
            return left[:-1] + right
        else:
            return [points[0], points[-1]]
            
    @staticmethod
    def _perpendicular_distance(point, line_start, line_end):
        """计算点到直线的垂直距离"""
        if np.array_equal(line_start, line_end):
            return np.linalg.norm(point - line_start)
            
        # 计算点到直线的距离
        n = np.linalg.norm(line_end - line_start)
        distance = np.linalg.norm(
            np.cross(line_end - line_start, line_start - point)
        ) / n
        
        return distance
        
    @staticmethod
    def uniform_sampling(points, target_count):
        """均匀采样"""
        if len(points) <= target_count:
            return points
            
        indices = np.linspace(0, len(points) - 1, target_count, dtype=int)
        return [points[i] for i in indices]
        
    @staticmethod
    def temporal_sampling(trajectory, time_interval):
        """时间均匀采样"""
        if not trajectory.points:
            return trajectory
            
        resampled = TrajectoryData.Trajectory(trajectory.id, trajectory.object_type)
        
        current_time = trajectory.start_time
        while current_time <= trajectory.end_time:
            point = trajectory.get_point_at_time(current_time)
            if point:
                resampled.add_point(point)
            current_time += time_interval
            
        return resampled
        
    @staticmethod
    def speed_based_sampling(trajectory, speed_threshold=0.1):
        """基于速度变化的采样"""
        if len(trajectory.points) < 2:
            return trajectory
            
        simplified_points = [trajectory.points[0]]
        
        for i in range(1, len(trajectory.points) - 1):
            # 计算速度变化
            speed_change = abs(
                trajectory.points[i].velocity - trajectory.points[i-1].velocity
            )
            
            # 如果速度变化显著,保留该点
            if speed_change > speed_threshold:
                simplified_points.append(trajectory.points[i])
                
        simplified_points.append(trajectory.points[-1])
        
        simplified_traj = TrajectoryData.Trajectory(trajectory.id, trajectory.object_type)
        for point in simplified_points:
            simplified_traj.add_point(point)
            
        return simplified_traj

3. 基础轨迹可视化技术

3.1 简单轨迹线绘制

最基本的轨迹可视化是绘制连接轨迹点的线:

python 复制代码
import pyvista as pv
import numpy as np

class BasicTrajectoryVisualizer:
    """基础轨迹可视化器"""
    
    def __init__(self):
        self.plotter = None
        self.trajectory_meshes = {}
        
    def create_simple_line(self, points, color='white', line_width=2, name=None):
        """创建简单轨迹线"""
        if len(points) < 2:
            return None
            
        # 转换为NumPy数组
        points_array = np.array(points)
        
        # 创建线
        line = pv.lines_from_points(points_array)
        
        # 添加属性
        line['distance'] = np.linspace(0, 1, len(points_array))
        
        return line
        
    def create_spline(self, points, resolution=100, degree=3, name=None):
        """创建样条曲线"""
        if len(points) < 2:
            return None
            
        # 转换为NumPy数组
        points_array = np.array(points)
        
        # 创建样条曲线
        spline = pv.Spline(points_array, resolution)
        
        return spline
        
    def create_tube(self, points, radius=0.5, n_sides=8, name=None):
        """创建管道轨迹"""
        if len(points) < 2:
            return None
            
        # 创建样条曲线
        spline = self.create_spline(points)
        if spline is None:
            return None
            
        # 创建管道
        tube = spline.tube(radius=radius, n_sides=n_sides)
        
        return tube
        
    def add_trajectory_line(self, trajectory, style='line', **kwargs):
        """添加轨迹线到场景"""
        if not trajectory.points:
            return None
            
        # 提取位置点
        points = [p.position for p in trajectory.points]
        
        # 根据样式创建轨迹
        if style == 'line':
            mesh = self.create_simple_line(points, **kwargs)
        elif style == 'spline':
            mesh = self.create_spline(points, **kwargs)
        elif style == 'tube':
            mesh = self.create_tube(points, **kwargs)
        else:
            mesh = self.create_simple_line(points, **kwargs)
            
        if mesh is None:
            return None
            
        # 添加到轨迹网格字典
        traj_id = trajectory.id
        self.trajectory_meshes[traj_id] = mesh
        
        return mesh
        
    def visualize_trajectories(self, trajectories, window_size=(1200, 800), 
                              show_axes=True, show_grid=True):
        """可视化多条轨迹"""
        # 创建绘图窗口
        self.plotter = pv.Plotter(window_size=window_size, title="轨迹可视化")
        
        # 添加轨迹
        for trajectory in trajectories:
            # 随机颜色
            color = np.random.rand(3)
            
            # 创建轨迹线
            mesh = self.add_trajectory_line(
                trajectory,
                style='tube',
                radius=0.3,
                color=color
            )
            
            if mesh and self.plotter:
                # 添加轨迹线
                self.plotter.add_mesh(
                    mesh,
                    color=color,
                    opacity=0.8,
                    name=f'trajectory_{trajectory.id}',
                    show_edges=False
                )
                
                # 添加轨迹点
                points = np.array([p.position for p in trajectory.points])
                if len(points) > 0:
                    # 均匀采样点
                    sample_indices = np.linspace(0, len(points)-1, 20, dtype=int)
                    sample_points = points[sample_indices]
                    
                    points_mesh = pv.PolyData(sample_points)
                    self.plotter.add_mesh(
                        points_mesh,
                        color=color,
                        point_size=5,
                        render_points_as_spheres=True,
                        name=f'points_{trajectory.id}'
                    )
        
        # 设置场景
        if show_axes:
            self.plotter.add_axes()
            
        if show_grid:
            self.plotter.show_grid()
            
        # 设置相机
        self.plotter.camera_position = [(100, 100, 100), (0, 0, 0), (0, 0, 1)]
        self.plotter.set_background('black')
        
        # 显示
        self.plotter.show()

3.2 颜色映射与轨迹属性可视化

轨迹的不同属性可以通过颜色映射来可视化:

python 复制代码
class EnhancedTrajectoryVisualizer(BasicTrajectoryVisualizer):
    """增强轨迹可视化器(支持颜色映射)"""
    
    def create_colored_trajectory(self, trajectory, color_by='speed', 
                                 colormap='plasma', style='tube', **kwargs):
        """创建带颜色映射的轨迹"""
        if not trajectory.points:
            return None
            
        # 提取位置点和属性
        points = np.array([p.position for p in trajectory.points])
        
        # 根据属性获取颜色值
        if color_by == 'speed':
            scalars = np.array([p.velocity for p in trajectory.points])
        elif color_by == 'altitude':
            scalars = np.array([p.position[2] for p in trajectory.points])
        elif color_by == 'time':
            # 归一化时间
            times = np.array([p.timestamp for p in trajectory.points])
            if isinstance(times[0], (int, float)):
                scalars = (times - times.min()) / (times.max() - times.min() + 1e-10)
            else:
                # 如果是datetime,转换为时间戳
                timestamps = np.array([t.timestamp() for t in times])
                scalars = (timestamps - timestamps.min()) / (timestamps.max() - timestamps.min() + 1e-10)
        elif color_by == 'distance':
            # 计算累积距离
            distances = [0]
            for i in range(1, len(points)):
                dist = np.linalg.norm(points[i] - points[i-1])
                distances.append(distances[-1] + dist)
            scalars = np.array(distances)
        else:
            scalars = np.linspace(0, 1, len(points))
            
        # 创建轨迹几何
        if style == 'line':
            mesh = self.create_simple_line(points, **kwargs)
        elif style == 'spline':
            mesh = self.create_spline(points, **kwargs)
        elif style == 'tube':
            mesh = self.create_tube(points, **kwargs)
            
        if mesh is None:
            return None
            
        # 添加标量数据
        # 由于样条插值,需要将标量数据映射到新的点
        if len(scalars) > 0:
            # 对于样条或管道,需要插值标量
            if style in ['spline', 'tube']:
                # 创建参数化表示
                t = np.linspace(0, 1, len(points))
                t_new = np.linspace(0, 1, len(mesh.points))
                
                # 线性插值标量
                from scipy import interpolate
                if len(t) > 1:
                    interp_func = interpolate.interp1d(t, scalars, 
                                                     bounds_error=False, 
                                                     fill_value='extrapolate')
                    mesh_scalars = interp_func(t_new)
                else:
                    mesh_scalars = np.ones(len(mesh.points)) * scalars[0]
            else:
                mesh_scalars = scalars
                
            mesh[color_by] = mesh_scalars
            
        return mesh
        
    def add_color_mapped_trajectory(self, trajectory, color_by='speed', 
                                   colormap='plasma', style='tube', **kwargs):
        """添加颜色映射轨迹到场景"""
        mesh = self.create_colored_trajectory(
            trajectory, color_by, colormap, style, **kwargs
        )
        
        if mesh is None:
            return None
            
        traj_id = trajectory.id
        self.trajectory_meshes[traj_id] = mesh
        
        return mesh
        
    def visualize_with_color_mapping(self, trajectories, color_by='speed', 
                                    colormap='plasma', window_size=(1400, 900)):
        """可视化带颜色映射的轨迹"""
        self.plotter = pv.Plotter(window_size=window_size, 
                                 title=f"轨迹可视化 - 颜色映射: {color_by}")
        
        for trajectory in trajectories:
            # 创建颜色映射轨迹
            mesh = self.add_color_mapped_trajectory(
                trajectory,
                color_by=color_by,
                colormap=colormap,
                style='tube',
                radius=0.3
            )
            
            if mesh and self.plotter:
                # 添加轨迹(带颜色映射)
                self.plotter.add_mesh(
                    mesh,
                    scalars=color_by,
                    cmap=colormap,
                    opacity=0.8,
                    clim=[mesh[color_by].min(), mesh[color_by].max()],
                    show_scalar_bar=True,
                    scalar_bar_args={'title': f'{color_by}'},
                    name=f'trajectory_{trajectory.id}',
                    show_edges=False
                )
                
                # 添加轨迹起始点
                if trajectory.points:
                    start_point = trajectory.points[0].position
                    end_point = trajectory.points[-1].position
                    
                    # 起始点
                    start_mesh = pv.Sphere(center=start_point, radius=0.5)
                    self.plotter.add_mesh(
                        start_mesh,
                        color='green',
                        name=f'start_{trajectory.id}'
                    )
                    
                    # 终点
                    end_mesh = pv.Sphere(center=end_point, radius=0.5)
                    self.plotter.add_mesh(
                        end_mesh,
                        color='red',
                        name=f'end_{trajectory.id}'
                    )
        
        # 设置场景
        self.plotter.add_axes()
        self.plotter.show_grid()
        
        # 设置相机
        self.plotter.camera_position = [(100, 100, 100), (0, 0, 0), (0, 0, 1)]
        self.plotter.set_background('black')
        
        # 显示
        self.plotter.show()

4. 案例1:目标飞行轨迹可视化

4.1 数据生成与加载

首先,我们创建一个模拟的目标飞行轨迹数据集:

python 复制代码
def generate_flight_trajectory(num_points=100, trajectory_id='001'):
    """生成模拟飞行轨迹数据"""
    from datetime import datetime, timedelta
    
    # 初始参数
    start_time = datetime(2024, 1, 1, 12, 0, 0)
    start_pos = np.array([0, 0, 1000])  # 起始位置
    start_speed = 200  # 起始速度 (m/s)
    
    trajectory = TrajectoryData.Trajectory(trajectory_id, 'aircraft')
    
    # 生成轨迹点
    for i in range(num_points):
        t = i * 10  # 10秒间隔
        
        # 计算位置(圆周运动 + 爬升)
        angle = math.radians(t * 2)  # 每10秒转2度
        radius = 5000  # 半径
        
        x = start_pos[0] + radius * math.cos(angle)
        y = start_pos[1] + radius * math.sin(angle)
        z = start_pos[2] + t * 5  # 每10秒爬升50米
        
        # 计算速度(逐渐加速)
        speed = start_speed + t * 0.2  # 每秒加速0.2m/s
        
        # 计算航向
        heading = math.degrees(angle + math.pi/2)  # 切线方向
        
        # 创建轨迹点
        timestamp = start_time + timedelta(seconds=t)
        point = TrajectoryData.TrajectoryPoint(
            timestamp=timestamp,
            position=[x, y, z],
            velocity=speed,
            heading=heading
        )
        
        trajectory.add_point(point)
    
    return trajectory

def save_trajectory_to_csv(trajectory, filename='flight_trajectory.csv'):
    """保存轨迹到CSV文件"""
    data = []
    
    for point in trajectory.points:
        row = {
            'timestamp': point.timestamp,
            'object_id': trajectory.id,
            'x': point.position[0],
            'y': point.position[1],
            'z': point.position[2],
            'speed': point.velocity,
            'heading': point.heading
        }
        data.append(row)
    
    df = pd.DataFrame(data)
    df.to_csv(filename, index=False)
    print(f"轨迹已保存到: {filename}")
    
    return df

4.2 完整的案例1实现

现在,我们实现一个完整的飞行轨迹可视化案例:

python 复制代码
class FlightTrajectoryDemo:
    """飞行轨迹演示类"""
    
    def __init__(self):
        self.trajectory_data = TrajectoryData()
        self.visualizer = EnhancedTrajectoryVisualizer()
        
    def load_or_generate_data(self):
        """加载或生成轨迹数据"""
        # 尝试从文件加载
        try:
            self.trajectory_data.load_from_csv(
                'flight_trajectory.csv',
                time_column='timestamp',
                id_column='object_id',
                x_column='x',
                y_column='y',
                z_column='z'
            )
            print("从文件加载轨迹数据成功")
        except FileNotFoundError:
            print("未找到轨迹文件,生成模拟数据...")
            # 生成模拟轨迹
            trajectory = generate_flight_trajectory(100, '001')
            self.trajectory_data.trajectories['001'] = trajectory
            # 保存到文件
            save_trajectory_to_csv(trajectory)
            
        return self.trajectory_data.get_summary()
        
    def create_terrain(self, plotter):
        """创建地形"""
        # 创建简单地形网格
        x = np.linspace(-10000, 10000, 50)
        y = np.linspace(-10000, 10000, 50)
        xx, yy = np.meshgrid(x, y)
        
        # 创建起伏地形
        z = 1000 + 200 * np.sin(0.0005 * xx) * np.cos(0.0005 * yy)
        
        terrain = pv.StructuredGrid(xx, yy, z)
        terrain['elevation'] = z.ravel()
        
        plotter.add_mesh(
            terrain,
            cmap='terrain',
            scalars='elevation',
            opacity=0.7,
            show_edges=False,
            name='terrain'
        )
        
        return terrain
        
    def add_aircraft_model(self, plotter, position, scale=10.0, color='white'):
        """添加飞机模型"""
        # 简化飞机模型
        fuselage = pv.Cylinder(center=[0, 0, 0], direction=[1, 0, 0], 
                              radius=1*scale, height=6*scale)
        wing = pv.Box(bounds=[-0.5*scale, 0.5*scale, -4*scale, 4*scale, 
                             -0.2*scale, 0.2*scale])
        tail = pv.Box(bounds=[-3*scale, -2*scale, -1.5*scale, 1.5*scale, 
                             -0.5*scale, 0.5*scale])
        
        aircraft = fuselage.boolean_union(wing)
        aircraft = aircraft.boolean_union(tail)
        
        # 定位
        aircraft.translate(position, inplace=True)
        
        plotter.add_mesh(aircraft, color=color, name='aircraft_model')
        
        return aircraft
        
    def add_info_panel(self, plotter, trajectory):
        """添加信息面板"""
        if not trajectory.points:
            return
            
        # 轨迹统计
        stats = trajectory.get_statistics()
        
        info_text = f"飞行轨迹信息\n"
        info_text += f"目标ID: {trajectory.id}\n"
        info_text += f"目标类型: {trajectory.object_type}\n"
        info_text += f"轨迹点数: {stats['point_count']}\n"
        info_text += f"持续时间: {stats['duration']:.1f}s\n"
        info_text += f"总距离: {stats['total_distance']:.1f}m\n"
        info_text += f"平均速度: {stats['avg_speed']:.1f}m/s\n"
        info_text += f"最大速度: {stats['max_speed']:.1f}m/s\n"
        info_text += f"高度范围: {stats['min_altitude']:.1f}-{stats['max_altitude']:.1f}m\n"
        
        # 添加文本
        plotter.add_text(
            info_text,
            position='upper_left',
            font_size=10,
            color='white',
            name='info_panel'
        )
        
    def add_time_slider(self, plotter, trajectory):
        """添加时间滑块"""
        if not trajectory.points:
            return
            
        # 创建时间点
        times = [p.timestamp for p in trajectory.points]
        
        # 如果是datetime,转换为时间戳
        if isinstance(times[0], datetime):
            time_values = [t.timestamp() for t in times]
        else:
            time_values = times
            
        time_min = min(time_values)
        time_max = max(time_values)
        
        # 创建滑块
        def update_time(value):
            # 找到最接近的时间点
            idx = np.argmin(np.abs(np.array(time_values) - value))
            if 0 <= idx < len(trajectory.points):
                point = trajectory.points[idx]
                
                # 更新飞机位置
                if 'aircraft_model' in plotter.actors:
                    plotter.remove_actor(plotter.actors['aircraft_model'])
                    
                self.add_aircraft_model(plotter, point.position)
                
                # 更新时间文本
                time_text = f"时间: {point.timestamp}\n"
                time_text += f"位置: {point.position}\n"
                time_text += f"速度: {point.velocity:.1f}m/s\n"
                time_text += f"航向: {point.heading:.1f}°"
                
                if 'time_info' in plotter.actors:
                    plotter.remove_actor(plotter.actors['time_info'])
                    
                plotter.add_text(
                    time_text,
                    position='upper_right',
                    font_size=10,
                    color='yellow',
                    name='time_info'
                )
        
        # 添加滑块
        plotter.add_slider_widget(
            update_time,
            [time_min, time_max],
            value=time_min,
            title='时间',
            pointa=(0.1, 0.9),
            pointb=(0.4, 0.9),
            style='modern'
        )
        
    def run_demo(self, color_by='speed'):
        """运行演示"""
        print("飞行轨迹可视化演示")
        print("=" * 50)
        
        # 加载数据
        summary = self.load_or_generate_data()
        print(f"数据摘要: {summary}")
        
        # 获取轨迹
        trajectories = self.trajectory_data.get_all_trajectories()
        if not trajectories:
            print("没有找到轨迹数据")
            return
            
        trajectory = trajectories[0]
        
        # 创建可视化窗口
        plotter = pv.Plotter(window_size=(1600, 1000), 
                            title=f"飞行轨迹可视化 - 颜色映射: {color_by}")
        
        # 添加地形
        self.create_terrain(plotter)
        
        # 添加轨迹(带颜色映射)
        mesh = self.visualizer.create_colored_trajectory(
            trajectory,
            color_by=color_by,
            colormap='plasma',
            style='tube',
            radius=50
        )
        
        if mesh:
            plotter.add_mesh(
                mesh,
                scalars=color_by,
                cmap='plasma',
                opacity=0.8,
                clim=[mesh[color_by].min(), mesh[color_by].max()],
                show_scalar_bar=True,
                scalar_bar_args={
                    'title': f'{color_by}',
                    'vertical': True,
                    'position_x': 0.85,
                    'position_y': 0.3,
                    'height': 0.4
                },
                name='trajectory',
                show_edges=False
            )
        
        # 添加轨迹起始点
        if trajectory.points:
            # 起始点
            start_point = trajectory.points[0].position
            start_mesh = pv.Sphere(center=start_point, radius=100)
            plotter.add_mesh(
                start_mesh,
                color='green',
                name='start_point'
            )
            
            # 终点
            end_point = trajectory.points[-1].position
            end_mesh = pv.Sphere(center=end_point, radius=100)
            plotter.add_mesh(
                end_mesh,
                color='red',
                name='end_point'
            )
            
            # 添加飞机模型(在起始位置)
            self.add_aircraft_model(plotter, start_point, scale=20)
        
        # 添加轨迹点(采样显示)
        points = np.array([p.position for p in trajectory.points])
        if len(points) > 0:
            # 均匀采样
            sample_indices = np.linspace(0, len(points)-1, 20, dtype=int)
            sample_points = points[sample_indices]
            
            points_mesh = pv.PolyData(sample_points)
            plotter.add_mesh(
                points_mesh,
                color='white',
                point_size=10,
                render_points_as_spheres=True,
                name='trajectory_points'
            )
            
            # 添加点标签
            for i, idx in enumerate(sample_indices):
                if idx < len(trajectory.points):
                    point = trajectory.points[idx]
                    label_text = f"t={point.timestamp if isinstance(point.timestamp, (int, float)) else point.timestamp.strftime('%H:%M:%S')}"
                    plotter.add_point_labels(
                        [point.position],
                        [label_text],
                        font_size=8,
                        point_color='white',
                        point_size=0,
                        name=f'label_{i}'
                    )
        
        # 添加信息面板
        self.add_info_panel(plotter, trajectory)
        
        # 添加时间滑块
        self.add_time_slider(plotter, trajectory)
        
        # 设置场景
        plotter.add_axes()
        plotter.show_grid()
        
        # 设置相机
        plotter.camera_position = [(20000, 20000, 10000), (0, 0, 5000), (0, 0, 1)]
        plotter.set_background('linear_gradient', bottom='#0a0a2a', top='#1a1a3a')
        
        # 添加控制说明
        controls = "控制说明:\n"
        controls += "鼠标拖拽: 旋转视角\n"
        controls += "鼠标右键拖拽: 平移视角\n"
        controls += "鼠标滚轮: 缩放\n"
        controls += "R键: 重置视角\n"
        controls += "1键: 按速度着色\n"
        controls += "2键: 按高度着色\n"
        controls += "3键: 按时间着色\n"
        controls += "4键: 按距离着色\n"
        
        plotter.add_text(
            controls,
            position='lower_left',
            font_size=10,
            color='cyan',
            name='controls'
        )
        
        # 添加键盘事件
        def set_color_by_speed():
            plotter.remove_actor(plotter.actors['trajectory'])
            mesh = self.visualizer.create_colored_trajectory(
                trajectory,
                color_by='speed',
                colormap='plasma',
                style='tube',
                radius=50
            )
            plotter.add_mesh(
                mesh,
                scalars='speed',
                cmap='plasma',
                opacity=0.8,
                name='trajectory',
                show_edges=False
            )
            print("颜色映射: 速度")
            
        def set_color_by_altitude():
            plotter.remove_actor(plotter.actors['trajectory'])
            mesh = self.visualizer.create_colored_trajectory(
                trajectory,
                color_by='altitude',
                colormap='terrain',
                style='tube',
                radius=50
            )
            plotter.add_mesh(
                mesh,
                scalars='altitude',
                cmap='terrain',
                opacity=0.8,
                name='trajectory',
                show_edges=False
            )
            print("颜色映射: 高度")
            
        def set_color_by_time():
            plotter.remove_actor(plotter.actors['trajectory'])
            mesh = self.visualizer.create_colored_trajectory(
                trajectory,
                color_by='time',
                colormap='viridis',
                style='tube',
                radius=50
            )
            plotter.add_mesh(
                mesh,
                scalars='time',
                cmap='viridis',
                opacity=0.8,
                name='trajectory',
                show_edges=False
            )
            print("颜色映射: 时间")
            
        def set_color_by_distance():
            plotter.remove_actor(plotter.actors['trajectory'])
            mesh = self.visualizer.create_colored_trajectory(
                trajectory,
                color_by='distance',
                colormap='hot',
                style='tube',
                radius=50
            )
            plotter.add_mesh(
                mesh,
                scalars='distance',
                cmap='hot',
                opacity=0.8,
                name='trajectory',
                show_edges=False
            )
            print("颜色映射: 距离")
            
        plotter.add_key_event("1", set_color_by_speed)
        plotter.add_key_event("2", set_color_by_altitude)
        plotter.add_key_event("3", set_color_by_time)
        plotter.add_key_event("4", set_color_by_distance)
        
        print("\n演示已启动")
        print("使用键盘1-4键切换颜色映射")
        
        # 显示
        plotter.show()

# 运行案例1
def run_case1():
    demo = FlightTrajectoryDemo()
    demo.run_demo(color_by='speed')

if __name__ == "__main__":
    run_case1()

5. 案例2:多目标轨迹对比分析

5.1 多目标数据生成

创建多个目标的轨迹数据用于对比分析:

python 复制代码
def generate_multiple_trajectories(num_trajectories=3, points_per_trajectory=50):
    """生成多个目标的轨迹数据"""
    trajectories = []
    
    for i in range(num_trajectories):
        traj_id = f"T{i+1:03d}"
        
        # 不同类型的轨迹
        if i == 0:
            # 直线飞行
            trajectory = generate_straight_trajectory(traj_id, points_per_trajectory)
        elif i == 1:
            # 圆周飞行
            trajectory = generate_circular_trajectory(traj_id, points_per_trajectory)
        else:
            # 随机飞行
            trajectory = generate_random_trajectory(traj_id, points_per_trajectory)
            
        trajectories.append(trajectory)
        
    return trajectories

def generate_straight_trajectory(traj_id, num_points):
    """生成直线飞行轨迹"""
    from datetime import datetime, timedelta
    
    start_time = datetime(2024, 1, 1, 12, 0, 0)
    start_pos = np.array([-5000, -5000, 1000])
    
    trajectory = TrajectoryData.Trajectory(traj_id, 'aircraft')
    
    for i in range(num_points):
        t = i * 10
        x = start_pos[0] + t * 20
        y = start_pos[1] + t * 15
        z = start_pos[2] + t * 2
        
        timestamp = start_time + timedelta(seconds=t)
        point = TrajectoryData.TrajectoryPoint(
            timestamp=timestamp,
            position=[x, y, z],
            velocity=200 + i * 0.5,
            heading=45
        )
        
        trajectory.add_point(point)
        
    return trajectory

def generate_circular_trajectory(traj_id, num_points):
    """生成圆周飞行轨迹"""
    from datetime import datetime, timedelta
    
    start_time = datetime(2024, 1, 1, 12, 0, 0)
    center = np.array([0, 0, 2000])
    radius = 3000
    
    trajectory = TrajectoryData.Trajectory(traj_id, 'aircraft')
    
    for i in range(num_points):
        t = i * 10
        angle = math.radians(t * 3)  # 每10秒转3度
        
        x = center[0] + radius * math.cos(angle)
        y = center[1] + radius * math.sin(angle)
        z = center[2] + math.sin(angle * 2) * 500
        
        # 计算速度(圆周运动速度)
        angular_speed = math.radians(3)  # 弧度/秒
        speed = radius * angular_speed
        
        # 计算航向(切线方向)
        heading = math.degrees(angle + math.pi/2)
        
        timestamp = start_time + timedelta(seconds=t)
        point = TrajectoryData.TrajectoryPoint(
            timestamp=timestamp,
            position=[x, y, z],
            velocity=speed,
            heading=heading
        )
        
        trajectory.add_point(point)
        
    return trajectory

def generate_random_trajectory(traj_id, num_points):
    """生成随机飞行轨迹"""
    from datetime import datetime, timedelta
    import random
    
    start_time = datetime(2024, 1, 1, 12, 0, 0)
    start_pos = np.array([5000, 5000, 1500])
    
    trajectory = TrajectoryData.Trajectory(traj_id, 'aircraft')
    
    # 随机行走
    current_pos = start_pos.copy()
    current_heading = 0
    current_speed = 180
    
    for i in range(num_points):
        t = i * 10
        
        # 随机变化
        heading_change = random.uniform(-10, 10)
        speed_change = random.uniform(-5, 5)
        altitude_change = random.uniform(-20, 20)
        
        current_heading += heading_change
        current_speed = max(100, min(300, current_speed + speed_change))
        
        # 计算新位置
        heading_rad = math.radians(current_heading)
        dx = current_speed * 10 * math.cos(heading_rad)
        dy = current_speed * 10 * math.sin(heading_rad)
        dz = altitude_change * 10
        
        new_pos = current_pos + np.array([dx, dy, dz])
        current_pos = new_pos
        
        timestamp = start_time + timedelta(seconds=t)
        point = TrajectoryData.TrajectoryPoint(
            timestamp=timestamp,
            position=new_pos.tolist(),
            velocity=current_speed,
            heading=current_heading
        )
        
        trajectory.add_point(point)
        
    return trajectory

5.2 完整的案例2实现

实现多目标轨迹对比分析的可视化系统:

python 复制代码
class MultiTrajectoryAnalysisDemo:
    """多目标轨迹分析演示"""
    
    def __init__(self):
        self.trajectories = []
        self.visualizer = EnhancedTrajectoryVisualizer()
        self.plotter = None
        self.color_palette = [
            [1, 0, 0],    # 红色
            [0, 1, 0],    # 绿色
            [0, 0, 1],    # 蓝色
            [1, 1, 0],    # 黄色
            [1, 0, 1],    # 紫色
            [0, 1, 1],    # 青色
        ]
        
    def generate_data(self, num_trajectories=4):
        """生成多目标轨迹数据"""
        print(f"生成 {num_trajectories} 个目标的轨迹数据...")
        
        self.trajectories = generate_multiple_trajectories(num_trajectories, 50)
        
        # 打印统计信息
        for i, traj in enumerate(self.trajectories):
            stats = traj.get_statistics()
            print(f"轨迹 {traj.id}: {stats['point_count']}个点, "
                  f"距离{stats['total_distance']:.0f}m, "
                  f"平均速度{stats['avg_speed']:.1f}m/s")
                  
    def create_comparison_plot(self, plotter):
        """创建对比分析图表"""
        if not self.trajectories:
            return
            
        # 提取统计数据
        traj_ids = []
        avg_speeds = []
        total_distances = []
        durations = []
        
        for traj in self.trajectories:
            stats = traj.get_statistics()
            traj_ids.append(traj.id)
            avg_speeds.append(stats['avg_speed'])
            total_distances.append(stats['total_distance'])
            durations.append(stats['duration'])
            
        # 创建子图
        plotter.subplot(0, 1)
        
        # 平均速度条形图
        y_pos = np.arange(len(traj_ids))
        
        # 创建条形图网格
        bar_width = 0.6
        bars = []
        
        for i, (traj_id, speed) in enumerate(zip(traj_ids, avg_speeds)):
            bar = pv.Box(bounds=[
                i - bar_width/2, i + bar_width/2,
                0, speed/50,  # 缩放速度值
                0, 1
            ])
            bars.append(bar)
            
        # 合并条形
        if bars:
            bar_mesh = bars[0]
            for bar in bars[1:]:
                bar_mesh = bar_mesh.merge(bar)
                
            # 为每个条形设置颜色
            colors = []
            for i, traj_id in enumerate(traj_ids):
                color_idx = i % len(self.color_palette)
                colors.extend([self.color_palette[color_idx]] * bar_mesh.n_points)
                
            bar_mesh['colors'] = colors
            
            plotter.add_mesh(
                bar_mesh,
                scalars='colors',
                rgb=True,
                name='speed_bars',
                show_edges=True
            )
            
        # 设置坐标轴
        plotter.add_text(
            "平均速度 (m/s)",
            position='upper_center',
            font_size=10,
            name='speed_title'
        )
        
        # 添加轨迹ID标签
        for i, traj_id in enumerate(traj_ids):
            plotter.add_text(
                traj_id,
                position=(i, -0.5, 0),
                font_size=8,
                name=f'label_{traj_id}'
            )
            
        plotter.subplot(0, 0)  # 返回主图
        
    def add_legend(self, plotter):
        """添加图例"""
        if not self.trajectories:
            return
            
        legend_text = "轨迹图例:\n"
        
        for i, traj in enumerate(self.trajectories):
            color_idx = i % len(self.color_palette)
            color = self.color_palette[color_idx]
            color_hex = '#{:02x}{:02x}{:02x}'.format(
                int(color[0]*255), int(color[1]*255), int(color[2]*255)
            )
            
            stats = traj.get_statistics()
            legend_text += f"<span style='color:{color_hex}'>■</span> "
            legend_text += f"{traj.id}: {stats['avg_speed']:.1f}m/s, "
            legend_text += f"{stats['total_distance']:.0f}m\n"
            
        plotter.add_text(
            legend_text,
            position='upper_right',
            font_size=9,
            name='legend',
            font='arial'
        )
        
    def add_trajectory_controls(self, plotter):
        """添加轨迹控制"""
        if not self.trajectories:
            return
            
        def toggle_trajectory(traj_index, visible):
            """切换轨迹显示"""
            if 0 <= traj_index < len(self.trajectories):
                traj = self.trajectories[traj_index]
                actor_name = f'trajectory_{traj.id}'
                
                if visible:
                    # 显示轨迹
                    if actor_name in plotter.actors:
                        plotter.remove_actor(plotter.actors[actor_name])
                        
                    # 创建轨迹
                    mesh = self.visualizer.create_colored_trajectory(
                        traj,
                        color_by='speed',
                        colormap='plasma',
                        style='tube',
                        radius=30
                    )
                    
                    if mesh:
                        plotter.add_mesh(
                            mesh,
                            scalars='speed',
                            cmap='plasma',
                            opacity=0.7,
                            name=actor_name,
                            show_edges=False
                        )
                else:
                    # 隐藏轨迹
                    if actor_name in plotter.actors:
                        plotter.remove_actor(plotter.actors[actor_name])
                        
        # 为每个轨迹添加复选框
        for i, traj in enumerate(self.trajectories):
            plotter.add_checkbox_button_widget(
                lambda state, idx=i: toggle_trajectory(idx, state),
                value=True,
                position=(10, 10 + i*30),
                size=20,
                border_size=2,
                color_on='green',
                color_off='red',
                background_color='white'
            )
            
            # 添加标签
            plotter.add_text(
                traj.id,
                position=(40, 10 + i*30),
                font_size=10,
                color='white',
                name=f'checkbox_label_{i}'
            )
            
    def add_time_synchronization(self, plotter):
        """添加时间同步"""
        if not self.trajectories:
            return
            
        # 找到共同的时间范围
        all_times = []
        for traj in self.trajectories:
            if traj.points:
                times = [p.timestamp for p in traj.points]
                if isinstance(times[0], datetime):
                    times = [t.timestamp() for t in times]
                all_times.extend(times)
                
        if not all_times:
            return
            
        time_min = min(all_times)
        time_max = max(all_times)
        
        # 添加时间滑块
        def update_all_trajectories(time_value):
            """更新所有轨迹的时间点"""
            for traj in self.trajectories:
                # 找到对应时间的点
                point = traj.get_point_at_time(time_value)
                if point:
                    # 更新标记点
                    marker_name = f'marker_{traj.id}'
                    if marker_name in plotter.actors:
                        plotter.remove_actor(plotter.actors[marker_name])
                        
                    # 创建标记
                    marker = pv.Sphere(center=point.position, radius=50)
                    color_idx = self.trajectories.index(traj) % len(self.color_palette)
                    color = self.color_palette[color_idx]
                    
                    plotter.add_mesh(
                        marker,
                        color=color,
                        name=marker_name
                    )
                    
        plotter.add_slider_widget(
            update_all_trajectories,
            [time_min, time_max],
            value=time_min,
            title='同步时间',
            pointa=(0.7, 0.1),
            pointb=(0.9, 0.1),
            style='modern'
        )
        
    def run_demo(self, num_trajectories=4):
        """运行演示"""
        print("多目标轨迹对比分析演示")
        print("=" * 50)
        
        # 生成数据
        self.generate_data(num_trajectories)
        
        # 创建绘图窗口
        self.plotter = pv.Plotter(window_size=(1800, 900), 
                                 shape=(1, 2),
                                 title="多目标轨迹对比分析")
        
        # 主图 (3D轨迹)
        self.plotter.subplot(0, 0)
        
        # 添加地形
        x = np.linspace(-10000, 10000, 50)
        y = np.linspace(-10000, 10000, 50)
        xx, yy = np.meshgrid(x, y)
        z = 1000 + 300 * np.sin(0.0005 * xx) * np.cos(0.0005 * yy)
        terrain = pv.StructuredGrid(xx, yy, z)
        terrain['elevation'] = z.ravel()
        
        self.plotter.add_mesh(
            terrain,
            cmap='terrain',
            scalars='elevation',
            opacity=0.5,
            show_edges=False,
            name='terrain'
        )
        
        # 添加轨迹
        for i, traj in enumerate(self.trajectories):
            color_idx = i % len(self.color_palette)
            color = self.color_palette[color_idx]
            
            # 创建轨迹
            mesh = self.visualizer.create_colored_trajectory(
                traj,
                color_by='speed',
                colormap='plasma',
                style='tube',
                radius=30
            )
            
            if mesh:
                self.plotter.add_mesh(
                    mesh,
                    scalars='speed',
                    cmap='plasma',
                    opacity=0.7,
                    name=f'trajectory_{traj.id}',
                    show_edges=False
                )
                
            # 添加起始点和终点
            if traj.points:
                # 起始点
                start_mesh = pv.Sphere(center=traj.points[0].position, radius=100)
                self.plotter.add_mesh(
                    start_mesh,
                    color='green',
                    name=f'start_{traj.id}'
                )
                
                # 终点
                end_mesh = pv.Sphere(center=traj.points[-1].position, radius=100)
                self.plotter.add_mesh(
                    end_mesh,
                    color='red',
                    name=f'end_{traj.id}'
                )
                
        # 设置主图
        self.plotter.add_axes()
        self.plotter.show_grid()
        self.plotter.camera_position = [(20000, 20000, 10000), (0, 0, 5000), (0, 0, 1)]
        self.plotter.set_background('linear_gradient', bottom='#0a0a2a', top='#1a1a3a')
        
        # 添加图例
        self.add_legend(self.plotter)
        
        # 添加轨迹控制
        self.add_trajectory_controls(self.plotter)
        
        # 添加时间同步
        self.add_time_synchronization(self.plotter)
        
        # 对比分析图表
        self.create_comparison_plot(self.plotter)
        
        # 添加控制说明
        controls = "控制说明:\n"
        controls += "左侧复选框: 显示/隐藏轨迹\n"
        controls += "时间滑块: 同步时间点\n"
        controls += "鼠标交互: 旋转/平移/缩放\n"
        
        self.plotter.add_text(
            controls,
            position='lower_left',
            font_size=9,
            color='cyan',
            name='controls'
        )
        
        print("\n演示已启动")
        print("使用左侧复选框控制轨迹显示")
        print("使用时间滑块同步查看不同时间点的位置")
        
        # 显示
        self.plotter.show()

# 运行案例2
def run_case2():
    demo = MultiTrajectoryAnalysisDemo()
    demo.run_demo(num_trajectories=4)

if __name__ == "__main__":
    run_case2()

6. 案例3:雷达探测历史可视化

6.1 雷达探测数据生成

模拟雷达探测历史数据:

python 复制代码
def generate_radar_detection_history(num_targets=3, detection_points_per_target=20):
    """生成雷达探测历史数据"""
    from datetime import datetime, timedelta
    import random
    
    detections = []
    start_time = datetime(2024, 1, 1, 12, 0, 0)
    
    # 雷达位置
    radar_position = np.array([0, 0, 0])
    
    for target_idx in range(num_targets):
        target_id = f"Target{target_idx+1:03d}"
        
        # 目标起始位置
        if target_idx == 0:
            start_pos = np.array([-5000, 0, 2000])
        elif target_idx == 1:
            start_pos = np.array([0, 5000, 2500])
        else:
            start_pos = np.array([5000, 0, 3000])
            
        # 目标速度
        speed = 200 + random.uniform(-50, 50)
        
        for i in range(detection_points_per_target):
            t = i * 30  # 30秒间隔
            
            # 计算目标位置
            if target_idx == 0:
                # 直线运动
                x = start_pos[0] + t * 20
                y = start_pos[1] + t * 10
                z = start_pos[2] + t * 2
            elif target_idx == 1:
                # 圆周运动
                angle = math.radians(t * 2)
                radius = 4000
                x = radius * math.cos(angle)
                y = radius * math.sin(angle)
                z = start_pos[2] + math.sin(angle) * 200
            else:
                # 随机运动
                x = start_pos[0] + random.uniform(-100, 100) * t
                y = start_pos[1] + random.uniform(-100, 100) * t
                z = start_pos[2] + random.uniform(-5, 5) * t
                
            target_position = np.array([x, y, z])
            
            # 计算雷达探测参数
            range_vec = target_position - radar_position
            distance = np.linalg.norm(range_vec)
            
            # 计算角度
            azimuth = math.degrees(math.atan2(range_vec[1], range_vec[0]))
            elevation = math.degrees(math.asin(range_vec[2] / distance))
            
            # 添加噪声
            range_noise = random.gauss(0, 10)  # 距离噪声
            angle_noise = random.gauss(0, 0.5)  # 角度噪声
            
            # 探测信噪比
            snr = 20 - distance/1000 + random.uniform(-5, 5)
            
            # 创建探测点
            timestamp = start_time + timedelta(seconds=t)
            detection = {
                'timestamp': timestamp,
                'target_id': target_id,
                'position': target_position.tolist(),
                'distance': distance + range_noise,
                'azimuth': azimuth + angle_noise,
                'elevation': elevation + angle_noise,
                'snr': snr,
                'radar_position': radar_position.tolist(),
                'is_tracked': random.random() > 0.3  # 70%的概率被跟踪
            }
            
            detections.append(detection)
            
    return detections, radar_position

def save_detections_to_csv(detections, filename='radar_detections.csv'):
    """保存探测数据到CSV"""
    data = []
    
    for det in detections:
        row = {
            'timestamp': det['timestamp'],
            'target_id': det['target_id'],
            'x': det['position'][0],
            'y': det['position'][1],
            'z': det['position'][2],
            'distance': det['distance'],
            'azimuth': det['azimuth'],
            'elevation': det['elevation'],
            'snr': det['snr'],
            'is_tracked': det['is_tracked']
        }
        data.append(row)
        
    df = pd.DataFrame(data)
    df.to_csv(filename, index=False)
    print(f"探测数据已保存到: {filename}")
    
    return df

6.2 完整的案例3实现

实现雷达探测历史的三维可视化:

python 复制代码
class RadarDetectionVisualizer:
    """雷达探测可视化器"""
    
    def __init__(self):
        self.detections = []
        self.radar_position = np.array([0, 0, 0])
        self.plotter = None
        
    def load_detection_data(self, num_targets=3):
        """加载或生成探测数据"""
        try:
            # 尝试从文件加载
            df = pd.read_csv('radar_detections.csv')
            
            self.detections = []
            for _, row in df.iterrows():
                detection = {
                    'timestamp': pd.to_datetime(row['timestamp']),
                    'target_id': row['target_id'],
                    'position': np.array([row['x'], row['y'], row['z']]),
                    'distance': row['distance'],
                    'azimuth': row['azimuth'],
                    'elevation': row['elevation'],
                    'snr': row['snr'],
                    'is_tracked': bool(row['is_tracked'])
                }
                self.detections.append(detection)
                
            print(f"从文件加载 {len(self.detections)} 个探测点")
            
        except FileNotFoundError:
            print("未找到探测数据文件,生成模拟数据...")
            self.detections, self.radar_position = generate_radar_detection_history(
                num_targets, 20
            )
            save_detections_to_csv(self.detections)
            
        return len(self.detections)
        
    def group_detections_by_target(self):
        """按目标分组探测点"""
        targets = {}
        
        for det in self.detections:
            target_id = det['target_id']
            if target_id not in targets:
                targets[target_id] = []
            targets[target_id].append(det)
            
        return targets
        
    def create_radar_model(self, plotter, radar_position, scale=100):
        """创建雷达模型"""
        # 雷达基座
        base = pv.Cylinder(center=radar_position, direction=[0, 0, 1], 
                          radius=scale, height=scale*0.5)
        
        # 雷达天线
        antenna = pv.Cone(center=radar_position + [0, 0, scale*0.75], 
                         direction=[0, 0, 1], height=scale, radius=scale*0.3)
        
        # 添加雷达模型
        plotter.add_mesh(base, color='gray', name='radar_base')
        plotter.add_mesh(antenna, color='darkgray', name='radar_antenna')
        
        return base, antenna
        
    def create_detection_points(self, detections, plotter, color_by='snr'):
        """创建探测点可视化"""
        if not detections:
            return None
            
        # 提取位置和其他属性
        positions = []
        snr_values = []
        distances = []
        target_ids = []
        timestamps = []
        
        for det in detections:
            positions.append(det['position'])
            snr_values.append(det['snr'])
            distances.append(det['distance'])
            target_ids.append(det['target_id'])
            timestamps.append(det['timestamp'])
            
        positions = np.array(positions)
        
        # 创建点云
        points_mesh = pv.PolyData(positions)
        
        # 添加属性
        points_mesh['snr'] = snr_values
        points_mesh['distance'] = distances
        points_mesh['target_id'] = target_ids
        
        # 时间属性
        if isinstance(timestamps[0], datetime):
            timestamps_sec = [t.timestamp() for t in timestamps]
        else:
            timestamps_sec = timestamps
            
        time_min = min(timestamps_sec)
        time_max = max(timestamps_sec)
        time_norm = [(t - time_min) / (time_max - time_min + 1e-10) for t in timestamps_sec]
        points_mesh['time'] = time_norm
        
        return points_mesh
        
    def create_detection_lines(self, detections_by_target, plotter):
        """创建探测连线"""
        for target_id, detections in detections_by_target.items():
            if len(detections) < 2:
                continue
                
            # 按时间排序
            detections_sorted = sorted(detections, key=lambda x: x['timestamp'])
            
            # 提取位置
            positions = [d['position'] for d in detections_sorted]
            positions = np.array(positions)
            
            # 创建线
            line = pv.lines_from_points(positions)
            
            # 添加时间属性
            timestamps = [d['timestamp'] for d in detections_sorted]
            if isinstance(timestamps[0], datetime):
                timestamps_sec = [t.timestamp() for t in timestamps]
            else:
                timestamps_sec = timestamps
                
            
            time_min = min(timestamps_sec)
            time_max = max(timestamps_sec)
            time_norm = [(t - time_min) / (time_max - time_min + 1e-10) for t in timestamps_sec]
            
            # 由于lines_from_points会插值,我们需要将时间属性映射到线上
            # 使用参数化方法
            line_length = line.length
            line['time'] = np.linspace(0, 1, line.n_points)
            
            # 根据目标ID选择颜色
            target_colors = {
                'Target001': [1, 0, 0],  # 红色
                'Target002': [0, 1, 0],  # 绿色
                'Target003': [0, 0, 1],  # 蓝色
                'Target004': [1, 1, 0],  # 黄色
            }
            
            color = target_colors.get(target_id, [0.5, 0.5, 0.5])
            
            # 添加连线
            plotter.add_mesh(
                line,
                color=color,
                line_width=3,
                opacity=0.6,
                name=f'detection_line_{target_id}'
            )
            
    def create_detection_cones(self, detections, plotter, radar_position, max_range=10000):
        """创建探测锥体(表示雷达波束)"""
        if not detections:
            return
            
        # 按时间分组探测点
        time_groups = {}
        for det in detections:
            # 简化时间精度(按10秒分组)
            if isinstance(det['timestamp'], datetime):
                time_key = det['timestamp'].replace(second=det['timestamp'].second//10 * 10)
            else:
                time_key = det['timestamp'] // 10 * 10
                
            if time_key not in time_groups:
                time_groups[time_key] = []
            time_groups[time_key].append(det)
            
        # 为每个时间组创建探测锥体
        for time_key, time_detections in list(time_groups.items())[::3]:  # 每3组显示一个
            if not time_detections:
                continue
                
            # 计算平均方位角和俯仰角
            azimuths = [d['azimuth'] for d in time_detections]
            elevations = [d['elevation'] for d in time_detections]
            
            avg_azimuth = np.mean(azimuths)
            avg_elevation = np.mean(elevations)
            
            # 创建探测锥体
            cone_height = max_range * 0.8
            cone_radius = cone_height * math.tan(math.radians(5))  # 5度波束宽度
            
            # 创建锥体(指向平均方向)
            cone = pv.Cone(center=radar_position, direction=[1, 0, 0], 
                          height=cone_height, radius=cone_radius)
            
            # 旋转到正确方向
            cone.rotate_z(avg_azimuth, inplace=True)
            cone.rotate_y(avg_elevation, inplace=True)
            
            # 添加锥体到场景(半透明)
            plotter.add_mesh(
                cone,
                color='yellow',
                opacity=0.1,
                style='wireframe' if len(time_detections) > 1 else 'surface',
                name=f'detection_cone_{time_key}'
            )
            
    def add_time_animation(self, plotter, detections_by_target):
        """添加时间动画控件"""
        if not detections_by_target:
            return
            
        # 收集所有时间点
        all_timestamps = []
        for detections in detections_by_target.values():
            for det in detections:
                if isinstance(det['timestamp'], datetime):
                    all_timestamps.append(det['timestamp'].timestamp())
                else:
                    all_timestamps.append(det['timestamp'])
                    
        if not all_timestamps:
            return
            
        time_min = min(all_timestamps)
        time_max = max(all_timestamps)
        
        # 当前时间指针
        self.current_animation_time = time_min
        self.animation_speed = 1.0  # 实时速度
        self.is_animating = False
        
        # 时间滑块
        def update_time_slider(value):
            self.current_animation_time = value
            self._update_animation_frame(plotter, detections_by_target)
            
        plotter.add_slider_widget(
            update_time_slider,
            [time_min, time_max],
            value=time_min,
            title='时间',
            pointa=(0.7, 0.9),
            pointb=(0.9, 0.9),
            style='modern'
        )
        
        # 播放/暂停按钮
        def toggle_animation():
            self.is_animating = not self.is_animating
            state = "播放" if self.is_animating else "暂停"
            print(f"动画{state}")
            
        plotter.add_checkbox_button_widget(
            toggle_animation,
            value=False,
            position=(10, 10),
            size=30,
            color_on='green',
            color_off='red',
            background_color='white'
        )
        
        # 速度控制
        def set_animation_speed(value):
            self.animation_speed = value
            print(f"动画速度: {value}x")
            
        plotter.add_slider_widget(
            set_animation_speed,
            [0.1, 5.0],
            value=1.0,
            title='速度',
            pointa=(0.7, 0.8),
            pointb=(0.9, 0.8),
            style='modern'
        )
        
    def _update_animation_frame(self, plotter, detections_by_target):
        """更新动画帧"""
        # 清除当前帧的标记
        for actor_name in list(plotter.actors.keys()):
            if actor_name.startswith(('current_detection_', 'time_marker_')):
                plotter.remove_actor(plotter.actors[actor_name])
                
        # 更新每个目标的当前位置标记
        for target_id, detections in detections_by_target.items():
            # 找到最接近当前时间的探测点
            closest_det = None
            min_time_diff = float('inf')
            
            for det in detections:
                if isinstance(det['timestamp'], datetime):
                    det_time = det['timestamp'].timestamp()
                else:
                    det_time = det['timestamp']
                    
                time_diff = abs(det_time - self.current_animation_time)
                if time_diff < min_time_diff:
                    min_time_diff = time_diff
                    closest_det = det
                    
            if closest_det and min_time_diff < 30:  # 30秒内认为有效
                # 创建当前位置标记
                marker = pv.Sphere(center=closest_det['position'], radius=100)
                
                target_colors = {
                    'Target001': [1, 0, 0],
                    'Target002': [0, 1, 0], 
                    'Target003': [0, 0, 1],
                    'Target004': [1, 1, 0],
                }
                
                color = target_colors.get(target_id, [0.5, 0.5, 0.5])
                
                plotter.add_mesh(
                    marker,
                    color=color,
                    name=f'current_detection_{target_id}'
                )
                
                # 添加时间标签
                time_text = closest_det['timestamp'].strftime('%H:%M:%S') if isinstance(closest_det['timestamp'], datetime) else str(closest_det['timestamp'])
                label_text = f"{target_id}\n{time_text}\nSNR: {closest_det['snr']:.1f}dB"
                
                plotter.add_point_labels(
                    [closest_det['position']],
                    [label_text],
                    font_size=8,
                    point_color=color,
                    point_size=0,
                    name=f'time_marker_{target_id}'
                )
                
        # 更新时间显示
        if 'time_display' in plotter.actors:
            plotter.remove_actor(plotter.actors['time_display'])
            
        current_time_str = datetime.fromtimestamp(self.current_animation_time).strftime('%Y-%m-%d %H:%M:%S') if self.current_animation_time > 0 else str(self.current_animation_time)
        time_text = f"当前时间: {current_time_str}"
        
        plotter.add_text(
            time_text,
            position='upper_center',
            font_size=12,
            color='white',
            name='time_display'
        )
        
    def add_animation_callback(self, plotter, detections_by_target):
        """添加动画回调函数"""
        self.last_animation_time = time.time()
        
        def animation_callback():
            if not self.is_animating:
                return
                
            current_time = time.time()
            delta_time = current_time - self.last_animation_time
            self.last_animation_time = current_time
            
            # 更新时间
            time_delta = delta_time * self.animation_speed
            self.current_animation_time += time_delta
            
            # 检查时间范围
            all_timestamps = []
            for detections in detections_by_target.values():
                for det in detections:
                    if isinstance(det['timestamp'], datetime):
                        all_timestamps.append(det['timestamp'].timestamp())
                    else:
                        all_timestamps.append(det['timestamp'])
                        
            if all_timestamps:
                time_max = max(all_timestamps)
                if self.current_animation_time > time_max:
                    self.current_animation_time = min(all_timestamps)  # 循环播放
                    
            # 更新帧
            self._update_animation_frame(plotter, detections_by_target)
            
        plotter.add_callback(animation_callback, interval=50)  # 20fps
        
    def create_statistics_panel(self, plotter, detections_by_target):
        """创建统计信息面板"""
        if not detections_by_target:
            return
            
        stats_text = "雷达探测统计\n"
        stats_text += "=" * 20 + "\n"
        
        total_detections = 0
        for target_id, detections in detections_by_target.items():
            stats_text += f"{target_id}:\n"
            stats_text += f"  探测点数: {len(detections)}\n"
            
            if detections:
                snr_values = [d['snr'] for d in detections]
                distances = [d['distance'] for d in detections]
                
                stats_text += f"  平均SNR: {np.mean(snr_values):.1f}dB\n"
                stats_text += f"  平均距离: {np.mean(distances):.0f}m\n"
                stats_text += f"  跟踪率: {sum(1 for d in detections if d['is_tracked'])/len(detections)*100:.1f}%\n"
                
            stats_text += "\n"
            total_detections += len(detections)
            
        stats_text += f"总探测点: {total_detections}\n"
        
        plotter.add_text(
            stats_text,
            position='upper_left',
            font_size=10,
            color='white',
            name='statistics_panel'
        )
        
    def create_visibility_controls(self, plotter):
        """创建可视化控制"""
        controls = {
            'show_detection_points': True,
            'show_detection_lines': True, 
            'show_detection_cones': False,
            'show_radar_model': True
        }
        
        # 探测点显示控制
        def toggle_detection_points(state):
            controls['show_detection_points'] = state
            self._update_visibility(plotter, controls)
            
        plotter.add_checkbox_button_widget(
            toggle_detection_points,
            value=True,
            position=(10, 50),
            size=25,
            color_on='green',
            color_off='red'
        )
        plotter.add_text("探测点", position=(40, 50), font_size=10, color='white')
        
        # 探测线显示控制
        def toggle_detection_lines(state):
            controls['show_detection_lines'] = state
            self._update_visibility(plotter, controls)
            
        plotter.add_checkbox_button_widget(
            toggle_detection_lines,
            value=True,
            position=(10, 85),
            size=25,
            color_on='green', 
            color_off='red'
        )
        plotter.add_text("探测线", position=(40, 85), font_size=10, color='white')
        
        # 探测锥体显示控制
        def toggle_detection_cones(state):
            controls['show_detection_cones'] = state
            self._update_visibility(plotter, controls)
            
        plotter.add_checkbox_button_widget(
            toggle_detection_cones,
            value=False,
            position=(10, 120),
            size=25,
            color_on='green',
            color_off='red'
        )
        plotter.add_text("探测波束", position=(40, 120), font_size=10, color='white')
        
        # 雷达模型显示控制
        def toggle_radar_model(state):
            controls['show_radar_model'] = state
            self._update_visibility(plotter, controls)
            
        plotter.add_checkbox_button_widget(
            toggle_radar_model,
            value=True,
            position=(10, 155),
            size=25,
            color_on='green',
            color_off='red'
        )
        plotter.add_text("雷达模型", position=(40, 155), font_size=10, color='white')
        
        return controls
        
    def _update_visibility(self, plotter, controls):
        """更新可视化元素显示状态"""
        # 更新探测点显示
        for actor_name in list(plotter.actors.keys()):
            if actor_name.startswith('detection_points'):
                plotter.actors[actor_name].SetVisibility(controls['show_detection_points'])
                
        # 更新探测线显示
        for actor_name in list(plotter.actors.keys()):
            if actor_name.startswith('detection_line'):
                plotter.actors[actor_name].SetVisibility(controls['show_detection_lines'])
                
        # 更新探测锥体显示
        for actor_name in list(plotter.actors.keys()):
            if actor_name.startswith('detection_cone'):
                plotter.actors[actor_name].SetVisibility(controls['show_detection_cones'])
                
        # 更新雷达模型显示
        for actor_name in list(plotter.actors.keys()):
            if actor_name.startswith('radar_'):
                plotter.actors[actor_name].SetVisibility(controls['show_radar_model'])
                
        plotter.update()
        
    def run_demo(self, num_targets=3):
        """运行雷达探测历史可视化演示"""
        print("雷达探测历史可视化演示")
        print("=" * 50)
        
        # 加载数据
        detection_count = self.load_detection_data(num_targets)
        print(f"加载了 {detection_count} 个探测点")
        
        # 按目标分组
        detections_by_target = self.group_detections_by_target()
        print(f"检测到 {len(detections_by_target)} 个目标")
        
        # 创建绘图窗口
        self.plotter = pv.Plotter(window_size=(1600, 900), 
                                 title="雷达探测历史可视化")
        
        # 设置场景
        self.plotter.set_background('linear_gradient', bottom='#0a0a1a', top='#1a1a2a')
        self.plotter.add_axes()
        self.plotter.show_grid()
        
        # 添加地形
        x = np.linspace(-10000, 10000, 30)
        y = np.linspace(-10000, 10000, 30) 
        xx, yy = np.meshgrid(x, y)
        z = 100 + 50 * np.sin(0.001 * xx) * np.cos(0.001 * yy)
        terrain = pv.StructuredGrid(xx, yy, z)
        terrain['elevation'] = z.ravel()
        
        self.plotter.add_mesh(
            terrain,
            cmap='terrain',
            scalars='elevation',
            opacity=0.3,
            show_edges=False,
            name='terrain'
        )
        
        # 添加雷达模型
        radar_scale = 200
        self.create_radar_model(self.plotter, self.radar_position, radar_scale)
        
        # 添加探测点
        all_detections = []
        for target_detections in detections_by_target.values():
            all_detections.extend(target_detections)
            
        points_mesh = self.create_detection_points(all_detections, self.plotter, 'snr')
        if points_mesh is not None:
            self.plotter.add_mesh(
                points_mesh,
                scalars='snr',
                cmap='hot',
                point_size=8,
                render_points_as_spheres=True,
                opacity=0.8,
                name='detection_points',
                show_scalar_bar=True,
                scalar_bar_args={'title': '信噪比 (dB)'}
            )
            
        # 添加探测连线
        self.create_detection_lines(detections_by_target, self.plotter)
        
        # 添加探测锥体
        self.create_detection_cones(all_detections, self.plotter, self.radar_position)
        
        # 添加时间动画控件
        self.add_time_animation(self.plotter, detections_by_target)
        
        # 添加动画回调
        self.add_animation_callback(self.plotter, detections_by_target)
        
        # 添加统计信息面板
        self.create_statistics_panel(self.plotter, detections_by_target)
        
        # 添加可视化控制
        controls = self.create_visibility_controls(self.plotter)
        
        # 添加控制说明
        instructions = "控制说明:\n"
        instructions += "左侧复选框: 显示/隐藏元素\n"
        instructions += "时间滑块: 手动控制时间\n" 
        instructions += "播放按钮: 开始/暂停动画\n"
        instructions += "速度滑块: 调整动画速度\n"
        instructions += "鼠标交互: 旋转/平移/缩放视角\n"
        
        self.plotter.add_text(
            instructions,
            position='lower_left',
            font_size=10,
            color='cyan',
            name='instructions'
        )
        
        # 设置相机
        self.plotter.camera_position = [
            (self.radar_position[0], self.radar_position[1] - 15000, 5000),
            self.radar_position,
            (0, 0, 1)
        ]
        
        print("\n演示已启动")
        print("使用左侧复选框控制不同元素的显示")
        print("使用时间控件查看不同时间的探测情况")
        
        # 显示
        self.plotter.show()

# 运行案例3
def run_case3():
    demo = RadarDetectionVisualizer()
    demo.run_demo(num_targets=3)

if __name__ == "__main__":
    run_case3()

7. 知识点总结与扩展应用

7.1 核心技术要点总结

1. 轨迹数据处理技术

  • 多格式数据加载(CSV、JSON、数据库)

  • 坐标系统转换与数据标准化

  • 时间序列数据处理与插值

  • 轨迹优化与采样算法

2. 3D可视化技术

  • 多种轨迹表示方法(线、样条、管道)

  • 颜色映射与属性可视化

  • 动态时间轴与动画控制

  • 交互式控件与用户界面

3. 雷达探测可视化

  • 探测点云的可视化

  • 雷达波束与探测范围表示

  • 多目标跟踪与轨迹重建

  • 探测质量(SNR)的可视化

7.2 性能优化技巧

大规模轨迹数据处理

python 复制代码
# 1. 数据分块加载
def load_large_trajectory_chunked(filepath, chunk_size=10000):
    """分块加载大规模轨迹数据"""
    chunks = []
    for chunk in pd.read_csv(filepath, chunksize=chunk_size):
        # 处理每个数据块
        processed_chunk = process_trajectory_chunk(chunk)
        chunks.append(processed_chunk)
    return pd.concat(chunks)

# 2. 细节层次(LOD)技术
def create_lod_trajectory(trajectory, lod_levels=3):
    """创建多细节层次轨迹"""
    lod_meshes = []
    for level in range(lod_levels):
        if level == 0:  # 最高细节
            mesh = create_high_detail_trajectory(trajectory)
        else:  # 较低细节
            sample_ratio = 1.0 / (2 ** level)
            sampled_points = uniform_sampling(trajectory.points, sample_ratio)
            mesh = create_simplified_trajectory(sampled_points)
        lod_meshes.append(mesh)
    return lod_meshes

实时渲染优化

python 复制代码
# 1. 实例化渲染
def render_multiple_trajectories_instanced(trajectories):
    """使用实例化渲染多个相似轨迹"""
    base_mesh = create_base_trajectory_mesh()
    instance_matrices = []
    
    for traj in trajectories:
        # 计算变换矩阵
        matrix = calculate_transform_matrix(traj)
        instance_matrices.append(matrix)
    
    # 批量渲染
    plotter.add_mesh(base_mesh, transforms=instance_matrices)

# 2. 视锥体裁剪
def frustum_culling(trajectories, camera_frustum):
    """视锥体裁剪,只渲染可见轨迹"""
    visible_trajectories = []
    for traj in trajectories:
        if is_trajectory_in_frustum(traj, camera_frustum):
            visible_trajectories.append(traj)
    return visible_trajectories

7.3 扩展应用方向

军事仿真应用

  • 战场态势实时可视化

  • 导弹防御系统模拟

  • 电子对抗效果评估

  • 作战方案推演验证

民用领域应用

  • 空中交通管制系统

  • 无人机航迹监控

  • 车辆轨迹分析

  • 运动目标行为分析

科学研究应用

  • 动物迁徙轨迹研究

  • 气象数据可视化

  • 海洋洋流分析

  • 天体运动轨迹模拟

8. 结语

本文介绍了使用PyVista进行雷达与目标轨迹可视化的完整技术方案,涵盖了从数据加载处理到高级可视化的全流程。通过三个实战案例,我们展示了:

  1. 单目标飞行轨迹可视化​ - 基础轨迹表示与属性映射

  2. 多目标轨迹对比分析​ - 复杂场景下的轨迹管理与比较

  3. 雷达探测历史可视化​ - 时间序列数据的动态展示

这些技术不仅适用于军事仿真领域,在交通监控、环境监测、科学研究等众多领域都有广泛应用价值。PyVista作为强大的3D可视化工具,为轨迹数据的直观理解和深度分析提供了有力支持。

随着数据规模的不断扩大和分析需求的日益复杂,轨迹可视化技术将继续向实时化、智能化、交互式方向发展。掌握这些核心技术,将为应对未来的数据可视化挑战奠定坚实基础。

复制代码
复制代码
复制代码
复制代码
复制代码
复制代码
相关推荐
Pth_you1 小时前
Python权限问题终极解决方案
开发语言·python
Lun3866buzha1 小时前
基于VFNet的O型圈缺陷检测与分类系统设计
人工智能·分类·数据挖掘
张张努力变强1 小时前
C++ 类和对象(二):实例化、this指针、构造函数、析构函数详解
开发语言·c++
gaize12131 小时前
云计算服务和云解决方案-阿里云
开发语言·php
果粒蹬i2 小时前
AI Agent工业化落地:任务拆解 + 工具调用 + 反馈优化三板斧
人工智能·microsoft
沫儿笙2 小时前
ABB焊接机器人碳钢焊接节气
人工智能·机器人
全干engineer2 小时前
一篇文章看懂AI名词-Prompt,Agent,MCP,FunctionCalling是什么
人工智能·ai·prompt·agent
njsgcs2 小时前
python qt做ai透明对话框
人工智能·python·qt
AI工具测评大师2 小时前
如何确保你的英文写作通过ZeroGPT检测?
人工智能·自然语言处理·ai写作·ai自动写作