From 280120961d6015e5c12c854f04acc04dcdf75dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20G=C3=B3mez?= Date: Fri, 7 Jul 2023 14:06:42 +0200 Subject: [PATCH 1/4] Added option use TLEs, some minor refactoring --- paseos/actors/actor_builder.py | 46 +++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/paseos/actors/actor_builder.py b/paseos/actors/actor_builder.py index c56f5d2b..afd265b9 100644 --- a/paseos/actors/actor_builder.py +++ b/paseos/actors/actor_builder.py @@ -16,18 +16,19 @@ class ActorBuilder: """This class is used to construct actors.""" - def __new__(self): - if not hasattr(self, "instance"): - self.instance = super(ActorBuilder, self).__new__(self) + def __new__(cls): + if not hasattr(cls, "instance"): + cls.instance = super(ActorBuilder, cls).__new__(cls) else: logger.debug( "Tried to create another instance of ActorBuilder. Keeping original one..." ) - return self.instance + return cls.instance def __init__(self): logger.trace("Initializing ActorBuilder") + @staticmethod def get_actor_scaffold(name: str, actor_type: object, epoch: pk.epoch): """Initiates an actor with minimal properties. @@ -50,6 +51,7 @@ def get_actor_scaffold(name: str, actor_type: object, epoch: pk.epoch): return actor_type(name, epoch) + @staticmethod def set_ground_station_location( actor: GroundstationActor, latitude: float, @@ -81,6 +83,36 @@ def set_ground_station_location( ) actor._minimum_altitude_angle = minimum_altitude_angle + @staticmethod + def set_TLE( + actor: SpacecraftActor, + line1: str, + line2: str, + ): + """Define the orbit of the actor using a TLE. For more information on TLEs see + https://en.wikipedia.org/wiki/Two-line_element_set . + + TLEs can be obtained from https://www.space-track.org/ or https://celestrak.com/NORAD/elements/ + + Args: + actor (SpacecraftActor): Actor to update. + line1 (str): First line of the TLE. + line2 (str): Second line of the TLE. + + Raises: + RuntimeError: If the TLE could not be read. + """ + try: + actor._orbital_parameters = pk.planet.tle(line1, line2) + # TLE only works around Earth + actor._central_body = pk.planet.jpl_lp("earth") + except RuntimeError: + logger.error("Error reading TLE \n", line1, "\n", line2) + raise RuntimeError("Error reading TLE") + + logger.debug(f"Added TLE to actor {actor}") + + @staticmethod def set_orbit( actor: SpacecraftActor, position, @@ -115,6 +147,7 @@ def set_orbit( logger.debug(f"Added orbit to actor {actor}") + @staticmethod def set_position(actor: BaseActor, position: list): """Sets the actors position. Use this if you do not want the actor to have a keplerian orbit around a central body. @@ -133,6 +166,7 @@ def set_position(actor: BaseActor, position: list): actor._position = position logger.debug(f"Setting position {position} on actor {actor}") + @staticmethod def set_power_devices( actor: SpacecraftActor, battery_level_in_Ws: float, @@ -182,6 +216,7 @@ def set_power_devices( + f"ChargingRate={charging_rate_in_W}W to actor {actor}" ) + @staticmethod def set_radiation_model( actor: SpacecraftActor, data_corruption_events_per_s: float, @@ -215,6 +250,7 @@ def set_radiation_model( ) logger.debug(f"Added radiation model to actor {actor}.") + @staticmethod def set_thermal_model( actor: SpacecraftActor, actor_mass: float, @@ -310,6 +346,7 @@ def set_thermal_model( power_consumption_to_heat_ratio=power_consumption_to_heat_ratio, ) + @staticmethod def add_comm_device(actor: BaseActor, device_name: str, bandwidth_in_kbps: float): """Creates a communication device. @@ -327,6 +364,7 @@ def add_comm_device(actor: BaseActor, device_name: str, bandwidth_in_kbps: float logger.debug(f"Added comm device with bandwith={bandwidth_in_kbps} kbps to actor {actor}.") + @staticmethod def add_custom_property( actor: BaseActor, property_name: str, initial_value: Any, update_function: Callable ): From b57efb6496cd6a03fe86904967d0970f9a537e46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20G=C3=B3mez?= Date: Fri, 7 Jul 2023 14:06:56 +0200 Subject: [PATCH 2/4] Added test for TLEs, checking SGP4 use --- paseos/tests/actor_builder_test.py | 44 +++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/paseos/tests/actor_builder_test.py b/paseos/tests/actor_builder_test.py index b5160922..7dafab5b 100644 --- a/paseos/tests/actor_builder_test.py +++ b/paseos/tests/actor_builder_test.py @@ -5,11 +5,53 @@ sys.path.append("../..") -from paseos import ActorBuilder +from paseos import ActorBuilder, SpacecraftActor from test_utils import get_default_instance +def test_set_TLE(): + """Check if we can set a TLE correctly""" + + _, sentinel2a, earth = get_default_instance() + # Set the TLE + line1 = "1 40697U 15028A 23188.15862373 .00000171 00000+0 81941-4 0 9994" + line2 = "2 40697 98.5695 262.3977 0001349 91.8221 268.3116 14.30817084419867" + ActorBuilder.set_TLE(sentinel2a, line1, line2) + + # Check that get_altitude returns a sensible value + earth_radius = 6371000 + assert sentinel2a.get_altitude() > earth_radius + 780000 + assert sentinel2a.get_altitude() < earth_radius + 820000 + + # Check that get_position_velocity returns sensible values + position, velocity = sentinel2a.get_position_velocity(sentinel2a.local_time) + assert position is not None + assert velocity is not None + + # Create an actor with a keplerian orbit and check that the position and velocity + # diverge over time + s2a_kep = ActorBuilder.get_actor_scaffold("s2a_kep", SpacecraftActor, sentinel2a.local_time) + ActorBuilder.set_orbit(s2a_kep, position, velocity, sentinel2a.local_time, earth) + + # After some orbits the differences should be significant + # since the TLE uses SGP4 and the other actor uses Keplerian elements + t0_later = pk.epoch(sentinel2a.local_time.mjd2000 + 1) + r, v = sentinel2a.get_position_velocity(t0_later) + r_kep, v_kep = s2a_kep.get_position_velocity(t0_later) + print("r,v SGP4 after 1 day") + print(r) + print(v) + print("r,v Kep after 1 day") + print(r_kep) + print(v_kep) + print("Differences in r and v") + print(np.linalg.norm(np.array(r) - np.array(r_kep))) + print(np.linalg.norm(np.array(v) - np.array(v_kep))) + assert np.linalg.norm(np.array(r) - np.array(r_kep)) > 100000 + assert np.linalg.norm(np.array(v) - np.array(v_kep)) > 400 + + def test_set_orbit(): """Check if we can specify an orbit correctly""" _, sat1, earth = get_default_instance() From 2c6361ba710bf71641badfe8f58eecbcf47e5858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20G=C3=B3mez?= Date: Fri, 7 Jul 2023 14:16:28 +0200 Subject: [PATCH 3/4] Added readme entry on SGP4 / TLE and updated docs --- README.md | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 26f90c25..bd33f7a7 100644 --- a/README.md +++ b/README.md @@ -244,7 +244,11 @@ sim.add_known_actor(grndStation) #### Set an orbit for a PASEOS SpacecraftActor -Once you have defined a [SpacecraftActor](#spacecraftactor), you can assign a [Keplerian orbit](https://en.wikipedia.org/wiki/Kepler_orbit) to it. To this aim, you need to define the central body the [SpacecraftActor](#spacecraftactor) is orbiting around and specify its position and velocity (in the central body's [inertial frame](https://en.wikipedia.org/wiki/Inertial_frame_of_reference)) and an epoch. In this case, we will use `Earth` as a central body. +Once you have defined a [SpacecraftActor](#spacecraftactor), you can assign a [Keplerian orbit](https://en.wikipedia.org/wiki/Kepler_orbit) or use [SGP4 (Earth orbit only)](https://en.wikipedia.org/wiki/Simplified_perturbations_models). + +##### Keplerian Orbit + +To this aim, you need to define the central body the [SpacecraftActor](#spacecraftactor) is orbiting around and specify its position and velocity (in the central body's [inertial frame](https://en.wikipedia.org/wiki/Inertial_frame_of_reference)) and an epoch. In this case, we will use `Earth` as a central body. ```py import pykep as pk @@ -264,6 +268,37 @@ ActorBuilder.set_orbit(actor=sat_actor, epoch=pk.epoch(0), central_body=earth) ``` +##### SGP4 / Two-line element (TLE) + +For using SGP4 / [Two-line element (TLE)](https://en.wikipedia.org/wiki/Two-line_element_set) you need to specify the TLE of the [SpacecraftActor](#spacecraftactor). In this case, we will use the TLE of the [Sentinel-2A](https://en.wikipedia.org/wiki/Sentinel-2) satellite from [celestrak](https://celestrak.com/). + +```py +from paseos import ActorBuilder, SpacecraftActor +# Define an actor of type SpacecraftActor +sat_actor = ActorBuilder.get_actor_scaffold(name="Sentinel-2A", + actor_type=SpacecraftActor, + epoch=pk.epoch(0)) + +# Specify your TLE +line1 = "1 40697U 15028A 23188.15862373 .00000171 00000+0 81941-4 0 9994" +line2 = "2 40697 98.5695 262.3977 0001349 91.8221 268.3116 14.30817084419867" + +# Set the orbit of the actor +ActorBuilder.set_TLE(sat_actor, line1, line2) +``` + +##### Accessing the orbit +You can access the orbit of a [SpacecraftActor](#spacecraftactor) with + +```py +# Position, velocity and altitude can be accessed like this +t0 = pk.epoch("2022-06-16 00:00:00.000") # Define the time (epoch) +print(sat_actor.get_position(t0)) +print(sat_actor.get_position_velocity(t0)) +print(sat_actor.get_altitude(t0)) +``` + + #### How to add a communication device The following code snippet shows how to add a communication device to a [SpacecraftActors] (#spacecraftactor). A communication device is needed to model the communication between [SpacecraftActors] (#spacecraftactor) or a [SpacecraftActor](#spacecraftactor) and [GroundstationActor](#ground-stationactor). Currently, given the maximum transmission data rate of a communication device, PASEOS calculates the maximum data that can be transmitted by multiplying the transmission data rate by the length of the communication window. The latter is calculated by taking the period for which two actors are in line-of-sight into account. From 8b459f6c88de3190b9d51a2eed45435f2308964c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20G=C3=B3mez?= Date: Mon, 10 Jul 2023 16:34:54 +0200 Subject: [PATCH 4/4] Updated S2 notebook --- .../Sentinel2_example_notebook.ipynb | 72 ++++--------------- 1 file changed, 14 insertions(+), 58 deletions(-) diff --git a/examples/Sentinel_2_example_notebook/Sentinel2_example_notebook.ipynb b/examples/Sentinel_2_example_notebook/Sentinel2_example_notebook.ipynb index 20103021..52a24775 100644 --- a/examples/Sentinel_2_example_notebook/Sentinel2_example_notebook.ipynb +++ b/examples/Sentinel_2_example_notebook/Sentinel2_example_notebook.ipynb @@ -25,7 +25,6 @@ "import os\n", "sys.path.insert(1, os.path.join(\"..\",\"..\"))\n", "import pykep as pk\n", - "import numpy as np\n", "import paseos\n", "from paseos import ActorBuilder, SpacecraftActor\n", "from utils import s2pix_detector, acquire_data\n", @@ -55,8 +54,12 @@ "metadata": {}, "outputs": [], "source": [ + "#Define today as pykep epoch (27-10-22)\n", + "#please, refer to https://esa.github.io/pykep/documentation/core.html#pykep.epoch\n", + "today = pk.epoch_from_string('2022-10-27 12:00:00.000')\n", + "\n", "# Define local actor\n", - "S2B = ActorBuilder.get_actor_scaffold(name=\"Sentinel2-B\", actor_type=SpacecraftActor, epoch=pk.epoch(0))" + "S2B = ActorBuilder.get_actor_scaffold(name=\"Sentinel2-B\", actor_type=SpacecraftActor, epoch=today)" ] }, { @@ -65,30 +68,7 @@ "metadata": {}, "source": [ "#### 1.a) - Add an orbit for S2B\n", - "\n", - "Since **S2B** is orbiting around Earth, let's define `earth` as `pykep.planet` object. \n", - "\n", - "Internally, paseos uses pykep.planet objects to model gravitational acceleration and Keplerian orbits." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "30691b25", - "metadata": {}, - "outputs": [], - "source": [ - "# Define central body\n", - "earth = pk.planet.jpl_lp(\"earth\")" - ] - }, - { - "cell_type": "markdown", - "id": "013bf2e2", - "metadata": {}, - "source": [ - "To find realistic orbits for **S2B**, we can exploit `Two Line Elements (TLEs)` (Downloaded on 27-10-2022). This would allow finding their ephemerides at time = 27-10-2022 12:00:00.\n", - "Please, refer to [Two-line_element_set](https://en.wikipedia.org/wiki/Two-line_element_set) to know more about TLEs." + "We can make use of the [two-line element](https://en.wikipedia.org/wiki/Two-line_element_set) actor creation [inside PASEOS](https://github.com/aidotse/PASEOS/tree/allow_using_TLE#sgp4--two-line-element-tle) for set the orbit of the PASEOS actor (TLE Downloaded on 27-10-2022)." ] }, { @@ -98,38 +78,10 @@ "metadata": {}, "outputs": [], "source": [ - "#Define today as pykep epoch (27-10-22)\n", - "#please, refer to https://esa.github.io/pykep/documentation/core.html#pykep.epoch\n", - "today = pk.epoch_from_string('2022-10-27 12:00:00.000')\n", - "\n", "sentinel2B_line1 = \"1 42063U 17013A 22300.18652110 .00000099 00000+0 54271-4 0 9998\"\n", "sentinel2B_line2 = \"2 42063 98.5693 13.0364 0001083 104.3232 255.8080 14.30819357294601\"\n", - "sentinel2B = pk.planet.tle(sentinel2B_line1, sentinel2B_line2)\n", "\n", - "#Calculating S2B ephemerides.\n", - "sentinel2B_eph=sentinel2B.eph(today)\n" - ] - }, - { - "cell_type": "markdown", - "id": "8eab58f6", - "metadata": {}, - "source": [ - "Now we define the actor's orbit for **S2B**." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3567ebb8", - "metadata": {}, - "outputs": [], - "source": [ - "#Adding orbit around Earth based on previously calculated ephemerides\n", - "ActorBuilder.set_orbit(actor=S2B, \n", - " position=sentinel2B_eph[0], \n", - " velocity=sentinel2B_eph[1], \n", - " epoch=today, central_body=earth)" + "ActorBuilder.set_TLE(S2B, sentinel2B_line1, sentinel2B_line2)" ] }, { @@ -463,7 +415,9 @@ "source": [ "### 3.d) - Showing detected volcanic eruptions\n", "\n", - "The next plot will show an example of onboard coarse volcanic eruptions detection on some Sentinel-2 L1C tiles. The different eruptions will be surrounded a bounding box, and their coordinates will be printed to raise an alert." + "The next plot will show an example of onboard coarse volcanic eruptions detection on some Sentinel-2 L1C tiles. The different eruptions will be surrounded a bounding box, and their coordinates will be printed to raise an alert.\n", + "\n", + "The execution and rendering of the images may take a few minutes." ] }, { @@ -480,7 +434,9 @@ "cell_type": "code", "execution_count": null, "id": "85bbd3ac", - "metadata": {}, + "metadata": { + "scrolled": false + }, "outputs": [], "source": [ "fig, ax=plt.subplots(1,3,figsize=(10,4))\n", @@ -502,7 +458,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.10.6 ('paseos')", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" },