JMOT.extra
1import math 2import numpy as np 3 4def tuple2array(vec:tuple[float, float, float])->np.ndarray: 5 '''Convert a tuple to a numpy array, and swap the second and third elements''' 6 vec = list(vec) 7 tmp = vec[1] 8 vec[1] = vec[2] 9 vec[2] = tmp 10 return np.array(vec) 11 12def array2tuple(vec:np.ndarray)->tuple[float, float, float]: 13 '''Convert a numpy array to a tuple, and swap the second and third elements''' 14 vec = vec.tolist() 15 tmp = vec[1] 16 vec[1] = vec[2] 17 vec[2] = tmp 18 vec = tuple(vec) 19 return vec 20 21 """ 22 修复 JMOT 底层暴力拆解导致的列表错位问题。 23 将被切碎的 '[1'、'3)]' 等残骸重新拼接回完整的数字和元组。 24 """ 25 if not raw_list: 26 return [] 27 28 result = [] 29 i = 0 30 31 while i < len(raw_list): 32 item = str(raw_list[i]) 33 34 # 1. 处理被切断的列表开头:比如 '[1' 35 if item.startswith('['): 36 clean_num = item.lstrip("[").strip("' ") 37 try: 38 result.append(float(clean_num)) 39 except ValueError: 40 result.append(clean_num) 41 42 # 2. 处理被切断的元组:寻找以 '(' 开头但不以 ')' 结尾的碎片 43 elif item.startswith('(') and not item.endswith(')'): 44 tuple_parts = [item] 45 # 一直往后找,直到遇到以 ')' 结尾的碎片 46 j = i + 1 47 while j < len(raw_list): 48 tuple_parts.append(str(raw_list[j])) 49 if str(raw_list[j]).endswith(')'): 50 break 51 j += 1 52 53 # 把碎片拼起来,例如 "(1" + "2.0" + "3)" -> "(12.03)" 54 full_tuple_str = "".join(tuple_parts) 55 56 # 提取括号内的内容并转为浮点数元组 57 inner = full_tuple_str.strip("()") 58 try: 59 nums = [float(x.strip()) for x in inner.split(",")] 60 result.append(tuple(nums)) 61 except ValueError: 62 result.append(full_tuple_str) # 转换失败则保留原样 63 64 i = j # 跳过已经处理过的碎片 65 66 # 3. 处理普通的中间元素(如 "'aaa'" 或 3.14) 67 else: 68 clean_item = item.strip("' ") 69 try: 70 result.append(float(clean_item)) 71 except ValueError: 72 result.append(clean_item) 73 74 i += 1 75 76 return result 77 78def eci2ecef(r_eci, total_seconds): 79 '''DO NOT USE THIS FUNCTION, IT IS NOT ACCURATE ENOUGH''' 80 theta = 2 * np.pi * (total_seconds / 86400) 81 r_eci = np.array([r_eci[0], r_eci[1], r_eci[2]]) 82 Rz = np.array([ 83 [np.cos(theta), np.sin(theta), 0], 84 [-np.sin(theta), np.cos(theta), 0], 85 [0, 0, 1] 86 ]) 87 88 r_ecef = Rz @ r_eci 89 return r_ecef 90 91def lla2ecef(lon, lat, h, R=6371010.0): 92 '''DO NOT USE THIS FUNCTION, IT IS NOT ACCURATE ENOUGH''' 93 lon_rad = np.radians(lon) 94 lat_rad = np.radians(lat) 95 X = (R + h) * np.cos(lat_rad) * np.cos(lon_rad) 96 Y = (R + h) * np.cos(lat_rad) * np.sin(lon_rad) 97 Z = (R + h) * np.sin(lat_rad) 98 return np.array([X, Y, Z]) 99 100 101def ecef2enu(lon, lat, v_ECEF): 102 '''DO NOT USE THIS FUNCTION, IT IS NOT ACCURATE ENOUGH''' 103 lon = np.radians(lon) 104 lat = np.radians(lat) 105 v_ECEF = np.array([v_ECEF[0], v_ECEF[1], v_ECEF[2]]) 106 107 R = np.array([ 108 [-np.sin(lon), np.cos(lon), 0], 109 [-np.sin(lat) * np.cos(lon), -np.sin(lat) * np.sin(lon), np.cos(lat)], 110 [np.cos(lat) * np.cos(lon), np.cos(lat) * np.sin(lon), np.sin(lat)] 111 ]) 112 113 return R @ v_ECEF 114 115 116def enu_to_pitch_heading(v_e, v_n, v_u): 117 '''DO NOT USE THIS FUNCTION, IT IS NOT ACCURATE ENOUGH''' 118 heading = np.degrees(np.arctan2(v_e, v_n)) 119 120 norm = np.sqrt(v_e**2 + v_n**2 + v_u**2) 121 pitch = np.degrees(np.arcsin(v_u / norm)) 122 123 return pitch, heading 124 125 126def eci_to_enu_velocity(r_eci, v_eci): 127 '''DO NOT USE THIS FUNCTION, IT IS NOT ACCURATE ENOUGH''' 128 """Convert ECI velocity to ENU velocity.""" 129 x, y, z = r_eci 130 vx, vy, vz = v_eci 131 132 # Compute longitude (lambda) and latitude (phi) 133 lambda_ = math.atan2(y, x) # Longitude 134 phi = math.atan2(z, math.sqrt(x**2 + y**2)) # Latitude 135 136 # ECI to ENU rotation matrix 137 R = np.array([ 138 [-math.sin(lambda_), math.cos(lambda_), 0 ], 139 [-math.sin(phi)*math.cos(lambda_), -math.sin(phi)*math.sin(lambda_), math.cos(phi) ], 140 [ math.cos(phi)*math.cos(lambda_), math.cos(phi)*math.sin(lambda_), math.sin(phi) ] 141 ]) 142 143 v_enu = R @ np.array([vx, vy, vz]) 144 return v_enu 145 146def compute_heading_angle(r_eci, v_eci): 147 """Compute heading angle from ECI position and velocity.""" 148 '''DO NOT USE THIS FUNCTION, IT IS NOT ACCURATE ENOUGH''' 149 v_enu = eci_to_enu_velocity(r_eci, v_eci) 150 v_east, v_north, v_up = v_enu 151 152 heading = math.atan2(v_east, v_north) # [-π, π] 153 heading_deg = np.degrees(heading) % 360 # [0°, 360°] 154 return heading_deg 155 156def calculate_roll_angle(pitch_axis, roll_axis, gravity_vector): 157 """ 158 计算航天器的滚转角(roll angle)。 159 160 参数:\n 161 pitch_axis: ECI坐标系下的pitch轴矢量 (3维数组)\n 162 roll_axis: ECI坐标系下的roll轴矢量 (3维数组)\n 163 gravity_vector: ECI坐标系下的重力矢量 (3维数组)\n 164 165 返回: 166 滚转角(弧度),范围 [-pi, pi] 167 """ 168 '''DO NOT USE THIS FUNCTION, IT IS NOT ACCURATE ENOUGH''' 169 roll_axis_normalized = roll_axis / np.linalg.norm(roll_axis) 170 gravity_normalized = gravity_vector / np.linalg.norm(gravity_vector) 171 172 up_direction = -gravity_normalized 173 up_direction = up_direction - np.dot(up_direction, roll_axis_normalized) * roll_axis_normalized 174 up_direction_normalized = up_direction / np.linalg.norm(up_direction) 175 176 pitch_projection = pitch_axis - np.dot(pitch_axis, roll_axis_normalized) * roll_axis_normalized 177 pitch_projection_normalized = pitch_projection / np.linalg.norm(pitch_projection) 178 179 right_direction = np.cross(roll_axis_normalized, up_direction_normalized) 180 right_direction_normalized = right_direction / np.linalg.norm(right_direction) 181 182 roll_angle = np.arctan2( 183 np.dot(pitch_projection_normalized, right_direction_normalized), 184 np.dot(pitch_projection_normalized, up_direction_normalized) 185 ) 186 187 return np.degrees(roll_angle) -90
def
tuple2array(vec: tuple[float, float, float]) -> numpy.ndarray:
5def tuple2array(vec:tuple[float, float, float])->np.ndarray: 6 '''Convert a tuple to a numpy array, and swap the second and third elements''' 7 vec = list(vec) 8 tmp = vec[1] 9 vec[1] = vec[2] 10 vec[2] = tmp 11 return np.array(vec)
Convert a tuple to a numpy array, and swap the second and third elements
def
array2tuple(vec: numpy.ndarray) -> tuple[float, float, float]:
13def array2tuple(vec:np.ndarray)->tuple[float, float, float]: 14 '''Convert a numpy array to a tuple, and swap the second and third elements''' 15 vec = vec.tolist() 16 tmp = vec[1] 17 vec[1] = vec[2] 18 vec[2] = tmp 19 vec = tuple(vec) 20 return vec 21 22 """ 23 修复 JMOT 底层暴力拆解导致的列表错位问题。 24 将被切碎的 '[1'、'3)]' 等残骸重新拼接回完整的数字和元组。 25 """ 26 if not raw_list: 27 return [] 28 29 result = [] 30 i = 0 31 32 while i < len(raw_list): 33 item = str(raw_list[i]) 34 35 # 1. 处理被切断的列表开头:比如 '[1' 36 if item.startswith('['): 37 clean_num = item.lstrip("[").strip("' ") 38 try: 39 result.append(float(clean_num)) 40 except ValueError: 41 result.append(clean_num) 42 43 # 2. 处理被切断的元组:寻找以 '(' 开头但不以 ')' 结尾的碎片 44 elif item.startswith('(') and not item.endswith(')'): 45 tuple_parts = [item] 46 # 一直往后找,直到遇到以 ')' 结尾的碎片 47 j = i + 1 48 while j < len(raw_list): 49 tuple_parts.append(str(raw_list[j])) 50 if str(raw_list[j]).endswith(')'): 51 break 52 j += 1 53 54 # 把碎片拼起来,例如 "(1" + "2.0" + "3)" -> "(12.03)" 55 full_tuple_str = "".join(tuple_parts) 56 57 # 提取括号内的内容并转为浮点数元组 58 inner = full_tuple_str.strip("()") 59 try: 60 nums = [float(x.strip()) for x in inner.split(",")] 61 result.append(tuple(nums)) 62 except ValueError: 63 result.append(full_tuple_str) # 转换失败则保留原样 64 65 i = j # 跳过已经处理过的碎片 66 67 # 3. 处理普通的中间元素(如 "'aaa'" 或 3.14) 68 else: 69 clean_item = item.strip("' ") 70 try: 71 result.append(float(clean_item)) 72 except ValueError: 73 result.append(clean_item) 74 75 i += 1 76 77 return result
Convert a numpy array to a tuple, and swap the second and third elements
def
eci2ecef(r_eci, total_seconds):
79def eci2ecef(r_eci, total_seconds): 80 '''DO NOT USE THIS FUNCTION, IT IS NOT ACCURATE ENOUGH''' 81 theta = 2 * np.pi * (total_seconds / 86400) 82 r_eci = np.array([r_eci[0], r_eci[1], r_eci[2]]) 83 Rz = np.array([ 84 [np.cos(theta), np.sin(theta), 0], 85 [-np.sin(theta), np.cos(theta), 0], 86 [0, 0, 1] 87 ]) 88 89 r_ecef = Rz @ r_eci 90 return r_ecef
DO NOT USE THIS FUNCTION, IT IS NOT ACCURATE ENOUGH
def
lla2ecef(lon, lat, h, R=6371010.0):
92def lla2ecef(lon, lat, h, R=6371010.0): 93 '''DO NOT USE THIS FUNCTION, IT IS NOT ACCURATE ENOUGH''' 94 lon_rad = np.radians(lon) 95 lat_rad = np.radians(lat) 96 X = (R + h) * np.cos(lat_rad) * np.cos(lon_rad) 97 Y = (R + h) * np.cos(lat_rad) * np.sin(lon_rad) 98 Z = (R + h) * np.sin(lat_rad) 99 return np.array([X, Y, Z])
DO NOT USE THIS FUNCTION, IT IS NOT ACCURATE ENOUGH
def
ecef2enu(lon, lat, v_ECEF):
102def ecef2enu(lon, lat, v_ECEF): 103 '''DO NOT USE THIS FUNCTION, IT IS NOT ACCURATE ENOUGH''' 104 lon = np.radians(lon) 105 lat = np.radians(lat) 106 v_ECEF = np.array([v_ECEF[0], v_ECEF[1], v_ECEF[2]]) 107 108 R = np.array([ 109 [-np.sin(lon), np.cos(lon), 0], 110 [-np.sin(lat) * np.cos(lon), -np.sin(lat) * np.sin(lon), np.cos(lat)], 111 [np.cos(lat) * np.cos(lon), np.cos(lat) * np.sin(lon), np.sin(lat)] 112 ]) 113 114 return R @ v_ECEF
DO NOT USE THIS FUNCTION, IT IS NOT ACCURATE ENOUGH
def
enu_to_pitch_heading(v_e, v_n, v_u):
117def enu_to_pitch_heading(v_e, v_n, v_u): 118 '''DO NOT USE THIS FUNCTION, IT IS NOT ACCURATE ENOUGH''' 119 heading = np.degrees(np.arctan2(v_e, v_n)) 120 121 norm = np.sqrt(v_e**2 + v_n**2 + v_u**2) 122 pitch = np.degrees(np.arcsin(v_u / norm)) 123 124 return pitch, heading
DO NOT USE THIS FUNCTION, IT IS NOT ACCURATE ENOUGH
def
eci_to_enu_velocity(r_eci, v_eci):
127def eci_to_enu_velocity(r_eci, v_eci): 128 '''DO NOT USE THIS FUNCTION, IT IS NOT ACCURATE ENOUGH''' 129 """Convert ECI velocity to ENU velocity.""" 130 x, y, z = r_eci 131 vx, vy, vz = v_eci 132 133 # Compute longitude (lambda) and latitude (phi) 134 lambda_ = math.atan2(y, x) # Longitude 135 phi = math.atan2(z, math.sqrt(x**2 + y**2)) # Latitude 136 137 # ECI to ENU rotation matrix 138 R = np.array([ 139 [-math.sin(lambda_), math.cos(lambda_), 0 ], 140 [-math.sin(phi)*math.cos(lambda_), -math.sin(phi)*math.sin(lambda_), math.cos(phi) ], 141 [ math.cos(phi)*math.cos(lambda_), math.cos(phi)*math.sin(lambda_), math.sin(phi) ] 142 ]) 143 144 v_enu = R @ np.array([vx, vy, vz]) 145 return v_enu
DO NOT USE THIS FUNCTION, IT IS NOT ACCURATE ENOUGH
def
compute_heading_angle(r_eci, v_eci):
147def compute_heading_angle(r_eci, v_eci): 148 """Compute heading angle from ECI position and velocity.""" 149 '''DO NOT USE THIS FUNCTION, IT IS NOT ACCURATE ENOUGH''' 150 v_enu = eci_to_enu_velocity(r_eci, v_eci) 151 v_east, v_north, v_up = v_enu 152 153 heading = math.atan2(v_east, v_north) # [-π, π] 154 heading_deg = np.degrees(heading) % 360 # [0°, 360°] 155 return heading_deg
Compute heading angle from ECI position and velocity.
def
calculate_roll_angle(pitch_axis, roll_axis, gravity_vector):
157def calculate_roll_angle(pitch_axis, roll_axis, gravity_vector): 158 """ 159 计算航天器的滚转角(roll angle)。 160 161 参数:\n 162 pitch_axis: ECI坐标系下的pitch轴矢量 (3维数组)\n 163 roll_axis: ECI坐标系下的roll轴矢量 (3维数组)\n 164 gravity_vector: ECI坐标系下的重力矢量 (3维数组)\n 165 166 返回: 167 滚转角(弧度),范围 [-pi, pi] 168 """ 169 '''DO NOT USE THIS FUNCTION, IT IS NOT ACCURATE ENOUGH''' 170 roll_axis_normalized = roll_axis / np.linalg.norm(roll_axis) 171 gravity_normalized = gravity_vector / np.linalg.norm(gravity_vector) 172 173 up_direction = -gravity_normalized 174 up_direction = up_direction - np.dot(up_direction, roll_axis_normalized) * roll_axis_normalized 175 up_direction_normalized = up_direction / np.linalg.norm(up_direction) 176 177 pitch_projection = pitch_axis - np.dot(pitch_axis, roll_axis_normalized) * roll_axis_normalized 178 pitch_projection_normalized = pitch_projection / np.linalg.norm(pitch_projection) 179 180 right_direction = np.cross(roll_axis_normalized, up_direction_normalized) 181 right_direction_normalized = right_direction / np.linalg.norm(right_direction) 182 183 roll_angle = np.arctan2( 184 np.dot(pitch_projection_normalized, right_direction_normalized), 185 np.dot(pitch_projection_normalized, up_direction_normalized) 186 ) 187 188 return np.degrees(roll_angle) -90
计算航天器的滚转角(roll angle)。
参数:
pitch_axis: ECI坐标系下的pitch轴矢量 (3维数组)
roll_axis: ECI坐标系下的roll轴矢量 (3维数组)
gravity_vector: ECI坐标系下的重力矢量 (3维数组)
返回: 滚转角(弧度),范围 [-pi, pi]