Access Computation¶
This tutorial demonstrates how to compute access.
Setup¶
import pandas as pd
import plotly.graph_objs as go
from ostk.mathematics.object import RealInterval
from ostk.physics import Environment
from ostk.physics.unit import Length
from ostk.physics.unit import Angle
from ostk.physics.time import Scale
from ostk.physics.time import Instant
from ostk.physics.time import Duration
from ostk.physics.time import Interval
from ostk.physics.time import DateTime
from ostk.physics.time import Time
from ostk.physics.coordinate.spherical import LLA
from ostk.astrodynamics.trajectory import Orbit
from ostk.astrodynamics.trajectory.orbit.model import Kepler
from ostk.astrodynamics.trajectory.orbit.model.kepler import COE
from ostk.astrodynamics.trajectory.orbit.model import SGP4
from ostk.astrodynamics.trajectory.orbit.model.sgp4 import TLE
from ostk.astrodynamics.access import Generator as AccessGenerator
from ostk.astrodynamics.access import AccessTarget
from ostk.astrodynamics.access import VisibilityCriterion
from ostk.astrodynamics.utilities import compute_trajectory_geometry
from ostk.astrodynamics.utilities import compute_time_lla_aer_coordinates
Access¶
An access represents an object-to-object visibility period.
In this example, let's compute accesses between a fixed position on the ground and a satellite in LEO.
Environment¶
Let's setup an environment (which describes where planets are, etc...):
environment = Environment.default(True) # Set global environment instance as the default instance
earth = environment.access_celestial_object_with_name("Earth")
Origin¶
Visibility Criterion¶
Let's define a visibility criterion, there are currently 4 types of Visibility Criterion
AERInterval : Defined by Azimuth, Elevation and Range intervals (typically used for accesses with Ground Stations)
ElevationInterval : Defined solely by an Elevation interval (typically used for imaging windows)
AERMask : Defined by an Azimuth-Elevation mask, and a Range interval (typically used for accesses with Ground Stations)
LineOfSight : Defined by a pure line of sight constraint, occluded by the bodies in the provided Environment (typically used for Satellite <-> Satellite accesses)
visibility_criterion = VisibilityCriterion.from_aer_interval(
azimuth_interval=RealInterval.closed(0.0, 360.0), # [deg]
elevation_interval=RealInterval.closed(0.0, 90.0), # [deg]
range_interval=RealInterval.closed(0.0, 10000e3), # [m]
)
# visibility_criterion = VisibilityCriterion.from_aer_mask(
# azimuth_elevation_mask={0.0: 0.0, 90.0: 45.0, 270.0: 30.0},
# range_interval=RealInterval.closed(0.0, 10000e3),
# )
# visibility_criterion = VisibilityCriterion.from_elevation_interval(
# elevation_interval=RealInterval.closed(80.0, 90.0),
# )
# visibility_criterion = VisibilityCriterion.from_line_of_sight(
# environment=environment,
# )
Access Target¶
Now that we have defined a visibility criterion, we can create an access target to compute accesses with. Let's define a fixed ground position, using its geographic coordinates. We can create a list of multiple access targets and compute a list of accesses corresponding to each access target
access_target = AccessTarget.from_lla(
visibility_criterion=visibility_criterion,
lla=LLA(Angle.degrees(50.0), Angle.degrees(20.0), Length.meters(30.0)),
celestial=earth,
)
another_access_target = AccessTarget.from_lla(
visibility_criterion=visibility_criterion,
lla=LLA(Angle.degrees(-50.0), Angle.degrees(10.0), Length.meters(30.0)),
celestial=earth,
)
access_targets = [access_target, another_access_target]
Target¶
Let's consider a satellite in Low-Earth Orbit.
We can define its orbit with Classical Orbital Elements:
a = earth.get_equatorial_radius() + Length.kilometers(500.0)
e = 0.000
i = Angle.degrees(97.8893)
raan = Angle.degrees(100.372)
aop = Angle.degrees(0.0)
nu = Angle.degrees(0.0201851)
coe = COE(a, e, i, raan, aop, nu)
... and by using a Keplerian orbital model:
epoch = Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC)
keplerian_model = Kepler(coe, epoch, earth, Kepler.PerturbationType.J2)
Or with a Two-Line Element (TLE) set:
tle = TLE(
"ISS (ZARYA)",
"1 25544U 98067A 18268.86272795 .00002184 00000-0 40781-4 0 9990",
"2 25544 51.6405 237.0010 0003980 205.4375 242.3358 15.53733046134172",
)
... along with its associated SGP4 orbital model:
sgp4_model = SGP4(tle)
Locking local repository [/var/cache/open-space-toolkit-data/data/manifest]...
Fetching Data Manifest from [https://github.com/open-space-collective/open-space-toolkit-data/raw/v1/data/manifest.json]...
Unlocking local repository [/var/cache/open-space-toolkit-data/data/manifest]...
Data Manifest [/var/cache/open-space-toolkit-data/data/manifest/manifest.json] has been successfully fetched from [https://github.com/open-space-collective/open-space-toolkit-data/raw/v1/data/manifest.json].
Fetching latest Bulletin A...
Locking local repository [/var/cache/open-space-toolkit-data/data/coordinate/frame/provider/iers]...
Creating temporary directory [/var/cache/open-space-toolkit-data/data/coordinate/frame/provider/iers/bulletin-A/tmp]...
Fetching Bulletin A from [https://github.com/open-space-collective/open-space-toolkit-data/raw/v1/data//coordinate/frame/provider/iers/bulletin-A/ser7.dat]...
Unlocking local repository [/var/cache/open-space-toolkit-data/data/coordinate/frame/provider/iers]...
Bulletin A [/var/cache/open-space-toolkit-data/data/coordinate/frame/provider/iers/bulletin-A/ser7.dat] has been successfully fetched from [https://github.com/open-space-collective/open-space-toolkit-data/raw/v1/data//coordinate/frame/provider/iers/bulletin-A/ser7.dat].
Below, we select which orbital model to use:
orbital_model = keplerian_model
# orbital_model = sgp4_model
We then obtain the satellite orbit (which is a Trajectory object):
satellite_orbit = Orbit(orbital_model, earth)
Alternatively, the Orbit class can provide some useful shortcuts (for usual orbit types):
epoch = Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC)
satellite_orbit = Orbit.sun_synchronous(
epoch, Length.kilometers(500.0), Time(12, 0, 0), earth
)
Access¶
Now that the origin and the target trajectories are well defined, we can compute the Access.
Let's first define an analysis interval:
start_instant = Instant.date_time(DateTime.parse("2018-01-01 00:00:00"), Scale.UTC)
end_instant = Instant.date_time(DateTime.parse("2018-01-10 00:00:00"), Scale.UTC)
interval = Interval.closed(start_instant, end_instant)
Then, using an Access Generator, we can compute the accesses within the intervals of interest:
access_generator = AccessGenerator(environment=environment)
accesses = access_generator.compute_accesses(
interval=interval,
access_targets=access_targets,
to_trajectory=satellite_orbit,
)
assert len(accesses) == len(access_targets) # a list of accesses per target
first_target_accesses = accesses[0]
second_target_accesses = accesses[1]
all_accesses = first_target_accesses + second_target_accesses
accesses_data = [
(
str(access.get_type()),
repr(access.get_acquisition_of_signal()),
repr(access.get_time_of_closest_approach()),
repr(access.get_loss_of_signal()),
float(access.get_duration().in_seconds()),
"first_target",
) for access in first_target_accesses
]
accesses_data += [
(
str(access.get_type()),
repr(access.get_acquisition_of_signal()),
repr(access.get_time_of_closest_approach()),
repr(access.get_loss_of_signal()),
float(access.get_duration().in_seconds()),
"second_target",
) for access in second_target_accesses
]
And format the output using a dataframe:
accesses_df = pd.DataFrame(
data=accesses_data,
columns=["Type", "AOS", "TCA", "LOS", "Duration", "Target"],
)
Output¶
Print accesses:
accesses_df
Type | AOS | TCA | LOS | Duration | Target | |
---|---|---|---|---|---|---|
0 | Type.Complete | 2018-01-01 00:10:48.691.727.513 [UTC] | 2018-01-01 00:13:07.422.341.866 [UTC] | 2018-01-01 00:15:32.871.969.978 [UTC] | 284.180242 | first_target |
1 | Type.Complete | 2018-01-01 09:57:49.624.946.040 [UTC] | 2018-01-01 10:02:55.818.813.623 [UTC] | 2018-01-01 10:07:55.028.955.753 [UTC] | 605.404010 | first_target |
2 | Type.Complete | 2018-01-01 11:31:16.376.710.963 [UTC] | 2018-01-01 11:37:02.533.622.949 [UTC] | 2018-01-01 11:42:43.005.386.542 [UTC] | 686.628676 | first_target |
3 | Type.Complete | 2018-01-01 13:06:09.698.392.313 [UTC] | 2018-01-01 13:09:58.212.688.283 [UTC] | 2018-01-01 13:13:42.224.919.802 [UTC] | 452.526527 | first_target |
4 | Type.Complete | 2018-01-01 20:41:15.275.981.816 [UTC] | 2018-01-01 20:46:05.882.558.137 [UTC] | 2018-01-01 20:51:01.381.094.552 [UTC] | 586.105113 | first_target |
... | ... | ... | ... | ... | ... | ... |
108 | Type.Complete | 2018-01-09 01:53:59.269.923.527 [UTC] | 2018-01-09 01:57:28.651.787.612 [UTC] | 2018-01-09 02:00:53.534.163.413 [UTC] | 414.264240 | second_target |
109 | Type.Complete | 2018-01-09 09:28:38.419.034.034 [UTC] | 2018-01-09 09:33:39.084.591.238 [UTC] | 2018-01-09 09:38:44.723.391.949 [UTC] | 606.304358 | second_target |
110 | Type.Complete | 2018-01-09 11:01:26.277.987.717 [UTC] | 2018-01-09 11:07:08.135.307.687 [UTC] | 2018-01-09 11:12:56.397.738.412 [UTC] | 690.119751 | second_target |
111 | Type.Complete | 2018-01-09 12:38:38.602.425.206 [UTC] | 2018-01-09 12:41:50.444.699.315 [UTC] | 2018-01-09 12:45:09.170.303.183 [UTC] | 390.567878 | second_target |
112 | Type.Complete | 2018-01-09 22:26:55.632.669.418 [UTC] | 2018-01-09 22:31:43.647.618.185 [UTC] | 2018-01-09 22:36:24.604.249.470 [UTC] | 568.971580 | second_target |
113 rows × 6 columns
Let's calculate the geographic coordinate of the satellite, during access:
def compute_access_geometry(access):
return [
compute_time_lla_aer_coordinates(state, access_target.get_position(), environment)
for state in satellite_orbit.get_states_at(
access.get_interval().generate_grid(Duration.seconds(1.0))
)
]
satellite_orbit_geometry_df = pd.DataFrame(
[lla.to_vector() for lla in compute_trajectory_geometry(satellite_orbit, interval)],
columns=["Latitude", "Longitude", "Altitude"],
)
satellite_orbit_geometry_df.head()
Latitude | Longitude | Altitude | |
---|---|---|---|
0 | -0.020152 | -0.001105 | 500000.002625 |
1 | 3.772321 | -0.734928 | 500091.839402 |
2 | 7.564114 | -1.472946 | 500367.684689 |
3 | 11.354544 | -2.219521 | 500822.628803 |
4 | 15.142919 | -2.979323 | 501448.578113 |
access_geometry_dfs = [
pd.DataFrame(
compute_access_geometry(access),
columns=[
"Time",
"Latitude",
"Longitude",
"Altitude",
"Azimuth",
"Elevation",
"Range",
],
)
for access in all_accesses
]
def get_max_elevation(df):
return df.loc[df["Elevation"].idxmax()]["Elevation"]
And plot the geometries onto a map:
data = []
# Target geometry
for access_target in access_targets:
data.append(
dict(
type="scattergeo",
lon=[float(access_target.get_lla(earth).in_degrees())],
lat=[float(access_target.get_lla(earth).in_degrees())],
mode="markers",
marker=dict(size=10, color="orange"),
)
)
# Orbit geometry
data.append(
dict(
type="scattergeo",
lon=satellite_orbit_geometry_df["Longitude"],
lat=satellite_orbit_geometry_df["Latitude"],
mode="lines",
line=dict(
width=1,
color="rgba(0, 0, 0, 0.1)",
),
)
)
# Access geometry
for access_geometry_df in access_geometry_dfs:
data.append(
dict(
type="scattergeo",
lon=access_geometry_df["Longitude"],
lat=access_geometry_df["Latitude"],
mode="lines",
line=dict(
width=1,
color="red",
),
)
)
layout = dict(
title=None,
showlegend=False,
width=1200,
height=600,
geo=dict(
showland=True,
landcolor="rgb(243, 243, 243)",
countrycolor="rgb(204, 204, 204)",
),
)
figure = go.Figure(data=data, layout=layout)
#figure.show("svg")
figure.show()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[25], line 9
3 # Target geometry
5 for access_target in access_targets:
6 data.append(
7 dict(
8 type="scattergeo",
----> 9 lon=[float(access_target.get_lla(earth).in_degrees())],
10 lat=[float(access_target.get_lla(earth).in_degrees())],
11 mode="markers",
12 marker=dict(size=10, color="orange"),
13 )
14 )
16 # Orbit geometry
18 data.append(
19 dict(
20 type="scattergeo",
(...)
28 )
29 )
AttributeError: 'ostk.physics.coordinate.spherical.LLA' object has no attribute 'in_degrees'