def cartesian2Geo(julian_date, x, y, z, precess_j2000=False): """ Convert Cartesian ECI coordinates of a point (origin in Earth's centre) to geographical coordinates. Arguments: julian_date: [float] decimal julian date X: [float] X coordinate of a point in space (meters) Y: [float] Y coordinate of a point in space (meters) Z: [float] Z coordinate of a point in space (meters) Keyword arguments: precess_j2000: [bool] The given coordinates are in J2000. False by default, which means that they should be in the epoch of date. Return: (lon, lat, ele): [tuple of floats] lat: longitude of the point in radians lon: latitude of the point in radians ele: elevation in meters """ # Precess the coordinates to epoch of date, if they are not already in it if precess_j2000: ### Precess coordinates from J2000 to epoch of date ### # Convert rectangular to spherical coordiantes re, delta_e, alpha_e = cartesianToSpherical(x, y, z) # Dynamical Julian date jd_dyn = jd2DynamicalTimeJD(julian_date) # Precess coordinates to J2000 alpha_ej, delta_ej = equatorialCoordPrecession(J2000_JD.days, jd_dyn, alpha_e, delta_e) # Convert coordinates back to rectangular x, y, z = sphericalToCartesian(re, delta_ej, alpha_ej) ### # Calculate LLA lat, r_LST, ele = ecef2LatLonAlt(x, y, z) # Calculate proper longitude from the given JD lon, _ = LST2LongitudeEast(julian_date, np.degrees(r_LST)) # Convert longitude to radians lon = np.radians(lon) # Convert the height from WGS84 to MSL ele = wmpl.Utils.GeoidHeightEGM96.wgs84toMSLHeight(lat, lon, ele) return lat, lon, ele
def calcOrbit(radiant_eci, v_init, v_avg, eci_ref, jd_ref, stations_fixed=False, reference_init=True, \ rotation_correction=False): """ Calculate the meteor's orbit from the given meteor trajectory. The orbit of the meteoroid is defined relative to the centre of the Sun (heliocentric). Arguments: radiant_eci: [3 element ndarray] Radiant vector in ECI coordinates (meters). v_init: [float] Initial velocity (m/s). v_avg: [float] Average velocity of a meteor (m/s). eci_ref: [float] reference ECI coordinates in the epoch of date (meters, in the epoch of date) of the meteor trajectory. They can be calculated with the geo2Cartesian function. Ceplecha (1987) assumes this to the the average point on the trajectory, while Jennsikens et al. (2011) assume this to be the first point on the trajectory as that point is not influenced by deceleration. NOTE: If the stations are not fixed, the reference ECI coordinates should be the ones of the initial point on the trajectory, NOT of the average point! jd_ref: [float] reference Julian date of the meteor trajectory. Ceplecha (1987) takes this as the average time of the trajectory, while Jenniskens et al. (2011) take this as the the first point on the trajectory. Keyword arguments: stations_fixed: [bool] If True, the correction for Earth's rotation will be performed on the radiant, but not the velocity. This should be True ONLY in two occasions: - if the ECEF coordinate system was used for trajectory estimation - if the ECI coordinate system was used for trajectory estimation, BUT the stations were not moved in time, but were kept fixed at one point, regardless of the trajectory estimation method. It is necessary to perform this correction for the intersecting planes method, but not for the lines of sight method ONLY when the stations are not fixed. Of course, if one is using the lines of sight method with fixed stations, one should perform this correction! reference_init: [bool] If True (default), the initial point on the trajectory is given as the reference one, i.e. the reference ECI coordinates are the ECI coordinates of the initial point on the trajectory, where the meteor has the velocity v_init. If False, then the reference point is the average point on the trajectory, and the average velocity will be used to do the corrections. rotation_correction: [bool] If True, the correction of the initial velocity for Earth's rotation will be performed. False by default. This should ONLY be True if the coordiante system for trajectory estimation was ECEF, i.e. did not rotate with the Earth. In all other cases it should be False, even if fixed station coordinates were used in the ECI coordinate system! Return: orb: [Orbit object] Object containing the calculated orbit. """ ### Correct the velocity vector for the Earth's rotation if the stations are fixed ### ########################################################################################################## eci_x, eci_y, eci_z = eci_ref # Calculate the geocentric latitude (latitude which considers the Earth as an elipsoid) of the reference # trajectory point lat_geocentric = np.arctan2(eci_z, np.sqrt(eci_x**2 + eci_y**2)) # Calculate the dynamical JD jd_dyn = jd2DynamicalTimeJD(jd_ref) # Calculate the geographical coordinates of the reference trajectory ECI position lat_ref, lon_ref, ht_ref = cartesian2Geo(jd_ref, *eci_ref) # Initialize a new orbit structure and assign calculated parameters orb = Orbit() # Calculate the velocity of the Earth rotation at the position of the reference trajectory point (m/s) v_e = 2 * np.pi * vectMag(eci_ref) * np.cos(lat_geocentric) / 86164.09053 # Calculate the equatorial coordinates of east from the reference position on the trajectory azimuth_east = np.pi / 2 altitude_east = 0 ra_east, dec_east = altAz2RADec(azimuth_east, altitude_east, jd_ref, lat_ref, lon_ref) # Compute velocity components of the state vector if reference_init: # If the initial velocity was the reference velocity, use it for the correction v_ref_vect = v_init * radiant_eci else: # Calculate reference velocity vector using the average point on the trajectory and the average # velocity v_ref_vect = v_avg * radiant_eci # Apply the Earth rotation correction if the station coordinates are fixed (a MUST for the # intersecting planes method!) if stations_fixed: ### Set fixed stations radiant info ### # If the stations are fixed, then the input state vector is already fixed to the ground orb.ra_norot, orb.dec_norot = eci2RaDec(radiant_eci) # Apparent azimuth and altitude (no rotation) orb.azimuth_apparent_norot, orb.elevation_apparent_norot = raDec2AltAz(orb.ra_norot, orb.dec_norot, \ jd_ref, lat_ref, lon_ref) # Estimated average velocity (no rotation) orb.v_avg_norot = v_avg # Estimated initial velocity (no rotation) orb.v_init_norot = v_init ### ### v_ref_corr = np.zeros(3) # Calculate the corrected reference velocity vector/radiant v_ref_corr[0] = v_ref_vect[0] - v_e * np.cos(ra_east) v_ref_corr[1] = v_ref_vect[1] - v_e * np.sin(ra_east) v_ref_corr[2] = v_ref_vect[2] else: # MOVING STATIONS # Velocity vector will remain unchanged if the stations were moving if reference_init: v_ref_corr = v_init * radiant_eci else: v_ref_corr = v_avg * radiant_eci ### ### # If the rotation correction does not have to be applied, meaning that the rotation is already # included, compute a version of the radiant and the velocity without Earth's rotation # (REPORTING PURPOSES ONLY, THESE VALUES ARE NOT USED IN THE CALCULATION) v_ref_nocorr = np.zeros(3) # Calculate the derotated reference velocity vector/radiant v_ref_nocorr[0] = v_ref_vect[0] + v_e * np.cos(ra_east) v_ref_nocorr[1] = v_ref_vect[1] + v_e * np.sin(ra_east) v_ref_nocorr[2] = v_ref_vect[2] # Compute the radiant without Earth's rotation included orb.ra_norot, orb.dec_norot = eci2RaDec(vectNorm(v_ref_nocorr)) orb.azimuth_apparent_norot, orb.elevation_apparent_norot = raDec2AltAz(orb.ra_norot, orb.dec_norot, \ jd_ref, lat_ref, lon_ref) orb.v_init_norot = vectMag(v_ref_nocorr) orb.v_avg_norot = orb.v_init_norot - v_init + v_avg ### ### ########################################################################################################## ### Correct velocity for Earth's gravity ### ########################################################################################################## # If the reference velocity is the initial velocity if reference_init: # Use the corrected velocity for Earth's rotation (when ECEF coordinates are used) if rotation_correction: v_init_corr = vectMag(v_ref_corr) else: # IMPORTANT NOTE: The correction in this case is only done on the radiant (even if the stations # were fixed, but NOT on the initial velocity!). Thus, correction from Ceplecha 1987, # equation (35) is not needed. If the initial velocity is determined from time vs. length and in # ECI coordinates, whose coordinates rotate with the Earth, the moving stations play no role in # biasing the velocity. v_init_corr = v_init else: if rotation_correction: # Calculate the corrected initial velocity if the reference velocity is the average velocity v_init_corr = vectMag(v_ref_corr) + v_init - v_avg else: v_init_corr = v_init # Calculate apparent RA and Dec from radiant state vector orb.ra, orb.dec = eci2RaDec(radiant_eci) orb.v_init = v_init orb.v_avg = v_avg # Calculate the apparent azimuth and altitude (geodetic latitude, because ra/dec are calculated from ECI, # which is calculated from WGS84 coordinates) orb.azimuth_apparent, orb.elevation_apparent = raDec2AltAz( orb.ra, orb.dec, jd_ref, lat_ref, lon_ref) orb.jd_ref = jd_ref orb.lon_ref = lon_ref orb.lat_ref = lat_ref orb.ht_ref = ht_ref orb.lat_geocentric = lat_geocentric # Assume that the velocity in infinity is the same as the initial velocity (after rotation correction, if # it was needed) orb.v_inf = v_init_corr # Make sure the velocity of the meteor is larger than the escape velocity if v_init_corr**2 > (2 * 6.67408 * 5.9722) * 1e13 / vectMag(eci_ref): # Calculate the geocentric velocity (sqrt of squared inital velocity minus the square of the Earth escape # velocity at the height of the trajectory), units are m/s. # Square of the escape velocity is: 2GM/r, where G is the 2014 CODATA-recommended value of # 6.67408e-11 m^3/(kg s^2), and the mass of the Earth is M = 5.9722e24 kg v_g = np.sqrt(v_init_corr**2 - (2 * 6.67408 * 5.9722) * 1e13 / vectMag(eci_ref)) # Calculate the radiant corrected for Earth's rotation (ONLY if the stations were fixed, otherwise it # is the same as the apparent radiant) ra_corr, dec_corr = eci2RaDec(vectNorm(v_ref_corr)) # Calculate the Local Sidreal Time of the reference trajectory position lst_ref = np.radians(jd2LST(jd_ref, np.degrees(lon_ref))[0]) # Calculate the apparent zenith angle zc = np.arccos(np.sin(dec_corr)*np.sin(lat_geocentric) \ + np.cos(dec_corr)*np.cos(lat_geocentric)*np.cos(lst_ref - ra_corr)) # Calculate the zenith attraction correction delta_zc = 2 * np.arctan2( (v_init_corr - v_g) * np.tan(zc / 2), v_init_corr + v_g) # Zenith distance of the geocentric radiant zg = zc + np.abs(delta_zc) ########################################################################################################## ### Calculate the geocentric radiant ### ########################################################################################################## # Get the azimuth from the corrected RA and Dec azimuth_corr, _ = raDec2AltAz(ra_corr, dec_corr, jd_ref, lat_geocentric, lon_ref) # Calculate the geocentric radiant ra_g, dec_g = altAz2RADec(azimuth_corr, np.pi / 2 - zg, jd_ref, lat_geocentric, lon_ref) ### Precess ECI coordinates to J2000 ### # Convert rectangular to spherical coordiantes re, delta_e, alpha_e = cartesianToSpherical(*eci_ref) # Precess coordinates to J2000 alpha_ej, delta_ej = equatorialCoordPrecession(jd_ref, J2000_JD.days, alpha_e, delta_e) # Convert coordinates back to rectangular eci_ref = sphericalToCartesian(re, delta_ej, alpha_ej) eci_ref = np.array(eci_ref) ###### # Precess the geocentric radiant to J2000 ra_g, dec_g = equatorialCoordPrecession(jd_ref, J2000_JD.days, ra_g, dec_g) # Calculate the ecliptic latitude and longitude of the geocentric radiant (J2000 epoch) L_g, B_g = raDec2Ecliptic(J2000_JD.days, ra_g, dec_g) # Load the JPL ephemerids data jpl_ephem_data = SPK.open(config.jpl_ephem_file) # Get the position of the Earth (km) and its velocity (km/s) at the given Julian date (J2000 epoch) # The position is given in the ecliptic coordinates, origin of the coordinate system is in the centre # of the Sun earth_pos, earth_vel = calcEarthRectangularCoordJPL( jd_dyn, jpl_ephem_data, sun_centre_origin=True) # print('Earth position:') # print(earth_pos) # print('Earth velocity:') # print(earth_vel) # Convert the Earth's position to rectangular equatorial coordinates (FK5) earth_pos_eq = rotateVector(earth_pos, np.array([1, 0, 0]), J2000_OBLIQUITY) # print('Earth position (FK5):') # print(earth_pos_eq) # print('Meteor ECI:') # print(eci_ref) # Add the position of the meteor's trajectory to the position of the Earth to calculate the # equatorial coordinates of the meteor (in kilometers) meteor_pos = earth_pos_eq + eci_ref / 1000 # print('Meteor position (FK5):') # print(meteor_pos) # Convert the position of the trajectory from FK5 to heliocentric ecliptic coordinates meteor_pos = rotateVector(meteor_pos, np.array([1, 0, 0]), -J2000_OBLIQUITY) # print('Meteor position:') # print(meteor_pos) ########################################################################################################## # Calculate components of the heliocentric velocity of the meteor (km/s) v_h = np.array(earth_vel) + np.array( eclipticToRectangularVelocityVect(L_g, B_g, v_g / 1000)) # Calculate the heliocentric velocity in km/s v_h_mag = vectMag(v_h) # Calculate the corrected heliocentric ecliptic coordinates of the meteoroid using the method of # Sato and Watanabe (2014). L_h, B_h, met_v_h = correctedEclipticCoord(L_g, B_g, v_g / 1000, earth_vel) # Calculate the solar longitude la_sun = jd2SolLonJPL(jd_dyn) # Calculations below done using Dave Clark's Master thesis equations # Specific orbital energy epsilon = (vectMag(v_h)**2) / 2 - SUN_MU / vectMag(meteor_pos) # Semi-major axis in AU a = -SUN_MU / (2 * epsilon * AU) # Calculate mean motion in rad/day n = np.sqrt(G * SUN_MASS / ((np.abs(a) * AU * 1000.0)**3)) * 86400.0 # Calculate the orbital period in years T = 2 * np.pi * np.sqrt( ((a * AU)**3) / SUN_MU) / (86400 * SIDEREAL_YEAR) # Calculate the orbit angular momentum h_vect = np.cross(meteor_pos, v_h) # Calculate inclination incl = np.arccos(h_vect[2] / vectMag(h_vect)) # Calculate eccentricity e_vect = np.cross(v_h, h_vect) / SUN_MU - vectNorm(meteor_pos) eccentricity = vectMag(e_vect) # Calculate perihelion distance (source: Jenniskens et al., 2011, CAMS overview paper) if eccentricity == 1: q = (vectMag(meteor_pos) + np.dot(e_vect, meteor_pos)) / (1 + vectMag(e_vect)) else: q = a * (1.0 - eccentricity) # Calculate the aphelion distance Q = a * (1.0 + eccentricity) # Normal vector to the XY reference frame k_vect = np.array([0, 0, 1]) # Vector from the Sun pointing to the ascending node n_vect = np.cross(k_vect, h_vect) # Calculate node if vectMag(n_vect) == 0: node = 0 else: node = np.arctan2(n_vect[1], n_vect[0]) node = node % (2 * np.pi) # Calculate argument of perihelion if vectMag(n_vect) != 0: peri = np.arccos( np.dot(n_vect, e_vect) / (vectMag(n_vect) * vectMag(e_vect))) if e_vect[2] < 0: peri = 2 * np.pi - peri else: peri = np.arccos(e_vect[0] / vectMag(e_vect)) peri = peri % (2 * np.pi) # Calculate the longitude of perihelion pi = (node + peri) % (2 * np.pi) ### Calculate true anomaly true_anomaly = np.arccos( np.dot(e_vect, meteor_pos) / (vectMag(e_vect) * vectMag(meteor_pos))) if np.dot(meteor_pos, v_h) < 0: true_anomaly = 2 * np.pi - true_anomaly true_anomaly = true_anomaly % (2 * np.pi) ### # Calculate eccentric anomaly eccentric_anomaly = np.arctan2(np.sqrt(1 - eccentricity**2)*np.sin(true_anomaly), eccentricity \ + np.cos(true_anomaly)) # Calculate mean anomaly mean_anomaly = eccentric_anomaly - eccentricity * np.sin( eccentric_anomaly) mean_anomaly = mean_anomaly % (2 * np.pi) # Calculate the time in days since the last perihelion passage of the meteoroid dt_perihelion = (mean_anomaly * a**(3.0 / 2)) / 0.01720209895 if not np.isnan(dt_perihelion): # Calculate the date and time of the last perihelion passage last_perihelion = jd2Date(jd_dyn - dt_perihelion, dt_obj=True) else: last_perihelion = None # Calculate Tisserand's parameter with respect to Jupiter Tj = 2 * np.sqrt( (1 - eccentricity**2) * a / 5.204267) * np.cos(incl) + 5.204267 / a # Assign calculated parameters orb.lst_ref = lst_ref orb.jd_dyn = jd_dyn orb.v_g = v_g orb.ra_g = ra_g orb.dec_g = dec_g orb.meteor_pos = meteor_pos orb.L_g = L_g orb.B_g = B_g orb.v_h_x, orb.v_h_y, orb.v_h_z = met_v_h orb.L_h = L_h orb.B_h = B_h orb.zc = zc orb.zg = zg orb.v_h = v_h_mag * 1000 orb.la_sun = la_sun orb.a = a orb.e = eccentricity orb.i = incl orb.peri = peri orb.node = node orb.pi = pi orb.q = q orb.Q = Q orb.true_anomaly = true_anomaly orb.eccentric_anomaly = eccentric_anomaly orb.mean_anomaly = mean_anomaly orb.last_perihelion = last_perihelion orb.n = n orb.T = T orb.Tj = Tj return orb
def geo2Cartesian(lat_rad, lon_rad, h, julian_date, precess_j2000=False): """ Convert geographical Earth coordinates to Cartesian ECI coordinate system (Earth center as origin). The Earth is considered as an elipsoid. Arguments: lat_rad: [float] Latitude of the observer in radians (+N), WGS84. lon_rad: [float] Longitde of the observer in radians (+E), WGS84. h: [int or float] Elevation of the observer in meters (EGS96 convention). julian_date: [float] Julian date, epoch J2000.0. Keyword arguments: precess_j2000: [bool] Precess ECI coordinates to J2000. False by default. Return: (x, y, z): [tuple of floats] a tuple of X, Y, Z Cartesian ECI coordinates """ lon = np.degrees(lon_rad) # Convert MSL height (i.e. height above sea level) to WGS84 height h = wmpl.Utils.GeoidHeightEGM96.mslToWGS84Height(lat_rad, lon_rad, h) # Calculate ECEF coordinates ecef_x, ecef_y, ecef_z = latLonAlt2ECEF(lat_rad, lon_rad, h) # Get Local Sidereal Time (apparent) LST_rad = np.radians(jd2LST(julian_date, lon)[0]) # Calculate the Earth radius at given latitude Rh = math.sqrt(ecef_x**2 + ecef_y**2 + ecef_z**2) # Calculate the geocentric latitude (latitude which considers the Earth as an elipsoid) lat_geocentric = math.atan2(ecef_z, math.sqrt(ecef_x**2 + ecef_y**2)) # Calculate Cartesian ECI coordinates (in meters), in the epoch of date x = Rh * np.cos(lat_geocentric) * np.cos(LST_rad) y = Rh * np.cos(lat_geocentric) * np.sin(LST_rad) z = Rh * np.sin(lat_geocentric) if precess_j2000: ### Precess coordinates to J2000 ### # Convert rectangular to spherical coordiantes re, delta_e, alpha_e = cartesianToSpherical(x, y, z) # Dynamical Julian date jd_dyn = jd2DynamicalTimeJD(julian_date) # Precess coordinates to J2000 alpha_ej, delta_ej = equatorialCoordPrecession(jd_dyn, J2000_JD.days, alpha_e, delta_e) # Convert coordinates back to rectangular x_ej, y_ej, z_ej = sphericalToCartesian(re, delta_ej, alpha_ej) ### return x_ej, y_ej, z_ej else: # Leave the coordinates in the epoch of date return x, y, z