diff --git a/pyproject.toml b/pyproject.toml index 5241a12..e3571c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ classifiers = [ "Development Status :: 4 - Beta" ] dependencies = [ + "astropy", "geopandas >= 0.13.2", "joblib", "numba", diff --git a/src/tatc/schemas/orbit.py b/src/tatc/schemas/orbit.py index f9f80c1..3ee171e 100644 --- a/src/tatc/schemas/orbit.py +++ b/src/tatc/schemas/orbit.py @@ -11,6 +11,7 @@ from typing import List, Optional, Tuple, Union import numpy as np +from astropy.time import Time from pydantic import AfterValidator, BaseModel, Field from sgp4.api import Satrec, WGS72 from sgp4 import exporter @@ -1181,3 +1182,86 @@ def get_derived_orbit( eccentricity=self.eccentricity, perigee_argument=self.perigee_argument, ) + + +class GeosynchronousOrbit(CircularOrbit): + """ + Geosynchronous orbit defined by longitude to be observed. + Geostationary orbit can be specified with inclination equal to 0. + """ + + type: Literal["geosynchronous"] = Field( + "geosynchronous", description="Orbit type discriminator." + ) + altitude: float = Field(35786000, description="Mean altitude (meters).") + longitude: float = Field(0, description="Longitude (degrees).", ge=0, lt=360) + + def get_true_anomaly(self) -> float: + """ + Gets the true anomaly for the satellite over a given longitude at a given epoch. + + Returns: + float: True anomaly in degrees. + """ + # convert to astropy time + t = Time(self.epoch, scale="utc") + # get greenwich sidereal time in degrees + gst = t.sidereal_time("mean", "greenwich").deg + # return earth-centered inertial angle + return (self.longitude + gst) % 360 + + def get_derived_orbit( + self, delta_mean_anomaly: float, delta_raan: float + ) -> GeosynchronousOrbit: + """ + Gets a derived geosynchronous orbit with pertubations to the + mean anomaly (interpreted as a shift in observed longitude) and + right ascension of ascending node. + + Args: + delta_mean_anomaly (float): Delta mean anomaly (degrees). + delta_raan (float): Delta right ascension of ascending node (degrees). + + Returns: + GeosynchronousOrbit: the derived orbit + """ + true_anomaly = utils.mean_anomaly_to_true_anomaly( + np.mod(self.get_mean_anomaly() + delta_mean_anomaly, 360) + ) + longitude = np.mod(self.longitude + delta_mean_anomaly, 360) + raan = np.mod(self.right_ascension_ascending_node + delta_raan, 360) + return GeosynchronousOrbit( + altitude=self.altitude, + true_anomaly=true_anomaly, + epoch=self.epoch, + inclination=self.inclination, + right_ascension_ascending_node=raan, + longitude=longitude, + ) + + def to_tle(self, lazy_load: bool = None) -> TwoLineElements: + """ + Converts this orbit to a two line elements representation. + + Args: + lazy_load (bool): True, if this tle should be lazy-loaded. + + Returns: + TwoLineElements: the two line elements orbit + """ + if lazy_load is None: + lazy_load = config.rc.orbit_tle_lazy_load + if lazy_load: + tle = self.__dict__.get("tle") + else: + tle = None + if tle is None: + tle = CircularOrbit( + altitude=self.altitude, + inclination=self.inclination, + right_ascension_ascending_node=self.right_ascension_ascending_node, + true_anomaly=self.compute_true_anomaly(), + epoch=self.epoch, + ).to_tle() + self.__dict__["tle"] = tle + return tle diff --git a/src/tatc/utils.py b/src/tatc/utils.py index 04f2202..d8b0e9e 100644 --- a/src/tatc/utils.py +++ b/src/tatc/utils.py @@ -40,7 +40,7 @@ def mean_anomaly_to_true_anomaly(mean_anomaly: float, eccentricity: float = 0) - Args: mean_anomaly (float): The mean anomaly (degrees). - true_anomaly (float): The orbit eccentricity. + eccentricity (float): The orbit eccentricity. Returns: float: The true anomaly (degrees).