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
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}.")
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
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)
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
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
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)
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
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
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
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
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}.")
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)
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
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()
def get_sat_epoch(y, d): y += 2000 return sum(jday(y, *days2mdhms(y, d)))
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))
def test_jday2(): jd, fr = jday(2019, 10, 9, 16, 57, 15) assertEqual(jd, 2458765.5) assertAlmostEqual(fr, 0.7064236111111111)
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
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