def _from_periapsis( cls, semilatus_rectum_au, eccentricity, inclination_degrees, longitude_of_ascending_node_degrees, argument_of_perihelion_degrees, t_periapsis, gm_km3_s2, center=None, target=None, ): """Build a `KeplerOrbit` given its parameters and date of periapsis.""" gm_au3_d2 = gm_km3_s2 * _CONVERT_GM pos, vel = ele_to_vec( semilatus_rectum_au, eccentricity, DEG2RAD * inclination_degrees, DEG2RAD * longitude_of_ascending_node_degrees, DEG2RAD * argument_of_perihelion_degrees, 0.0, gm_au3_d2, ) return cls( Distance(pos), Velocity(vel), t_periapsis, gm_au3_d2, center, target, )
def check_orbit(p, e, i, Om, w, v, ts): """Checks that the given set of elements are calculated properly by elementslib.py Converts the given elements to state vectors using ele_to_vec, then uses those state vectors to create an OsculatingElements object, and then checks that the data in the OsculatingElements object matches the input elements. """ length = 1 for item in p, e, i, Om, w, v: if isinstance(item, (list, ndarray)): length = len(item) mu = 403503.2355022598 pos_vec, vel_vec = ele_to_vec(p, e, i, Om, w, v, mu) time_tt = ts.utc(2018).tt time = ts.tt(jd=repeat(time_tt, pos_vec[0].size)) elements = OsculatingElements(Distance(km=pos_vec), Velocity(km_per_s=vel_vec), time, mu) check_types(elements, length) compare(time, elements.time, 1e-9) compare(elements.semi_latus_rectum.km, p, 1e-9) compare(elements.eccentricity, e, 1e-14) compare(elements.inclination.radians, i, 1e-14, mod=True) compare(elements.longitude_of_ascending_node.radians, Om, 1e-14, mod=True) compare(elements.argument_of_periapsis.radians, w, 1e-14, mod=True) compare(elements.true_anomaly.radians, v, 1e-14, mod=True)
def midpoint(p1, p2): '''The midpoint between two celestial coordinates. Given a point as a tuple of either ``(Ra, Dec)`` or ``(Ra, Dec, Dist)``. RA is of Skyfield type Angle, with ``preference='hours'``. Dec is of Skyfield type Angle. Distance is Skyfield type Distance, in AU units. If dist is included, the returned tuple is (Mid_RA, Mid_Dec, Mid_Dist). Otherwise, it is (Mid_RA, Mid_Dec). You can alternatively use ``center`` for multiple points.''' if (not isinstance(p1, tuple) or not isinstance(p2, tuple)): raise ValueError('Can only pass two tuples to this function.') return None mean_ra = (p1[0].radians + p2[0].radians) / 2.0 mean_dec = (p1[1].radians + p2[1].radians) / 2.0 if (len(p1) == 3 and len(p2) == 3): mean_dist = (p1[2].AU + p2[2].AU) / 2.0 return (Angle(radians=mean_ra, preference='hours'), Angle(radians=mean_dec, signed=True), Distance(AU=mean_dist)) else: return (Angle(radians=mean_ra, preference='hours'), Angle(radians=mean_dec, signed=True))
def from_true_anomaly(cls, p, e, i, Om, w, v, epoch, mu_km_s=None, mu_au_d=None, center=None, target=None, center_name=None, target_name=None, ): """ Creates a `KeplerOrbit` object from elements using true anomaly Parameters ---------- p : Distance Semi-Latus Rectum e : float Eccentricity i : Angle Inclination Om : Angle Longitude of Ascending Node w : Angle Argument of periapsis v : Angle True anomaly epoch : Time Time corresponding to `position` and `velocity` mu_km_s : float Value of mu (G * M) in km^3/s^2 mu_au_d : float Value of mu (G * M) in au^3/d^2 center : int NAIF ID of the primary body, 399 for geocentric orbits, 10 for heliocentric orbits target : int NAIF ID of the secondary body """ if (mu_km_s and mu_au_d) or (not mu_km_s and not mu_au_d): raise ValueError('Either mu_km_s or mu_au_d should be used, but not both') if mu_au_d: mu_km_s = mu_au_d * AU_KM**3 / DAY_S**2 position, velocity = ele_to_vec(p.km, e, i.radians, Om.radians, w.radians, v.radians, mu_km_s, ) return cls(Distance(km=position), Velocity(km_per_s=velocity), epoch, mu_km_s, center=center, target=target, center_name=center_name, target_name=target_name, )
def _from_comet_dataframe(cls, df, ts): # TODO: rewrite this once skyfield.mpc._mpc_comets() goes live. mu_km_s = GM_dict[10] mu_au_d = mu_km_s / (AU_KM**3) * (DAY_S**2) e = df.e a = df.Perihelion_dist / (1 - e) p = a * (1 - e**2) n = sqrt(mu_au_d / a**3) peri_day = ts.tt(df.Year_of_perihelion, 0, df.Day_of_perihelion) epoch = ts.tt(df.Epoch_year, df.Epoch_month, df.Epoch_day) M = n * (epoch - peri_day) return cls.from_mean_anomaly( p=Distance(au=p), e=e, i=Angle(degrees=df.i), Om=Angle(degrees=df.Node), w=Angle(degrees=df.Peri), M=Angle(radians=M), epoch=epoch, mu_km_s=mu_km_s, center=10, # TODO: infer target SPK-ID from info in dataframe center_name='SUN', target_name=df.Designation_and_name, )
def test_against_horizons(): # See the following files in the Skyfield repository: # # horizons/ceres-orbital-elements # horizons/ceres-position ts = load.timescale() t = ts.tdb_jd(2458886.500000000) a = 2.768873850275102E+00 # A e = 7.705857791518426E-02 # EC p_au = a * (1 - e*e) # Wikipedia k = KeplerOrbit._from_mean_anomaly( semilatus_rectum_au=p_au, eccentricity=e, inclination_degrees=2.718528770987308E+01, longitude_of_ascending_node_degrees=2.336112629072238E+01, argument_of_perihelion_degrees=1.328964361683606E+02, mean_anomaly_degrees=1.382501360489816E+02, epoch=t, gm_km3_s2=GM_SUN, center=None, target=None, ) r, v = k._at(t)[:2] sun_au = [ -0.004105894975783999, 0.006739680703224941, 0.002956344702049446, ] horizons_au = [ 1.334875927366032E+00, -2.239607658161781E+00, -1.328895183461897E+00, ] epsilon = Distance(m=0.001).au assert abs(r + sun_au - horizons_au).max() < epsilon
def check_orbit(p, e, i, Om, w, v, p_eps=None, e_eps=None, i_eps=None, Om_eps=None, w_eps=None, v_eps=None): pos0, vel0 = ele_to_vec(p, e, i, Om, w, v, mu) pos1, vel1 = propagate(pos0, vel0, 0, times, mu) ele = OsculatingElements(Distance(km=pos1), Velocity(km_per_s=vel1), dummy_time, mu) if p_eps: compare(p, ele.semi_latus_rectum.km, p_eps) if e_eps: compare(e, ele.eccentricity, e_eps) if i_eps: compare(i, ele.inclination.radians, i_eps, mod=True) if Om_eps: compare(Om, ele.longitude_of_ascending_node.radians, Om_eps, mod=True) if w_eps: compare(w, ele.argument_of_periapsis.radians, w_eps, mod=True) if v_eps: compare(v, ele.true_anomaly.radians, v_eps, mod=True)
def from_mpcorb_dataframe(cls, df, ts): if 'Number' in df: target = int(df.Number.strip('()')) + 2000000 else: target = None if 'Name' not in df or df.Name == nan: target_name = df.Principal_desig else: target_name = df.Name p = df.a * (1 - df.e**2) return cls.from_mean_anomaly( p=Distance(au=p), e=df.e, i=Angle(degrees=df.i), Om=Angle(degrees=df.Node), w=Angle(degrees=df.Peri), M=Angle(degrees=df.M), epoch=ts.tdb_jd(df.Epoch), mu_km_s=GM_dict[10] + GM_dict.get(target, 0), center=10, target=target, center_name='SUN', target_name=target_name, )
def test_stringifying_vector_distance(): a = array([1.23, 4.56]) s = str(Distance(au=a)) if '[1' in s: # Python 3.5, says Travis CI. No idea. assert s == '[1.23 4.56] au' else: # Every other version of Python. assert s == '[ 1.23 4.56] au'
def test_helpful_exceptions(): distance = Distance(1.234) expect = '''\ to use this Distance, ask for its value in a particular unit: distance.au distance.km distance.m''' with assert_raises(UnpackingError) as a: x, y, z = distance assert str(a.exception) == expect with assert_raises(UnpackingError) as a: distance[0] assert str(a.exception) == expect velocity = Velocity(1.234) expect = '''\ to use this Velocity, ask for its value in a particular unit: velocity.au_per_d velocity.km_per_s velocity.m_per_s''' with assert_raises(UnpackingError) as a: x, y, z = velocity assert str(a.exception) == expect with assert_raises(UnpackingError) as a: velocity[0] assert str(a.exception) == expect angle = Angle(radians=1.234) expect = '''\ to use this Angle, ask for its value in a particular unit: angle.degrees angle.hours angle.radians''' with assert_raises(UnpackingError) as a: x, y, z = angle assert str(a.exception) == expect with assert_raises(UnpackingError) as a: angle[0] assert str(a.exception) == expect
def from_dataframe(cls, df, ts): # https://minorplanetcenter.net/iau/info/PackedDates.html # if 'Number' in df: # target = int(df.Number.strip('()')) + 2000000 # else: target = None name = 'foo' # if 'Name' not in df or df.Name == nan: # target_name = df.Principal_desig # else: #target_name = df.Name p = df.semimajor_axis_au.values * (1.0 - df.eccentricity.values**2.0) # TODO: rework the epoch conversion using arrays, if possible, as in # https://stackoverflow.com/questions/49503173/ def n(c): return ord(c) - 48 if c.isdigit() else ord(c) - 55 def d(s): year = 100 * n(s[0]) + int(s[1:3]) month = n(s[3]) day = n(s[4]) return julian_day(year, month, day) epoch_jd = [d(s) for s in df.epoch_packed.values] t = ts.tt_jd(epoch_jd) # TODO: vectorize return cls.from_mean_anomaly( p=Distance(au=p[0]), e=df.eccentricity.values[0], i=Angle(degrees=df.inclination_degrees.values[0]), Om=Angle(degrees=df.longitude_of_ascending_node_degrees.values[0]), w=Angle(degrees=df.argument_of_perihelion_degrees.values[0]), M=Angle(degrees=df.mean_anomaly_degrees.values[0]), epoch=t[0], mu_km_s=GM_dict[10] + GM_dict.get(target, 0), center=10, target=target, center_name='SUN', target_name=name, )
def test_against_horizons(): # See the following files in the Skyfield repository: # # horizons/ceres-orbital-elements # horizons/ceres-position ts = load.timescale(builtin=True) t = ts.tdb_jd(2458886.500000000) a = 2.768873850275102E+00 # A e = 7.705857791518426E-02 # EC p_au = a * (1 - e**2) # Wikipedia k = KeplerOrbit.from_mean_anomaly( p=Distance(au=p_au), # see above e=e, i=Angle(degrees=2.718528770987308E+01), Om=Angle(degrees=2.336112629072238E+01), w=Angle(degrees=1.328964361683606E+02), M=Angle(degrees=1.382501360489816E+02), epoch=t, #? mu_km_s=None, mu_au_d=2.9591220828559093E-04, center=None, target=None, center_name=None, target_name=None, ) r, v = k._at(t)[:2] sun_au = [ -0.004105894975783999, 0.006739680703224941, 0.002956344702049446, ] horizons_au = [ 1.334875927366032E+00, -2.239607658161781E+00, -1.328895183461897E+00, ] assert max(abs(r + sun_au - horizons_au)) < 2e-15
def geoDistance( a: GeographicPosition, b: GeographicPosition, geoid: Optional[Geoid] = None, ) -> Distance: """Compute the surface distance between two geographic positions. TODO: Add this to the Geoid class instead. """ geoid = geoid or wgs84 # We use Lambert's formula. See # https://en.wikipedia.org/wiki/Geographical_distance#Lambert's_formula_for_long_lines omf = 1.0 - (1.0 / geoid.inverse_flattening) # Compute reduced latitudes beta1 = numpy.arctan(omf * numpy.tan(a.latitude.radians)) beta2 = numpy.arctan(omf * numpy.tan(b.latitude.radians)) # The (spherical) central angle between the two points sigma = numpy.arccos( numpy.sin(beta1) * numpy.sin(beta2) + numpy.cos(beta1) * numpy.cos(beta2) * numpy.cos(b.longitude.radians - a.longitude.radians)) # And now Lambert's equation. sinSigma = numpy.sin(sigma) P = (beta2 + beta1) / 2 Q = (beta2 - beta1) / 2 xBase = numpy.sin(P) * numpy.cos(Q) / numpy.cos(sigma / 2) yBase = numpy.cos(P) * numpy.sin(Q) / numpy.sin(sigma / 2) X = (sigma - sinSigma) * xBase * xBase Y = (sigma + sinSigma) * yBase * yBase arc = sigma - (X + Y) / (2 * geoid.inverse_flattening) return Distance.from_au(geoid.radius.au * arc)
def test_converting_from_m_to_km(): distance = Distance(m=1234.0) assert abs(distance.km - 1.234) < 1e-15
def test_converting_from_km_to_m(): distance = Distance(km=1.234) assert abs(distance.m - 1234.0) < 1e-15
def test_constructors_accept_plain_lists(): Distance(au=[1, 2, 3]) Distance(km=[1, 2, 3]) Distance(m=[1, 2, 3]) Velocity(au_per_d=[1, 2, 3]) Velocity(km_per_s=[1, 2, 3])
def test_converting_distance_with_astropy(): distance = Distance(au=1.234) value1 = distance.km value2 = distance.to(u.km) epsilon = 0.02 # definitions of AU seem to disagree slightly assert abs(value1 - value2.value) < epsilon
for lon in range(-181, 181): longitudes[lon] = GSO_longitudes.count(lon) fig, ax = plt.subplots() ax.bar(longitudes.keys(), longitudes.values()) ax.set_title(u'Распределение геостационарных КА по долготе') ax.set_xlabel(u'Долгота,°') ax.set_ylabel(u'Кол-во спутников') plt.savefig('Распределение геостационарных КА по долготе.png') min_distances = [] for s1 in GSO_satellites: p1 = s1.at(t) min_dist = Distance(au=100).km for s2 in GSO_satellites: if s2 == s1: continue p2 = s2.at(t) dist = (p2 - p1).distance().km if dist < min_dist: min_dist = dist min_distances.append(min_dist) fig, ax = plt.subplots() hist(min_distances, bins='scott', density=True, alpha=0.5, ax=ax) ax.set_title(u'Распределение КА по расстоянию между «ближайшими соседями»') ax.set_xlabel(u'Расстояние, км') ax.set_ylabel(u'Частота') plt.savefig('Распределение КА по расстоянию.png')
def _from_mean_anomaly( cls, semilatus_rectum_au, eccentricity, inclination_degrees, longitude_of_ascending_node_degrees, argument_of_perihelion_degrees, mean_anomaly_degrees, epoch, gm_km3_s2, center=None, target=None, ): """ Creates a `KeplerOrbit` object from elements using mean anomaly Parameters ---------- p : Distance Semi-Latus Rectum e : float Eccentricity i : Angle Inclination Om : Angle Longitude of Ascending Node w : Angle Argument of periapsis M : Angle Mean anomaly epoch : Time Time corresponding to `position` and `velocity` mu_km_s : float Value of mu (G * M) in km^3/s^2 mu_au3_d2 : float Value of mu (G * M) in au^3/d^2 center : int NAIF ID of the primary body, 399 for geocentric orbits, 10 for heliocentric orbits target : int NAIF ID of the secondary body """ M = DEG2RAD * mean_anomaly_degrees gm_au3_d2 = gm_km3_s2 * _CONVERT_GM if eccentricity < 1.0: E = eccentric_anomaly(eccentricity, M) v = true_anomaly_closed(eccentricity, E) elif eccentricity > 1.0: E = eccentric_anomaly(eccentricity, M) v = true_anomaly_hyperbolic(eccentricity, E) else: v = true_anomaly_parabolic(semilatus_rectum_au, gm_au3_d2, M) pos, vel = ele_to_vec( semilatus_rectum_au, eccentricity, DEG2RAD * inclination_degrees, DEG2RAD * longitude_of_ascending_node_degrees, DEG2RAD * argument_of_perihelion_degrees, v, gm_au3_d2, ) return cls( Distance(pos), Velocity(vel), epoch, gm_au3_d2, center, target, )
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 festoon_ephemeris(ephemeris): for name, radius_km in radii_km: name = name.lower().split()[-1] getattr(ephemeris, name).radius = Distance(km=radius_km)
def test_iterating_over_raw_measurement(): distance = Distance(au=1.234) with assert_raises(UnpackingError) as a: x, y, z = distance assert str(a.exception) == '''\
"Jupiter", "Saturn", "Uranus", "Neptune", "Pluto", ): planet = planet_name + " Barycenter" position = (planets[planet] - sun).at(t) # This is broken, see # https://github.com/skyfielders/python-skyfield/issues/655#issuecomment-960377889 # osculating_elements_of(position, ecliptic_frame) # So we have to do things manually elems = OsculatingElements( Distance(np.einsum("mnr,nr->mr", ecliptic_frame, position.position.au)), # type: ignore Velocity( np.einsum("mnr,nr->mr", ecliptic_frame, position.velocity.au_per_d)), # type: ignore position.t, GM_dict[position.center] + GM_dict[position.target], ) c_object = CelestialObject(position.target, planet_name, elems) c_object.glue() position = (planets["Moon"] - planets["Earth"]).at(t) elems = osculating_elements_of(position) c_object = CelestialObject(position.target, "Moon", elems) c_object.glue()