import numpy as npimport matplotlib.pyplot as pltimport matplotlib.font_manager as fm# 自动查找支持中文的字体def get_chinese_font(): # 常见中文字体名称(按优先级排序) chinese_font_names = [ 'Microsoft YaHei', 'SimHei', 'WenQuanYi Zen Hei', 'Noto Sans CJK SC', 'Noto Sans CJK TC', 'STHeiti', 'AR PL UMing CN', 'Droid Sans Fallback' ] available_fonts = [f.name for f in fm.fontManager.ttflist] for font in chinese_font_names: if font in available_fonts: return font # 如果没有找到,手动搜索包含 'CJK' 或 'Hei' 的字体 for f in fm.fontManager.ttflist: if 'CJK' in f.name or 'Hei' in f.name or '黑体' in f.name: return f.name return None # 未找到,使用默认chinese_font = get_chinese_font()if chinese_font: plt.rcParams['font.sans-serif'] = [chinese_font] + plt.rcParams['font.sans-serif']else: print("警告:未找到中文字体,图表中的中文将显示为方框。")plt.rcParams['axes.unicode_minus'] = Falsefrom mpl_toolkits.mplot3d import Axes3Dfrom matplotlib.patches import FancyBboxPatchimport tkinter as tkfrom tkinter import ttk, messagebox, scrolledtext, filedialogfrom datetime import datetime, timedeltaimport cartopy.crs as ccrsimport cartopy.feature as cfeaturefrom scipy.spatial.transform import Rotation as Rimport jsonimport csvimport osfrom dataclasses import dataclassfrom typing import Dict, List, Optional, Tuple, Any, Unionfrom enum import Enumclass OrbitElementType(Enum): """轨道根数类型枚举""" KEPLERIAN = "开普勒轨道根数" CARTESIAN = "笛卡尔坐标" EQUINOCTIAL = "春分点轨道根数" TLE = "TLE两行元素" CUSTOM = "自定义"@dataclassclass OrbitalElement: """轨道根数数据结构""" name: str symbol: str value: float unit: str description: str required: bool = True constraint: Optional[Tuple[float, float]] = None # (min, max)class ElementSet: """轨道根数集合管理""" def __init__(self): self.elements: List[OrbitalElement] = [] self.element_type = OrbitElementType.CUSTOM self.metadata: Dict[str, Any] = {} def add_element(self, element: OrbitalElement): """添加轨道根数""" self.elements.append(element) def remove_element(self, index: int): """移除轨道根数""" if 0 <= index < len(self.elements): self.elements.pop(index) def validate(self) -> Tuple[bool, str]: """验证轨道根数集合""" errors = [] # 检查必需参数 required_elements = [e for e in self.elements if e.required] for element in required_elements: if element.constraint: min_val, max_val = element.constraint if not (min_val <= element.value <= max_val): errors.append(f"{element.name}超出范围[{min_val}, {max_val}]") return len(errors) == 0, "\n".join(errors) def to_dict(self) -> Dict: """转换为字典""" return { 'type': self.element_type.value, 'metadata': self.metadata, 'elements': [ { 'name': e.name, 'symbol': e.symbol, 'value': e.value, 'unit': e.unit, 'description': e.description, 'required': e.required } for e in self.elements ] } @classmethod def from_dict(cls, data: Dict): """从字典创建""" element_set = cls() element_set.element_type = OrbitElementType(data['type']) element_set.metadata = data.get('metadata', {}) for elem_data in data['elements']: element = OrbitalElement( name=elem_data['name'], symbol=elem_data['symbol'], value=elem_data['value'], unit=elem_data['unit'], description=elem_data['description'], required=elem_data.get('required', True) ) element_set.add_element(element) return element_setclass UniversalOrbitConverter: """通用轨道转换器 - 支持任意数量的轨道根数""" def __init__(self): # 地球物理常数 self.constants = { 'GM': 3.986004418e14, # 地球引力常数 (m^3/s^2) 'J2': 1.08263e-3, # J2摄动项 'J3': -2.5327e-6, # J3摄动项 'J4': -1.6196e-6, # J4摄动项 'Re': 6378137.0, # 地球赤道半径 (m) 'f': 1/298.257223563, # 地球扁率 'omega_e': 7.2921159e-5, # 地球自转角速度 (rad/s) 'c': 299792458.0, # 光速 (m/s) } # 支持的轨道根数系统 self.supported_systems = { 'keplerian': { 'required': ['a', 'e', 'i', 'Omega', 'omega', 'M'], 'optional': ['B_star', 'n_dot', 'n_ddot', 'BSTAR', 'drag_coefficient'], 'converter': self.keplerian_to_eci }, 'cartesian': { 'required': ['x', 'y', 'z', 'vx', 'vy', 'vz'], 'optional': ['mass', 'area'], 'converter': self.cartesian_to_eci }, 'equinoctial': { 'required': ['a', 'h', 'k', 'p', 'q', 'lambda'], 'optional': ['B_star'], 'converter': self.equinoctial_to_eci }, 'tle': { 'required': ['inclination', 'raan', 'eccentricity', 'arg_perigee', 'mean_anomaly', 'mean_motion'], 'optional': ['bstar', 'epoch', 'rev_number'], 'converter': self.tle_to_eci } } # 坐标系转换链 self.coordinate_chains = { 'eci_to_ecef': self._eci_to_ecef, 'eci_to_geodetic': self._eci_to_geodetic, 'ecef_to_geodetic': self._ecef_to_geodetic, 'eci_to_ric': self._eci_to_ric, 'eci_to_ntw': self._eci_to_ntw, } # 历史记录 self.history = [] def keplerian_to_eci(self, elements: Dict[str, float], t: float = 0) -> Tuple[np.ndarray, np.ndarray]: """ 开普勒轨道根数转ECI坐标系 支持额外的动力学参数: - B*: BSTAR阻力项 - n_dot, n_ddot: 平均运动变化率 - 光压系数 """ a = elements['a'] e = elements['e'] i = elements['i'] Omega = elements['Omega'] omega = elements['omega'] M0 = elements['M'] # 应用额外的动力学修正 n = np.sqrt(self.constants['GM'] / a**3) if 'n_dot' in elements: n += elements['n_dot'] * t / 2 if 'n_ddot' in elements: n += elements['n_ddot'] * t**2 / 6 # 修正平近点角 M = M0 + n * t if 'drag_coefficient' in elements: # 应用大气阻力修正 rho0 = 1.225 # 海平面大气密度 H = 8500 # 大气标高 a_modified = a - elements.get('B_star', 0) * rho0 * np.exp(-(a-self.constants['Re'])/H) * t**2 / 2 a = a_modified # 求解开普勒方程 E = self._solve_kepler(M, e) # 计算真近点角 nu = 2 * np.arctan2(np.sqrt(1 + e) * np.sin(E/2), np.sqrt(1 - e) * np.cos(E/2)) # 轨道平面内的位置 r = a * (1 - e * np.cos(E)) x_orb = r * np.cos(nu) y_orb = r * np.sin(nu) # 轨道平面内的速度 p = a * (1 - e**2) vx_orb = -np.sqrt(self.constants['GM'] / p) * np.sin(nu) vy_orb = np.sqrt(self.constants['GM'] / p) * (e + np.cos(nu)) # 旋转到ECI R_ECI = self._rotation_matrix_3d(Omega, i, omega) r_eci = R_ECI @ np.array([x_orb, y_orb, 0]) v_eci = R_ECI @ np.array([vx_orb, vy_orb, 0]) # 应用J2和其他摄动 if elements.get('include_j2', True): r_eci, v_eci = self._apply_j2_perturbation(r_eci, v_eci, t) if elements.get('include_j3', False): r_eci, v_eci = self._apply_jn_perturbation(r_eci, v_eci, t, 3) if elements.get('include_solar_pressure', False): solar_coeff = elements.get('solar_pressure_coeff', 1.0) r_eci, v_eci = self._apply_solar_pressure(r_eci, v_eci, t, solar_coeff) return r_eci, v_eci def cartesian_to_eci(self, elements: Dict[str, float], t: float = 0) -> Tuple[np.ndarray, np.ndarray]: """笛卡尔坐标转ECI(直接返回,已在ECI中)""" r_eci = np.array([elements['x'], elements['y'], elements['z']]) v_eci = np.array([elements['vx'], elements['vy'], elements['vz']]) return r_eci, v_eci def equinoctial_to_eci(self, elements: Dict[str, float], t: float = 0) -> Tuple[np.ndarray, np.ndarray]: """春分点轨道根数转ECI""" a = elements['a'] h = elements['h'] k = elements['k'] p = elements['p'] q = elements['q'] lambda_ = elements['lambda'] # 计算辅助变量 s2 = 1 + h**2 + k**2 w = 1 + p**2 + q**2 # 位置和速度矢量在春分点坐标系中 r_equi = np.zeros(3) v_equi = np.zeros(3) # 这里需要根据具体的春分点轨道根数定义来实现 # 此处为简化实现 r_equi = np.array([a * (1 - h**2 - k**2), 0, 0]) v_equi = np.array([0, np.sqrt(self.constants['GM'] / a), 0]) return r_equi, v_equi def tle_to_eci(self, elements: Dict[str, float], t: float = 0) -> Tuple[np.ndarray, np.ndarray]: """ TLE格式转ECI 使用SGP4简化模型 """ inclination = elements['inclination'] raan = elements['raan'] eccentricity = elements['eccentricity'] arg_perigee = elements['arg_perigee'] mean_anomaly = elements['mean_anomaly'] mean_motion = elements['mean_motion'] bstar = elements.get('bstar', 0.0) # 转换为经典轨道根数 # 半长轴从平均运动推导 a = (self.constants['GM'] / (mean_motion * 2 * np.pi / 86400)**2)**(1/3) keplerian_elements = { 'a': a, 'e': eccentricity, 'i': inclination, 'Omega': raan, 'omega': arg_perigee, 'M': mean_anomaly, 'B_star': bstar } # 使用开普勒转换 return self.keplerian_to_eci(keplerian_elements, t) def _solve_kepler(self, M: float, e: float, tolerance: float = 1e-12) -> float: """求解开普勒方程(通用方法)""" if e < 0.8: E = M else: E = np.pi for _ in range(100): dE = (E - e * np.sin(E) - M) / (1 - e * np.cos(E)) E -= dE if abs(dE) < tolerance: break return E def _rotation_matrix_3d(self, Omega: float, i: float, omega: float) -> np.ndarray: """3D旋转矩阵""" cO, sO = np.cos(Omega), np.sin(Omega) ci, si = np.cos(i), np.sin(i) co, so = np.cos(omega), np.sin(omega) return np.array([ [cO*co - sO*ci*so, -cO*so - sO*ci*co, sO*si], [sO*co + cO*ci*so, -sO*so + cO*ci*co, -cO*si], [si*so, si*co, ci] ]) def _apply_j2_perturbation(self, r: np.ndarray, v: np.ndarray, t: float) -> Tuple[np.ndarray, np.ndarray]: """应用J2摄动""" r_norm = np.linalg.norm(r) factor = -1.5 * self.constants['J2'] * (self.constants['Re']/r_norm)**2 a_j2 = np.zeros(3) a_j2[0] = factor * (1 - 5*(r[2]/r_norm)**2) * r[0] / r_norm a_j2[1] = factor * (1 - 5*(r[2]/r_norm)**2) * r[1] / r_norm a_j2[2] = factor * (3 - 5*(r[2]/r_norm)**2) * r[2] / r_norm a_j2 *= self.constants['GM'] / r_norm**2 return r, v + a_j2 * t def _apply_jn_perturbation(self, r: np.ndarray, v: np.ndarray, t: float, n: int) -> Tuple[np.ndarray, np.ndarray]: """应用高阶Jn摄动""" # 简化实现 return r, v def _apply_solar_pressure(self, r: np.ndarray, v: np.ndarray, t: float, coeff: float) -> Tuple[np.ndarray, np.ndarray]: """应用太阳光压""" # 太阳光压常数 P_sun = 4.56e-6 # N/m^2 # 简化的太阳方向(在地球轨道上) sun_dir = np.array([1, 0, 0]) / np.linalg.norm([1, 0, 0]) a_srp = coeff * P_sun * sun_dir return r, v + a_srp * t def _eci_to_ecef(self, r_eci: np.ndarray, t: float) -> np.ndarray: """ECI转ECEF""" # 格林威治恒星时角 GMST = self._calculate_gmst(t) R = np.array([ [np.cos(GMST), np.sin(GMST), 0], [-np.sin(GMST), np.cos(GMST), 0], [0, 0, 1] ]) return R @ r_eci def _calculate_gmst(self, t: float) -> float: """计算格林威治恒星时角""" # 简化计算 return self.constants['omega_e'] * t def _eci_to_geodetic(self, r_eci: np.ndarray, t: float) -> Tuple[float, float, float]: """ECI转大地坐标""" r_ecef = self._eci_to_ecef(r_eci, t) return self._ecef_to_geodetic(r_ecef) def _ecef_to_geodetic(self, r_ecef: np.ndarray) -> Tuple[float, float, float]: """ECEF转大地坐标""" x, y, z = r_ecef # 经度 lon = np.arctan2(y, x) # 纬度(迭代法) p = np.sqrt(x**2 + y**2) lat = np.arctan2(z, p * (1 - self.constants['f'])) for _ in range(10): N = self.constants['Re'] / np.sqrt(1 - self.constants['f'] * (2 - self.constants['f']) * np.sin(lat)**2) h = p / np.cos(lat) - N lat_new = np.arctan2(z, p * (1 - self.constants['f'] * N/(N + h))) if abs(lat_new - lat) < 1e-12: break lat = lat_new N = self.constants['Re'] / np.sqrt(1 - self.constants['f'] * (2 - self.constants['f']) * np.sin(lat)**2) h = p / np.cos(lat) - N return np.degrees(lat), np.degrees(lon), h def _eci_to_ric(self, r_eci: np.ndarray, v_eci: np.ndarray) -> np.ndarray: """ECI转RIC坐标系(径向、沿轨、法向)""" # R方向:径向 r_unit = r_eci / np.linalg.norm(r_eci) # C方向:沿轨(垂直于径向和法向) h = np.cross(r_eci, v_eci) c_unit = np.cross(h, r_eci) c_unit = c_unit / np.linalg.norm(c_unit) # I方向:法向 i_unit = h / np.linalg.norm(h) return np.array([r_unit, c_unit, i_unit]) def _eci_to_ntw(self, r_eci: np.ndarray, v_eci: np.ndarray) -> np.ndarray: """ECI转NTW坐标系(法向、切向、径向)""" # 简化实现 return np.eye(3) def convert(self, elements: Dict[str, float], time_array: np.ndarray = None, target_coordinates: List[str] = None) -> Dict: """ 通用转换接口 Parameters: ----------- elements: 轨道根数字典 time_array: 时间点数组 target_coordinates: 目标坐标系列表 """ if time_array is None: time_array = np.linspace(0, 86400, 100) if target_coordinates is None: target_coordinates = ['ECEF', 'Geodetic'] # 检测轨道根数类型 element_type = self._detect_element_type(elements) # 获取转换器 converter = self.supported_systems[element_type]['converter'] # 存储结果 results = { 'time': time_array, 'ECI': [], 'ECEF': [], 'Geodetic': [], 'RIC': [], 'NTW': [] } # 执行转换 for t in time_array: r_eci, v_eci = converter(elements, t) # 基础转换 results['ECI'].append((r_eci, v_eci)) # 转换到目标坐标系 if 'ECEF' in target_coordinates: r_ecef = self._eci_to_ecef(r_eci, t) results['ECEF'].append(r_ecef) if 'Geodetic' in target_coordinates: lat, lon, h = self._eci_to_geodetic(r_eci, t) results['Geodetic'].append((lat, lon, h)) if 'RIC' in target_coordinates: ric = self._eci_to_ric(r_eci, v_eci) results['RIC'].append(ric) # 转换为数组 for key in ['ECEF', 'Geodetic']: if results[key]: results[key] = np.array(results[key]) return results def _detect_element_type(self, elements: Dict[str, float]) -> str: """自动检测轨道根数类型""" # 检查开普勒根数 keplerian_keys = {'a', 'e', 'i', 'Omega', 'omega', 'M'} if keplerian_keys.issubset(elements.keys()): return 'keplerian' # 检查笛卡尔坐标 cartesian_keys = {'x', 'y', 'z', 'vx', 'vy', 'vz'} if cartesian_keys.issubset(elements.keys()): return 'cartesian' # 检查TLE元素 tle_keys = {'inclination', 'raan', 'eccentricity', 'arg_perigee', 'mean_anomaly', 'mean_motion'} if tle_keys.issubset(elements.keys()): return 'tle' # 检查春分点轨道根数 equinoctial_keys = {'a', 'h', 'k', 'p', 'q', 'lambda'} if equinoctial_keys.issubset(elements.keys()): return 'equinoctial' return 'keplerian' # 默认使用开普勒根数 def add_custom_coordinate_chain(self, name: str, chain_func): """添加自定义坐标转换链""" self.coordinate_chains[name] = chain_func def get_history(self) -> List[Dict]: """获取转换历史""" return self.historyclass AdvancedOrbitVisualizer: """高级轨道可视化器""" def __init__(self): self.converter = UniversalOrbitConverter() self.figures = [] def create_comprehensive_visualization(self, results: Dict, elements: Dict): """创建综合可视化""" fig = plt.figure(figsize=(20, 12)) # 1. 3D轨道视图 ax1 = fig.add_subplot(231, projection='3d') self._plot_3d_orbit(ax1, results) # 2. 地面轨迹 ax2 = fig.add_subplot(232, projection=ccrs.PlateCarree()) self._plot_ground_track(ax2, results) # 3. 轨道参数变化 ax3 = fig.add_subplot(233) self._plot_orbital_parameters(ax3, results, elements) # 4. 速度剖面 ax4 = fig.add_subplot(234) self._plot_velocity_profile(ax4, results) # 5. 坐标系对比 ax5 = fig.add_subplot(235) self._plot_coordinate_comparison(ax5, results) # 6. 轨道能量 ax6 = fig.add_subplot(236) self._plot_orbital_energy(ax6, results) plt.tight_layout() self.figures.append(fig) plt.show() def _plot_3d_orbit(self, ax, results): """绘制3D轨道""" if 'ECEF' in results and len(results['ECEF']) > 0: ecef_positions = np.array(results['ECEF']) ax.plot(ecef_positions[:, 0]/1000, ecef_positions[:, 1]/1000, ecef_positions[:, 2]/1000, 'b-', linewidth=1, alpha=0.8) # 绘制地球 self._draw_earth(ax) ax.set_xlabel('X (km)') ax.set_ylabel('Y (km)') ax.set_zlabel('Z (km)') ax.set_title('3D轨道 (ECEF)') def _draw_earth(self, ax, alpha=0.3): """绘制地球""" u = np.linspace(0, 2 * np.pi, 50) v = np.linspace(0, np.pi, 50) R = self.converter.constants['Re'] / 1000 x = R * np.outer(np.cos(u), np.sin(v)) y = R * np.outer(np.sin(u), np.sin(v)) z = R * np.outer(np.ones(np.size(u)), np.cos(v)) ax.plot_surface(x, y, z, color='lightblue', alpha=alpha) def _plot_ground_track(self, ax, results): """绘制地面轨迹""" if 'Geodetic' in results and len(results['Geodetic']) > 0: geodetic = np.array(results['Geodetic']) ax.set_global() ax.add_feature(cfeature.LAND, facecolor='lightgray') ax.add_feature(cfeature.OCEAN, facecolor='lightblue') ax.add_feature(cfeature.COASTLINE, linewidth=0.5) ax.plot(geodetic[:, 1], geodetic[:, 0], 'r-', linewidth=1, transform=ccrs.Geodetic()) ax.gridlines(draw_labels=True) ax.set_title('地面轨迹') def _plot_orbital_parameters(self, ax, results, elements): """绘制轨道参数变化""" ax.text(0.5, 0.9, '轨道参数', transform=ax.transAxes, ha='center', fontsize=12, fontweight='bold') # 显示关键参数 params_text = [] for key, value in elements.items(): if not key.startswith('_'): params_text.append(f"{key}: {value:.3f}") ax.text(0.1, 0.7, '\n'.join(params_text[:8]), transform=ax.transAxes, fontfamily='monospace', fontsize=9) ax.axis('off') def _plot_velocity_profile(self, ax, results): """绘制速度剖面""" if 'ECI' in results: velocities = [] for r_eci, v_eci in results['ECI']: v_mag = np.linalg.norm(v_eci) velocities.append(v_mag) velocities = np.array(velocities) time = results['time'] ax.plot(time/3600, velocities/1000, 'g-', linewidth=2) ax.set_xlabel('时间 (小时)') ax.set_ylabel('速度 (km/s)') ax.set_title('速度剖面') ax.grid(True) def _plot_coordinate_comparison(self, ax, results): """绘制坐标系对比""" if 'ECEF' in results and 'ECI' in results: ecef = np.array(results['ECEF']) eci = np.array([r for r, _ in results['ECI']]) time = results['time'] # 计算差异 diff = np.linalg.norm(ecef - eci, axis=1) ax.plot(time/3600, diff/1000, 'b-', linewidth=2) ax.set_xlabel('时间 (小时)') ax.set_ylabel('位置差异 (km)') ax.set_title('ECEF vs ECI 差异') ax.grid(True) def _plot_orbital_energy(self, ax, results): """绘制轨道能量""" if 'ECI' in results: energies = [] for r_eci, v_eci in results['ECI']: r = np.linalg.norm(r_eci) v = np.linalg.norm(v_eci) # 比机械能 energy = v**2/2 - self.converter.constants['GM']/r energies.append(energy/1e6) # 转换为MJ/kg time = results['time'] ax.plot(time/3600, energies, 'r-', linewidth=2) ax.set_xlabel('时间 (小时)') ax.set_ylabel('比机械能 (MJ/kg)') ax.set_title('轨道能量') ax.grid(True) def create_custom_visualization(self, results: Dict, plot_types: List[str]): """创建自定义可视化""" n_plots = len(plot_types) n_cols = min(3, n_plots) n_rows = (n_plots + n_cols - 1) // n_cols fig = plt.figure(figsize=(6*n_cols, 5*n_rows)) gs = fig.add_gridspec(n_rows, n_cols) # 每种图类型需要的投影配置 plot_specs = { '3d_orbit': {'projection': '3d'}, 'ground_track': {'projection': ccrs.PlateCarree()}, 'velocity': {}, 'energy': {}, 'comparison': {}, } plot_map = { '3d_orbit': self._plot_3d_orbit, 'ground_track': self._plot_ground_track, 'velocity': self._plot_velocity_profile, 'energy': self._plot_orbital_energy, 'comparison': self._plot_coordinate_comparison, } for i, plot_type in enumerate(plot_types): if plot_type in plot_map: row = i // n_cols col = i % n_cols spec = plot_specs.get(plot_type, {}) ax = fig.add_subplot(gs[row, col], **spec) plot_map[plot_type](ax, results) plt.tight_layout() self.figures.append(fig) plt.show()class UniversalOrbitGUI: """通用轨道转换GUI""" def __init__(self): self.root = tk.Tk() self.root.title("通用轨道转换器 - 支持任意数量轨道根数") self.root.geometry("1200x800") self.converter = UniversalOrbitConverter() self.visualizer = AdvancedOrbitVisualizer() self.element_set = ElementSet() self.current_elements: Dict[str, tk.Variable] = {} self.custom_elements: List[Dict] = [] self.setup_ui() self.load_presets() def setup_ui(self): """设置用户界面""" # 创建主框架 self.main_frame = ttk.Frame(self.root, padding="10") self.main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) # 配置网格权重 self.root.columnconfigure(0, weight=1) self.root.rowconfigure(0, weight=1) self.main_frame.columnconfigure(1, weight=1) self.main_frame.rowconfigure(0, weight=1) # 左侧控制面板 self.create_control_panel() # 右侧可视化面板 self.create_visualization_panel() def create_control_panel(self): """创建控制面板""" control_frame = ttk.Frame(self.main_frame) control_frame.grid(row=0, column=0, sticky=(tk.N, tk.S, tk.W), padx=(0, 10)) # 轨道系统选择 system_frame = ttk.LabelFrame(control_frame, text="轨道根数系统", padding="10") system_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=5) self.system_var = tk.StringVar(value="开普勒轨道根数") systems = ["开普勒轨道根数", "笛卡尔坐标", "春分点轨道根数", "TLE两行元素", "自定义"] system_combo = ttk.Combobox(system_frame, textvariable=self.system_var, values=systems, state="readonly") system_combo.grid(row=0, column=0, sticky=(tk.W, tk.E)) system_combo.bind('<<ComboboxSelected>>', self.on_system_change) ttk.Button(system_frame, text="加载预设", command=self.load_presets).grid(row=0, column=1, padx=5) # 轨道根数输入区域 self.elements_frame = ttk.LabelFrame(control_frame, text="轨道根数", padding="10") self.elements_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5) # 创建滚动容器 self.elements_canvas = tk.Canvas(self.elements_frame, height=300) scrollbar = ttk.Scrollbar(self.elements_frame, orient="vertical", command=self.elements_canvas.yview) self.scrollable_frame = ttk.Frame(self.elements_canvas) self.scrollable_frame.bind( "<Configure>", lambda e: self.elements_canvas.configure(scrollregion=self.elements_canvas.bbox("all")) ) self.elements_canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw") self.elements_canvas.configure(yscrollcommand=scrollbar.set) self.elements_canvas.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S)) # 添加/删除轨道根数按钮 button_frame = ttk.Frame(control_frame) button_frame.grid(row=2, column=0, pady=5) ttk.Button(button_frame, text="+ 添加根数", command=self.add_custom_element).pack(side=tk.LEFT, padx=2) ttk.Button(button_frame, text="- 删除根数", command=self.remove_last_element).pack(side=tk.LEFT, padx=2) ttk.Button(button_frame, text="清空", command=self.clear_elements).pack(side=tk.LEFT, padx=2) # 高级选项 advanced_frame = ttk.LabelFrame(control_frame, text="高级选项", padding="10") advanced_frame.grid(row=3, column=0, sticky=(tk.W, tk.E), pady=5) # 坐标系选择 ttk.Label(advanced_frame, text="目标坐标系:").grid(row=0, column=0, sticky=tk.W) self.coord_vars = { 'ECEF': tk.BooleanVar(value=True), 'Geodetic': tk.BooleanVar(value=True), 'RIC': tk.BooleanVar(value=False), 'NTW': tk.BooleanVar(value=False) } for i, (name, var) in enumerate(self.coord_vars.items()): ttk.Checkbutton(advanced_frame, text=name, variable=var).grid( row=1, column=i, padx=5) # 摄动选项 self.perturbation_vars = { 'J2': tk.BooleanVar(value=True), 'J3': tk.BooleanVar(value=False), 'J4': tk.BooleanVar(value=False), '光压': tk.BooleanVar(value=False), '大气阻力': tk.BooleanVar(value=False) } ttk.Label(advanced_frame, text="摄动项:").grid(row=2, column=0, sticky=tk.W, pady=(10, 0)) for i, (name, var) in enumerate(self.perturbation_vars.items()): ttk.Checkbutton(advanced_frame, text=name, variable=var).grid( row=3, column=i, padx=5) # 时间设置 time_frame = ttk.Frame(advanced_frame) time_frame.grid(row=4, column=0, columnspan=4, pady=10, sticky=(tk.W, tk.E)) ttk.Label(time_frame, text="时间范围 (小时):").pack(side=tk.LEFT) self.time_var = tk.DoubleVar(value=24) ttk.Entry(time_frame, textvariable=self.time_var, width=8).pack(side=tk.LEFT, padx=5) ttk.Label(time_frame, text="采样点数:").pack(side=tk.LEFT) self.points_var = tk.IntVar(value=200) ttk.Entry(time_frame, textvariable=self.points_var, width=8).pack(side=tk.LEFT, padx=5) # 执行按钮 execute_frame = ttk.Frame(control_frame) execute_frame.grid(row=4, column=0, pady=10) ttk.Button(execute_frame, text="执行转换", command=self.execute_conversion, style="Accent.TButton").pack(side=tk.LEFT, padx=5) ttk.Button(execute_frame, text="保存配置", command=self.save_configuration).pack(side=tk.LEFT, padx=5) ttk.Button(execute_frame, text="加载配置", command=self.load_configuration).pack(side=tk.LEFT, padx=5) # 结果显示 result_frame = ttk.LabelFrame(control_frame, text="结果摘要", padding="10") result_frame.grid(row=5, column=0, sticky=(tk.W, tk.E, tk.S), pady=5) self.result_text = scrolledtext.ScrolledText(result_frame, height=10, width=40) self.result_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) def create_visualization_panel(self): """创建可视化面板""" viz_frame = ttk.LabelFrame(self.main_frame, text="可视化控制", padding="10") viz_frame.grid(row=0, column=1, sticky=(tk.N, tk.S, tk.E, tk.W)) # 可视化类型选择 ttk.Label(viz_frame, text="选择可视化类型:").grid(row=0, column=0, sticky=tk.W) self.viz_vars = { '3D轨道': tk.BooleanVar(value=True), '地面轨迹': tk.BooleanVar(value=True), '速度剖面': tk.BooleanVar(value=True), '轨道能量': tk.BooleanVar(value=True), '坐标系对比': tk.BooleanVar(value=False), '参数变化': tk.BooleanVar(value=True) } for i, (name, var) in enumerate(self.viz_vars.items()): ttk.Checkbutton(viz_frame, text=name, variable=var).grid( row=1, column=i, padx=5) # 自定义绘图选项 custom_frame = ttk.Frame(viz_frame) custom_frame.grid(row=2, column=0, columnspan=6, pady=10) ttk.Button(custom_frame, text="综合可视化", command=self.show_comprehensive_view).pack(side=tk.LEFT, padx=5) ttk.Button(custom_frame, text="自定义视图", command=self.show_custom_view).pack(side=tk.LEFT, padx=5) ttk.Button(custom_frame, text="导出图形", command=self.export_figure).pack(side=tk.LEFT, padx=5) # 图形预览区域 self.preview_frame = ttk.Frame(viz_frame, relief=tk.SUNKEN, borderwidth=2) self.preview_frame.grid(row=3, column=0, columnspan=6, sticky=(tk.N, tk.S, tk.E, tk.W), pady=10) self.preview_label = ttk.Label(self.preview_frame, text="图形预览区域\n(执行转换后显示)", anchor=tk.CENTER) self.preview_label.pack(expand=True, fill=tk.BOTH) # 配置预览区域可扩展 viz_frame.rowconfigure(3, weight=1) viz_frame.columnconfigure(0, weight=1) def on_system_change(self, event=None): """轨道系统改变时的处理""" system = self.system_var.get() self.clear_elements() if system == "开普勒轨道根数": self.load_keplerian_system() elif system == "笛卡尔坐标": self.load_cartesian_system() elif system == "春分点轨道根数": self.load_equinoctial_system() elif system == "TLE两行元素": self.load_tle_system() elif system == "自定义": self.load_custom_system() def load_keplerian_system(self): """加载开普勒轨道根数系统""" elements = [ ("半长轴 a (km):", "a", 26500.0), ("偏心率 e:", "e", 0.01), ("轨道倾角 i (deg):", "i", 55.0), ("升交点赤经 Ω (deg):", "Omega", 45.0), ("近地点幅角 ω (deg):", "omega", 90.0), ("平近点角 M (deg):", "M", 0.0), ] for label, key, default in elements: self.add_element_input(label, key, default) def load_cartesian_system(self): """加载笛卡尔坐标系统""" elements = [ ("X位置 (km):", "x", 0.0), ("Y位置 (km):", "y", 26500.0), ("Z位置 (km):", "z", 0.0), ("X速度 (km/s):", "vx", 0.0), ("Y速度 (km/s):", "vy", 0.0), ("Z速度 (km/s):", "vz", 7.8), ] for label, key, default in elements: self.add_element_input(label, key, default) def load_equinoctial_system(self): """加载春分点轨道根数系统""" elements = [ ("半长轴 a (km):", "a", 26500.0), ("h分量:", "h", 0.0), ("k分量:", "k", 0.01), ("p分量:", "p", 0.0), ("q分量:", "q", np.tan(np.radians(27.5))), ("平经度 λ (deg):", "lambda", 0.0), ] for label, key, default in elements: self.add_element_input(label, key, default) def load_tle_system(self): """加载TLE系统""" elements = [ ("轨道倾角 (deg):", "inclination", 51.6), ("升交点赤经 (deg):", "raan", 45.0), ("偏心率:", "eccentricity", 0.001), ("近地点幅角 (deg):", "arg_perigee", 90.0), ("平近点角 (deg):", "mean_anomaly", 0.0), ("平均运动 (rev/day):", "mean_motion", 15.5), ("BSTAR:", "bstar", 0.0001), ] for label, key, default in elements: self.add_element_input(label, key, default) def load_custom_system(self): """加载自定义系统(空)""" pass def add_element_input(self, label: str, key: str, default: float, required: bool = True): """添加轨道根数输入框""" if key in self.current_elements: return row = len(self.current_elements) frame = ttk.Frame(self.scrollable_frame) frame.grid(row=row, column=0, sticky=(tk.W, tk.E), pady=2) ttk.Label(frame, text=label, width=25).pack(side=tk.LEFT) var = tk.DoubleVar(value=default) entry = ttk.Entry(frame, textvariable=var, width=15) entry.pack(side=tk.LEFT, padx=5) if not required: var.set(None) entry.configure(state='disabled') # 可选复选框 if not required: enabled_var = tk.BooleanVar(value=False) ttk.Checkbutton(frame, text="启用", variable=enabled_var, command=lambda e=entry: self.toggle_element(e)).pack(side=tk.LEFT) self.current_elements[key] = var def toggle_element(self, entry): """切换轨道根数启用状态""" if entry.cget('state') == 'disabled': entry.configure(state='normal') else: entry.configure(state='disabled') def add_custom_element(self): """添加自定义轨道根数""" dialog = tk.Toplevel(self.root) dialog.title("添加自定义轨道根数") dialog.geometry("300x250") ttk.Label(dialog, text="名称:").pack(pady=5) name_var = tk.StringVar() ttk.Entry(dialog, textvariable=name_var).pack() ttk.Label(dialog, text="符号:").pack(pady=5) symbol_var = tk.StringVar() ttk.Entry(dialog, textvariable=symbol_var).pack() ttk.Label(dialog, text="默认值:").pack(pady=5) value_var = tk.DoubleVar(value=0.0) ttk.Entry(dialog, textvariable=value_var).pack() ttk.Label(dialog, text="单位:").pack(pady=5) unit_var = tk.StringVar(value="") ttk.Entry(dialog, textvariable=unit_var).pack() def add(): name = name_var.get() symbol = symbol_var.get() or name value = value_var.get() unit = unit_var.get() if name: self.custom_elements.append({ 'name': name, 'symbol': symbol, 'value': value, 'unit': unit, 'required': False }) self.add_element_input(f"{name} ({unit}):", symbol, value, required=False) dialog.destroy() ttk.Button(dialog, text="添加", command=add).pack(pady=10) def remove_last_element(self): """移除最后一个轨道根数""" if self.current_elements: last_key = list(self.current_elements.keys())[-1] del self.current_elements[last_key] # 移除对应的UI元素 for widget in self.scrollable_frame.winfo_children()[-1:]: widget.destroy() def clear_elements(self): """清空所有轨道根数""" self.current_elements.clear() for widget in self.scrollable_frame.winfo_children(): widget.destroy() def get_elements_dict(self) -> Dict[str, float]: """获取当前轨道根数字典""" elements = {} system = self.system_var.get() for key, var in self.current_elements.items(): try: value = var.get() # 角度转换为弧度 angle_keys = ['i', 'Omega', 'omega', 'M', 'lambda', 'inclination', 'raan', 'arg_perigee', 'mean_anomaly'] if key in angle_keys: value = np.radians(value) # 距离转换为米 distance_keys = ['a', 'x', 'y', 'z'] if key in distance_keys: value *= 1000 # 速度转换为m/s velocity_keys = ['vx', 'vy', 'vz'] if key in velocity_keys: value *= 1000 elements[key] = value except: continue # 添加摄动参数 for name, var in self.perturbation_vars.items(): if var.get(): if name == 'J2': elements['include_j2'] = True elif name == 'J3': elements['include_j3'] = True elif name == '光压': elements['include_solar_pressure'] = True elements['solar_pressure_coeff'] = 1.0 elif name == '大气阻力': elements['include_drag'] = True return elements def execute_conversion(self): """执行轨道转换""" try: elements = self.get_elements_dict() if not elements: messagebox.showwarning("警告", "请先输入轨道根数") return # 设置时间范围 total_time = self.time_var.get() * 3600 # 转换为秒 n_points = self.points_var.get() time_array = np.linspace(0, total_time, n_points) # 获取目标坐标系 target_coords = [name for name, var in self.coord_vars.items() if var.get()] # 执行转换 self.current_results = self.converter.convert( elements, time_array, target_coords ) # 显示结果摘要 self.display_results_summary(elements) # 自动显示可视化 self.show_auto_visualization() except Exception as e: messagebox.showerror("错误", f"转换失败: {str(e)}") def display_results_summary(self, elements: Dict): """显示结果摘要""" self.result_text.delete(1.0, tk.END) self.result_text.insert(tk.END, "=== 轨道转换结果 ===\n\n") self.result_text.insert(tk.END, f"轨道根数系统: {self.system_var.get()}\n") self.result_text.insert(tk.END, f"根数数量: {len(elements)}\n\n") if 'ECEF' in self.current_results: ecef = np.array(self.current_results['ECEF']) self.result_text.insert(tk.END, "ECEF坐标范围:\n") for i, coord in enumerate(['X', 'Y', 'Z']): self.result_text.insert(tk.END, f" {coord}: {np.min(ecef[:, i])/1000:.2f} - {np.max(ecef[:, i])/1000:.2f} km\n") if 'Geodetic' in self.current_results: geo = np.array(self.current_results['Geodetic']) self.result_text.insert(tk.END, "\n大地坐标范围:\n") self.result_text.insert(tk.END, f" 纬度: {np.min(geo[:, 0]):.2f}° - {np.max(geo[:, 0]):.2f}°\n") self.result_text.insert(tk.END, f" 经度: {np.min(geo[:, 1]):.2f}° - {np.max(geo[:, 1]):.2f}°\n") self.result_text.insert(tk.END, f" 高度: {np.min(geo[:, 2])/1000:.2f} - {np.max(geo[:, 2])/1000:.2f} km\n") def show_auto_visualization(self): """自动显示可视化""" selected_types = [] for name, var in self.viz_vars.items(): if var.get(): if name == '3D轨道': selected_types.append('3d_orbit') elif name == '地面轨迹': selected_types.append('ground_track') elif name == '速度剖面': selected_types.append('velocity') elif name == '轨道能量': selected_types.append('energy') elif name == '坐标系对比': selected_types.append('comparison') if selected_types and hasattr(self, 'current_results'): self.visualizer.create_custom_visualization(self.current_results, selected_types) def show_comprehensive_view(self): """显示综合视图""" if not hasattr(self, 'current_results'): messagebox.showwarning("警告", "请先执行转换") return elements = self.get_elements_dict() self.visualizer.create_comprehensive_visualization(self.current_results, elements) def show_custom_view(self): """显示自定义视图""" if not hasattr(self, 'current_results'): messagebox.showwarning("警告", "请先执行转换") return dialog = tk.Toplevel(self.root) dialog.title("自定义可视化") dialog.geometry("400x300") plot_options = ['3D轨道', '地面轨迹', '速度剖面', '轨道能量', '坐标系对比'] plot_vars = {name: tk.BooleanVar(value=True) for name in plot_options} for name, var in plot_vars.items(): ttk.Checkbutton(dialog, text=name, variable=var).pack(pady=5) def show(): selected = [name for name, var in plot_vars.items() if var.get()] plot_types = [] for name in selected: if name == '3D轨道': plot_types.append('3d_orbit') elif name == '地面轨迹': plot_types.append('ground_track') elif name == '速度剖面': plot_types.append('velocity') elif name == '轨道能量': plot_types.append('energy') elif name == '坐标系对比': plot_types.append('comparison') if plot_types: self.visualizer.create_custom_visualization(self.current_results, plot_types) dialog.destroy() ttk.Button(dialog, text="显示", command=show).pack(pady=10) def save_configuration(self): """保存配置""" elements = self.get_elements_dict() config = { 'system': self.system_var.get(), 'elements': {k: v for k, v in elements.items() if not k.startswith('_')}, 'time_hours': self.time_var.get(), 'n_points': self.points_var.get(), 'coordinates': {k: v.get() for k, v in self.coord_vars.items()}, 'perturbations': {k: v.get() for k, v in self.perturbation_vars.items()}, 'visualizations': {k: v.get() for k, v in self.viz_vars.items()} } filename = filedialog.asksaveasfilename( defaultextension=".json", filetypes=[("JSON files", "*.json"), ("All files", "*.*")] ) if filename: with open(filename, 'w') as f: json.dump(config, f, indent=2) messagebox.showinfo("成功", f"配置已保存到: {filename}") def load_configuration(self): """加载配置""" filename = filedialog.askopenfilename( filetypes=[("JSON files", "*.json"), ("All files", "*.*")] ) if filename: try: with open(filename, 'r') as f: config = json.load(f) # 恢复系统类型 self.system_var.set(config['system']) self.on_system_change() # 恢复轨道根数 for key, value in config['elements'].items(): if key in self.current_elements: self.current_elements[key].set(value) # 恢复其他设置 self.time_var.set(config.get('time_hours', 24)) self.points_var.set(config.get('n_points', 200)) for key, value in config.get('coordinates', {}).items(): if key in self.coord_vars: self.coord_vars[key].set(value) for key, value in config.get('perturbations', {}).items(): if key in self.perturbation_vars: self.perturbation_vars[key].set(value) for key, value in config.get('visualizations', {}).items(): if key in self.viz_vars: self.viz_vars[key].set(value) messagebox.showinfo("成功", "配置加载完成") except Exception as e: messagebox.showerror("错误", f"加载配置失败: {str(e)}") def export_figure(self): """导出图形""" if self.visualizer.figures: filename = filedialog.asksaveasfilename( defaultextension=".png", filetypes=[("PNG files", "*.png"), ("PDF files", "*.pdf"), ("SVG files", "*.svg"), ("All files", "*.*")] ) if filename: self.visualizer.figures[-1].savefig(filename, dpi=300, bbox_inches='tight') messagebox.showinfo("成功", f"图形已保存到: {filename}") else: messagebox.showwarning("警告", "没有可导出的图形") def load_presets(self): """加载预设轨道""" dialog = tk.Toplevel(self.root) dialog.title("加载预设轨道") dialog.transient(self.root) dialog.grab_set() dialog.focus_force() dialog.lift() dialog.attributes('-topmost', True) # 计算弹窗在主窗口中心的位置 width, height = 400, 300 self.root.update_idletasks() root_x = self.root.winfo_rootx() root_y = self.root.winfo_rooty() root_width = self.root.winfo_width() root_height = self.root.winfo_height() x = root_x + (root_width - width) // 2 y = root_y + (root_height - height) // 2 dialog.geometry(f"{width}x{height}+{x}+{y}") presets = { "LEO卫星": { 'system': '开普勒轨道根数', 'a': 26500, 'e': 0.001, 'i': 51.6, 'Omega': 45, 'omega': 90, 'M': 0 }, "MEO卫星": { 'system': '开普勒轨道根数', 'a': 26500, 'e': 0.01, 'i': 55, 'Omega': 0, 'omega': 0, 'M': 0 }, "GEO卫星": { 'system': '开普勒轨道根数', 'a': 42164, 'e': 0.0, 'i': 0, 'Omega': 0, 'omega': 0, 'M': 0 }, "高椭圆轨道": { 'system': '开普勒轨道根数', 'a': 42164, 'e': 0.7, 'i': 63.4, 'Omega': 0, 'omega': 270, 'M': 0 }, "太阳同步轨道": { 'system': '开普勒轨道根数', 'a': 7000, 'e': 0.0, 'i': 98, 'Omega': 0, 'omega': 0, 'M': 0 } } listbox = tk.Listbox(dialog) listbox.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) for preset_name in presets.keys(): listbox.insert(tk.END, preset_name) def load_selected(): selection = listbox.curselection() if selection: preset_name = listbox.get(selection[0]) preset = presets[preset_name] self.system_var.set(preset['system']) self.on_system_change() for key, value in preset.items(): if key != 'system' and key in self.current_elements: self.current_elements[key].set(value) dialog.destroy() ttk.Button(dialog, text="加载", command=load_selected).pack(pady=10) def run(self): """运行GUI""" self.root.mainloop()def main(): """主函数""" print("=" * 60) print(" 通用轨道转换器 - 支持任意数量轨道根数") print("=" * 60) print("\n功能特性:") print(" 1. 支持多种轨道根数系统") print(" 2. 可自定义添加任意数量的轨道根数") print(" 3. 多坐标系转换 (ECI, ECEF, Geodetic, RIC, NTW)") print(" 4. 各种摄动模型 (J2, J3, J4, 光压, 大气阻力)") print(" 5. 丰富的可视化选项") print(" 6. 配置保存/加载") print(" 7. 数据导出功能") print("\n启动GUI界面...") app = UniversalOrbitGUI() app.run()if __name__ == "__main__": main()