Coordinate Systems
This example demonstrates coordinate conversions, rotations, and projections.
Overview
Coordinate transformations are fundamental for tracking:
Cartesian-Spherical: Range, azimuth, elevation conversions
Geodetic-ECEF: Earth-fixed coordinates
Local frames: ENU, NED transformations
Map projections: UTM, Mercator, Lambert
Coordinate Systems
- Cartesian (x, y, z)
Standard 3D coordinates
Used for state estimation
- Spherical (range, azimuth, elevation)
Sensor-centric measurements
Radar and lidar output
- Geodetic (latitude, longitude, altitude)
Geographic coordinates
Navigation reference
- ECEF (Earth-Centered, Earth-Fixed)
Rotates with Earth
GPS coordinates
- ENU/NED (Local Tangent Plane)
East-North-Up or North-East-Down
Local navigation frame
Rotation Representations
Rotation matrices: 3x3 orthogonal matrices
Quaternions: 4D unit vectors, singularity-free
Euler angles: Roll, pitch, yaw
Axis-angle: Rotation axis and angle
Code Highlights
The example demonstrates:
cart2sphere()andsphere2cart()geodetic_to_ecef()andecef_to_geodetic()ecef2enu()andenu2ecef()euler2quat()andquat2euler()slerp()for quaternion interpolation
Source Code
1"""
2Coordinate Systems Example.
3
4This example demonstrates:
51. Cartesian to spherical coordinate conversions
62. Geodetic (WGS84) to ECEF transformations
73. Local tangent plane (ENU/NED) coordinates
84. Rotation matrices and quaternions
95. Jacobian-based covariance transformations
10
11Run with: python examples/coordinate_systems.py
12"""
13
14import sys
15from pathlib import Path
16
17sys.path.insert(0, str(Path(__file__).parent.parent))
18
19import numpy as np # noqa: E402
20import plotly.graph_objects as go # noqa: E402
21from plotly.subplots import make_subplots # noqa: E402
22
23from pytcl.coordinate_systems import ( # noqa: E402
24 cart2sphere,
25 cross_covariance_transform,
26 ecef2enu,
27 ecef2geodetic,
28 ecef2ned,
29 enu2ecef,
30 euler2rotmat,
31 geodetic2ecef,
32 quat2rotmat,
33 quat_multiply,
34 rotmat2euler,
35 rotmat2quat,
36 rotz,
37 slerp,
38 sphere2cart,
39 spherical_jacobian_inv,
40)
41
42
43def spherical_conversions_demo() -> None:
44 """Demonstrate spherical coordinate conversions."""
45 print("=" * 60)
46 print("1. SPHERICAL COORDINATE CONVERSIONS")
47 print("=" * 60)
48
49 # Define a point in Cartesian coordinates
50 cart_point = np.array([1000.0, 2000.0, 500.0]) # meters
51 print(
52 f"\nCartesian point: x={cart_point[0]:.1f}, y={cart_point[1]:.1f}, "
53 f"z={cart_point[2]:.1f} m"
54 )
55
56 # Convert to spherical (range, azimuth, elevation)
57 r, az, el = cart2sphere(cart_point)
58 print("\nSpherical coordinates:")
59 print(f" Range: {r:.2f} m")
60 print(f" Azimuth: {np.degrees(az):.2f} deg")
61 print(f" Elevation: {np.degrees(el):.2f} deg")
62
63 # Convert back to Cartesian
64 cart_recovered = sphere2cart(r, az, el)
65 print(
66 f"\nRecovered Cartesian: x={cart_recovered[0]:.1f}, "
67 f"y={cart_recovered[1]:.1f}, z={cart_recovered[2]:.1f} m"
68 )
69
70 # Verify roundtrip
71 error = np.linalg.norm(cart_point - cart_recovered)
72 print(f"Roundtrip error: {error:.2e} m")
73
74
75def geodetic_conversions_demo() -> None:
76 """Demonstrate geodetic coordinate conversions."""
77 print("\n" + "=" * 60)
78 print("2. GEODETIC (WGS84) COORDINATE CONVERSIONS")
79 print("=" * 60)
80
81 # Define a geodetic point (Washington DC area)
82 lat = np.radians(38.9072) # Latitude in radians
83 lon = np.radians(-77.0369) # Longitude in radians
84 alt = 100.0 # Altitude in meters
85
86 print("\nGeodetic coordinates:")
87 print(f" Latitude: {np.degrees(lat):.4f} deg")
88 print(f" Longitude: {np.degrees(lon):.4f} deg")
89 print(f" Altitude: {alt:.1f} m")
90
91 # Convert to ECEF
92 ecef = geodetic2ecef(lat, lon, alt)
93 print("\nECEF coordinates:")
94 print(f" X: {ecef[0]/1000:.3f} km")
95 print(f" Y: {ecef[1]/1000:.3f} km")
96 print(f" Z: {ecef[2]/1000:.3f} km")
97
98 # Convert back to geodetic
99 lat_r, lon_r, alt_r = ecef2geodetic(ecef)
100 print("\nRecovered geodetic:")
101 print(f" Latitude: {np.degrees(lat_r):.4f} deg")
102 print(f" Longitude: {np.degrees(lon_r):.4f} deg")
103 print(f" Altitude: {alt_r:.1f} m")
104
105
106def local_tangent_plane_demo() -> None:
107 """Demonstrate local tangent plane (ENU/NED) conversions."""
108 print("\n" + "=" * 60)
109 print("3. LOCAL TANGENT PLANE (ENU/NED) CONVERSIONS")
110 print("=" * 60)
111
112 # Reference point (origin of local frame)
113 ref_lat = np.radians(38.9072)
114 ref_lon = np.radians(-77.0369)
115
116 # Compute ECEF reference point for altitude = 0
117 ref_ecef = geodetic2ecef(ref_lat, ref_lon, 0.0)
118
119 # Target point 1 km East, 2 km North, 100 m Up from reference
120 enu_offset = np.array([1000.0, 2000.0, 100.0]) # East, North, Up
121 print("\nENU offset from reference:")
122 print(f" East: {enu_offset[0]:.1f} m")
123 print(f" North: {enu_offset[1]:.1f} m")
124 print(f" Up: {enu_offset[2]:.1f} m")
125
126 # Convert ENU to ECEF
127 target_ecef = enu2ecef(enu_offset, ref_lat, ref_lon, ref_ecef)
128 print("\nTarget ECEF coordinates:")
129 print(f" X: {target_ecef[0]/1000:.3f} km")
130 print(f" Y: {target_ecef[1]/1000:.3f} km")
131 print(f" Z: {target_ecef[2]/1000:.3f} km")
132
133 # Convert ECEF back to ENU
134 enu_recovered = ecef2enu(target_ecef, ref_lat, ref_lon, ref_ecef)
135 print("\nRecovered ENU:")
136 print(f" East: {enu_recovered[0]:.1f} m")
137 print(f" North: {enu_recovered[1]:.1f} m")
138 print(f" Up: {enu_recovered[2]:.1f} m")
139
140 # Also show NED (North, East, Down) - common in aviation
141 ned = ecef2ned(target_ecef, ref_lat, ref_lon, ref_ecef)
142 print("\nNED coordinates (aviation convention):")
143 print(f" North: {ned[0]:.1f} m")
144 print(f" East: {ned[1]:.1f} m")
145 print(f" Down: {ned[2]:.1f} m")
146
147
148def rotation_demo() -> None:
149 """Demonstrate rotation matrices and Euler angles."""
150 print("\n" + "=" * 60)
151 print("4. ROTATION MATRICES AND EULER ANGLES")
152 print("=" * 60)
153
154 # Create individual rotation matrices
155 roll = np.radians(10.0) # Roll about X
156 pitch = np.radians(20.0) # Pitch about Y
157 yaw = np.radians(30.0) # Yaw about Z
158
159 print("\nEuler angles (ZYX convention):")
160 print(f" Roll (X): {np.degrees(roll):.1f} deg")
161 print(f" Pitch (Y): {np.degrees(pitch):.1f} deg")
162 print(f" Yaw (Z): {np.degrees(yaw):.1f} deg")
163
164 # Combined rotation (ZYX order: yaw, then pitch, then roll)
165 # euler2rotmat expects [angle1, angle2, angle3] for the sequence
166 R = euler2rotmat([yaw, pitch, roll], sequence="ZYX")
167 print("\nRotation matrix (3x3):")
168 print(R)
169
170 # Verify it's a proper rotation (det = 1, orthogonal)
171 print(f"\nDeterminant: {np.linalg.det(R):.6f} (should be 1)")
172 print(f"R @ R.T = I check: {np.allclose(R @ R.T, np.eye(3))}")
173
174 # Extract Euler angles back
175 angles_recovered = rotmat2euler(R, sequence="ZYX")
176 yaw_r, pitch_r, roll_r = angles_recovered
177 print("\nRecovered Euler angles:")
178 print(f" Roll: {np.degrees(roll_r):.1f} deg")
179 print(f" Pitch: {np.degrees(pitch_r):.1f} deg")
180 print(f" Yaw: {np.degrees(yaw_r):.1f} deg")
181
182
183def quaternion_demo() -> None:
184 """Demonstrate quaternion operations and interpolation."""
185 print("\n" + "=" * 60)
186 print("5. QUATERNIONS AND SLERP INTERPOLATION")
187 print("=" * 60)
188
189 # Create a rotation matrix (ZYX = yaw, pitch, roll order)
190 roll, pitch, yaw = np.radians(15.0), np.radians(25.0), np.radians(45.0)
191 R = euler2rotmat([yaw, pitch, roll], sequence="ZYX")
192
193 # Convert to quaternion [w, x, y, z]
194 q = rotmat2quat(R)
195 print("\nQuaternion [w, x, y, z]:")
196 print(f" q = [{q[0]:.4f}, {q[1]:.4f}, {q[2]:.4f}, {q[3]:.4f}]")
197 print(f" Norm: {np.linalg.norm(q):.6f} (should be 1)")
198
199 # Convert back to rotation matrix
200 R_recovered = quat2rotmat(q)
201 print(f"\nRotation matrix roundtrip check: {np.allclose(R, R_recovered)}")
202
203 # Quaternion multiplication (composing rotations)
204 q2 = rotmat2quat(rotz(np.radians(90.0))) # 90 deg yaw rotation
205 q_composed = quat_multiply(q, q2)
206 print("\nComposed quaternion (original + 90 deg yaw):")
207 print(
208 f" q = [{q_composed[0]:.4f}, {q_composed[1]:.4f}, "
209 f"{q_composed[2]:.4f}, {q_composed[3]:.4f}]"
210 )
211
212 # SLERP interpolation between two orientations
213 print("\nSLERP interpolation (identity to 90 deg yaw):")
214 q_start = np.array([1.0, 0.0, 0.0, 0.0]) # Identity
215 q_end = rotmat2quat(rotz(np.radians(90.0)))
216
217 for t in [0.0, 0.25, 0.5, 0.75, 1.0]:
218 q_interp = slerp(q_start, q_end, t)
219 R_interp = quat2rotmat(q_interp)
220 # rotmat2euler returns [angle1, angle2, angle3] for ZYX = [yaw, pitch, roll]
221 angles = rotmat2euler(R_interp, sequence="ZYX")
222 yaw_interp = angles[0]
223 print(f" t={t:.2f}: yaw = {np.degrees(yaw_interp):.1f} deg")
224
225
226def jacobian_covariance_demo() -> None:
227 """Demonstrate Jacobian-based covariance transformation."""
228 print("\n" + "=" * 60)
229 print("6. JACOBIAN-BASED COVARIANCE TRANSFORMATION")
230 print("=" * 60)
231
232 # Sensor measures in spherical coordinates with uncertainty
233 r = 5000.0 # Range in meters
234 az = np.radians(45.0) # Azimuth
235 el = np.radians(10.0) # Elevation
236
237 # Measurement covariance in spherical coordinates
238 sigma_r = 10.0 # Range std (meters)
239 sigma_az = np.radians(0.5) # Azimuth std (radians)
240 sigma_el = np.radians(0.5) # Elevation std (radians)
241
242 P_spherical = np.diag([sigma_r**2, sigma_az**2, sigma_el**2])
243
244 print("\nSpherical measurement:")
245 print(f" Range: {r:.1f} +/- {sigma_r:.1f} m")
246 print(f" Azimuth: {np.degrees(az):.1f} +/- {np.degrees(sigma_az):.2f} deg")
247 print(f" Elevation: {np.degrees(el):.1f} +/- {np.degrees(sigma_el):.2f} deg")
248
249 # Get Jacobian of Cartesian w.r.t. spherical at this point
250 # spherical_jacobian_inv: d[x,y,z] = J @ d[r,az,el]
251 J = spherical_jacobian_inv(r, az, el)
252
253 print("\nJacobian (dCartesian/dSpherical):")
254 print(J)
255
256 # Transform covariance to Cartesian
257 P_cartesian = cross_covariance_transform(P_spherical, J)
258
259 print("\nCartesian covariance matrix:")
260 print(P_cartesian)
261
262 # Extract position uncertainties
263 sigma_x = np.sqrt(P_cartesian[0, 0])
264 sigma_y = np.sqrt(P_cartesian[1, 1])
265 sigma_z = np.sqrt(P_cartesian[2, 2])
266
267 # Convert mean to Cartesian
268 cart = sphere2cart(r, az, el)
269 print("\nCartesian position with uncertainties:")
270 print(f" x = {cart[0]:.1f} +/- {sigma_x:.1f} m")
271 print(f" y = {cart[1]:.1f} +/- {sigma_y:.1f} m")
272 print(f" z = {cart[2]:.1f} +/- {sigma_z:.1f} m")
273
274
275def main() -> None:
276 """Run all coordinate system demonstrations."""
277 print("\nCoordinate Systems Examples")
278 print("=" * 60)
279 print("Demonstrating pytcl coordinate transformation capabilities")
280
281 spherical_conversions_demo()
282 geodetic_conversions_demo()
283 local_tangent_plane_demo()
284 rotation_demo()
285 quaternion_demo()
286 jacobian_covariance_demo()
287
288 # Visualization
289 visualize_coordinate_transforms()
290
291 print("\n" + "=" * 60)
292 print("Done!")
293 print("=" * 60)
294
295
296def visualize_coordinate_transforms() -> None:
297 """Visualize coordinate system transformations."""
298 print("\nGenerating coordinate transform visualization...")
299
300 # Create a grid of points in spherical coordinates
301 r = 1000.0
302 azimuths = np.linspace(0, 360, 9)
303 elevations = np.linspace(-90, 90, 5)
304
305 points_cart = []
306 for az in azimuths:
307 for el in elevations:
308 cart = sphere2cart(r, np.radians(az), np.radians(el))
309 points_cart.append(cart)
310
311 points_cart = np.array(points_cart)
312
313 # Create 3D scatter plot
314 fig = go.Figure()
315
316 fig.add_trace(
317 go.Scatter3d(
318 x=points_cart[:, 0],
319 y=points_cart[:, 1],
320 z=points_cart[:, 2],
321 mode="markers",
322 marker=dict(size=5, color="blue", opacity=0.8),
323 name="Spherical Coords (Cartesian)",
324 )
325 )
326
327 fig.update_layout(
328 title="Spherical to Cartesian Coordinate Transformation",
329 scene=dict(
330 xaxis_title="X (m)",
331 yaxis_title="Y (m)",
332 zaxis_title="Z (m)",
333 camera=dict(eye=dict(x=1.5, y=1.5, z=1.5)),
334 ),
335 height=600,
336 width=800,
337 )
338
339 fig.show()
340
341
342if __name__ == "__main__":
343 main()
Running the Example
python examples/coordinate_systems.py
See Also
Coordinate Visualization - 3D visualizations
Navigation and Geodesy - Geodetic calculations
tracking_3d - Using coordinates in tracking