Ejemplo n.º 1
0
def teme2geodetic_spherical(x: float, y: float, z: float, t: datetime):
    """
    Converts TEME/ECI coords (x,y,z - expressed in km) to LLA (longitude, lattitude, altitude).
    This function assumes the Earth is completely round.

    The calculations here are based on T.S. Kelso's excellent paper "Orbital Coordinate Systems, Part III
    https://celestrak.com/columns/v02n03/.

    Parameters
    ==========
    x,y,z : float - coordates in TEME (True Equator Mean Equinoex) version of ECI (Earth Centered Intertial) coords system.
            This is the system that's produced by SGP4 models.
    t : datetime

    Returns
    =======

    lat, lon, alt - latitude, longitude (degrees), altitude (km)
    """

    jd, fr = jday(t.year, t.month, t.day, t.hour, t.minute, t.second)
    gmst = julianDateToGMST2(jd, fr)[0]

    lat = atan2(z, sqrt(x * x + y * y))  # phi
    lon = atan2(y, x) - gmst  # lambda-E
    lon = longitude_trunc(lon)
    alt = sqrt(x * x + y * y + z * z) - RE  # h

    # TODO: convert this to radians and use radians everywhere.
    return lat * RAD2DEG, lon * RAD2DEG, alt
Ejemplo n.º 2
0
def write_PV_to_file_test(tle_obj_vec: list[dict],
                          filepath: str = "sats-TLE-example.json") -> None:
    import json
    import datetime
    from sgp4.api import Satrec, jday

    json_posveldata = {}
    # all TLEs (w/ diff epochs) will be propagated to current time.

    now = datetime.datetime.now()

    jd, fr = jday(now.year, now.month, now.day, now.hour, now.minute,
                  now.seconds, 0)

    for item in tle_obj_vec:
        sat_name = item["NORAD_CAT_ID"]

        sat = Satrec.twoline2rv(item["TLE_LINE1"], item["TLE_LINE2"])
        e, r, v = sat.sgp4(jd, fr)
        r = [poskm * 1000 for poskm in r]
        v = [velkms * 1000 for velkms in v]
        json_posveldata[sat_name] = [r, v]

    # # tle_json_data = {item["NORAD_CAT_ID"]: Satrec.twoline2rv(item["TLE_LINE1"], item["TLE_LINE2"])
    #                     for item in tle_obj_vec }

    with open(filepath, "w") as file:
        json.dump(json_posveldata, file, indent=6)

    print(f"PV File written successfully at {filepath}.")
Ejemplo n.º 3
0
def _propagate(sat, dt):
    '''
    True equator mean equinox (TEME) position from `sgp4` at given time.

    Parameters
    ----------
    sat : `sgp4.io.Satellite` instance
        Satellite object filled from TLE
    dt : `~datetime.datetime`
        Time

    Returns
    -------
    xs, ys, zs : float
        TEME (=True equator mean equinox) position of satellite [km]
    '''

    # pos [km], vel [km/s]

    try:
        from sgp4.api import jday, SGP4_ERRORS
    except ImportError:
        raise ImportError(
            'The "sgp4" package (version 2+) is necessary to use this '
            'function.')

    jd, fr = jday(dt.year, dt.month, dt.day, dt.hour, dt.minute,
                  dt.second + dt.microsecond / 1e6)
    err_code, position, velocity = sat.sgp4(jd, fr)

    if err_code:
        raise ValueError('Satellite propagation error', err_code,
                         '({})'.format(SGP4_ERRORS[err_code]))

    return position
Ejemplo n.º 4
0
def jday_from_epochs(epochs):
    jd_l, fr_l = [], []
    for epoch in epochs:
        jd, fr = jday(*datetime_components(epoch))
        jd_l.append(jd)
        fr_l.append(fr)

    return np.array(jd_l), np.array(fr_l)
Ejemplo n.º 5
0
def pass_countries(tle):
    print(tle)
    tle = tle.split('\n')
    sat = Satrec.twoline2rv(tle[1], tle[2])
    jd, fr = jday(2019, 12, 9, 12, 0, 0)
    e, r, v = sat.sgp4(2458827, 0.362605)
    start_time = datetime.datetime.now()
    jds, frs = [], []
    times = []
    for i in range(60 * 60 * 6):
        time = start_time + datetime.timedelta(seconds=i)
        times.append(time)
        jd, fr = jday(time.year, time.month, time.day, time.hour, time.minute,
                      time.second)
        jds.append(jd)
        frs.append(fr)
    jds = np.array(jds)
    frs = np.array(frs)
    e, r, v = sat.sgp4_array(jds, frs)
    R = 6371
    lats, lons = [], []
    last_country_id = -1
    pass_country = []
    for i, p in enumerate(r):
        x, y, z = p[0], p[1], p[2]
        lat = np.degrees(np.arctan(z / (x * x + y * y)))
        lon = np.degrees(np.arctan2(y, x))
        country_id = pos2idx(lat, lon)
        if country_id != last_country_id:
            pass_country.append({
                'country':
                country_id,
                'start_time':
                times[i].strftime('%Y-%m-%d %H:%M:%S'),
                'end_time':
                ''
            })
        last_country_id = country_id
    for i in range(len(pass_country) - 1):
        pass_country[i]['end_time'] = pass_country[i + 1]['start_time']
    pass_country = list(
        filter(lambda x: x['country'] != -1, pass_country[1:-2]))
    for i in range(len(pass_country)):
        pass_country[i]['country'] = name[pass_country[i]['country']]
        pass_country[i]['sat'] = tle[0]
    return pass_country
    def Orbit_parameters(self):
        ####################
        # ORBIT PARAMETERS #
        ####################

        eccentricity = 0.000092  # Update eccentricity list
        inclination = 60  #self.constellation.inclination_per_sat*self.sat_num     # degrees
        Semi_major_axis = 6879.55  # km The distance from the satellite to the earth + the earth radius
        Height_above_earth_surface = 500e3  # distance above earth surface
        Scale_height = 8500  # scale height of earth atmosphere
        RAAN = self.constellation.RAAN_per_sat * self.sat_num  # Right ascension of the ascending node in radians
        #RAAN = 275*pi/180                                       # Right ascension of the ascending node in radians
        AP = 0  # argument of perigee
        Re = 6371.2  # km magnetic reference radius
        Mean_motion = 15.2355000000  # rev/day
        Mean_motion_per_second = Mean_motion / (3600.0 * 24.0)
        Mean_anomaly = 29.3  # degrees
        Argument_of_perigee = 57.4  # in degrees
        omega = Argument_of_perigee
        Period = 86400 / Mean_motion  # seconds
        self.J_t, self.fr = jday(2020, 2, 16, 15, 30, 0)  # current julian date
        epoch = self.J_t - 2433281.5 + self.fr
        Drag_term = 0.000194  # Remember to update the list term
        wo = Mean_motion_per_second * (2 * pi)  # rad/s

        ############
        # TLE DATA #
        ############

        # s list
        satellite_number_list = '1 25544U'
        international_list = ' 98067A   '
        epoch_list = str("{:.8f}".format(epoch))
        mean_motion_derivative_first_list = '  .00001764'
        mean_motion_derivative_second_list = '  00000-0'
        Drag_term_list = '  19400-4'  # B-star
        Ephereris_list = ' 0'
        element_num_checksum_list = '  7030'
        self.s_list = satellite_number_list + international_list + epoch_list + mean_motion_derivative_first_list + mean_motion_derivative_second_list + Drag_term_list + Ephereris_list + element_num_checksum_list
        # t list
        line_and_satellite_number_list = '2 27843  '
        inclination_list = str("{:.4f}".format(inclination))
        intermediate_list = ' '
        RAAN_list = str("{:.4f}".format(RAAN * 180 / pi))
        intermediate_list_2 = ' '
        eccentricity_list = '0000920  '
        perigree_list = str("{:.4f}".format(Argument_of_perigee))
        intermediate_list_3 = intermediate_list_2 + ' '
        mean_anomaly_list = str("{:.4f}".format(Mean_anomaly))
        intermediate_list_4 = intermediate_list_2
        mean_motion_list = str("{:8f}".format(Mean_motion)) + '00'
        Epoch_rev_list = '000009'
        self.t_list = line_and_satellite_number_list + inclination_list + intermediate_list + RAAN_list + intermediate_list_2 + eccentricity_list + perigree_list + intermediate_list_3 + mean_anomaly_list + intermediate_list_4 + mean_motion_list + Epoch_rev_list
        self.a_G0 = 0  #self.constellation.a_G0_per_sat*self.sat_num
Ejemplo n.º 7
0
def teme2geodetic_oblate(x: float, y: float, z: float, t: datetime,
                         ellipsoid: Ellipsoid):
    """
    Converts TEME/ECI coords (x,y,z - expressed in km) to LLA (longitude, lattitude, altitude).
    ellipsoid is Earth ellipsoid to be used (e.g. ellipsoid_wgs84).

    The calculations here are based on T.S. Kelso's excellent paper "Orbital Coordinate Systems, Part III
    https://celestrak.com/columns/v02n03/.

    Parameters
    ==========
    x,y,z : float - coordates in TEME (True Equator Mean Equinoex) version of ECI (Earth Centered Intertial) coords system.
            This is the system that's produced by SGP4 models.
    t : datetime - time of the observation
    ellipsoid: Ellipsoid - an Earth exlipsoid specifying Earth oblateness, e.g. Ellipsoid_wgs84. Two params are used from it:
            a and inverse of f. Both must be specified in kms

    Returns
    =======
    lat, lon, alt - latitude, longitude (both in degrees), alt (in km)
    """

    # First, we need to do some basic calculations for Earth oblateness
    a = ellipsoid.a
    f = 1.0 / ellipsoid.finv
    b = a * (1 - 1.0 / f)
    e2 = f * (2 - f)

    phii = 1  # This is the starting value for initial iteration

    # There should be a check on |phii - phi| value, but let's always do 5 iterations. Good enough for now.
    for _ in range(1, 5):

        C = 1 / (sqrt(1 - e2 * pow(sin(phii), 2)))
        # This is not explicitly stated on clestrak page, but it's shown on a diagram.
        R = sqrt(x * x + y * y)
        phi = atan2(z + a * C * e2 * sin(phii), R)
        h = R / (cos(phi)) - a * C

        phii = phi

    jd, fr = jday(t.year, t.month, t.day, t.hour, t.minute, t.second)
    gmst = julianDateToGMST2(jd, fr)[0]

    lon = atan2(y, x) - gmst  # lambda-E
    lon = longitude_trunc(lon)

    return phi * RAD2DEG, lon * RAD2DEG, h
Ejemplo n.º 8
0
def test_single_satellite_single_date(single_satellite_single_date_data,
                                      benchmark):
    (line1,
     line2), epoch, expected_r, expected_v = single_satellite_single_date_data
    jd, fr = jday(*datetime_components(epoch))

    satrec = PurePythonSatrec.twoline2rv(line1, line2, WGS72)

    def f():
        return satrec.sgp4(jd, fr)

    e, r, v = benchmark(f)

    assert e == 0
    assert r == pytest.approx(expected_r)
    assert v == pytest.approx(expected_v)
Ejemplo n.º 9
0
    def convert2JulianDate(date_list: List[str]) -> Tuple[NDArray, NDArray]:
        """
        Function that creates a list of Julian Dates and decimals from the UTC date list
        :param date_list: the list of dates: List[str]
        :return: the list of Julian Dates and fr
        """

        dates = [parse(dt) for dt in date_list]
        jd, fr = [], []
        for d in dates:
            jd_temp, fr_temp = jday(d.year, d.month, d.day, d.hour, d.minute, d.second)
            jd.append(jd_temp)
            fr.append(fr_temp)
        # Convert to numpy array
        jd = np.array(jd)
        fr = np.array(fr)
        return jd, fr
Ejemplo n.º 10
0
    def generate_trace(self,
                       start_datetime,
                       interval=300,
                       unit=UNIT_M,
                       total_second=None):
        self.trace['start_datetime'] = start_datetime
        scale = 1000 if unit == UNIT_M else 1

        if total_second is None:
            total_second = 86400 + interval

        for second in range(0, total_second, interval):
            args = start_datetime + (second, )
            error, position, v = self.satellite.sgp4(*jday(*args))
            if error == 0:
                # has no error
                self.trace['data'].append({
                    'time': second,
                    'x': position[0] * scale,
                    'y': position[1] * scale,
                    'z': position[2] * scale,
                })
        return self
Ejemplo n.º 11
0
    def propagate(self, tnew, drmin=1e-1, dvmin=1e-3, niter=100):
        # New epoch
        epochyr, epochdoy = datetime_to_epoch(tnew)
        jd, fr = jday(tnew.year, tnew.month, tnew.day, tnew.hour, tnew.minute,
                      tnew.second + tnew.microsecond / 1000000)

        # Compute reference state vector
        sat = Satrec.twoline2rv(self.line1, self.line2)
        e, r, v = sat.sgp4(jd, fr)
        r0, v0 = np.asarray(r), np.asarray(v)

        # Start loop
        rnew, vnew = r0, v0
        converged = False
        for i in range(niter):
            # Convert state vector into new TLE
            newtle = classical_elements(rnew, vnew, epochyr, epochdoy, self)

            # Propagate with SGP4
            sat = Satrec.twoline2rv(newtle.line1, newtle.line2)
            e, rtmp, vtmp = sat.sgp4(sat.jdsatepoch, sat.jdsatepochF)
            rsgp4, vsgp4 = np.asarray(rtmp), np.asarray(vtmp)

            # Vector difference and magnitudes
            dr, dv = r0 - rsgp4, v0 - vsgp4
            drm, dvm = np.linalg.norm(dr), np.linalg.norm(dv)

            # Exit check
            if (np.abs(drm) < drmin) and (np.abs(dvm) < dvmin):
                converged = True
                break

            # Update state vector
            rnew = rnew + dr
            vnew = vnew + dv

        return newtle, converged
Ejemplo n.º 12
0
def get_satepoch(y, d):
    """
    Compute satellite epoch Julian date
    """
    y += 2000
    return sum(jday(y, *days2mdhms(y, d)))
command_create = """CREATE TABLE IF NOT EXISTS Satellite_data(id INTEGER PRIMARY KEY, tle_id INTEGER, satnum INTEGER, latitude REAL, longitude REAL,
elevation REAL, error_code INTEGER, rx REAL, ry REAL, rz REAL, vx REAL, vy REAL, vz REAL, velocity REAL, epoch_year REAL, epoch_days REAL,
bstar REAL, inclination REAL, right_ascension  REAL, eccentricity REAL, argument_of_perigee REAL, mean_anomaly REAL, mean_motion REAL, timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (tle_id) REFERENCES Satellite_TLE (id));"""

command_insert = """INSERT OR IGNORE INTO Satellite_data(tle_id, satnum, latitude, longitude,elevation, error_code, rx, ry, rz, vx, vy, vz, velocity, epoch_year, epoch_days,bstar, inclination, right_ascension, eccentricity, argument_of_perigee, mean_anomaly, mean_motion) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);"""

cursor.execute(command_create)
cursor.execute(command_select)
records = cursor.fetchall()

while True:
    ts = load.timescale()
    t = ts.now()

    time = datetime.utcnow()
    jd, fr = jday(time.year, time.month, time.day, time.hour, time.minute,
                  time.second)

    for row in records:
        id = row[0]
        name = row[1]
        line1, line2 = row[2].splitlines()
        satellite = EarthSatellite(line1, line2, name, ts)
        geocentric = satellite.at(t)
        subpoint = geocentric.subpoint()
        # Latitude and Longitude in degrees
        latitude = subpoint.latitude.degrees
        longitude = subpoint.longitude.degrees
        # Elevation in meters
        elevation = int(subpoint.elevation.m)
        sat = Satrec.twoline2rv(line1, line2)
        # e  Non zero error code if the satellite position could not be computed
Ejemplo n.º 14
0
def write_PV_to_file(tle_obj_vec: list[dict],
                     t0: datetime,
                     tf: datetime,
                     timestep: float,
                     filepath: str = "sats-TLE-example.json") -> None:
    import json
    from collections import OrderedDict

    import numpy as np
    from sgp4.api import Satrec, SatrecArray, jday
    from skyfield.api import load
    from skyfield.sgp4lib import TEME
    from skyfield.units import Velocity, Distance
    from skyfield.framelib import ecliptic_J2000_frame
    from skyfield.positionlib import ICRF

    json_posveldata = OrderedDict()
    # all TLEs (w/ diff epochs) will be propagated to current time.

    epoch_error_flag = False
    time_vec = [t0]
    t = t0
    while t < tf:
        t += datetime.timedelta(seconds=timestep)
        time_vec.append(t)

    years, months, days, hours, minutes, seconds = zip(*[(t.year, t.month,
                                                          t.day, t.hour,
                                                          t.minute, t.second)
                                                         for t in time_vec])

    years, months, days, hours, minutes, seconds = np.array(years), np.array(
        months), np.array(days), np.array(hours), np.array(minutes), np.array(
            seconds)

    jds, frs = jday(years, months, days, hours, minutes, seconds)

    satrecs_vec = []
    for item in tle_obj_vec:
        sat_name = item["NORAD_CAT_ID"]
        epoch = datetime.datetime.fromisoformat(item["EPOCH"])

        if (tf > (epoch + datetime.timedelta(days=5))) or (
                tf < (epoch - datetime.timedelta(days=5))):
            # raise TypeError(f" Final propagation date  {tf} is more than 5 days away from latest TLE epoch ({epoch}), for satellite {sat_name}} ")
            epoch_error_flag = True

        sat = Satrec.twoline2rv(item["TLE_LINE1"], item["TLE_LINE2"])
        satrecs_vec.append(sat)
        json_posveldata[sat_name] = {"P": [], "V": []}

    satrecs = SatrecArray(satrecs_vec)

    errors, rs, vs = satrecs.sgp4(jds, frs)  # r,v in TEME frame

    ts = load.timescale()
    for idxsat, satdata in enumerate(json_posveldata.values()):

        for idxtime, (jdi, fri) in enumerate(zip(jds, frs)):

            # convert to J2000
            ttime = ts.ut1_jd(jdi + fri)

            rvicrf = ICRF.from_time_and_frame_vectors(
                t=ttime,
                frame=TEME,
                distance=Distance(km=rs[idxsat][idxtime]),
                velocity=Velocity(km_per_s=vs[idxsat][idxtime]))
            # rvj2000 = rvicrf.frame_xyz_and_velocity(ecliptic_J2000_frame)
            # pos_timestep_j2000, vel_timestep_j2000 = rvj2000[0].m, rvj2000[1].m_per_s

            pos_timestep_j2000, vel_timestep_j2000 = rvicrf.position.m, rvicrf.velocity.m_per_s

            satdata["P"].append(pos_timestep_j2000.tolist())
            satdata["V"].append(vel_timestep_j2000.tolist())

        # satdata["P"] = rs[idxsat].tolist() #TEME
        # satdata["V"] = vs[idxsat].tolist() #TEME

    if errors.any():
        print("SGP4 errors found.")  # check errror type.
    if epoch_error_flag:
        print(
            f"Warning: Results obtained might be inaccurate. Final propagation date  {tf} is more than 5 days away from latest TLE epoch ({epoch}), for satellite {sat_name} "
        )

    with open(filepath, "w") as file:
        json.dump(json_posveldata, file, indent=6)

    print(f"PV File written successfully at {filepath}.")
Ejemplo n.º 15
0
def getThis_JD_FR(now, numberOfMinutesFromNow):
    time = now + timedelta(minutes=numberOfMinutesFromNow)
    return jday(time.year, time.month, time.day, time.hour, time.minute,
                time.second)
Ejemplo n.º 16
0
def generate_tles_from_scratch_with_sgp(filename_out, constellation_name,
                                        num_orbits, num_sats_per_orbit,
                                        phase_diff, inclination_degree,
                                        eccentricity, arg_of_perigee_degree,
                                        mean_motion_rev_per_day):

    with open(filename_out, "w+") as f_out:

        # First line:
        #
        # <number of orbits> <number of satellites per orbit>
        #
        f_out.write("%d %d\n" % (num_orbits, num_sats_per_orbit))

        # Each of the subsequent (number of orbits * number of satellites per orbit) blocks
        # define a satellite as follows:
        #
        # <constellation_name> <global satellite id>
        # <TLE line 1>
        # <TLE line 2>
        satellite_counter = 0
        for orbit in range(0, num_orbits):

            # Orbit-dependent
            raan_degree = orbit * 360.0 / num_orbits
            orbit_wise_shift = 0
            if orbit % 2 == 1:
                if phase_diff:
                    orbit_wise_shift = 360.0 / (num_sats_per_orbit * 2.0)

            # For each satellite in the orbit
            for n_sat in range(0, num_sats_per_orbit):
                mean_anomaly_degree = orbit_wise_shift + (n_sat * 360 /
                                                          num_sats_per_orbit)

                # Epoch is set to the year 2000
                # This conveniently in TLE format gives 00001.00000000
                # for the epoch year and Julian day fraction entry
                jd, fr = jday(2000, 1, 1, 0, 0, 0)

                # Use SGP-4 to generate TLE
                sat_sgp4 = Satrec()

                # Based on: https://pypi.org/project/sgp4/
                sat_sgp4.sgp4init(
                    WGS72,  # Gravity model [1]
                    'i',  # Operating mode (a = old AFPSC mode, i = improved mode)
                    satellite_counter + 1,  # satnum:  satellite number
                    (jd + fr) -
                    2433281.5,  # epoch:   days since 1949 December 31 00:00 UT [2]
                    0.0,  # bstar:   drag coefficient (kg/m2er)
                    0.0,  # ndot:    ballistic coefficient (revs/day)
                    0.0,  # nndot:   second derivative of mean motion (revs/day^3)
                    eccentricity,  # ecco:    eccentricity
                    math.radians(arg_of_perigee_degree
                                 ),  # argpo:   argument or perigee (radians)
                    math.radians(
                        inclination_degree),  # inclo:    inclination(radians)
                    math.radians(mean_anomaly_degree
                                 ),  # mo:       mean anomaly (radians)
                    mean_motion_rev_per_day * 60 /
                    13750.9870831397,  # no_kazai: mean motion (radians/minute) [3]
                    math.radians(raan_degree)  # nodeo:    right ascension of
                    #           ascending node (radians)
                )

                # Side notes:
                # [1] WGS72 is also used in the NS-3 model
                # [2] Due to a bug in sgp4init, the TLE below irrespective of the value here gives zeros.
                # [3] Conversion factor from:
                #     https://www.translatorscafe.com/unit-converter/en-US/ (continue on next line)
                #     velocity-angular/1-9/radian/second-revolution/day/
                #

                # Export TLE from the SGP-4 object
                line1, line2 = export_tle(sat_sgp4)

                # Line 1 has some problems: there are unknown characters entered for the international
                # designator, and the Julian date is not respected
                # As such, we set our own bogus international designator 00000ABC
                # and we set our own epoch date as 1 January, 2000
                # Why it's 00001.00000000: https://www.celestrak.com/columns/v04n03/#FAQ04
                tle_line1 = line1[:7] + "U 00000ABC 00001.00000000 " + line1[
                    33:]
                tle_line1 = tle_line1[:68] + str(
                    calculate_tle_line_checksum(tle_line1[:68]))
                tle_line2 = line2

                # Check that the checksum is correct
                if len(tle_line1) != 69 or calculate_tle_line_checksum(
                        tle_line1[:68]) != int(tle_line1[68]):
                    raise ValueError("TLE line 1 checksum failed")
                if len(tle_line2) != 69 or calculate_tle_line_checksum(
                        tle_line2[:68]) != int(tle_line2[68]):
                    raise ValueError("TLE line 2 checksum failed")

                # Write TLE to file
                f_out.write(constellation_name + " " +
                            str(orbit * num_sats_per_orbit + n_sat) + "\n")
                f_out.write(tle_line1 + "\n")
                f_out.write(tle_line2 + "\n")

                # One more satellite there
                satellite_counter += 1
Ejemplo n.º 17
0
def track():
    global r_list

    r = np.zeros((3, 200))
    points = 100
    SPG4_R_vec = np.zeros((3, points))
    per = (1 / (15.49141851239476 / (24 * 60 * 60)))
    interval = per / points

    ss1 = "1 25544U 98067A   20227.03014521  .00002523  00000-0  53627-4 0  9998"
    tt1 = "2 00001  51.6461  62.1977 0001435  30.4470 104.2862 15.49164713240985"
    ss = '1 42982U 98067NE  20226.63852685  .00012705  00000-0  10205-3 0  9996'
    tt = '2 42982  51.6351 349.1177 0003261 317.3782  42.6962 15.71399752160064'

    t_test = "2 00001  90  0 0000000000000001 30.4470 0 15.49164713240985"
    s_test = "1 25544U 98067A   20227.03014521  .00002523  00000-0  53627-4 0  9998"

    ISS = Satrec.twoline2rv(s_test, t_test)
    # print('interval: ',interval)
    # print(interval)

    for i in range(points):
        news = interval * i
        seconds = news % (24 * 3600)
        hour = seconds // 3600
        seconds %= 3600
        minutes = seconds // 60
        seconds %= 60
        jd, fr = jday(2020, 8, 7, 8 + hour, 0 + minutes, 0 + seconds)
        e, r, v = ISS.sgp4(jd, fr)
        SPG4_R_vec[0, i] = r[0]
        SPG4_R_vec[1, i] = r[1]
        SPG4_R_vec[2, i] = r[2]
    x1 = SPG4_R_vec[0, :]
    y1 = SPG4_R_vec[1, :]
    z1 = SPG4_R_vec[2, :]
    c = [
        "blue", "red", "green", "black", "purple", "brown", "orange", "cyan",
        "grey", "lime", "teal", "indigo", "pink"
    ]
    c_new = []
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')
    #handles = []
    for i in range(len(sat_list)):
        temp = sat_list[i]
        r = temp.gen_data(temp.Mean_motion, 100, 0)
        r_list.append(r)
        x = r[0, :]
        y = r[1, :]
        z = r[2, :]
        #handles.append(str(sat_list[i].Sat_num))
        #c_new.append(c[i])
        ax.scatter(x, y, z, color=c[i], s=1, label=str(sat_list[i].Sat_num))
    ax.scatter(x1, y1, z1, color="indigo")
    # print(handles)
    ax.legend(title="Legend")
    s = 12742
    ax.scatter(0, 0, 0, s=s)

    #handles, labels1 = scatter.legend_elements(prop="sizes", alpha=0.6)
    #legend2 = ax.legend(handles, labels1, loc="upper right", title="Sizes")
    #ax.legend()
    ax.set_xlabel("X axis (km)")
    ax.set_ylabel("Y axis (km)")
    ax.set_zlabel("Z axis (km)")
    #ax.scatter(x1,y1,z1,color='red',s=1)
    # ax.legend()
    plt.show()
Ejemplo n.º 18
0
 def get_sat_epoch(y, d):
     y += 2000
     return sum(jday(y, *days2mdhms(y, d)))
Ejemplo n.º 19
0
def location():
    global JD_global
    global TOImat
    global timeload
    throws = 0
    print("Calculating location")
    tempp = entry4.get()
    year, month, day, hour, minn, sec = map(int, tempp.split('-'))
    # year= int(tempp[0]+tempp[1]+tempp[2]+tempp[3])
    # month= int(tempp[4]+tempp[5])
    #  day=int(tempp[6]+tempp[7])
    #  hour=int(tempp[8]+tempp[9])
    #   minn=int(tempp[10]+tempp[11])
    #   sec=int(tempp[12]+tempp[13])

    JD_global, throws = jday(year, month, day, hour, minn, sec)
    d1 = datetime.datetime(2020, 8, 1, 12, 0, 0)
    d2 = datetime.datetime(year, month, day, hour, minn, sec)
    delta = d2 - d1
    tot = delta.days * 24 * 3600 + delta.seconds
    timeload = tot
    print("Seconds_location", tot)
    tmat = ECI2ECEF(tot)
    TOImat = tmat
    c = [
        "pink", "red", "green", "black", "purple", "brown", "orange", "cyan",
        "grey", "lime", "teal", "indigo", "blue"
    ]
    fig = plt.figure(
        figsize=(10, 10)
    )  #Adjusts the aspect ratio and enlarges the figure (text does not enlarge)
    ax = fig.gca(projection='3d')
    ax.set_zlim(-6900, 6900)
    ax.set_xlim(-8000, 8000)
    ax.set_ylim(-8000, 8000)
    ECEF_list.clear()
    ecef = proj.Proj(proj='geocent', ellps='WGS84', datum='WGS84')
    lla = proj.Proj(proj='latlong', ellps='WGS84', datum='WGS84')
    lla_list = []
    dicts = {'Sattelite': [], 'Latitude': [], 'Longitude': []}
    dicts_orbit_map = {'Sattelite': [], 'Latitude': [], 'Longitude': []}
    starts = np.zeros(3)
    starts[0] = 3070.134092
    starts[1] = -4112.927314
    starts[2] = -3774.618733
    for i in range(len(sat_list)):
        lla_mat = np.zeros((3, len(sat_list)))
        temp = sat_list[i]
        r = tmat.dot(temp.calc(tot))
        #print("Seconds_single",tot)
        ECEF_coord = r.dot(tmat)

        print("Single location:", ECEF_coord)
        lon, lat, alt = proj.transform(ecef,
                                       lla,
                                       ECEF_coord[0] * 1000,
                                       ECEF_coord[1] * 1000,
                                       ECEF_coord[2] * 1000,
                                       radians=True)
        lon = lon * 100
        lat = lat * 100
        alt = alt
        if (lon > 180):
            lon = 360 - lon
        if (lon < -180):
            lon = -360 - lon
        if (lat > 90):
            lat = 180 - lat
        if (lat < -90):
            lat = -180 - lat
        dicts['Sattelite'].append(str(i + 1))
        dicts['Latitude'].append(lat)
        dicts['Longitude'].append(lon)

        lla_mat[0, i] = lon
        lla_mat[1, i] = lat
        lla_mat[2, i] = alt
        ECEF_list.append(ECEF_coord)
        xf = np.zeros(2)
        yf = np.zeros(2)
        zf = np.zeros(2)
        xf[0] = starts[0]
        xf[1] = r[0]
        yf[0] = starts[1]
        yf[1] = r[1]
        zf[0] = starts[2]
        zf[1] = r[2]
        ax.scatter(0, 0, 0, color="red", s=20)
        # ax.plot3D(xf,yf,zf,alpha=0.3)
        # ax.scatter(starts[0],starts[1],starts[2],s=100)
        ax.scatter(r[0],
                   r[1],
                   r[2],
                   color=c[i],
                   s=100,
                   label=str(temp.Sat_num),
                   alpha=1)
        #print("Vectore single value:",r)
    plt.show()
    ax.legend(title="Legend", loc="upper left")

    if Checkk == 1:
        for i in range(len(sat_list)):
            temp1 = sat_list[i]
            r1 = temp1.gen_data(temp1.Mean_motion, 100, timeload, tot)
            x = r1[0, :]
            y = r1[1, :]
            z = r1[2, :]
            ax.scatter(x,
                       y,
                       z,
                       color=c[i],
                       s=5,
                       label=str(sat_list[i].Sat_num))
        ax.scatter(x[0], y[0], z[0], color="red", s=15)
        ax.scatter(x[1], y[1], z[1], color="red", s=15)
        ax.scatter(x[2], y[2], z[2], color="red", s=15)

    for i in range(len(sat_list)):
        lla_mat_map = np.zeros((3, len(sat_list)))
        r = temp.calc(tot)
        ECEF_coord = tmat.dot(r)
        lon, lat, alt = proj.transform(ecef,
                                       lla,
                                       ECEF_coord[0] * 1000,
                                       ECEF_coord[1] * 1000,
                                       ECEF_coord[2] * 1000,
                                       radians=True)
        lon = lon * 100
        lat = lat * 100
        alt = alt
        if (lon > 180):
            lon = 360 - lon
        if (lon < -180):
            lon = -360 - lon

        if (lat > 90):
            lat = 180 - lat
        if (lat < -90):
            lat = -180 - lat

        dicts_orbit_map['Sattelite'].append(str(i + 1))
        dicts_orbit_map['Latitude'].append(lat)
        dicts_orbit_map['Longitude'].append(lon)

    df = pd.DataFrame(dicts)
    gdf = gpd.GeoDataFrame(df,
                           geometry=gpd.points_from_xy(df.Longitude,
                                                       df.Latitude))
    world = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
    fig5 = plt.figure(figsize=(10, 10))
    ax5 = fig5.add_subplot(111)
    world.plot(ax=ax5, color="lightgray")
    ax5.set(xlabel="Longitude(Degrees)",
            ylabel="Latitude(Degrees)",
            title="WGS84 Datum (Degrees)")
    categories = df.Sattelite
    cat = str(np.linspace(0, len(sat_list), 1))
    gdf.plot(ax=ax5, color=c, categorical=True, markersize=20)
    plt.show()

    uee = np.linspace(0, np.pi, 40)
    vee = np.linspace(0, 2 * np.pi, 40)
    xee = np.outer(79.819 * np.sin(uee), 79.819 * np.sin(vee))
    yee = np.outer(79.819 * np.sin(uee), 79.819 * np.cos(vee))
    zee = np.outer(79.819 * np.cos(uee), 79.819 * np.ones_like(vee))
    ax.scatter(xee, yee, zee, s=2, alpha=0.4)
    ax.set_xlabel("X axis (km)")
    ax.set_ylabel("Y axis (km)")
    ax.set_zlabel("Z axis (km)")
    plt.show()
    df = pd.DataFrame(dicts)
    gdf = gpd.GeoDataFrame(df,
                           geometry=gpd.points_from_xy(df.Longitude,
                                                       df.Latitude))
Ejemplo n.º 20
0
def test_jday2():
    jd, fr = jday(2019, 10, 9, 16, 57, 15)
    assertEqual(jd, 2458765.5)
    assertAlmostEqual(fr, 0.7064236111111111)
Ejemplo n.º 21
0
class SET_PARAMS:
    # All the parameters specific to the satellite and the mission

    ####################
    # ORBIT PARAMETERS #
    ####################

    eccentricity = 0.000092  # Update eccentricity list
    inclination = 97.4  # degrees
    Semi_major_axis = 6879.55  # km The distance from the satellite to the earth + the earth radius
    Height_above_earth_surface = 500e3  # distance above earth surface
    Scale_height = 8500  # scale height of earth atmosphere
    RAAN = 275 * pi / 180  # Right ascension of the ascending node in radians
    AP = 0  # argument of perigee
    Re = 6371.2  # km magnetic reference radius
    Mean_motion = 15.2355000000  # rev/day
    Mean_motion_per_second = Mean_motion / (3600.0 * 24.0)
    Mean_anomaly = 29.3  # degrees
    Argument_of_perigee = 57.4  # in degrees
    omega = Argument_of_perigee
    Period = 86400 / Mean_motion  # seconds
    J_t, fr = jday(2020, 2, 16, 15, 30, 0)  # current julian date
    epoch = J_t - 2433281.5 + fr
    Drag_term = 0.000194  # Remember to update the list term
    wo = Mean_motion_per_second * (2 * pi)  # rad/s

    ############
    # TLE DATA #
    ############

    # s list
    satellite_number_list = '1 25544U'
    international_list = ' 98067A   '
    epoch_list = str("{:.8f}".format(epoch))
    mean_motion_derivative_first_list = '  .00001764'
    mean_motion_derivative_second_list = '  00000-0'
    Drag_term_list = '  19400-4'  # B-star
    Ephereris_list = ' 0'
    element_num_checksum_list = '  7030'
    s_list = satellite_number_list + international_list + epoch_list + mean_motion_derivative_first_list + mean_motion_derivative_second_list + Drag_term_list + Ephereris_list + element_num_checksum_list
    # t list
    line_and_satellite_number_list = '2 27843  '
    inclination_list = str("{:.4f}".format(inclination))
    intermediate_list = ' '
    RAAN_list = str("{:.4f}".format(RAAN * 180 / pi))
    intermediate_list_2 = ' '
    eccentricity_list = '0000920  '
    perigree_list = str("{:.4f}".format(Argument_of_perigee))
    intermediate_list_3 = intermediate_list_2 + ' '
    mean_anomaly_list = str("{:.4f}".format(Mean_anomaly))
    intermediate_list_4 = intermediate_list_2
    mean_motion_list = str("{:8f}".format(Mean_motion)) + '00'
    Epoch_rev_list = '000009'
    t_list = line_and_satellite_number_list + inclination_list + intermediate_list + RAAN_list + intermediate_list_2 + eccentricity_list + perigree_list + intermediate_list_3 + mean_anomaly_list + intermediate_list_4 + mean_motion_list + Epoch_rev_list

    #######################################################
    # OVERWRITE JANSEN VUUREN SE WAARDES MET SGP4 EXAMPLE #
    #######################################################
    """
    s_list = '1 25544U 98067A   19343.69339541  .00001764  00000-0  38792-4 0  9991'
    t_list = '2 25544  51.6439 211.2001 0007417  17.6667  85.6398 15.50103472202482'
    """

    #######################
    # POSITION PARAMETERS #
    #######################

    a_G0 = 0  # Angle from the greenwhich

    ############################
    # ATMOSPHERE (AERODYNAMIC) #
    ############################

    normal_accommodation = 0.8
    tangential_accommodation = 0.8
    ratio_of_molecular_exit = 0.05
    offset_vector = np.array(([0.01, 0.01, 0.01]))
    unit_normal_vector = np.array([[0, 1, 0], [1, 0, 0], [0, 0, 1]])
    atmospheric_reference_density = 1.225

    ###############################
    # EARTH EFFECTS (GEOMAGNETIC) #
    ###############################

    k = 10  #order of expansion
    Radius_earth = 6371e3  # in m
    w_earth = 7.2921150e-5  #rad/s

    ##################
    # SUN PARAMETERS #
    ##################

    Radius_sun = 696340e3  # in m

    ##################
    # SATELLITE BODY #
    ##################

    Mass = 20  #kg
    Dimensions = np.array(([0.3, 0.3, 0.4]))  # Lx, Ly, Lz
    Ix = 0.4  #kg.m^2
    Iy = 0.45  #kg.m^2
    Iz = 0.3  #kg.m^2
    Inertia = np.diag([Ix, Iy, Iz])
    Iw = 88.1e-6  #kgm^2 Inertia of the RW-06 wheel
    Surface_area_i = np.array(([
        Dimensions[0] * Dimensions[1], Dimensions[1] * Dimensions[2],
        Dimensions[0] * Dimensions[2]
    ]))
    kgx = 3 * wo**2 * (Iz - Iy)
    kgy = 3 * wo**2 * (Ix - Iz)
    kgz = 3 * wo**2 * (Iy - Ix)

    ##############################
    # SATELLITE INITIAL POSITION #
    ##############################

    quaternion_initial = np.array(([
        0, 0, -1, 0
    ]))  #Quaternion_functions.euler_to_quaternion(0,0,0) #roll, pitch, yaw
    wbi = np.array(([0.0], [0.0], [0.0]))
    initial_angular_wheels = np.zeros((3, 1))

    ###############################
    # MAX PARAMETERS OF ACTUATERS #
    ###############################

    wheel_angular_d_max = 2.0  #degrees per second (theta derived), angular velocity
    wheel_angular_d_d = 0.133  # degrees per second^2 (rotation speed derived), angular acceleration
    h_ws_max = 36.9e-3  # Nms
    N_ws_max = 10.6e-3  # Nm
    M_magnetic_max = 25e-6  # Nm
    RW_sigma_x = 14.6 / 10
    RW_sigma_y = 8.8 / 10
    RW_sigma_z = 21.2 / 10
    RW_sigma = np.mean([RW_sigma_x, RW_sigma_y, RW_sigma_y])
    Rotation_max = 2.0  # degrees per second

    ######################
    # CONTROL PARAMETERS #
    ######################

    w_ref = np.zeros((3, 1))  # desired angular velocity of satellite
    q_ref = np.array(([0, 0, 1, 0]))  # initial position of satellite
    time = 1
    Ts = 1  # Time_step
    wn = 90

    # For no filter
    Kp = 1.7e-2
    Kd = 1.1e-1

    # For RKF
    # Kd = Kd * 4
    # Kd = Kd/250

    # For EKF
    #Kp = Kp/10000
    #Kd = Kd/100000
    #Kp = Kp/10000000000000000000
    #Kd = Kd/10000000000000000000

    Kd_magnet = 1e-7
    Ks_magnet = 1e-7
    Kalman_filter_use = True

    ############################
    # KALMAN FILTER PARAMETERS #
    ############################
    Qw_t = np.diag([RW_sigma_x, RW_sigma_y, RW_sigma_z])
    Q_k = Ts * Qw_t

    P_k = np.eye(7)

    ######################
    # DISPLAY PARAMETERS #
    ######################

    faster_than_control = 1.0  # how much faster the satellite will move around the earth in simulation than the control
    Display = True  # if display is desired or not
    skip = 20  # the number of iterations before display

    #######################################################################
    # NUMBER OF REPETITIONS FOR ORBITS AND HOW MANY ORBITS PER REPETITION #
    #######################################################################

    Number_of_orbits = 1  # * This value can constantly be changed as well as the number of orbits
    Number_of_multiple_orbits = 17

    ##########################
    # VISUALIZE MEASUREMENTS #
    ##########################

    Visualize = True

    #######################
    # CSV FILE PARAMETERS #
    #######################

    save_as = ".xlsx"
    load_as = ".csv"

    ##################################
    # STORAGE OF DATA FOR PREDICTION #
    ##################################

    data_mode = "_buffer"
    buffer_mode = True
    buffer_size = 20

    # File names for the storage of the data attained during the simulation
    filename = "Data_files/Faults" + data_mode

    #####################
    # MODE OF OPERATION #
    #####################

    Mode = "Nominal"

    ####################################
    # FAULT TYPES AND FAULT PARAMETERS #
    ####################################

    number_of_faults = 17
    Fault_names = {
        "None": 1,
        "Electronics_of_RW": 2,
        "Overheated_RW": 3,
        "Catastrophic_RW": 4,
        "Catastrophic_sun": 5,
        "Erroneous_sun": 6,
        "Inverted_polarities_magnetorquers": 7,
        "Interference_magnetic": 8,
        "Stop_magnetometers": 9,
        "Closed_shutter": 10,
        "Increasing_angular_RW_momentum": 11,
        "Decreasing_angular_RW_momentum": 12,
        "Oscillating_angular_RW_momentum": 13,
        "Bit_flip": 14,
        "Sign_flip": 15,
        "Insertion_of_zero_bit": 16,
        "General_sensor_high_noise": 17,
    }
    likelyhood_multiplier = 1
    #Fault_simulation_mode = 1 # Continued failure, a mistake that does not go back to normal
    #Fault_simulation_mode = 0 # Failure is based on specified class failure rate. Multiple failures can occure simultaneously
    Fault_simulation_mode = 2  # A single fault occurs per orbit
    fixed_orbit_failure = 2

    #####################################################################################
    # FOR THE FAULT SIMULATION MODE 2, THE FAULT NAMES MUST BE THE VALUES BASED ON KEYS #
    #####################################################################################

    Fault_names_values = {value: key for key, value in Fault_names.items()}

    #################
    # SENSOR MODELS #
    #################
    # Star tracker
    star_tracker_vector = np.array([1.0, 1.0, 1.0])
    star_tracker_vector = star_tracker_vector / np.linalg.norm(
        star_tracker_vector)
    star_tracker_noise = 0.0001

    # Magnetometer
    Magnetometer_noise = 0.001  #standard deviation of magnetometer noise in Tesla

    # Earth sensor
    Earth_sensor_position = np.array(([0, 0, -1]))  # x, y, en z
    Earth_sensor_FOV = 180  # Field of view in degrees
    Earth_sensor_angle = Earth_sensor_FOV / 2  # The angle use to check whether the dot product angle is within the field of view
    Earth_noise = 0.01  #standard deviation away from where the actual earth is

    # Fine Sun sensor
    Fine_sun_sensor_position = np.array(([1, 0, 0]))  # x, y, en z
    Fine_sun_sensor_FOV = 180  # Field of view in degrees
    Fine_sun_sensor_angle = Fine_sun_sensor_FOV / 2  # The angle use to check whether the dot product angle is within the field of view
    Fine_sun_noise = 0.001  #standard deviation away from where the actual sun is

    # Coarse Sun Sensor
    Coarse_sun_sensor_position = np.array(([-1, 0, 0]))  # x, y, en z
    Coarse_sun_sensor_FOV = 180  # Field of view in degrees
    Coarse_sun_sensor_angle = Coarse_sun_sensor_FOV / 2  # The angle use to check whether the dot product angle is within the field of view
    Coarse_sun_noise = 0.01  #standard deviation away from where the actual sun is

    # Angular Momentum sensor
    Angular_sensor_noise = 0.001

    ############################
    # CONSTELLATION PARAMETERS #
    ############################
    Constellation = False
    Number_of_satellites = 1
    k_nearest_satellites = 5
Ejemplo n.º 22
0
def georef(method: Method, tle1: str, tle2: str, aos_txt: str, los_txt: str):
    """ This is a naive georeferencing method:
        - calculates the sat location at AOS and LOS points (using )
    then calculates distance between them. """

    # Convert date as a string datetime. Make sure to use UTC rather than the default (local timezone)
    d1 = datetime.fromisoformat(aos_txt).replace(tzinfo=timezone.utc)
    d2 = datetime.fromisoformat(los_txt).replace(tzinfo=timezone.utc)

    print("AOS time: %s" % d1)
    print("LOS time: %s" % d2)

    # STEP 1: Calculate sat location at AOS and LOS

    # Old sgp4 API 1.x used this approach, which is not recommended anymore.
    #sat_old = twoline2rv(tle1, tle2, wgs72)
    #pos1_old, _ = sat_old.propagate(d1.year, d1.month, d1.day, d1.hour, d1.minute, d1.second)
    #pos2_old, _ = sat_old.propagate(d1.year, d1.month, d1.day, d1.hour, d1.minute, d1.second)

    # This approach uses new API 2.x which gives a slightly different results.
    # In case of NOAA, the position is off by less than milimeter
    sat = Satrec.twoline2rv(tle1, tle2)
    jd1, fr1 = jday(d1.year, d1.month, d1.day, d1.hour, d1.minute, d1.second)
    jd2, fr2 = jday(d2.year, d2.month, d2.day, d2.hour, d2.minute, d2.second)

    # Take sat processing/transmission delay into consideration. At AOS time the signal received
    # was already NOAA_PROCESSING_DELAY seconds old.
    fr1 -= NOAA_PROCESSING_DELAY / 86400.0
    fr2 -= NOAA_PROCESSING_DELAY / 86400.0

    _, pos1, _ = sat.sgp4(
        jd1, fr1
    )  # returns error, position and velocity - we care about position only
    _, pos2, _ = sat.sgp4(jd2, fr2)

    # Delta between a point and a point+delta (the second delta point is used to calculate azimuth)
    DELTA = 30.0

    _, pos1delta, _ = sat.sgp4(jd1, fr1 + DELTA / 86400.0)
    _, pos2delta, _ = sat.sgp4(jd2, fr2 + DELTA / 86400.0)

    # STEP 2: Calculate sub-satellite point at AOS, LOS times
    # T.S. Kelso saves the day *again*: see here: https://celestrak.com/columns/v02n03/

    # Ok, we have sat position at time of AOS and LOS returned by SGP4 models. The tricky part here is those are in
    # Earth-Centered Intertial (ECI) reference system. The model used is TEME (True equator mean equinox).

    # Convert AOS coordinates to LLA
    aos_lla = teme2geodetic(method, pos1[0], pos1[1], pos1[2], d1)

    # Now caluclate a position for AOS + 30s. Will use it to determine the azimuth
    d1delta = d1 + timedelta(seconds=30.0)
    aos_bis = teme2geodetic(method, pos1delta[0], pos1delta[1], pos1delta[2],
                            d1delta)
    aos_az = calc_azimuth(aos_lla, aos_bis)

    print("AOS converted to LLA is lat=%f long=%f alt=%f, azimuth=%f" %
          (aos_lla[0], aos_lla[1], aos_lla[2], aos_az))

    # Now do the same for LOS
    los_lla = teme2geodetic(method, pos2[0], pos2[1], pos2[2], d2)

    # Let's use a point 30 seconds later. AOS and (AOS + 30s) will determine the azimuth
    d2delta = d2 + timedelta(seconds=30.0)
    los_bis = teme2geodetic(method, pos2delta[0], pos2delta[1], pos2delta[2],
                            d2delta)
    los_az = calc_azimuth(los_lla, los_bis)

    print("LOS converted to LLA is lat=%f long=%f alt=%f azimuth=%f" %
          (los_lla[0], los_lla[1], los_lla[2], los_az))

    # STEP 3: Find image corners. Here's an algorithm proposal:
    #
    # 1. calculate satellite flight azimuth AZ
    #    https://en.wikipedia.org/wiki/Great-circle_navigation
    #    In addition to AOS and LOS subsatellite points, we calculate AOSbis and LOSbis, subsat points
    #    after certain detla seconds. This is used to calculate azimuth
    #
    # 2. calculate directions that are perpendicular (+90, -90 degrees) AZ_L, AZ_R
    #    (basic math, add/subtract 90 degrees, modulo 360)
    #
    # 3. calculate sensor swath (left-right "width" of the observation), divite by 2 to get D
    #    - SMAD
    #    - WERTZ Mission Geometry, page 420
    #
    # 4. calculate terminal distance starting from SSP at the azimuth AZ_L and AZ_R and distance D
    #    https://www.fcc.gov/media/radio/find-terminal-coordinates
    #    https://stackoverflow.com/questions/877524/calculating-coordinates-given-a-bearing-and-a-distance

    # TODO: Calculcate if this pass is northbound or southbound

    # Let's assume this is AVHRR instrument. Let's use its field of view angle.
    fov = AVHRR_FOV

    # Now calculate corner positions (use only the first method)
    swath = calc_swath(aos_lla[2], fov)
    print(
        "Instrument angle is %f deg, altitude is %f km, swath (each side) is %f km, total swath is %f km"
        % (fov, aos_lla[2], swath, swath * 2))
    corner_ul = radial_distance(aos_lla[0], aos_lla[1],
                                azimuth_add(aos_az, +90), swath)
    corner_ur = radial_distance(aos_lla[0], aos_lla[1],
                                azimuth_add(aos_az, -90), swath)

    print("Upper left corner:  lat=%f lon=%f" % (corner_ul[0], corner_ul[1]))
    print("Upper right corner: lat=%f lon=%f" % (corner_ur[0], corner_ur[1]))

    # Now calculate corner positions (use only the first method)
    corner_ll = radial_distance(los_lla[0], los_lla[1],
                                azimuth_add(los_az, +90), swath)
    corner_lr = radial_distance(los_lla[0], los_lla[1],
                                azimuth_add(los_az, -90), swath)
    print("Lower left corner:  lat=%f lon=%f" % (corner_ll[0], corner_ll[1]))
    print("Lower right corner: lat=%f lon=%f" % (corner_lr[0], corner_lr[1]))

    # Ok, we have the sat position in LLA format. Getting sub-satellite point is trivial. Just assume altitude is 0.
    aos_lla = get_ssp(aos_lla)
    los_lla = get_ssp(los_lla)

    return d1, d2, aos_lla, los_lla, corner_ul, corner_ur, corner_ll, corner_lr