Comparing Access Time Analysis SGP4:STK and OreKit

I was working off the TLE_Propagation.ipynb template to generate access times to see if I can recreate the results in STK. I though it would be straight forward:

  1. Generate a model and one ground station and gather access times for elevation > 5 deg for a 24 hour duration.
  2. Generate a TLE + Ground Station Geodetic Coordinates + Alt. from STK and copy it into the TLE_Propagation.ipynb
  3. When I compare access times, the Epoch start access times for STK/Orekit are off by ~800s and the net duration is off by ~84 seconds.

Here is the TLE I used:
1 27424U 02022A 21307.66666667 .00000236 00000-0 52390-4 0 00004
2 27424 098.2183 247.0537 0000565 346.9863 173.2636 14.57126477037344

and here is the Ground Station:
27.5207 deg
-157.996 deg
402 m

Suggestions?

Did you check the units for angles? Orekit uses SI units only, so angles must be given in radians.

Another point: did you check you called withConstantElevation() and really use the detector it returns (i.e. use detector = detector.withMinElevation(FastMath.toRadians(5.0)) and not simply detector.withMinElevation(FastMath.toRadians(5.0)) which would not change the min elevation.

Okay here is a screen capture of the STK analysis. Sorry the elevation plot has Azimuth and Range (STK cant plot elevation alone). Note the elevation is above 5 deg (as part of the constraint). The first time at access is: 27150s from Epoch.
The contact Durations are: 685, 319, 634, 561 seconds.

Here is my modified code to the TLE_Propagation.ipynb:

I am not the best programmer, please don’t judge my elevation > 5 access time algorithm (or do, I love criticism). In summary:
The first time at access is 26370s from Epoch
My contact durations are: 640, 510, 440, 660 seconds.

You can see that the elevation>5 peaks are near the same area, but have different maximums. It’s close but something is off.

`# Two Line Elements Propagation
## Authors
Petrus Hyvönen, SSC

## Learning Goals
* *Propagate an TLE*: Specify and read a TLE and propagate it with the orekit TLE propagator
* *Representation of ground station*: How to represent ground points
* *Elementary plotting in Python*: Simple ways to plot data

## Keywords
orekit, TLE, ground station

## Summary

This small tutorial is a first step to a real propagation of a satellite orbit
based on the common TLE format. The results are propagated in an inertial coordinate
system and then converted to a topocentric coordinate system, such as a ground station.

# Set up 
%matplotlib inline
import numpy as np
## Initialize Orekit 
Import orekit and set up the java virtual machine.
import orekit
vm = orekit.initVM()
Read the orekit-data file with basic paramters. This file is assumed to be in the current directory.
from orekit.pyhelpers import setup_orekit_curdir
setup_orekit_curdir()
Now we are set up to import and use objects from the orekit library.
from org.orekit.frames import FramesFactory, TopocentricFrame
from org.orekit.bodies import OneAxisEllipsoid, GeodeticPoint
from org.orekit.time import TimeScalesFactory, AbsoluteDate, DateComponents, TimeComponents
from org.orekit.utils import IERSConventions, Constants
from org.orekit.attitudes import AttitudeProvider, NadirPointing;
from org.orekit.propagation.analytical.tle import TLE, TLEPropagator
from math import radians, pi
import matplotlib.pyplot as plt
## Setting up the TLE 
Specify the two line elements that describes the orbit of the satellite as strings. Here these are specified directly, in a larger example these would likely be fetched from a file or from an on-line service.
#AQUA27424 14th Oct 2021
# tle_line1 = "1 27424U 02022A   21287.75000000  .00000236  00000-0  52447-4 0 00007"
# tle_line2 = "2 27424 098.2183 227.4060 0000564 048.7337 097.6708 14.57117062034446"

# 3 Nov 2021
tle_line1 = "1 27424U 02022A   21307.66666667  .00000236  00000-0  52390-4 0 00004"
tle_line2 = "2 27424 098.2183 247.0537 0000565 346.9863 173.2636 14.57126477037344"

# GPS BIIF-2  (PRN 01)    
# tle_line1 = "1 37753U 11036A   21307.14859678 -.00000068  00000-0  00000-0 0  9992"
# tle_line2 = "2 37753  56.4944  37.5291 0109002  50.1825 311.3781  2.00563888 75443"



The orekit TLE object parses the two line strings. [See TLE Doc](https://www.orekit.org/static/apidocs/org/orekit/propagation/analytical/tle/TLE.html) The Epoch is the timestamp where the two line elements are referenced to.
mytle = TLE(tle_line1,tle_line2)

print (mytle)
print ('Epoch :',mytle.getDate())

Epoch : 2021-11-03T16:00:00.000

## Preparing the Coordinate systems 
Orekit supports several coordinate systems, for this example the International Terrestrial Reference Frame (ITRF) is used for the planet. A slightly elliptical body is created for the Earth shape, according to the WGS84 model.
ITRF = FramesFactory.getITRF(IERSConventions.IERS_2010, True)
earth = OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS, 
                         Constants.WGS84_EARTH_FLATTENING, 
                         ITRF)
## Define the station
The location of the station is defined, and a [TopocentricFrame](https://www.orekit.org/site-orekit-10.1/apidocs/org/orekit/frames/TopocentricFrame.html) specific for this location is created.

This frame is based on a position near the surface of a body shape. The origin of the frame is at the defining geodetic point location, and the right-handed canonical trihedra is:

- X axis in the local horizontal plane (normal to zenith direction) and following the local parallel towards East
- Y axis in the horizontal plane (normal to zenith direction) and following the local meridian towards North
- Z axis towards Zenith direction
longitude = radians(27.5207)
latitude  = radians(-157.996)
altitude  = 0.402*1000 #m
station = GeodeticPoint(latitude, longitude, altitude)
station_frame = TopocentricFrame(earth, station, "Esrange")
For the propagation a Earth centered inertial coordinate system is used, the EME2000. This frame is commonly also called J2000. This is a commonly used frame centered at the Earth, and fixed towards reference stars (not rotating with the Earth).
inertialFrame = FramesFactory.getEME2000()

# satelliteFrame = FramesFactory.getGCRF()
# attitudeProvider = NadirPointing(satelliteFrame, earth)
# propagator = TLEPropagator.selectExtrapolator(mytle,attitudeProvider,3117,FramesFactory.getTEME())

propagator = TLEPropagator.selectExtrapolator(mytle)
Set the start and end date that is then used for the propagation.
#14th october 2021
# extrapDate = AbsoluteDate(2021, 10, 14, 18, 0, 0.0, TimeScalesFactory.getUTC())
# finalDate = extrapDate.shiftedBy(60.0*60*24) #seconds

# 3 Nov 2021 16:00:00.000 UTCG
extrapDate = AbsoluteDate(2021, 11, 3, 16, 0, 0.0, TimeScalesFactory.getUTC())

#  Epoch : 2021-11-03T03:33:58.762
# extrapDate = AbsoluteDate(2021, 11, 3, 3, 33, 58.762, TimeScalesFactory.getUTC())

finalDate = extrapDate.shiftedBy(60.0*60*24) #seconds

print(extrapDate)
print(finalDate)
el=[]
pos=[]
contactTime = []
index=0

endAccessTime = []
endAccessTimeIndex = []
epochTime = []
epochTime_tmp = 0.0
accessTime = []

stepsize_s = 10.0

while (extrapDate.compareTo(finalDate) <= 0.0):  
    pv = propagator.getPVCoordinates(extrapDate, inertialFrame)
    pos_tmp = pv.getPosition()
    pos.append((pos_tmp.getX(),pos_tmp.getY(),pos_tmp.getZ()))
    
    el_tmp = station_frame.getElevation(pv.getPosition(),
                    inertialFrame,   
                    extrapDate)*180.0/pi
    el.append(el_tmp)
    extrapDate = extrapDate.shiftedBy(stepsize_s)

    epochTime_tmp += stepsize_s
    epochTime.append(epochTime_tmp)
      

    if el_tmp > 5:
        
        accessTime.append(epochTime_tmp)
        
        if accessTime[index] - accessTime[index-1] > stepsize_s:
            # endAccessTime.append(epochTime_tmp)
            endAccessTimeIndex.append(index-1)
        

        index += 1
print(len(endAccessTimeIndex))

for ii in range(len(endAccessTimeIndex)+2):
    if len(endAccessTimeIndex) != 0:
        if ii == 0:
            contactTime.append(accessTime[endAccessTimeIndex[ii]]-accessTime[0])
        elif ii == len(endAccessTimeIndex)+1:
            contactTime.append(accessTime[-1]-accessTime[endAccessTimeIndex[ii-2]+1])

        elif ii < 3:
            # print(ii)
            # print(accessTime[endAccessTimeIndex[ii]]-accessTime[endAccessTimeIndex[ii-1]+1])
            contactTime.append(accessTime[endAccessTimeIndex[ii]]-accessTime[endAccessTimeIndex[ii-1]+1])
    else:
        contactTime=accessTime[-1]-accessTime[0]


## Plot Results
Plot some of the results from the propagation. Note that in the plots below the x-axis are samples and not proper time.
for ii in range(len(contactTime)):
    print("The Contact Time for Access #{} is {} s.".format(ii,contactTime[ii]))
print("The total contact time is {} s.".format(sum(contactTime)))
print(accessTime[0])


The Contact Time for Access #0 is 640.0 s.
The Contact Time for Access #1 is 510.0 s.
The Contact Time for Access #2 is 440.0 s.
The Contact Time for Access #3 is 660.0 s.
The total contact time is 2250.0.
[26370.0]

# print(max(el))

plt.plot(el)
plt.ylim(5,90)
plt.title('Elevation')
plt.grid(True)
plt.plot(pos); 
plt.title('Inertial position');




`

The values for longitude and latitude seem reversed to me.
The inertial frame is wrong, TLE propagation uses TEME only and not EME2000 (this is mandated by the SGP4/SDP4 model). You can retrieve it using propagator.getFrame().

Also instead of using a loop with a 10 seconds steps, there are builtins events detection in Orekit to find accurately access time using ElevationDetector. There is also an ElevationExtremumDetector to get the maximum/minimum elevation (beware that it doesn’t care about the satellite is visible or not, in other words, if the max elevation is -10 degrees and the satellite does not raise above horizon, this detector will happily notify about this -10 degrees maximum).

Here is a code snippet (in Java) that I used to compute AOS/LOS and max elevation in your case (I have added an if statement to avoid printing minimum elevation or maximum elevation below horizon).

    Propagator propagator =
                    TLEPropagator.selectExtrapolator(new TLE("1 27424U 02022A   21307.66666667  .00000236  00000-0  52390-4 0 00004",
                                                             "2 27424 098.2183 247.0537 0000565 346.9863 173.2636 14.57126477037344"));

    final TopocentricFrame hawaii = new TopocentricFrame(new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
                                                                              Constants.WGS84_EARTH_FLATTENING,
                                                                      FramesFactory.getITRF(IERSConventions.IERS_2010, true)),
                                                         new GeodeticPoint(FastMath.toRadians(27.5207),
                                                                           FastMath.toRadians(-157.996),
                                                                           402),
                    "Hawaii");
    propagator.addEventDetector(new ElevationDetector(hawaii).
                                withConstantElevation(FastMath.toRadians(5.0)).
                                withMaxCheck(10).
                                withThreshold(1.0e-6).
                                withHandler((state, detector, increasing) -> {
                                    double az = hawaii.getAzimuth(state.getPVCoordinates().getPosition(),
                                                                  state.getFrame(),
                                                                  state.getDate());
                                    double el = hawaii.getElevation(state.getPVCoordinates().getPosition(),
                                                                    state.getFrame(),
                                                                    state.getDate());
                                    System.out.format(Locale.US, "%s %s %8.3f azimuth %8.3f elevation%n",
                                                      state.getDate(),
                                                      increasing ? "AOS" : "LOS",
                                                      FastMath.toDegrees(az),
                                                      FastMath.toDegrees(el));
                                    return Action.CONTINUE;
                                }));
    propagator.addEventDetector(new ElevationExtremumDetector(hawaii).
                                withMaxCheck(10).
                                withThreshold(1.0e-6).
                                withHandler((state, detector, increasing) -> {
                                    double az = hawaii.getAzimuth(state.getPVCoordinates().getPosition(),
                                                                  state.getFrame(),
                                                                  state.getDate());
                                    double el = hawaii.getElevation(state.getPVCoordinates().getPosition(),
                                                                    state.getFrame(),
                                                                    state.getDate());
                                    if (el > 0 && !increasing) {
                                    System.out.format(Locale.US, "%s %s %8.3f azimuth %8.3f elevation%n",
                                                      state.getDate(),
                                                      "max",
                                                      FastMath.toDegrees(az),
                                                      FastMath.toDegrees(el));
                                    }
                                    return Action.CONTINUE;
                                }));
    propagator.propagate(propagator.getInitialState().getDate().shiftedBy(Constants.JULIAN_DAY));

And here are the outputs I get:

2021-11-03T22:01:25.3569976565963Z max   63.012 azimuth    2.702 elevation
2021-11-03T23:32:30.60807440875259Z AOS  158.421 azimuth    5.000 elevation
2021-11-03T23:38:12.26524566086642Z max   75.881 azimuth   64.511 elevation
2021-11-03T23:43:55.64607641178647Z LOS  353.666 azimuth    5.000 elevation
2021-11-04T01:13:32.35773494114144Z AOS  238.900 azimuth    5.000 elevation
2021-11-04T01:16:11.70008915333878Z max  266.940 azimuth    7.924 elevation
2021-11-04T01:18:51.74122360404815Z LOS  295.032 azimuth    5.000 elevation
2021-11-04T11:39:31.40094139323974Z AOS   30.984 azimuth    5.000 elevation
2021-11-04T11:44:49.79226691338459Z max   98.135 azimuth   29.783 elevation
2021-11-04T11:50:05.98247890673792Z LOS  165.066 azimuth    5.000 elevation
2021-11-04T13:17:36.73031889279732Z AOS  344.374 azimuth    5.000 elevation
2021-11-04T13:22:17.49462556985657Z max  289.707 azimuth   19.467 elevation
2021-11-04T13:26:57.91398095657832Z LOS  234.794 azimuth    5.000 elevation

Computing time differences with respect to epoch and between AOS and LOS, these results show first access at 27150.608s after epoch and access durations of 685.038s, 319.384s, 634.581s and 561.184s, which is consistent with STK results.

Most probably the EME2000/TEME frame msimatch is the culprit (and I don’t understand how you could get results with a latitude at -157 degrees :wink:

1 Like

I CANNOT THANK YOU ENOUGH! It simply was the lat/long. I will try to implement the elevation detectors now so that I can get the full duration time. Amazing response time! Luc thank you! I’ve had so much fun working with this program, can’t wait to keep exploring.

I was curious what you meant by the propagator coordinate frame. I did propagator.getFrame() and it was TEME.

My bad.
In your code, I saw a line inertialFrame = FramesFactory.getEME2000() and later el_tmp = station_frame.getElevation(pv.getPosition(), inertialFrame, extrapDate)*180.0/pi, so I assumed you just got the coordinates from the propagated state which would be something like state.getPVCoordinates())) and assuming it was EME2000. But in fact you did it properly and used propagator.getPVCoordinates(extrapDate, inertialFrame) (you could also have done state.getPVCoordinates(inertialFrame), which would also be correct), i.e. you asked the propagator to give you the coordinates in a frame you prescribed. Internally, the SGP4/SDP4 propagators always use TEME, so it did the frame transform by itself before returning the coordinates to you.

1 Like

I’m struggling to understand the Java portion for event handling, particularly this portion:

  withHandler((state, detector, increasing) -> {
                                    double az = hawaii.getAzimuth(state.getPVCoordinates().getPosition(),
                                                                  state.getFrame(),
                                                                  state.getDate());
                                    double el = hawaii.getElevation(state.getPVCoordinates().getPosition(),
                                                                    state.getFrame(),
                                                                    state.getDate());
                                    System.out.format(Locale.US, "%s %s %8.3f azimuth %8.3f elevation%n",
                                                      state.getDate(),
                                                      increasing ? "AOS" : "LOS",
                                                      FastMath.toDegrees(az),
                                                      FastMath.toDegrees(el));
                                    return Action.CONTINUE;
# To add a new cell, type '# %%'
# To add a new markdown cell, type '# %% [markdown]'
# %%
from IPython import get_ipython

# %% [markdown]
# # Event Detectors in Orekit
# %% [markdown]
# ## Authors
# 
# Lots of parts are directly from the orekit documentation on [propagation](https://www.orekit.org/site-orekit-10.1/architecture/propagation.html), with some updates, simplifications and Pythonification by Petrus Hyvönen, SSC
# 
# ## Learning Goals
# * *What are Event Detectors*: Why are these useful
# * *How do I use Event Detectors*: How is it implemented in Orekit and Python
# 
# ## Keywords
# orekit, propagation, event detectors

# %%
get_ipython().run_line_magic('matplotlib', 'inline')

# %% [markdown]
# Initialize orkit and bring up the python-java interface

# %%
import orekit
vm = orekit.initVM()

# %% [markdown]
# Now set up the pointer to the orekit-data.zip file, using one of the helper files. The file should be in current directory if not specified otherwise.

# %%
from orekit.pyhelpers import setup_orekit_curdir, absolutedate_to_datetime
setup_orekit_curdir()

# %% [markdown]
# Now we are set up to import and use objects from the orekit library. Packages can be imported as they were native Python packages
# %% [markdown]
# # Event Detectors
# 
# _Before starting this introduction, please make sure you have refreshed the tutorials on Orbit Definition and Propagation._
# %% [markdown]
# The propagators in Orekit is part of an architecture that supports detecting certain discrete conditions that occur during the propagation. This can be that a spacecraft enters eclipse, becomes visible from a ground station, crosses the perigee or a number of other interesting things that may occur during the orbit.
# 
# This feature is activated by registering EventDetectors to the propagator. All proppagators in Orekit supports the EventDetector mechanism.
# %% [markdown]
# Users can define their own EventDetectors but there are also several predefined EventDetectors
# already available, amongst which :
# 
# - a simple DateDetector, which is simply triggered at a predefined date, and can be reset to add new dates on the run (which is useful to set up delays starting when a previous event is been detected)
# 
# - an ElevationDetector, which is triggered at raising or setting time of a satellite with respect to a ground point, taking atmospheric refraction into account and either constant elevation or ground mask when threshold elevation is azimuth-dependent
# 
# - an ElevationExtremumDetector, which is triggered at maximum (or minimum) satellite elevation with respect to a ground point
# 
# - an AltitudeDetector which is triggered when satellite crosses a predefined altitude limit and can be used to compute easily operational forecasts
# 
# - a FieldOfViewDetector which is triggered when some target enters or exits a satellite sensor Field Of View (any shape),
# - a CircularFieldOfViewDetector which is triggered when some target enters or exits a satellite sensor Field Of View (circular shape),
# - a FootprintOverlapDetector which is triggered when a sensor Field Of View (any shape, even split in non-connected parts or containing holes) overlaps a geographic zone, which can be non-convex, split in different sub-zones, have holes, contain the pole,
# - a GeographicZoneDetector, which is triggered when the spacecraft enters or leave a zone, which can be non-convex, split in different sub-zones, have holes, contain the pole,
# - a GroundFieldOfViewDetector, which is triggered when the spacecraft enters or leave a ground based Field Of View, which can be non-convex, split in different sub-zones, have holes,
# - an EclipseDetector, which is triggered when some body enters or exits the umbra or the penumbra of another occulting body,
# - an ApsideDetector, which is triggered at apogee and perigee,
# - a NodeDetector, which is triggered at ascending and descending nodes,
# - a PositionAngleDetector, which is triggered when satellite angle on orbit crosses some value (works with either anomaly, latitude argument or longitude argument and with either true, eccentric or mean angles),
# - LatitudeCrossingDetector, LatitudeExtremumDetector, LongitudeCrossingDetector, LongitudeExtremumDetector, which are triggered when satellite position with respect to central body reaches some predefined values,
# - an AlignmentDetector, which is triggered when satellite and some body projected in the orbital plane have a specified angular separation (the term AlignmentDetector is clearly a misnomer as the angular separation may be non-zero),
# - an AngularSeparationDetector, which is triggered when angular separation between satellite and some beacon as seen by an observer goes below a threshold. The beacon is typically the Sun, the observer is typically a ground station
# - An EventShifter is also provided in order to slightly shift the events occurrences times. A typical use case is for handling operational delays before or after some physical event really occurs.
# 
# An EventSlopeFilter is provided when user is only interested in one kind of events that occurs in pairs like raising in the raising/setting pair for elevation detector, or eclipse entry in the entry/exit pair for eclipse detector. The filter does not simply ignore events after they have been detected, it filters them before they are located and hence save some computation time by not doing an accurate search for events that will ultimately be ignored.
# 
# An EventEnablingPredicateFilter is provided when user wants to filter out some events based on an external condition set up by a user-provided enabling predicate function. This allow for example to dynamically turn some events on and off during propagation or to set up some elaborate logic like triggering on elevation first time derivative (i.e. one elevation maximum) but only when elevation itself is above some threshold. 
# 
# A BooleanDetector is provided to combine several other detectors with boolean operators and, or and not. This allows for example to detect when a satellite is both visible from a ground station and out of eclipse.

# %%
from org.orekit.orbits import KeplerianOrbit, PositionAngle
from org.orekit.propagation.analytical import KeplerianPropagator
from org.orekit.time import AbsoluteDate, TimeScalesFactory
from org.orekit.utils import Constants, IERSConventions
from org.orekit.frames import FramesFactory
from org.orekit.bodies import OneAxisEllipsoid, CelestialBodyFactory


# %%
from math import radians, degrees
import pandas as pd


# %%
utc = TimeScalesFactory.getUTC()

# %% [markdown]
# Let us do a small example, based on the orbit we used in the Propagation tutorial.

# %%
ra = 500 * 1000         #  Apogee
rp = 400 * 1000         #  Perigee
i = radians(87.0)      # inclination
omega = radians(20.0)   # perigee argument
raan = radians(10.0)  # right ascension of ascending node
lv = radians(0.0)    # True anomaly

epochDate = AbsoluteDate(2020, 1, 1, 0, 0, 00.000, utc)
initial_date = epochDate

a = (rp + ra + 2 * Constants.WGS84_EARTH_EQUATORIAL_RADIUS) / 2.0    
e = 1.0 - (rp + Constants.WGS84_EARTH_EQUATORIAL_RADIUS) / a

## Inertial frame where the satellite is defined
inertialFrame = FramesFactory.getEME2000()

## Orbit construction as Keplerian
initialOrbit = KeplerianOrbit(a, e, i, omega, raan, lv,
                              PositionAngle.TRUE,
                              inertialFrame, epochDate, Constants.WGS84_EARTH_MU)
initialOrbit


# %%
propagator = KeplerianPropagator(initialOrbit)

# %% [markdown]
# ## Adding Event Detectors
# %% [markdown]
# In the first example we create an EventDetector for eclipse, when the satellite is not illuminated by the Sun, and is in the full shadow of the Earth.

# %%
ITRF = FramesFactory.getITRF(IERSConventions.IERS_2010, True)
earth = OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS, 
                         Constants.WGS84_EARTH_FLATTENING, 
                         ITRF)


# %%
sun = CelestialBodyFactory.getSun()
sunRadius = 696000000.0


# %%
from org.orekit.propagation.events import EclipseDetector, EventsLogger
from org.orekit.propagation.events.handlers import ContinueOnEvent

# %% [markdown]
# The EclipseDetector class is documented at the [Orekit API](https://www.orekit.org/site-orekit-latest/apidocs/org/orekit/propagation/events/EclipseDetector.html) and will detect entering and leaving the full shadow (Umbra) or when some part of the Sun is covered by Earth (Penumbra). 
# 
# In the detector, we can also set which EventHandler that we want to use, we can also write our own. In this case we use an EventHandler that will just let the propagator continue after an event has been detected.

# %%
eclipse_detector = EclipseDetector(sun, sunRadius, earth).withUmbra().withHandler(ContinueOnEvent())

# %% [markdown]
# There are several ways to collect these events when they happen, one of the ways is to use an Orekit EventsLogger that will store the events during the propagation.

# %%
logger = EventsLogger()
logged_detector = logger.monitorDetector(eclipse_detector)

# %% [markdown]
# We add the eclipse detector, together with the eventslogger to our propagator.

# %%
propagator.addEventDetector(logged_detector)

# %% [markdown]
# The propagation is executed in same way as in previous examples.

# %%
state = propagator.propagate(initial_date, initial_date.shiftedBy(3600.0 * 24))
state.getDate().shiftedBy(Constants.JULIAN_DAY)

# %% [markdown]
# 
# %% [markdown]
# Now we can fetch the events that the logger found.

# %%
events = logger.getLoggedEvents()
events.size()

# %% [markdown]
# This is a code snippet that goes through the events and store them in a Pandas DataFrame that is very useful for handling tables in Python. In this, the dates are converted also to Python DateTime objects. Please note that if any further use of the data in Orekit is to be done, it is advisable also to save the Orekit AbsoluteDate.

# %%
start_time = None
result = pd.DataFrame()

for event in logger.getLoggedEvents():
   
    if not event.isIncreasing():
        start_time = event.getState().getDate()
    elif start_time:
        stop_time = event.getState().getDate()
        result = result.append({"Start":absolutedate_to_datetime(start_time), 
                           "Stop":absolutedate_to_datetime(stop_time), 
                            "EclipseDuration": stop_time.durationFrom(start_time)/60},
                          ignore_index=True)
        start_time = None
result

# %% [markdown]
# # Exercise 1
# %% [markdown]
# In this case you are to calculate the ground station visibilities for the above orbit, with a minimum elevation of 5 degrees above the horizon. The detector to use is the [ElevationDetector](https://www.orekit.org/site-orekit-latest/apidocs/org/orekit/propagation/events/ElevationDetector.html). Create a similar pandas table as above of the start / stop time of the visibilities.

# %%


# %% [markdown]
# # Exercise 2
# %% [markdown]
# The satellite you are simulating does not have any batteries onboard, so it needs to be fully in sunlight to operate. Perform a calculation of when your ground station has visibility to the spacecraft, and that the spacecraft is in full sunlight. 
# 
# Hint: Check the BooleanDetector, but there are other ways to do this too!
# 

# %%


Looking at the Event_detectors.ipynb, I was able to replicate getting the “logged events”. This made sense to me, whenever the event occurred, it would store it in the logged events.

Looking at your Java (which I have no experience with)
From what I understand the Handler allows you to output the event as it occurs. This is what the System.out I believe is. I am not sure what return Action.continue does, or what the " (state, detector, increasing) → " mapping does.

I got this to work for the ElevationDetector. But if I wanted to use the ElevationExtremumDetector on the same propagator.addEventDetector().It didn’t log ALL the events, it overrides it. Seems like I would need to use Handlers since you cannot log multiple EventDetectors. In essence, is there a good Python example to output multiple Detectors under one propagator.addEventDetector call?

Thank you in advanced.

I am by no way a Python expert, but I think you could do something like:

  logger = EventsLogger()

  elevation_detector = ElevationDetector(...)
  logged_elevation_detector = logger.monitorDetector(elevation_detector)
  propagator.addEventDetector(logged_elevation_detector)

  max_detector = ElevationExtremumDetector(...)
  logged_max_detector = logger.monitorDetector(max_detector)
  propagator.addEventDetector(logged_max_detector)

The logger should log all events that occur, even if they come from different detectors. You would have to sort out by yourself the type of each event.

The (state, detector, increasing) -> { ... some code ...} mapping is a lambda function that takes three arguments. The withHandler method expects an object that implements the EventHandler interface. This lambda function will be the implementation of the eventOccurred method from this interface. So each time the propagator precisely locates the event, it calls eventOccurred with the spacecraft state at event time, the detector that corresponds to the event, and a boolean indicating the crossing direction (for ElevationDetector, increasing is set to true for raising events and to false for setting events, for ElevationExtremumDetector, increasing is set to true for minimum elevation and set to false for maximum elevation, because the function that defines the event is the time derivative of the elevation). It then waits for the function to return, and looks at its return value to know what to do next. The eventOccurred method could return Action.CONTINUE to tell the propagator it can continue propagating further (this is what you want), or it could return Action.STOP to tell the propagator it should stop right now (this is useful , for example for event detectors that would monitor atmospheric reentry at some threshold altitude), or ti could return Action.RESET_STATE or Action.RESET_DERIVATIVES when the event would change some part of the state (this is useful for detectors associated with maneuvers start and stop).

Here, I just use the handlers to output messages when event are detected. If you just log the events and process the events list by yourself afterwards, you can just have an event handler that only returns Action.CONTINUE. There is a predefined class that just does that: ContinueOnEvent, which is in the org.orekit.propagation.events.handlers package.

Again, thank you Luc! I was able to get it to work through trial and error. Something else came up:

From what I understand, the the TLEPropagator takes the current TLE and you can propagate it to a final time. What if I wanted the start time to be something else, maybe some time in the future. Of course the most accurate propagation would be the TLE parameters @ Epoch to a final time.

What would happen if I modify the colums 19-32 in the TLE to a new initial date and propagate that. In essence, taking the original TLE with a new start date. Would this have horrible consequences?

yes

You can call propagate(start, stop), selecti,ng the start and stop dates at will, even representing a backward propagation if you want.

The TLE has a checksum at the end of the line, so you would have to modify that too. In addtion, you shjould have to change the angular parameter corresponding to the new position. chances are that it would not match the SGP4/SDP4 evolution.