The SSN sensor dataset provides representative locations, field-of-view limits, and calibration (bias/noise) values for U.S. Space Surveillance Network sites. This data is essential for:
Simulating radar/optical tracking: Build SimpleSSNSensor instances that generate az/el/range measurements consistent with a matching AzElRangeMeasurementModel
Access analysis: Determine when a sensor's field of view covers a target orbit
Orbit determination testing: Exercise EKF/UKF/BLS estimators against a realistic, multi-site sensor network
Brahe includes embedded GeoJSON data for 21 SSN sites. The data is:
Offline-capable: No network requests required
Calibrated: Includes bias and noise values for sensors with published Table 4-4 entries
Wrap-aware: Azimuth field-of-view windows that cross north are represented correctly
Total SSN sites: 21
Radar/phased-array/mechanical sites: 15
Optical (angles-only) sites: 6
Eglin
Location: (30.57, -86.21)
System: "Phased Array"
Category: "dedicated"
Elevation limits: 1.0 - 90.0 deg
Range max: 13210 km
Azimuth noise: 0.0154 deg
Example validated successfully!
bh.datasets.ssn_sensors.load() returns every site as a PointLocation. SimpleSSNSensor.from_locations() builds a sensor for every site -- radar (azel_range, measuring az/el/range) and optical (azel, angles-only az/el) alike -- defaulting the sites that lack full Table 4-4 calibration to zero noise and bias (flagged calibrated == False, overridable with with_noise()/with_bias()). from_locations_calibrated() restricts the result to the fully-calibrated sites -- this is the set used throughout the SSN Radar Tracking example.
Each site is a PointLocation with geodetic coordinates (lon(), lat(), alt()) and a properties dictionary:
Field
Type
Units
Description
sensor_type
str
--
"azel_range" (radar/phased-array/mechanical trackers, az/el/range) or "azel" (angles-only optical trackers, az/el)
system
str
--
Sensor system description, e.g. "Phased Array", "Radar", "GEODSS"
category
str
--
Vallado network category, e.g. "dedicated", "collateral", "contributing"
sensor_numbers
list[int]
--
SSN sensor ID number(s) at the site
az_min_deg
float, optional
degrees
Azimuth field-of-view start. Wrap-aware: when az_min_deg > az_max_deg, the window crosses north
az_max_deg
float, optional
degrees
Azimuth field-of-view end
el_min_deg
float, optional
degrees
Minimum elevation angle
el_max_deg
float, optional
degrees
Maximum elevation angle
range_max_m
float, optional
meters
Maximum range; absent means effectively unlimited
az_bias_deg
float, optional
degrees
Constant azimuth measurement bias (Table 4-4)
el_bias_deg
float, optional
degrees
Constant elevation measurement bias
range_bias_m
float, optional
meters
Constant range measurement bias
az_noise_deg
float, optional
degrees
Azimuth measurement noise standard deviation
el_noise_deg
float, optional
degrees
Elevation measurement noise standard deviation
range_noise_m
float, optional
meters
Range measurement noise standard deviation
sensor_type determines which fields are present: azel sites carry no range fields at all, and sites appearing only in Table 4-2 (location and field-of-view, no calibration) carry no bias/noise fields. SimpleSSNSensor.from_location() accepts both azel_range and azel sites; a site missing one or more noise fields still constructs, defaulted to zero noise and flagged uncalibrated, rather than raising an error.
Build a sensor from a single site, generate a measurement, and inspect the matching AzElRangeMeasurementModel that measurement_model() builds from the sensor's own bias and noise -- the model and the sensor stay consistent because both read the same calibration:
importnumpyasnpimportbraheasbh# Initialize EOP databh.initialize_eop()# Load a fully-calibrated radar site and build a sensor from itsites=bh.datasets.ssn_sensors.load()eglin_site=next(sforsinsitesifs.get_name()=="Eglin")sensor=bh.SimpleSSNSensor.from_location(eglin_site,seed=42)print(f"Sensor: {sensor.name}")print(f"Azimuth window: {sensor.az_min:.1f} - {sensor.az_max:.1f} deg")print(f"Elevation limits: {sensor.el_min:.1f} - {sensor.el_max:.1f} deg")print(f"Range max: {sensor.range_max/1e3:.0f} km")print(f"Calibrated: {sensor.calibrated}")# Build the matching measurement model: same bias/noise as the sensor, so a# filter built from it stays consistent with measurements the sensor producesmodel=sensor.measurement_model()print(f"Measurement model: {model.name()}")# A target 500 km away, due south (within Eglin's southwest-facing azimuth# window) and 45 deg above the horizon, built by offsetting the site in the# local East-North-Zenith frame and converting to ECI.epoch=bh.Epoch(2024,1,1,0,0,0.0)az,el,rng=np.radians(180.0),np.radians(45.0),500e3horizontal=rng*np.cos(el)enz_offset=np.array([horizontal*np.sin(az),horizontal*np.cos(az),rng*np.sin(el)])target_ecef=bh.relative_position_enz_to_ecef(eglin_site.center_ecef(),enz_offset,bh.EllipsoidalConversionType.GEODETIC)state_eci=bh.state_ecef_to_eci(epoch,np.concatenate([target_ecef,np.zeros(3)]))# True (noise-free, bias-free) geometry vs. a simulated measurementtruth=sensor.azelrange(epoch,state_eci)print(f"\nTrue az/el/range: [{truth[0]:.2f} deg, {truth[1]:.2f} deg, {truth[2]/1e3:.1f} km]")measurement=sensor.measure(epoch,state_eci)print(f"Measured az/el/range: [{measurement[0]:.2f} deg, {measurement[1]:.2f} deg, "f"{measurement[2]/1e3:.1f} km]")assertmeasurementisnotNone,"Target inside the field of view should be visible"assertabs(measurement[0]-truth[0])<1.0,"azimuth should stay close to truth"assertabs(measurement[1]-truth[1])<1.0,"elevation should stay close to truth"assertabs(measurement[2]-truth[2])<5000.0,"range should stay close to truth"print("\nExample validated successfully!")
usebraheasbh;usebh::datasets::ssn_sensors::load_ssn_sensors;usebh::estimation::SimpleSSNSensor;usebh::utils::Identifiable;usebh::AccessibleLocation;usenalgebra::{SVector,Vector3};fnmain(){bh::initialize_eop().unwrap();// Load a fully-calibrated radar site and build a sensor from itletsites=load_ssn_sensors().unwrap();leteglin_site=sites.iter().find(|s|s.get_name()==Some("Eglin")).unwrap();letmutsensor=SimpleSSNSensor::from_location(eglin_site).unwrap().with_seed(42);println!("Sensor: {}",sensor.name());println!("Azimuth window: {:.1} - {:.1} deg",sensor.az_min(),sensor.az_max());println!("Elevation limits: {:.1} - {:.1} deg",sensor.el_min(),sensor.el_max());println!("Range max: {:.0} km",sensor.range_max().unwrap()/1e3);println!("Calibrated: {}",sensor.calibrated());// Build the matching measurement model: same bias/noise as the sensor,// so a filter built from it stays consistent with measurements the// sensor produces.letmodel=sensor.measurement_model();println!("Measurement model: {}",model.name());// A target 500 km away, due south (within Eglin's southwest-facing// azimuth window) and 45 deg above the horizon, built by offsetting the// site in the local East-North-Zenith frame and converting to ECI.letepoch=bh::Epoch::from_datetime(2024,1,1,0,0,0.0,0.0,bh::TimeSystem::UTC);let(az,el,rng)=(180.0f64.to_radians(),45.0f64.to_radians(),500e3);lethorizontal=rng*el.cos();letenz_offset=Vector3::new(horizontal*az.sin(),horizontal*az.cos(),rng*el.sin());lettarget_ecef=bh::relative_position_enz_to_ecef(eglin_site.center_ecef(),enz_offset,bh::EllipsoidalConversionType::Geodetic,);letstate_ecef=SVector::<f64,6>::new(target_ecef[0],target_ecef[1],target_ecef[2],0.0,0.0,0.0,);letstate_eci=bh::state_ecef_to_eci(epoch,state_ecef);letstate_eci_dvec=nalgebra::DVector::from_column_slice(state_eci.as_slice());// True (noise-free, bias-free) geometry vs. a simulated measurementlettruth=sensor.azelrange(&epoch,&state_eci_dvec);println!("\nTrue az/el/range: [{:.2} deg, {:.2} deg, {:.1} km]",truth[0],truth[1],truth[2]/1e3);letmeasurement=sensor.measure(&epoch,&state_eci_dvec).unwrap();println!("Measured az/el/range: [{:.2} deg, {:.2} deg, {:.1} km]",measurement[0],measurement[1],measurement[2]/1e3);assert!((measurement[0]-truth[0]).abs()<1.0,"azimuth should stay close to truth");assert!((measurement[1]-truth[1]).abs()<1.0,"elevation should stay close to truth");assert!((measurement[2]-truth[2]).abs()<5000.0,"range should stay close to truth");println!("\nExample validated successfully!");}
Values are from Vallado, Fundamentals of Astrodynamics and Applications, 4th Ed., Tables 4-2 (site locations and systems), 4-3 (field-of-view limits), and 4-4 (bias/noise calibration). NAVSPASUR is excluded from the embedded dataset. These values are representative and dated -- they reflect the published tables, not current SSN configuration or performance, and should not be used for operational sensor modeling.