Access properties are geometric and temporal measurements computed for each access window. Brahe automatically calculates core properties during access searches, and provides both built-in and custom property computers for mission-specific analysis.
Core properties are attributes of the AccessWindow object returned by access computations and can be accessed directly like window.window_open or window.elevation_max.
Below are examples of accessing core properties in Python and Rust.
Property computers allow users to extend the access computation system to define and compute custom properties for each access window beyond the core set. These computations are performed after access windows are identified and refined.
Python users can implement property computers by subclassing AccessPropertyComputer, while in Rust you implement the AccessPropertyComputer trait. These traits require the implementation of the sampling_config and compute methods. sampling_config defines how satellite states are sampled during the access window, and compute performs the actual property calculation using those sampled states.
Brahe defines a few built-in property computers for common use cases, and users can create custom property computers for application-specific needs.
Property computers use SamplingConfig to determine when satellite states are sampled within the access window. That is, what epoch, state pairs are provided to the computer for its calculations.
You can choose from several sampling modes:
relative_points([0.0, 0.5, 1.0]) - Samples at specified fractions of the window duration with 0.0 being the start and 1.0 being the end
fixed_count(n) - Samples a fixed number of evenly spaced points within the window
fixed_interval(interval, offset) - Samples at regular time intervals (defined by seconds between samples) throughout the window with an optional offset
midpoint - Samples only at the midpoint of the window
This allows you to compute time-series data at specific intervals or points.
importbraheasbh# Single sample at window midpoint (default)config=bh.SamplingConfig.midpoint()print(f"Midpoint: {config}")# Specific relative points [0.0, 1.0] from window start to endconfig=bh.SamplingConfig.relative_points([0.0,0.25,0.5,0.75,1.0])print(f"Relative points: {config}")# Fixed time interval in secondsconfig=bh.SamplingConfig.fixed_interval(1.0,offset=0.0)# 1 secondprint(f"Fixed interval (1s): {config}")# Fixed number of evenly-spaced pointsconfig=bh.SamplingConfig.fixed_count(50)print(f"Fixed count (50): {config}")
//! ```usebrahe::access::SamplingConfig;fnmain(){// Single sample at window midpoint (default)letconfig=SamplingConfig::Midpoint;println!("Midpoint: {:?}",config);// Specific relative points [0.0, 1.0] from window start to endletconfig=SamplingConfig::RelativePoints(vec![0.0,0.25,0.5,0.75,1.0]);println!("Relative points: {:?}",config);// Fixed time interval in secondsletconfig=SamplingConfig::FixedInterval{interval:1.0,// 1 secondoffset:0.0};println!("Fixed interval (1s): {:?}",config);// Fixed number of evenly-spaced pointsletconfig=SamplingConfig::FixedCount(50);println!("Fixed count (50): {:?}",config);}
Downlink only: uplink=None, downlink=8.4e9 Hz
Both frequencies: uplink=2.0e9 Hz, downlink=8.4e9 Hz
First pass downlink Doppler shift range: -178593.2 to 178818.8 Hz
Doppler Physics:
Uplink: \(\Delta f = f_0\frac{v_{los}}{c - v_{los}}\) - Ground station pre-compensates transmit frequency
Downlink: \(\Delta f = -f_0\frac{v_{los}}{c}\) - Ground station adjusts receive frequency
Where \(v_{los}\) is the velocity of the object along the line of sight from the observer. With \(v_{los} < 0\) when approaching and \(v_{los} > 0\) when receding.
Computes line-of-sight velocity (range rate) with the convention that positive values indicate increasing range (satellite receding) and negative values indicate decreasing range (satellite approaching):
Range rate computer: RangeRateComputer()
Range rate varies from -6382.0 to 6372.9 m/s
Negative = approaching (decreasing distance)
Positive = receding (increasing distance)
Range rate computer: sampling=FixedInterval(0.5s)
Range rate varies from -6382.0 to 6372.9 m/s
Negative = approaching (decreasing distance)
Positive = receding (increasing distance)
You can also create your own property computer to compute application-specific properties values. The system will pre-sample the satellite state at the specified times defined by your SamplingConfig, so you don't need to manually propagate the trajectory.
This section provides examples of custom property computers in both Python and Rust.
importnumpyasnpimportbraheasbhbh.initialize_eop()classMaxSpeedComputer(bh.AccessPropertyComputer):"""Computes maximum ground speed during access."""defsampling_config(self):# Sample every 0.5 secondsreturnbh.SamplingConfig.fixed_interval(0.5,0.0)defcompute(self,window,sample_times,sample_states_ecef,location_ecef,location_geodetic):# Extract velocities from statesvelocities=sample_states_ecef[:,3:6]speeds=np.linalg.norm(velocities,axis=1)max_speed=np.max(speeds)# Single value -> returns as scalarreturn{"max_ground_speed":max_speed,# Will be stored as Scalar}defproperty_names(self):return["max_ground_speed"]# ISS orbittle_line1="1 25544U 98067A 25306.42331346 .00010070 00000-0 18610-3 0 9999"tle_line2="2 25544 51.6344 342.0717 0004969 8.9436 351.1640 15.49700017536601"propagator=bh.SGPPropagator.from_tle(tle_line1,tle_line2,60.0).with_name("ISS")epoch_start=propagator.epochepoch_end=epoch_start+24*3600.0# 24 hours# Ground stationlocation=bh.PointLocation(-74.0060,40.7128,0.0)# Compute with custom propertymax_speed=MaxSpeedComputer()constraint=bh.ElevationConstraint(min_elevation_deg=10.0)windows=bh.location_accesses(location,propagator,epoch_start,epoch_end,constraint,property_computers=[max_speed],)forwindowinwindows:speed=window.properties.additional["max_ground_speed"]print(f"Max speed: {speed:.1f} m/s")
To implement a custom property computer in Rust, create a struct that implements the AccessPropertyComputer trait by defining the sampling_config and compute methods.
AccessProperties is normally produced by an access search rather than constructed directly. For tests or custom pipelines that feed geometry from another source, AccessProperties.builder() returns a builder with every field unset; each of the 15 fields is set through a chained named setter, and build() raises an error naming any field left unset instead of applying a default. The flat constructor takes the same 15 fields positionally as an alternative, without naming each one:
importbraheasbhprops=(bh.AccessProperties.builder().azimuth_open(45.0).azimuth_close(135.0).elevation_min(10.0).elevation_max(85.0).elevation_open(12.0).elevation_close(10.5).off_nadir_min(5.0).off_nadir_max(80.0).local_time(43200.0).look_direction(bh.LookDirection.RIGHT).asc_dsc(bh.AscDsc.ASCENDING).center_lon(0.0).center_lat(45.0).center_alt(0.0).center_ecef([4517.59e3,4517.59e3,0.0]).build())print(f"Azimuth open/close: {props.azimuth_open}, {props.azimuth_close} deg")print(f"Elevation min/max: {props.elevation_min}, {props.elevation_max} deg")# The flat constructor takes the same 15 fields positionally, without# naming each one -- an alternative when all values are already at hand.props_flat=bh.AccessProperties(45.0,135.0,10.0,85.0,12.0,10.5,5.0,80.0,43200.0,bh.LookDirection.RIGHT,bh.AscDsc.ASCENDING,0.0,45.0,0.0,[4517.59e3,4517.59e3,0.0],)assertprops_flat.azimuth_open==props.azimuth_openprint("Example validated successfully!")
usebrahe::access::{AccessProperties,AscDsc,LookDirection};fnmain(){letprops=AccessProperties::builder().azimuth_open(45.0).azimuth_close(135.0).elevation_min(10.0).elevation_max(85.0).elevation_open(12.0).elevation_close(10.5).off_nadir_min(5.0).off_nadir_max(80.0).local_time(43200.0).look_direction(LookDirection::Right).asc_dsc(AscDsc::Ascending).center_lon(0.0).center_lat(45.0).center_alt(0.0).center_ecef([4517.59e3,4517.59e3,0.0]).build().unwrap();println!("Azimuth open/close: {}, {} deg",props.azimuth_open,props.azimuth_close);println!("Elevation min/max: {}, {} deg",props.elevation_min,props.elevation_max);// The flat constructor takes the same 15 fields positionally, without// naming each one -- an alternative when all values are already at hand.letprops_flat=AccessProperties::new(45.0,135.0,10.0,85.0,12.0,10.5,5.0,80.0,43200.0,LookDirection::Right,AscDsc::Ascending,0.0,45.0,0.0,[4517.59e3,4517.59e3,0.0],);assert_eq!(props_flat.azimuth_open,props.azimuth_open);println!("Example validated successfully!");}