Topocentric coordinate systems are local horizon-based reference frames centered on an observer, such as a ground station or radar site. These coordinate systems are essential for satellite tracking, visibility analysis, and determining where to point antennas or telescopes.
Unlike global coordinate systems (ECEF, ECI), topocentric systems define positions relative to a specific location on Earth, making it easy to determine whether a satellite is visible and where to look in the sky.
The SEZ system is sometimes used in radar and missile tracking applications. The main difference from ENZ is that the first two axes are rotated 180° around the zenith axis.
Info
Both ENZ and SEZ use a right-handed coordinate system with the zenith axis pointing up. The choice between them is typically driven by convention in your specific field or application.
To get the position of an object relative to a location, you need to convert the object's ECEF position to the local ENZ frame centered on the location:
importbraheasbhimportnumpyasnpbh.initialize_eop()# Define ground station location in geodetic coordinates# Stanford University: (lon=-122.17329°, lat=37.42692°, alt=32.0m)lon_deg=-122.17329lat_deg=37.42692alt_m=32.0print("Ground Station (Stanford):")print(f"Longitude: {lon_deg:.5f}° = {np.radians(lon_deg):.6f} rad")print(f"Latitude: {lat_deg:.5f}° = {np.radians(lat_deg):.6f} rad")print(f"Altitude: {alt_m:.1f} m\n")# Longitude: -122.17329° = -2.132605 rad# Latitude: 37.42692° = 0.653131 rad# Altitude: 32.0 m# Convert ground station to ECEFgeodetic_station=np.array([lon_deg,lat_deg,alt_m])station_ecef=bh.position_geodetic_to_ecef(geodetic_station,bh.AngleFormat.DEGREES)print("Ground Station ECEF:")print(f"x = {station_ecef[0]:.3f} m")print(f"y = {station_ecef[1]:.3f} m")print(f"z = {station_ecef[2]:.3f} m\n")# x = -2700691.122 m# y = -4292566.016 m# z = 3855395.780 m# Define satellite in sun-synchronous orbit at 500 km altitude# SSO orbit passes over Stanford at approximately 10:30 AM local time# Orbital elements: [a, e, i, RAAN, omega, M]oe=np.array([bh.R_EARTH+500e3,0.001,97.8,240.0,0.0,90.0])# Define epoch when satellite passes near Stanford (Jan 1, 2024, 17:05 UTC)epoch=bh.Epoch.from_datetime(2024,1,1,17,5,0.0,0.0,bh.TimeSystem.UTC)# Convert orbital elements to ECI statesat_state_eci=bh.state_koe_to_eci(oe,bh.AngleFormat.DEGREES)# Convert ECI state to ECEF at the given epochsat_state_ecef=bh.state_eci_to_ecef(epoch,sat_state_eci)sat_ecef=sat_state_ecef[0:3]# Extract position onlyyear,month,day,hour,minute,second,ns=epoch.to_datetime()print(f"Epoch: {year}-{month:02d}-{day:02d}{hour:02d}:{minute:02d}:{second:06.3f} UTC")print("Satellite ECEF:")print(f"x = {sat_ecef[0]:.3f} m")print(f"y = {sat_ecef[1]:.3f} m")print(f"z = {sat_ecef[2]:.3f} m\n")# Convert satellite position to ENZ coordinates relative to ground stationenz=bh.relative_position_ecef_to_enz(station_ecef,sat_ecef,bh.EllipsoidalConversionType.GEODETIC)print("Satellite position in ENZ frame (relative to Stanford):")print(f"East: {enz[0]/1000:.3f} km")print(f"North: {enz[1]/1000:.3f} km")print(f"Zenith: {enz[2]/1000:.3f} km")print(f"Range: {np.linalg.norm(enz)/1000:.3f} km")
usebraheasbh;usenalgebraasna;fnmain(){bh::initialize_eop().unwrap();// Define ground station location in geodetic coordinates// Stanford University: (lon=-122.17329°, lat=37.42692°, alt=32.0m)letlon_deg=-122.17329_f64;letlat_deg=37.42692_f64;letalt_m=32.0;println!("Ground Station (Stanford):");println!("Longitude: {:.5}° = {:.6} rad",lon_deg,lon_deg.to_radians());println!("Latitude: {:.5}° = {:.6} rad",lat_deg,lat_deg.to_radians());println!("Altitude: {:.1} m\n",alt_m);// Expected output:// Longitude: -122.17329° = -2.132605 rad// Latitude: 37.42692° = 0.653131 rad// Altitude: 32.0 m// Convert ground station to ECEFletgeodetic_station=na::Vector3::new(lon_deg,lat_deg,alt_m);letstation_ecef=bh::position_geodetic_to_ecef(geodetic_station,bh::AngleFormat::Degrees).unwrap();println!("Ground Station ECEF:");println!("x = {:.3} m",station_ecef[0]);println!("y = {:.3} m",station_ecef[1]);println!("z = {:.3} m\n",station_ecef[2]);// Expected output:// x = -2700691.122 m// y = -4292566.016 m// z = 3855395.780 m// Define satellite in sun-synchronous orbit at 500 km altitude// SSO orbit passes over Stanford at approximately 10:30 AM local time// Orbital elements: [a, e, i, RAAN, omega, M]letoe=na::SVector::<f64,6>::new(bh::R_EARTH+500e3,0.001,97.8_f64,240.0_f64,0.0_f64,90.0_f64,);// Define epoch when satellite passes near Stanford (Jan 1, 2024, 17:05 UTC)letepoch=bh::Epoch::from_datetime(2024,1,1,17,5,0.0,0.0,bh::TimeSystem::UTC);// Convert orbital elements to ECI stateletsat_state_eci=bh::state_koe_to_eci(oe,bh::AngleFormat::Degrees);// Convert ECI state to ECEF at the given epochletsat_state_ecef=bh::state_eci_to_ecef(epoch,sat_state_eci);letsat_ecef=na::Vector3::new(sat_state_ecef[0],sat_state_ecef[1],sat_state_ecef[2]);let(year,month,day,hour,minute,second,_ns)=epoch.to_datetime();println!("Epoch: {}-{:02}-{:02} {:02}:{:02}:{:06.3} UTC",year,month,day,hour,minute,second);println!("Satellite ECEF:");println!("x = {:.3} m",sat_ecef[0]);println!("y = {:.3} m",sat_ecef[1]);println!("z = {:.3} m\n",sat_ecef[2]);// Convert satellite position to ENZ coordinates relative to ground stationletenz=bh::relative_position_ecef_to_enz(station_ecef,sat_ecef,bh::EllipsoidalConversionType::Geodetic,);println!("Satellite position in ENZ frame (relative to Stanford):");println!("East: {:.3} km",enz[0]/1000.0);println!("North: {:.3} km",enz[1]/1000.0);println!("Zenith: {:.3} km",enz[2]/1000.0);println!("Range: {:.3} km",enz.norm()/1000.0);}
importbraheasbhimportnumpyasnpbh.initialize_eop()# Define ground station location in geodetic coordinates# Stanford University: (lon=-122.17329°, lat=37.42692°, alt=32.0m)lon_deg=-122.17329lat_deg=37.42692alt_m=32.0print("Ground Station (Stanford):")print(f"Longitude: {lon_deg:.5f}° = {np.radians(lon_deg):.6f} rad")print(f"Latitude: {lat_deg:.5f}° = {np.radians(lat_deg):.6f} rad")print(f"Altitude: {alt_m:.1f} m\n")# Longitude: -122.17329° = -2.132605 rad# Latitude: 37.42692° = 0.653131 rad# Altitude: 32.0 m# Convert ground station to ECEFgeodetic_station=np.array([lon_deg,lat_deg,alt_m])station_ecef=bh.position_geodetic_to_ecef(geodetic_station,bh.AngleFormat.DEGREES)print("Ground Station ECEF:")print(f"x = {station_ecef[0]:.3f} m")print(f"y = {station_ecef[1]:.3f} m")print(f"z = {station_ecef[2]:.3f} m\n")# x = -2700691.122 m# y = -4292566.016 m# z = 3855395.780 m# Define satellite in sun-synchronous orbit at 500 km altitude# SSO orbit passes over Stanford at approximately 10:30 AM local time# Orbital elements: [a, e, i, RAAN, omega, M]oe=np.array([bh.R_EARTH+500e3,0.001,97.8,240.0,0.0,90.0])# Define epoch when satellite passes near Stanford (Jan 1, 2024, 17:05 UTC)epoch=bh.Epoch.from_datetime(2024,1,1,17,5,0.0,0.0,bh.TimeSystem.UTC)# Convert orbital elements to ECI statesat_state_eci=bh.state_koe_to_eci(oe,bh.AngleFormat.DEGREES)# Convert ECI state to ECEF at the given epochsat_state_ecef=bh.state_eci_to_ecef(epoch,sat_state_eci)sat_ecef=sat_state_ecef[0:3]# Extract position onlyyear,month,day,hour,minute,second,ns=epoch.to_datetime()print(f"Epoch: {year}-{month:02d}-{day:02d}{hour:02d}:{minute:02d}:{second:06.3f} UTC")print("Satellite ECEF:")print(f"x = {sat_ecef[0]:.3f} m")print(f"y = {sat_ecef[1]:.3f} m")print(f"z = {sat_ecef[2]:.3f} m\n")# Convert satellite position to SEZ coordinates relative to ground stationsez=bh.relative_position_ecef_to_sez(station_ecef,sat_ecef,bh.EllipsoidalConversionType.GEODETIC)print("Satellite position in SEZ frame (relative to Stanford):")print(f"South: {sez[0]/1000:.3f} km")print(f"East: {sez[1]/1000:.3f} km")print(f"Zenith: {sez[2]/1000:.3f} km")print(f"Range: {np.linalg.norm(sez)/1000:.3f} km")
usebraheasbh;usenalgebraasna;fnmain(){bh::initialize_eop().unwrap();// Define ground station location in geodetic coordinates// Stanford University: (lon=-122.17329°, lat=37.42692°, alt=32.0m)letlon_deg=-122.17329_f64;letlat_deg=37.42692_f64;letalt_m=32.0;println!("Ground Station (Stanford):");println!("Longitude: {:.5}° = {:.6} rad",lon_deg,lon_deg.to_radians());println!("Latitude: {:.5}° = {:.6} rad",lat_deg,lat_deg.to_radians());println!("Altitude: {:.1} m\n",alt_m);// Expected output:// Longitude: -122.17329° = -2.132605 rad// Latitude: 37.42692° = 0.653131 rad// Altitude: 32.0 m// Convert ground station to ECEFletgeodetic_station=na::Vector3::new(lon_deg,lat_deg,alt_m);letstation_ecef=bh::position_geodetic_to_ecef(geodetic_station,bh::AngleFormat::Degrees).unwrap();println!("Ground Station ECEF:");println!("x = {:.3} m",station_ecef[0]);println!("y = {:.3} m",station_ecef[1]);println!("z = {:.3} m\n",station_ecef[2]);// Expected output:// x = -2700691.122 m// y = -4292566.016 m// z = 3855395.780 m// Define satellite in sun-synchronous orbit at 500 km altitude// SSO orbit passes over Stanford at approximately 10:30 AM local time// Orbital elements: [a, e, i, RAAN, omega, M]letoe=na::SVector::<f64,6>::new(bh::R_EARTH+500e3,0.001,97.8_f64,240.0_f64,0.0_f64,90.0_f64,);// Define epoch when satellite passes near Stanford (Jan 1, 2024, 17:05 UTC)letepoch=bh::Epoch::from_datetime(2024,1,1,17,5,0.0,0.0,bh::TimeSystem::UTC);// Convert orbital elements to ECI stateletsat_state_eci=bh::state_koe_to_eci(oe,bh::AngleFormat::Degrees);// Convert ECI state to ECEF at the given epochletsat_state_ecef=bh::state_eci_to_ecef(epoch,sat_state_eci);letsat_ecef=na::Vector3::new(sat_state_ecef[0],sat_state_ecef[1],sat_state_ecef[2]);let(year,month,day,hour,minute,second,_ns)=epoch.to_datetime();println!("Epoch: {}-{:02}-{:02} {:02}:{:02}:{:06.3} UTC",year,month,day,hour,minute,second);println!("Satellite ECEF:");println!("x = {:.3} m",sat_ecef[0]);println!("y = {:.3} m",sat_ecef[1]);println!("z = {:.3} m\n",sat_ecef[2]);// Convert satellite position to SEZ coordinates relative to ground stationletsez=bh::relative_position_ecef_to_sez(station_ecef,sat_ecef,bh::EllipsoidalConversionType::Geodetic,);println!("Satellite position in SEZ frame (relative to Stanford):");println!("South: {:.3} km",sez[0]/1000.0);println!("East: {:.3} km",sez[1]/1000.0);println!("Zenith: {:.3} km",sez[2]/1000.0);println!("Range: {:.3} km",sez.norm()/1000.0);}
usebraheasbh;usenalgebraasna;fnmain(){bh::initialize_eop().unwrap();// Define ground station location in geodetic coordinates// Stanford University: (lon=-122.17329°, lat=37.42692°, alt=32.0m)letlon_deg=-122.17329_f64;letlat_deg=37.42692_f64;letalt_m=32.0;println!("Ground Station (Stanford):");println!("Longitude: {:.5}° = {:.6} rad",lon_deg,lon_deg.to_radians());println!("Latitude: {:.5}° = {:.6} rad",lat_deg,lat_deg.to_radians());println!("Altitude: {:.1} m\n",alt_m);// Expected output:// Longitude: -122.17329° = -2.132605 rad// Latitude: 37.42692° = 0.653131 rad// Altitude: 32.0 m// Convert ground station to ECEFletgeodetic_station=na::Vector3::new(lon_deg,lat_deg,alt_m);letstation_ecef=bh::position_geodetic_to_ecef(geodetic_station,bh::AngleFormat::Degrees).unwrap();println!("Ground Station ECEF:");println!("x = {:.3} m",station_ecef[0]);println!("y = {:.3} m",station_ecef[1]);println!("z = {:.3} m\n",station_ecef[2]);// Expected output:// x = -2700691.122 m// y = -4292566.016 m// z = 3855395.780 m// Define relative position in SEZ coordinates// Example: 30 km South, 50 km East, 100 km Up from stationletsez=na::Vector3::new(30e3,50e3,100e3);println!("Relative position in SEZ frame:");println!("South: {:.1} km",sez[0]/1000.0);println!("East: {:.1} km",sez[1]/1000.0);println!("Zenith: {:.1} km\n",sez[2]/1000.0);// Expected output:// South: 30.0 km// East: 50.0 km// Zenith: 100.0 km// Convert SEZ relative position to absolute ECEF positionlettarget_ecef=bh::relative_position_sez_to_ecef(station_ecef,sez,bh::EllipsoidalConversionType::Geodetic,);println!("Target position in ECEF:");println!("x = {:.3} m",target_ecef[0]);println!("y = {:.3} m",target_ecef[1]);println!("z = {:.3} m",target_ecef[2]);println!("Distance from Earth center: {:.3} km",target_ecef.norm()/1000.0);}
Azimuth and Elevation from Topocentric Coordinates¶
For object tracking, it's often more intuitive to work with azimuth (compass direction) and elevation (angle above the horizon) rather than Cartesian ENZ or SEZ coordinates. Both ENZ and SEZ topocentric systems can be converted to azimuth-elevation-range format.
usebraheasbh;usenalgebraasna;fnmain(){bh::initialize_eop().unwrap();// Define several relative positions in SEZ coordinateslettest_cases=vec![("Directly overhead",na::Vector3::new(0.0,0.0,100e3)),("North horizon",na::Vector3::new(-100e3,0.0,0.0)),("East horizon",na::Vector3::new(0.0,100e3,0.0)),("South horizon",na::Vector3::new(100e3,0.0,0.0)),("West horizon",na::Vector3::new(0.0,-100e3,0.0)),("Northeast at 45° elevation",na::Vector3::new(-50e3,50e3,70.7e3)),];println!("Converting SEZ coordinates to Azimuth-Elevation-Range:\n");for(name,sez)intest_cases{// Convert to azimuth-elevation-rangeletazel=bh::position_sez_to_azel(sez,bh::AngleFormat::Degrees);println!("{}:",name);println!(" SEZ: S={:.1}km, E={:.1}km, Z={:.1}km",sez[0]/1000.0,sez[1]/1000.0,sez[2]/1000.0);println!(" Az/El: Az={:.1}°, El={:.1}°, Range={:.1}km\n",azel[0],azel[1],azel[2]/1000.0);}// Expected outputs:// Directly overhead: Az=0.0°, El=90.0°, Range=100.0km// North horizon: Az=0.0°, El=0.0°, Range=100.0km// East horizon: Az=90.0°, El=0.0°, Range=100.0km// South horizon: Az=180.0°, El=0.0°, Range=100.0km// West horizon: Az=270.0°, El=0.0°, Range=100.0km}
Info
Both ENZ and SEZ produce identical azimuth-elevation-range results for the same physical position. The choice between them is purely a matter of intermediate representation.