) from astropy.time import Time from pytest import approx from satmad.coordinates.frames import J2000, TEME, TIRS time: Time = Time("2004-04-06T07:51:28.386009", scale="utc") # Vallado IAU 2000 - Table 3-6 v_gcrs_true = CartesianDifferential( [-4.7432201610, 0.7905364950, 5.5337557240], unit=u.km / u.s) r_gcrs_true = CartesianRepresentation( [5102.50895290, 6123.01139910, 6378.13693380], unit=u.km) rv_gcrs_true = GCRS( r_gcrs_true.with_differentials(v_gcrs_true), obstime=time, representation_type="cartesian", differential_type="cartesian", ) # Vallado IAU 2000 - Table 3-6 J2000 v_j2000_true = CartesianDifferential( [-4.7432196000, 0.7905366000, 5.5337561900], unit=u.km / u.s) r_j2000_true = CartesianRepresentation( [5102.50960000, 6123.01152000, 6378.13630000], unit=u.km) rv_j2000_true = J2000( r_j2000_true.with_differentials(v_j2000_true), obstime=time, representation_type="cartesian", differential_type="cartesian", ) # Vallado IAU 2000 - Table 3-6 CIRS
def test_regression_8138(): sc = SkyCoord(1 * u.deg, 2 * u.deg) newframe = GCRS() sc2 = sc.transform_to(newframe) assert newframe.is_equivalent_frame(sc2.frame)
def EarthDynamics(t, X, params): ''' The state rate dynamics are used in Runge-Kutta integration method to calculate the next set of equinoctial element values. ''' ''' State Rates ''' # State parameter vector decomposed Pos_ECI = np.vstack((X[:3])) Vel_ECI = np.vstack((X[3:6])) M = X[6] CA = X[7] r = norm(Pos_ECI) [t0, Perturbations] = params ''' Primary Gravitational Acceleration ''' a_grav = -mu_e * Pos_ECI / r**3 ''' Third Body Perturbations: Sun ''' # Position vector from Earth to the Sun (m) T = Time(t / (24 * 60 * 60) + t0, format='jd', scale='utc') sun_gcrs = get_body('sun', T) rho_s = np.vstack( sun_gcrs.transform_to(GCRS(obstime=T)).cartesian.xyz.to(u.meter).value) # Third body perturbation acceleration (Sun) -ECI frame a_sun = ThirdBodyPerturbation(Pos_ECI, rho_s, mu_s) if Perturbations: ''' Atmospheric Drag Perturbation - Better Model Needed ''' if M > 0 and CA > 0 and r < R_e + 10e6: # To the top of the exosphere # NRLMSISE-00 atmospheric model [temp, atm_pres, rho_a, sos, dyn_vis] = NRLMSISE_00(Pos_ECI, T) # Atmospheric velocity v_atm = np.cross(np.vstack((0, 0, w_e)), Pos_ECI, axis=0) # Velocity relative to the atmosphere v_rel = Vel_ECI - v_atm v = norm(v_rel, axis=0) # Constants for drag coeff mach = v / sos # Mach Number re = reynolds(mach, rho_a, dyn_vis, 2 * np.sqrt(CA / np.pi)) # Reynolds Number kn = knudsen(mach, re) # Knudsen Number Cd = dragcoef(re, mach, kn) # Drag Coefficient #Cd = 2.0 # Approximation # Total drag perturbation a_drag = -1.0 / (2 * M) * rho_a * Cd * CA * v * v_rel else: # Total drag perturbation a_drag = np.nan ''' Gravitational J2 Perturbation ''' # Gravitational perturbation components if r < R_SOI: k = 3 * mu_e * J2 * R_e**2 / (2 * r**5) J2_x = k * Pos_ECI[0] * (5 * Pos_ECI[2]**2 / r**2 - 1) J2_y = k * Pos_ECI[1] * (5 * Pos_ECI[2]**2 / r**2 - 1) J2_z = k * Pos_ECI[2] * (5 * Pos_ECI[2]**2 / r**2 - 3) a_J2 = np.vstack((J2_x, J2_y, J2_z)) else: a_J2 = np.nan ''' Third Body Perturbations: Moon ''' # Position vector from Earth to Moon (m) moon_gcrs = get_body('moon', T) rho_m = np.vstack( moon_gcrs.transform_to(GCRS(obstime=T)).cartesian.xyz.to( u.meter).value) # Third body perturbation acceleration (Moon) -ECI frame # a_moon = -mu_m * (Pos_ECI - rho_m) / (norm(Pos_ECI - rho_m))**3 a_moon = ThirdBodyPerturbation(Pos_ECI, rho_m, mu_m) ''' Total Perturbing Acceleration ''' if np.isnan(norm(a_drag)) and np.isnan(norm(a_J2)): a_tot = a_grav + a_sun + a_moon elif np.isnan(norm(a_drag)): a_tot = a_grav + a_sun + a_moon + a_J2 else: a_tot = a_grav + a_sun + a_moon + a_J2 + a_drag # Record perturbtions [time, sun, earth, moon, J2, drag] # print([norm(a_moon), mu_m / (norm(Pos_ECI - rho_m))**2]) #<----sort this out # global Pert # SUN = mu_s / (norm(Pos_ECI - rho_s))**2 # MOON = mu_m / (norm(Pos_ECI - rho_m))**2 # Pert = np.hstack((Pert, np.vstack((t/(24*60*60), SUN, # norm(a_grav), MOON, norm(a_J2), norm(a_drag))) )) else: ''' Total Perturbing Acceleration ''' a_tot = a_grav + a_sun ''' State Rate Equation ''' X_dot = np.vstack((Vel_ECI, a_tot, 0.0, 0.0)) return X_dot.flatten()
def _get_dopplers(self, time): shifts = np.empty(shape=(len(self.emitters), len(time))) shifts[:, :] = 1 # get current moon moon_loc, moon_vel = get_body_barycentric_posvel('moon', time) moon_loc = SkyCoord(representation_type='cartesian', differential_type=CartesianDifferential, unit='m', frame='icrs', obstime=time, x=moon_loc.x, y=moon_loc.y, z=moon_loc.z, v_x=moon_vel.x, v_y=moon_vel.y, v_z=moon_vel.z) moon = moon_loc.transform_to(GCRS(obstime=time)) # get current receiver receiver_loc, receiver_vel = self.receiver.get_gcrs_posvel(time) receiver = SkyCoord(representation_type='cartesian', differential_type=CartesianDifferential, unit='m', frame='gcrs', obstime=time, x=receiver_loc.x, y=receiver_loc.y, z=receiver_loc.z, v_x=receiver_vel.x, v_y=receiver_vel.y, v_z=receiver_vel.z) # get moon altitude moon_altaz = moon.transform_to(AltAz(obstime=time, location=receiver)) if self.only_visible: shifts[:, ] = np.greater(moon_altaz.alt, 0, dtype=np.float64) # get current emitters emitters = [] for e in self.emitters: emitter_loc, emitter_vel = e.get_gcrs_posvel(time) emitter = SkyCoord(representation_type='cartesian', differential_type=CartesianDifferential, unit='m', frame='gcrs', obstime=time, x=emitter_loc.x, y=emitter_loc.y, z=emitter_loc.z, v_x=emitter_vel.x, v_y=emitter_vel.y, v_z=emitter_vel.z) emitters.append(emitter) # calculate moon to earth doppler shift delta_pos_m_e = CartesianRepresentation(x=(moon.cartesian.xyz - receiver.cartesian.xyz), unit='m') delta_v_m_e = CartesianDifferential( d_x=(moon.velocity - receiver.velocity).get_d_xyz(), unit='m/s').to_cartesian() theta_m_e = delta_pos_m_e.dot(delta_v_m_e) / (delta_pos_m_e.norm() * delta_v_m_e.norm()) delta_v_m_e_norm = delta_v_m_e.norm().value shifted_m_e = self.doppler(delta_v_m_e_norm, theta_m_e) # calculate earth to moon doppler shifts for i, emitter in enumerate(emitters): delta_pos_e_m = CartesianRepresentation(x=(emitter.cartesian.xyz - moon.cartesian.xyz), unit='m') delta_v_e_m = CartesianDifferential( d_x=(emitter.velocity - moon.velocity).get_d_xyz(), unit='m/s').to_cartesian() theta_e_m = delta_pos_e_m.dot(delta_v_e_m) / ( delta_pos_e_m.norm() * delta_v_e_m.norm()) delta_v_e_m_norm = delta_v_e_m.norm().value if self.only_visible: moon_altaz_2 = moon.transform_to( AltAz(obstime=time, location=emitter)) shifts[i] = shifts[i] * self.doppler( delta_v_e_m_norm, theta_e_m) * np.greater( moon_altaz_2.alt, 0, dtype=np.float64) else: shifts[i] = self.doppler(delta_v_e_m_norm, theta_e_m) shifts = shifts * shifted_m_e shifts[shifts == 0] = np.nan if self.signal: shifts[:] = shifts[:] * self.signal - self.signal return shifts
assert_allclose(cirsnod.ra, cirsnod3.ra) assert_allclose(cirsnod.dec, cirsnod3.dec) cirsnod4 = cirsnod.transform_to(cframe2) # should be different assert not allclose(cirsnod4.ra, cirsnod.ra, rtol=1e-8) assert not allclose(cirsnod4.dec, cirsnod.dec, rtol=1e-8) cirsnod5 = cirsnod4.transform_to(cframe1) # should be back to the same assert_allclose(cirsnod.ra, cirsnod5.ra) assert_allclose(cirsnod.dec, cirsnod5.dec) usph = golden_spiral_grid(200) dist = np.linspace(0.5, 1, len(usph)) * u.pc icrs_coords = [ICRS(usph), ICRS(usph.lon, usph.lat, distance=dist)] gcrs_frames = [GCRS(), GCRS(obstime=Time('J2005'))] @pytest.mark.parametrize('icoo', icrs_coords) def test_icrs_gcrs(icoo): """ Check ICRS<->GCRS for consistency """ gcrscoo = icoo.transform_to(gcrs_frames[0]) # uses the default time # first do a round-tripping test icoo2 = gcrscoo.transform_to(ICRS()) assert_allclose(icoo.distance, icoo2.distance) assert_allclose(icoo.ra, icoo2.ra) assert_allclose(icoo.dec, icoo2.dec) assert isinstance(icoo2.data, icoo.data.__class__)
def test_asteroid_velocity_frame_shifts(): """ This test mocks up the use case of observing a spectrum of an asteroid at different times and from different observer locations. """ time1 = time.Time('2018-12-13 9:00') dt = 12 * u.hour time2 = time1 + dt # make the silly but simplifying assumption that the astroid is moving along # the x-axis of GCRS, and makes a 10 earth-radius closest approach v_ast = [5, 0, 0] * u.km / u.s x1 = -v_ast[0] * dt / 2 x2 = v_ast[0] * dt / 2 z = 10 * u.Rearth cdiff = CartesianDifferential(v_ast) asteroid_loc1 = GCRS(CartesianRepresentation(x1.to(u.km), 0 * u.km, z.to(u.km), differentials=cdiff), obstime=time1) asteroid_loc2 = GCRS(CartesianRepresentation(x2.to(u.km), 0 * u.km, z.to(u.km), differentials=cdiff), obstime=time2) # assume satellites that are essentially fixed in geostationary orbit on # opposite sides of the earth observer1 = GCRS(CartesianRepresentation( [0 * u.km, 35000 * u.km, 0 * u.km]), obstime=time1) observer2 = GCRS(CartesianRepresentation( [0 * u.km, -35000 * u.km, 0 * u.km]), obstime=time2) wls = np.linspace(4000, 7000, 100) * u.angstrom spec_coord1 = SpectralCoord(wls, observer=observer1, target=asteroid_loc1) assert spec_coord1.radial_velocity < 0 * u.km / u.s assert spec_coord1.radial_velocity > -5 * u.km / u.s spec_coord2 = SpectralCoord(wls, observer=observer2, target=asteroid_loc2) assert spec_coord2.radial_velocity > 0 * u.km / u.s assert spec_coord2.radial_velocity < 5 * u.km / u.s # now check the behavior of in_observer_velocity_frame: we shift each coord # into the velocity frame of its *own* target. That would then be a # spectralcoord that would allow direct physical comparison of the two # diffferent spec_corrds. There's no way to test that, without # actual data, though. # spec_coord2 is redshifted, so we test that it behaves the way "shifting # to rest frame" should - the as-observed spectral coordinate should become # the rest frame, so something that starts out red should become bluer target_sc2 = spec_coord2.in_observer_velocity_frame(spec_coord2.target) assert np.all(target_sc2 < spec_coord2) # rv/redshift should be 0 since the observer and target velocities should # be the same assert_quantity_allclose(target_sc2.radial_velocity, 0 * u.km / u.s, atol=1e-7 * u.km / u.s) # check that the same holds for spec_coord1, but be more specific: it # should follow the standard redshift formula (which in this case yields # a blueshift, although the formula is the same as 1+z) target_sc1 = spec_coord1.in_observer_velocity_frame(spec_coord1.target) assert_quantity_allclose(target_sc1, spec_coord1 / (1 + spec_coord1.redshift))
def test_gcrs_itrs_cartesian_repr(): # issue 6436: transformation failed if coordinate representation was # Cartesian gcrs = GCRS(CartesianRepresentation((859.07256, -4137.20368, 5295.56871), unit='km'), representation_type='cartesian') gcrs.transform_to(ITRS)
inc=32.521 * u.deg, epoch=date_launch) #Moon Orbit aqcuisition and conversion to GCRS EPOCH = date_arrival moon = Orbit.from_body_ephem(Moon, EPOCH) moon_icrs = ICRS(x=moon.r[0], y=moon.r[1], z=moon.r[2], v_x=moon.v[0], v_y=moon.v[1], v_z=moon.v[2], representation=CartesianRepresentation, differential_cls=CartesianDifferential) moon_gcrs = moon_icrs.transform_to(GCRS(obstime=EPOCH)) moon_gcrs.representation = CartesianRepresentation moon_gcrs moon = Orbit.from_vectors( Earth, [moon_gcrs.x, moon_gcrs.y, moon_gcrs.z] * u.km, [moon_gcrs.v_x, moon_gcrs.v_y, moon_gcrs.v_z] * (u.km / u.s), epoch=EPOCH) ss0 = apollo ssf = moon #Solve for Lambert's Problem (Determining Translunar Trajectory) (v0, v), = iod.lambert(Earth.k, ss0.r, ssf.r, tof) ss0_trans = Orbit.from_vectors(Earth, ss0.r, v0, date_launch)
sc_init3 = SpectralCoord([4000, 5000] * u.angstrom, redshift=1, target=gcrs_origin) sc_init3.with_redshift(.5) sc_init4 = SpectralCoord([4000, 5000] * u.angstrom, redshift=1, observer=gcrs_origin, target=gcrs_origin) with pytest.raises(ValueError): # fails if both observer and target are set sc_init4.with_redshift(.5) gcrs_origin = GCRS(CartesianRepresentation([0 * u.km, 0 * u.km, 0 * u.km])) gcrs_not_origin = GCRS(CartesianRepresentation([1 * u.km, 0 * u.km, 0 * u.km])) @pytest.mark.parametrize("sc_kwargs", [ dict(radial_velocity=0 * u.km / u.s), dict(observer=gcrs_origin, radial_velocity=0 * u.km / u.s), dict(target=gcrs_origin, radial_velocity=0 * u.km / u.s), dict(observer=gcrs_origin, target=gcrs_not_origin) ]) def test_los_shift(sc_kwargs): wl = [4000, 5000] * u.angstrom sc_init = SpectralCoord(wl, **sc_kwargs) # these should always work in *all* cases because it's unambiguous that # a target shift should behave this way
json_list = [] print('Parsing file and converting to ECI...') with open(sys.argv[1], "r") as f: for line in f: temp = line.split() epoch_utc = Time(temp[4], scale='utc', format='isot') v = CartesianDifferential(list(np.float_(temp[17:20]))*(u.m/u.s)) r = CartesianRepresentation(list(np.float_(temp[14:17]))*u.m, differentials={'s': v}) r_ecef = ITRS(r, obstime=epoch_utc) r_eci_temp = r_ecef.transform_to(GCRS(obstime=epoch_utc)) v_eci = r_eci_temp.cartesian.differentials['s'].d_xyz # this may be off from Jonathan's initial notebook test r_eci = r_eci_temp.cartesian.xyz utc_ms = temp[4][:-3] # FIXME -- orbdetpy can handle 6 digits of precision in time so need to remove this json_object = {"Time": utc_ms + "Z", # add this to maintain ISO8601 format for orbdetpy "PositionVelocity": [ r_eci[0].value, r_eci[1].value, r_eci[2].value, v_eci[0].value*1000, # not sure why this changed to km/s v_eci[1].value*1000, v_eci[2].value*1000 ] } json_list.append(json_object)
def ephem(iaucode, peph, objcode, timefile, kern=None, output='sky'): ############## Reading planetary kernel ############################################## if peph[-3:] != 'bsp': peph = peph + '.bsp' if not os.path.isfile(peph): raise OSError('Planetary Kernel {} not found'.format(peph[:-4])) kernel = SPK.open(peph) ########## Reading Time File ####################################################### if isinstance(timefile, str): ## if input is a file jd = np.loadtxt(timefile) time = Time(jd, format='jd', scale='utc') elif isinstance(timefile, Time): ## if input is Time Object of Astropy time = timefile else: ## if input is an array or a number time = Time(timefile, format='jd', scale='utc') timetdb = time.tdb ####################### look for object kernel ########################################### objt, objn, objm, objk = objs(objcode, kern) objk = objk + '.bsp' if not os.path.isfile(objk): raise OSError('Object Kernel {} not found'.format(objk[:-4])) kernelobj = SPK.open(objk) ######## Checking if it is geocenter or not and getting topocentric vectors ############ ##### Running low yet, have to see for many times ####### if not isinstance(iaucode, str): iaucode = str(iaucode) if iaucode.lower() not in ['g', 'geocenter']: ### not geocenter gcrsx, gcrsy, gcrsz = [], [], [] site, siten = sitios(iaucode) for i in time.utc: itrs = site.get_itrs(obstime=i) gcrs = itrs.transform_to(GCRS(obstime=i)) gcrsx.append(gcrs.cartesian.x.value) gcrsy.append(gcrs.cartesian.y.value) gcrsz.append(gcrs.cartesian.z.value) gcrsx = gcrsx * u.m gcrsy = gcrsy * u.m gcrsz = gcrsz * u.m else: ### geocenter siten = 'Geocenter' ######################################################################################### ## compute vector Solar System Barycenter -> Earth Geocenter -> Topocenter if iaucode.lower() in ['g', 'geocenter']: geop = kernel[0, 3].compute(timetdb.jd) + kernel[3, 399].compute( timetdb.jd) else: geop = kernel[0, 3].compute(timetdb.jd) + kernel[3, 399].compute( timetdb.jd) + np.array([ gcrsx.to(u.km).value, gcrsy.to(u.km).value, gcrsz.to(u.km).value ]) ########### Calculates time delay ########################################### # delt = light time # n = number of loops necessary delt = 0 * u.s n = 0 while True: ### calculates new time tempo = timetdb - delt ### calculates vector Solar System Baricenter -> Object positm = np.array([0.0, 0.0, 0.0]) if objk == ephs['lau'] + '.bsp': objtj, objnj, objmj, objkj = objs(599) kernelj = SPK.open(objkj + '.bsp') objp = kernel[0, 5].compute(tempo.jd) + kernelj[5, 599].compute( tempo.jd)[0:3] + compute(kernelobj, 599, objn, tempo.jd)[0:3] elif objt in ['s', 'p']: cc = objn // 100 objp = kernel[0, cc].compute(tempo.jd) + compute( kernelobj, cc, objn, tempo.jd)[0:3] elif objt in ['t', 'c']: objp = kernel[0, 10].compute(tempo.jd) + compute( kernelobj, 10, objn, tempo.jd)[0:3] ### calculates vector Earth Geocenter -> Object position = (objp - geop) ### calculates linear distance Earth Geocenter -> Object dist = np.linalg.norm(position, axis=0) * u.km ### calculates new light time delt = (dist / const.c).decompose() n = n + 1 ### if difference between new and previous light time is smaller than 0.001 sec, than continue. if np.all(np.absolute(((timetdb - tempo) - delt).sec) < 0.001): break ############################################################################## ### Creating SkyCoord Object with the coordinates for each instant coord = SkyCoord(position[0], position[1], position[2], frame='icrs', unit=u.km, representation='cartesian') ### returning values if output == 'radec': return coord.spherical.lon.hourangle, coord.spherical.lat.deg elif output == 'sky': return SkyCoord(ra=coord.spherical.lon, dec=coord.spherical.lat, distance=coord.spherical.distance) elif output == 'xyz': return coord.cartesian.x, coord.cartesian.y, coord.cartesian.z elif isinstance(output, str): f = open(output, 'w') f.write('Object={}, Kernel={}+{}, Site={}\n\n'.format( objn, objk[:-4], peph[:-4], siten)) for i in np.arange(len(time)): f.write(' {:013.10f} {:+013.9f} {:16.8f} {:16.8f}\n'.format( coord[i].spherical.lon.hourangle, coord[i].spherical.lat.deg, time[i].utc.jd, time[i].tdb.jd)) f.close()
import astropy.units as u from astropy.time import Time from astropy.coordinates import SkyCoord, EarthLocation, AltAz, GCRS, solar_system_ephemeris, ICRS, Angle, Latitude, Longitude from astropy.utils.misc import JsonCustomEncoder mypos = EarthLocation(lat=33.979 * u.deg, lon=-117.339 * u.deg, height=1014 * u.m) utcoffset = -7 * u.hour time = Time.now() + utcoffset solar_system_ephemeris.set('jpl') m13 = SkyCoord.from_name('M13') m13_AltAz = m13.transform_to(AltAz(obstime=time, location=mypos)) #with solar_system_ephemeris.set('jpl'): m13_GCRS = m13.transform_to(GCRS(obstime=time)) moon_GCRS = get_moon(time, mypos) moon_AltAz = moon_GCRS.transform_to(AltAz(obstime=time, location=mypos)) json_output = { 'type': "AltAz", 'obs_date': moon_AltAz.obstime.to_value('iso', subfmt='date'), 'obs_time': str(moon_AltAz.obstime.ymdhms.hour) + ":" + str(moon_AltAz.obstime.ymdhms.minute) + ":" + str(moon_AltAz.obstime.ymdhms.second), 'obs_geoloc': { 'x': str(moon_AltAz.location.x),
cirsnod4 = cirsnod.transform_to(cframe2) # should be different assert not allclose(cirsnod4.ra, cirsnod.ra, rtol=1e-8) assert not allclose(cirsnod4.dec, cirsnod.dec, rtol=1e-8) cirsnod5 = cirsnod4.transform_to(cframe1) # should be back to the same assert_allclose(cirsnod.ra, cirsnod5.ra) assert_allclose(cirsnod.dec, cirsnod5.dec) ra, dec, dist = randomly_sample_sphere(200) icrs_coords = [ ICRS(ra=ra, dec=dec), ICRS(ra=ra, dec=dec, distance=dist * u.pc) ] gcrs_frames = [GCRS(), GCRS(obstime=Time('J2005', scale='utc'))] @pytest.mark.parametrize('icoo', icrs_coords) def test_icrs_gcrs(icoo): """ Check ICRS<->GCRS for consistency """ gcrscoo = icoo.transform_to(gcrs_frames[0]) # uses the default time # first do a round-tripping test icoo2 = gcrscoo.transform_to(ICRS) assert_allclose(icoo.distance, icoo2.distance) assert_allclose(icoo.ra, icoo2.ra) assert_allclose(icoo.dec, icoo2.dec) assert isinstance(icoo2.data, icoo.data.__class__)
def ecef2eci(x: float | ndarray, y: float | ndarray, z: float | ndarray, time: datetime, *, use_astropy: bool = True) -> tuple[ndarray, ndarray, ndarray]: """ Point => Point ECEF => ECI J2000 frame Parameters ---------- x : float target x ECEF coordinate y : float target y ECEF coordinate z : float target z ECEF coordinate time : datetime.datetime time of observation use_astropy: bool, optional use AstroPy (much more accurate) Results ------- x_eci : float x ECI coordinate y_eci : float y ECI coordinate z_eci : float z ECI coordinate """ if use_astropy and Time is not None: itrs = ITRS(CartesianRepresentation(x * u.m, y * u.m, z * u.m), obstime=time) gcrs = itrs.transform_to(GCRS(obstime=time)) eci = EarthLocation(*gcrs.cartesian.xyz) x_eci = eci.x.value y_eci = eci.y.value z_eci = eci.z.value else: x = atleast_1d(x) y = atleast_1d(y) z = atleast_1d(z) gst = atleast_1d(greenwichsrt(juliandate(time))) assert x.shape == y.shape == z.shape assert x.size == gst.size ecef = column_stack((x.ravel(), y.ravel(), z.ravel())) eci = empty((x.size, 3)) for i in range(x.size): eci[i, :] = R3(gst[i]).T @ ecef[i, :] x_eci = eci[:, 0].reshape(x.shape) y_eci = eci[:, 1].reshape(y.shape) z_eci = eci[:, 2].reshape(z.shape) return x_eci, y_eci, z_eci