def test_core_trans_kwargs_deprecated(self): """Test deprecated keywords in wrapped transformation functions.""" self.warn_msgs = [ "".join([ "`method` must be a string value in ", "v1.0.0+. Setting to function default." ]) ] self.warn_msgs = np.array(self.warn_msgs) # Catch the warnings. with warnings.catch_warnings(record=True) as war: OMMBV.python_ecef_to_geodetic(np.array([0.]), np.array([0.]), np.array([550.]), method=None) # Ensure the minimum number of warnings were raised. assert len(war) >= len(self.warn_msgs) # Test the warning messages, ensuring each attribute is present. eval_warnings(war, self.warn_msgs) return
def test_geodetic_to_ecef_to_geodetic(self): """Geodetic to ECEF and back""" lats, longs, alts = gen_data_fixed_alt(550.) ecf_x, ecf_y, ecf_z = OMMBV.geodetic_to_ecef(lats, longs, alts) lat, elong, alt = OMMBV.ecef_to_geodetic(ecf_x, ecf_y, ecf_z) lat2, elong2, alt2 = OMMBV.python_ecef_to_geodetic(ecf_x, ecf_y, ecf_z) idx, = np.where(elong < 0) elong[idx] += 360. idx, = np.where(elong2 < 0) elong2[idx] += 360. d_lat = lat - lats d_long = elong - longs d_alt = alt - alts assert np.all(np.abs(d_lat) < 1.E-5) assert np.all(np.abs(d_long) < 1.E-5) assert np.all(np.abs(d_alt) < 1.E-5) d_lat = lat2 - lats d_long = elong2 - longs d_alt = alt2 - alts assert np.all(np.abs(d_lat) < 1.E-5) assert np.all(np.abs(d_long) < 1.E-5) assert np.all(np.abs(d_alt) < 1.E-5)
def test_core_scalars_for_mapping(self, param): """Test deprecated inputs, `scalars_for_mapping_ion_drifts`.""" self.warn_msgs = [ " ".join([ param, "is deprecated, non-functional,", "and will be removed after OMMBV v1.0.0." ]) ] self.warn_msgs = np.array(self.warn_msgs) # Prep input kwargs = {param: 1} # Catch the warnings. with warnings.catch_warnings(record=True) as war: OMMBV.scalars_for_mapping_ion_drifts([0.], [0.], [550.], [self.date], **kwargs) # Ensure the minimum number of warnings were raised. assert len(war) >= len(self.warn_msgs) # Test the warning messages, ensuring each attribute is present. eval_warnings(war, self.warn_msgs) return
def test_igrf_end_to_ecef_back_to_end(self): """Check consistency ENU-ECEF and IGRF implementation""" # import pdb vx = 0.9 vy = 0.1 vz = np.sqrt(1. - vx ** 2 + vy ** 2) vz = -vz lats, longs, alts = gen_data_fixed_alt(550.) for lat, lon, alt in zip(lats, longs, alts): # print(vx, vy, vz, lat, lon) # pdb.set_trace() # input here is co-latitude, not latitude # inputs to fortran are in radians vxx, vyy, vzz = igrf.end_vector_to_ecef(vx, vy, vz, np.deg2rad(90. - lat), np.deg2rad(lon)) vx2, vy2, vz2 = OMMBV.enu_to_ecef_vector(vx, vy, -vz, lat, lon) # print ('end check ', vxx, vyy, vzz, vx2, vy2, vz2) asseq(vxx, vx2, 9) asseq(vyy, vy2, 9) asseq(vzz, vz2, 9) vxx, vyy, vzz = OMMBV.ecef_to_enu_vector(vxx, vyy, vzz, lat, lon) # convert upward component back to down vzz = -vzz # compare original inputs to outputs asseq(vx, vxx, 9) asseq(vy, vyy, 9) asseq(vz, vzz, 9)
def test_geodetic_to_ecef_to_geocentric_to_ecef_to_geodetic(self): """geodetic to ecef and geocentric transformations""" lats, longs, alts = gen_data_fixed_alt(550.) ecf_x, ecf_y, ecf_z = OMMBV.geodetic_to_ecef(lats, longs, alts) geo_lat, geo_long, geo_alt = OMMBV.ecef_to_geocentric(ecf_x, ecf_y, ecf_z) ecfs_x, ecfs_y, ecfs_z = OMMBV.geocentric_to_ecef(geo_lat, geo_long, geo_alt) lat, elong, alt = OMMBV.ecef_to_geodetic(ecfs_x, ecfs_y, ecfs_z) idx, = np.where(elong < 0) elong[idx] += 360. d_lat = lat - lats d_long = elong - longs d_alt = alt - alts assert np.all(np.abs(d_lat) < 1.E-5) assert np.all(np.abs(d_long) < 1.E-5) assert np.all(np.abs(d_alt) < 1.E-5) assert np.all(np.abs(ecf_x - ecfs_x) < 1.E-5) assert np.all(np.abs(ecf_y - ecfs_y) < 1.E-5) assert np.all(np.abs(ecf_z - ecfs_z) < 1.E-5)
def test_tracing_accuracy_w_recursion(self): """Establish performance field-line vs max_steps""" lats, longs, alts = gen_trace_data_fixed_alt(550.) ecf_x, ecf_y, ecf_z = OMMBV.geodetic_to_ecef(lats, longs, alts) # step size to be tried steps_goal = np.array([5.] * 13) # max number of steps (fixed) max_steps_goal = np.arange(13) max_steps_goal = 100000. / 2 ** max_steps_goal date = datetime.datetime(2000, 1, 1) dx = [] dy = [] dz = [] for x, y, z in zip(ecf_x, ecf_y, ecf_z): out = [] for steps, max_steps in zip(steps_goal, max_steps_goal): trace_n = OMMBV.field_line_trace(np.array([x, y, z]), date, 1., 0., step_size=steps, max_steps=max_steps) pt = trace_n[-1, :] out.append(pt) final_pt = pds.DataFrame(out, columns=['x', 'y', 'z']) dx.append(np.abs(final_pt.ix[1:, 'x'].values - final_pt.ix[:, 'x'].values[:-1])) dy.append(np.abs(final_pt.ix[1:, 'y'].values - final_pt.ix[:, 'y'].values[:-1])) dz.append(np.abs(final_pt.ix[1:, 'z'].values - final_pt.ix[:, 'z'].values[:-1])) dx = pds.DataFrame(dx) dy = pds.DataFrame(dy) dz = pds.DataFrame(dz) try: plt.figure() yerrx = np.nanstd(np.log10(dx), axis=0) yerry = np.nanstd(np.log10(dy), axis=0) yerrz = np.nanstd(np.log10(dz), axis=0) plt.errorbar(np.log10(max_steps_goal[1:]), np.log10(dx.mean(axis=0)), yerr=yerrx, label='x') plt.errorbar(np.log10(max_steps_goal[1:]), np.log10(dy.mean(axis=0)), yerr=yerry, label='y') plt.errorbar(np.log10(max_steps_goal[1:]), np.log10(dz.mean(axis=0)), yerr=yerrz, label='z') plt.xlabel('Log Number of Steps per Run') plt.ylabel('Change in Foot Point Position (km)') plt.title("Change in Final ECEF Position, Recursive Calls") plt.legend() plt.tight_layout() plt.ylabel('Log Change in Foot Point Position (km)') plt.savefig('Footpoint_position_vs_max_steps_recursion.pdf') plt.close() except: pass
def test_enu_to_ecef_back_to_enu(self): """Test ENU-ECEF-ENU""" vx = 0.9 vy = 0.1 vz = np.sqrt(1. - vx ** 2 + vy ** 2) lats, longs, alts = gen_data_fixed_alt(550.) for lat, lon, alt in zip(lats, longs, alts): vxx, vyy, vzz = OMMBV.enu_to_ecef_vector(vx, vy, vz, lat, lon) vxx, vyy, vzz = OMMBV.ecef_to_enu_vector(vxx, vyy, vzz, lat, lon) asseq(vx, vxx, 9) asseq(vy, vyy, 9) asseq(vz, vzz, 9)
def test_plot_apex_heights(self): """Check meridional vector along max in apex height gradient""" date = pysat.datetime(2010, 1, 1) delta = 1. ecef_x, ecef_y, ecef_z = OMMBV.geodetic_to_ecef([0.], [320.], [550.]) # get basis vectors zx, zy, zz, _, _, _, mx, my, mz = OMMBV.calculate_mag_drift_unit_vectors_ecef(ecef_x, ecef_y, ecef_z, [date], ecef_input=True) # get apex height for step along meridional directions, then around that direction _, _, _, _, _, nominal_max = OMMBV.apex_location_info(ecef_x + delta * mx, ecef_y + delta * my, ecef_z + delta * mz, [date], ecef_input=True, return_geodetic=True) steps = (np.arange(101) - 50.) * delta / 10000. output_max = [] for step in steps: del_x = delta * mx + step * zx del_y = delta * my + step * zy del_z = delta * mz + step * zz norm = np.sqrt(del_x ** 2 + del_y ** 2 + del_z ** 2) del_x /= norm del_y /= norm del_z /= norm _, _, _, _, _, loop_h = OMMBV.apex_location_info(ecef_x + del_x, ecef_y + del_y, ecef_z + del_z, [date], ecef_input=True, return_geodetic=True) output_max.append(loop_h) try: plt.figure() plt.plot(steps, output_max) plt.plot([0], nominal_max, color='r', marker='o', markersize=12) plt.ylabel('Apex Height (km)') plt.xlabel('Distance along Zonal Direction (km)') plt.savefig('comparison_apex_heights_and_meridional.pdf') plt.close() except: pass # make sure meridional direction is correct assert np.all(np.max(output_max) == nominal_max)
def test_basic_enu_to_ecef_rotations(self): """Test ENU to ECEF rotations""" # test basic transformations first # vector pointing east at 0, 0 is along y vx, vy, vz = OMMBV.enu_to_ecef_vector(1., 0., 0., 0., 0.) # print ('{:9f}, {:9f}, {:9f}'.format(vx, vy, vz)) asseq(vx, 0.0, 9) asseq(vy, 1.0, 9) asseq(vz, 0.0, 9) # vector pointing up at 0, 0 is along x vx, vy, vz = OMMBV.enu_to_ecef_vector(0., 0., 1., 0., 0.) asseq(vx, 1.0, 9) asseq(vy, 0.0, 9) asseq(vz, 0.0, 9) # vector pointing north at 0, 0 is along z vx, vy, vz = OMMBV.enu_to_ecef_vector(0., 1., 0., 0., 0.) asseq(vx, 0.0, 9) asseq(vy, 0.0, 9) asseq(vz, 1.0, 9) # east vector at 0, 90 long points along -x vx, vy, vz = OMMBV.enu_to_ecef_vector(1., 0., 0., 0., 90.) # print ('{:9f}, {:9f}, {:9f}'.format(vx, vy, vz)) asseq(vx, -1.0, 9) asseq(vy, 0.0, 9) asseq(vz, 0.0, 9) # vector pointing up at 0, 90 is along y vx, vy, vz = OMMBV.enu_to_ecef_vector(0., 0., 1., 0., 90.) asseq(vx, 0.0, 9) asseq(vy, 1.0, 9) asseq(vz, 0.0, 9) # vector pointing north at 0, 90 is along z vx, vy, vz = OMMBV.enu_to_ecef_vector(0., 1., 0., 0., 90.) asseq(vx, 0.0, 9) asseq(vy, 0.0, 9) asseq(vz, 1.0, 9) # vector pointing east at 0, 0 is along y vx, vy, vz = OMMBV.enu_to_ecef_vector(1., 0., 0., 0., 180.) # print ('{:9f}, {:9f}, {:9f}'.format(vx, vy, vz)) asseq(vx, 0.0, 9) asseq(vy, -1.0, 9) asseq(vz, 0.0, 9) # vector pointing up at 0, 180 is along -x vx, vy, vz = OMMBV.enu_to_ecef_vector(0., 0., 1., 0., 180.) asseq(vx, -1.0, 9) asseq(vy, 0.0, 9) asseq(vz, 0.0, 9) # vector pointing north at 0, 180 is along z vx, vy, vz = OMMBV.enu_to_ecef_vector(0., 1., 0., 0., 180.) asseq(vx, 0.0, 9) asseq(vy, 0.0, 9) asseq(vz, 1.0, 9)
def test_geodetic_to_ecef_to_geocentric_to_ecef_to_geodetic(self): """Test geodetic to ecef and geocentric transformations""" ecf_x, ecf_y, ecf_z = OMMBV.trans.geodetic_to_ecef( self.lats, self.longs, self.alts) geo_lat, geo_long, geo_alt = OMMBV.trans.ecef_to_geocentric( ecf_x, ecf_y, ecf_z) ecfs_x, ecfs_y, ecfs_z = OMMBV.trans.geocentric_to_ecef( geo_lat, geo_long, geo_alt) lat, elong, alt = OMMBV.ecef_to_geodetic(ecfs_x, ecfs_y, ecfs_z) idx, = np.where(elong < 0) elong[idx] += 360. assert_difference_tol(lat, self.lats) assert_difference_tol(elong, self.longs) assert_difference_tol(alt, self.alts) assert_difference_tol(ecf_x, ecfs_x) assert_difference_tol(ecf_y, ecfs_y) assert_difference_tol(ecf_z, ecfs_z) return
def test_igrf_ecef_to_geodetic_back_to_ecef(self): """Test IGRF_ECEF - Geodetic - and Back""" lats, longs, alts = gen_data_fixed_alt(550.) ecf_x, ecf_y, ecf_z = OMMBV.geodetic_to_ecef(lats, longs, alts) for ecef_x, ecef_y, ecef_z, geo_lat, geo_lon, geo_alt in zip(ecf_x, ecf_y, ecf_z, lats, longs, alts): pos = np.array([ecef_x, ecef_y, ecef_z]) lat, elong, alt = igrf.ecef_to_geodetic(pos) lat = np.rad2deg(lat) elong = np.rad2deg(elong) if (elong < 0): elong = elong + 360. d_lat = lat - geo_lat d_long = elong - geo_lon d_alt = alt - geo_alt # print ('Word', ecef_x, ecef_y, ecef_z) # print (geo_lat, geo_lon, geo_alt) # print (lat, elong, alt) assert np.all(np.abs(d_lat) < 1.E-5) assert np.all(np.abs(d_long) < 1.E-5) assert np.all(np.abs(d_alt) < 1.E-5)
def test_geocentric_to_ecef_to_geocentric(self): """Geocentric and ECEF transformations""" lats, longs, alts = gen_data_fixed_alt(550.) ecf_x, ecf_y, ecf_z = OMMBV.geocentric_to_ecef(lats, longs, alts) lat, elong, alt = OMMBV.ecef_to_geocentric(ecf_x, ecf_y, ecf_z) idx, = np.where(elong < 0) elong[idx] += 360. d_lat = lat - lats d_long = elong - longs d_alt = alt - alts assert np.all(np.abs(d_lat) < 1.E-5) assert np.all(np.abs(d_long) < 1.E-5) assert np.all(np.abs(d_alt) < 1.E-5)
def test_simple_geomagnetic_basis_interface(self): """Ensure simple geomagnetic basis interface runs.""" for i, p_lat in enumerate(self.lats): out_d = OMMBV.calculate_geomagnetic_basis( [p_lat] * len(self.longs), self.longs, self.alts, [self.date] * len(self.longs)) assert True return
def test_geodetic_to_ecef_to_geodetic_via_different_methods(self): """Multiple techniques for geodetic to ECEF to geodetic""" lats, longs, alts = gen_data_fixed_alt(550.) ecf_x, ecf_y, ecf_z = OMMBV.geodetic_to_ecef(lats, longs, alts) methods = ['closed', 'iterative'] for method in methods: lat, elong, alt = OMMBV.python_ecef_to_geodetic(ecf_x, ecf_y, ecf_z, method=method) idx, = np.where(elong < 0) elong[idx] += 360. d_lat = lat - lats d_long = elong - longs d_alt = alt - alts assert np.all(np.abs(d_lat) < 1.E-5) assert np.all(np.abs(d_long) < 1.E-5) assert np.all(np.abs(d_alt) < 1.E-5)
def test_igrf_ecef_to_geographic_with_colatitude(self): """Test IGRF_ECEF - Geographic""" lats, longs, alts = gen_data_fixed_alt(550.) ecf_x, ecf_y, ecf_z = OMMBV.geodetic_to_ecef(lats, longs, alts) for ecef_x, ecef_y, ecef_z, geo_lat, geo_lon, geo_alt in zip(ecf_x, ecf_y, ecf_z, lats, longs, alts): pos = np.array([ecef_x, ecef_y, ecef_z]) colat, lon, r = igrf.ecef_to_colat_long_r(pos) # results are returned in radians lat = 90. - np.rad2deg(colat) lon = np.rad2deg(lon) lat2, lon2, h2 = OMMBV.ecef_to_geocentric(*pos, ref_height=0) # print(lat, lon, r, lat2, lon2, h2) asseq(r, h2, 9) asseq(lat, lat2, 9) asseq(lon, lon2, 9)
def test_vectors_at_pole(self): """Ensure np.nan returned at magnetic pole for scalars and vectors.""" out = OMMBV.calculate_mag_drift_unit_vectors_ecef([80.97], [250.], [550.], [self.date], full_output=True, pole_tol=1.E-4) # Confirm vectors are np.nan assert np.all(np.isnan(out[0:3])) assert np.all(np.isnan(out[6:9])) for item in self.map_labels: assert np.isnan(out[-1][item]) return
def project_ecef_vector_onto_sc(inst, x_label, y_label, z_label, new_x_label, new_y_label, new_z_label, meta=None): """Express input vector using s/c attitude directions x - ram pointing y - generally southward z - generally nadir Parameters ---------- x_label : string Label used to get ECEF-X component of vector to be projected y_label : string Label used to get ECEF-Y component of vector to be projected z_label : string Label used to get ECEF-Z component of vector to be projected new_x_label : string Label used to set X component of projected vector new_y_label : string Label used to set Y component of projected vector new_z_label : string Label used to set Z component of projected vector meta : array_like of dicts (None) Dicts contain metadata to be assigned. """ # TODO: add checks for existence of ecef labels in inst x, y, z = OMMBV.project_ecef_vector_onto_basis( inst[x_label], inst[y_label], inst[z_label], inst['sc_xhat_ecef_x'], inst['sc_xhat_ecef_y'], inst['sc_xhat_ecef_z'], inst['sc_yhat_ecef_x'], inst['sc_yhat_ecef_y'], inst['sc_yhat_ecef_z'], inst['sc_zhat_ecef_x'], inst['sc_zhat_ecef_y'], inst['sc_zhat_ecef_z']) inst[new_x_label] = x inst[new_y_label] = y inst[new_z_label] = z if meta is not None: inst.meta[new_x_label] = meta[0] inst.meta[new_y_label] = meta[1] inst.meta[new_z_label] = meta[2] return
def test_apex_heights(self): """Check meridional vector along max in apex height gradient""" date = dt.datetime(2010, 1, 1) delta = 1. ecef_x, ecef_y, ecef_z = OMMBV.trans.geodetic_to_ecef([0.], [320.], [550.]) # Get basis vectors (zx, zy, zz, _, _, _, mx, my, mz) = OMMBV.calculate_mag_drift_unit_vectors_ecef(ecef_x, ecef_y, ecef_z, [date], ecef_input=True) # Get apex height for step along meridional directions, # then around that direction (_, _, _, _, _, nominal_max) = OMMBV.trace.apex_location_info(ecef_x + delta * mx, ecef_y + delta * my, ecef_z + delta * mz, [date], ecef_input=True, return_geodetic=True) steps = (np.arange(101) - 50.) * delta / 10000. output_max = [] for step in steps: del_x = delta * mx + step * zx del_y = delta * my + step * zy del_z = delta * mz + step * zz del_x, del_y, del_z = OMMBV.vector.normalize(del_x, del_y, del_z) (_, _, _, _, _, loop_h) = OMMBV.trace.apex_location_info(ecef_x + del_x, ecef_y + del_y, ecef_z + del_z, [date], ecef_input=True, return_geodetic=True) output_max.append(loop_h) # Make sure meridional direction is correct assert np.all(np.abs(np.max(output_max) - nominal_max) < 1.E-4) return
def test_geomag_efield_scalars(self, kwargs): """Test electric field and drift mapping values.""" data = {} for i, p_lat in enumerate(self.lats): templ = [p_lat] * len(self.longs) tempd = [self.date] * len(self.longs) scalars = OMMBV.scalars_for_mapping_ion_drifts( templ, self.longs, self.alts, tempd, **kwargs) for scalar in scalars: if scalar not in data: data[scalar] = np.full((len(self.lats), len(self.longs)), np.nan) data[scalar][i, :] = scalars[scalar] assert len(scalars.keys()) == 12 for scalar in scalars: assert np.all(np.isfinite(scalars[scalar])) return
def add_mag_drift_unit_vectors(inst, lat_label='latitude', long_label='longitude', alt_label='altitude', **kwargs): """Add unit vectors expressing the ion drift coordinate system organized by the geomagnetic field. Unit vectors are expressed in S/C coordinates. Interally, routine calls add_mag_drift_unit_vectors_ecef. See function for input parameter description. Requires the orientation of the S/C basis vectors in ECEF using naming, 'sc_xhat_x' where *hat (*=x,y,z) is the S/C basis vector and _* (*=x,y,z) is the ECEF direction. Parameters ---------- inst : pysat.Instrument object Instrument object to be modified max_steps : int Maximum number of steps taken for field line integration **kwargs Passed along to calculate_mag_drift_unit_vectors_ecef Returns ------- None Modifies instrument object in place. Adds 'unit_zon_*' where * = x,y,z 'unit_fa_*' and 'unit_mer_*' for zonal, field aligned, and meridional directions. Note that vector components are expressed in the S/C basis. """ # vectors are returned in geo/ecef coordinate system add_mag_drift_unit_vectors_ecef(inst, lat_label=lat_label, long_label=long_label, alt_label=alt_label, **kwargs) # convert them to S/C using transformation supplied by OA inst['unit_zon_x'], inst['unit_zon_y'], inst[ 'unit_zon_z'] = OMMBV.project_ecef_vector_onto_basis( inst['unit_zon_ecef_x'], inst['unit_zon_ecef_y'], inst['unit_zon_ecef_z'], inst['sc_xhat_x'], inst['sc_xhat_y'], inst['sc_xhat_z'], inst['sc_yhat_x'], inst['sc_yhat_y'], inst['sc_yhat_z'], inst['sc_zhat_x'], inst['sc_zhat_y'], inst['sc_zhat_z']) inst['unit_fa_x'], inst['unit_fa_y'], inst[ 'unit_fa_z'] = OMMBV.project_ecef_vector_onto_basis( inst['unit_fa_ecef_x'], inst['unit_fa_ecef_y'], inst['unit_fa_ecef_z'], inst['sc_xhat_x'], inst['sc_xhat_y'], inst['sc_xhat_z'], inst['sc_yhat_x'], inst['sc_yhat_y'], inst['sc_yhat_z'], inst['sc_zhat_x'], inst['sc_zhat_y'], inst['sc_zhat_z']) inst['unit_mer_x'], inst['unit_mer_y'], inst[ 'unit_mer_z'] = OMMBV.project_ecef_vector_onto_basis( inst['unit_mer_ecef_x'], inst['unit_mer_ecef_y'], inst['unit_mer_ecef_z'], inst['sc_xhat_x'], inst['sc_xhat_y'], inst['sc_xhat_z'], inst['sc_yhat_x'], inst['sc_yhat_y'], inst['sc_yhat_z'], inst['sc_zhat_x'], inst['sc_zhat_y'], inst['sc_zhat_z']) inst.meta['unit_zon_x'] = { 'long_name': 'Zonal direction along IVM-x', 'desc': 'Unit vector for the zonal geomagnetic direction.', 'label': 'Zonal Unit Vector: IVM-X component', 'axis': 'Zonal Unit Vector: IVM-X component', 'notes': ('The zonal vector is perpendicular to the ' 'local magnetic field and the magnetic meridional plane. ' 'The zonal vector maps to purely horizontal ' 'at the magnetic equator, with positive values ' 'pointed generally eastward. This vector ' 'is expressed here in the IVM instrument frame.' 'The IVM-x direction points along the instrument ' 'boresight, which is pointed into ram for ' 'standard operations.' 'Calculated using the corresponding unit vector ' 'in ECEF and the orientation ' 'of the IVM also expressed in ECEF (sc_*hat_*).'), 'scale': 'linear', 'units': '', 'value_min': -1., 'value_max': 1 } inst.meta['unit_zon_y'] = { 'long_name': 'Zonal direction along IVM-y', 'desc': 'Unit vector for the zonal geomagnetic direction.', 'label': 'Zonal Unit Vector: IVM-Y component', 'axis': 'Zonal Unit Vector: IVM-Y component', 'notes': ('The zonal vector is perpendicular to the ' 'local magnetic field and the magnetic meridional plane. ' 'The zonal vector maps to purely horizontal ' 'at the magnetic equator, with positive values ' 'pointed generally eastward. ' 'The unit vector is expressed here in the IVM coordinate system, ' 'where Y = Z x X, nominally southward when ' 'in standard pointing, X along ram. ' 'Calculated using the corresponding unit vector ' 'in ECEF and the orientation ' 'of the IVM also expressed in ECEF (sc_*hat_*).'), 'scale': 'linear', 'units': '', 'value_min': -1., 'value_max': 1 } inst.meta['unit_zon_z'] = { 'long_name': 'Zonal direction along IVM-z', 'desc': 'Unit vector for the zonal geomagnetic direction.', 'label': 'Zonal Unit Vector: IVM-Z component', 'axis': 'Zonal Unit Vector: IVM-Z component', 'notes': ('The zonal vector is perpendicular to the ' 'local magnetic field and the magnetic meridional plane. ' 'The zonal vector maps to purely horizontal ' 'at the magnetic equator, with positive values ' 'pointed generally eastward. This vector ' 'is expressed here in the IVM instrument frame.' 'The IVM-Z direction points towards nadir ' 'when IVM-X is pointed into ram for ' 'standard operations.' 'Calculated using the corresponding unit vector ' 'in ECEF and the orientation ' 'of the IVM also expressed in ECEF (sc_*hat_*).'), 'scale': 'linear', 'units': '', 'value_min': -1., 'value_max': 1 } inst.meta['unit_fa_x'] = { 'long_name': 'Field-aligned direction along IVM-x', 'desc': 'Unit vector for the geomagnetic field line direction.', 'label': 'Field Aligned Unit Vector: IVM-X component', 'axis': 'Field Aligned Unit Vector: IVM-X component', 'notes': ('The field-aligned vector points along the ' 'geomagnetic field, with positive values ' 'along the field direction, and is ' 'expressed here in the IVM instrument frame. ' 'The IVM-x direction points along the instrument ' 'boresight, which is pointed into ram for ' 'standard operations.' 'Calculated using the corresponding unit vector ' 'in ECEF and the orientation ' 'of the IVM also expressed in ECEF (sc_*hat_*).'), 'scale': 'linear', 'units': '', 'value_min': -1., 'value_max': 1 } inst.meta['unit_fa_y'] = { 'long_name': 'Field-aligned direction along IVM-y', 'desc': 'Unit vector for the geomagnetic field line direction.', 'label': 'Field Aligned Unit Vector: IVM-Y component', 'axis': 'Field Aligned Unit Vector: IVM-Y component', 'notes': ('The field-aligned vector points along the ' 'geomagnetic field, with positive values ' 'along the field direction. ' 'The unit vector is expressed here in the IVM coordinate system, ' 'where Y = Z x X, nominally southward when ' 'in standard pointing, X along ram. ' 'Calculated using the corresponding unit vector ' 'in ECEF and the orientation ' 'of the IVM also expressed in ECEF (sc_*hat_*).'), 'scale': 'linear', 'units': '', 'value_min': -1., 'value_max': 1 } inst.meta['unit_fa_z'] = { 'long_name': 'Field-aligned direction along IVM-z', 'desc': 'Unit vector for the geomagnetic field line direction.', 'label': 'Field Aligned Unit Vector: IVM-Z component', 'axis': 'Field Aligned Unit Vector: IVM-Z component', 'notes': ('The field-aligned vector points along the ' 'geomagnetic field, with positive values ' 'along the field direction, and is ' 'expressed here in the IVM instrument frame. ' 'The IVM-Z direction points towards nadir ' 'when IVM-X is pointed into ram for ' 'standard operations.' 'Calculated using the corresponding unit vector ' 'in ECEF and the orientation ' 'of the IVM also expressed in ECEF (sc_*hat_*).'), 'scale': 'linear', 'units': '', 'value_min': -1., 'value_max': 1 } inst.meta['unit_mer_x'] = { 'long_name': 'Meridional direction along IVM-x', 'desc': 'Unit vector for the geomagnetic meridional direction.', 'label': 'Meridional Unit Vector: IVM-X component', 'axis': 'Meridional Unit Vector: IVM-X component', 'notes': ('The meridional unit vector is perpendicular to the geomagnetic field ' 'and maps along magnetic field lines to vertical ' 'at the magnetic equator, where positive is up. ' 'The unit vector is expressed here in the IVM coordinate system, ' 'where x is along the IVM boresight, nominally along ram when ' 'in standard pointing. ' 'Calculated using the corresponding unit vector in ECEF and the orientation ' 'of the IVM also expressed in ECEF (sc_*hat_*).'), 'scale': 'linear', 'units': '', 'value_min': -1., 'value_max': 1 } inst.meta['unit_mer_y'] = { 'long_name': 'Meridional direction along IVM-y', 'desc': 'Unit vector for the geomagnetic meridional direction.', 'label': 'Meridional Unit Vector: IVM-Y component', 'axis': 'Meridional Unit Vector: IVM-Y component', 'notes': ('The meridional unit vector is perpendicular to the geomagnetic field ' 'and maps along magnetic field lines to vertical ' 'at the magnetic equator, where positive is up. ' 'The unit vector is expressed here in the IVM coordinate system, ' 'where Y = Z x X, nominally southward when ' 'in standard pointing, X along ram. ' 'Calculated using the corresponding unit vector in ECEF and the orientation ' 'of the IVM also expressed in ECEF (sc_*hat_*).'), 'scale': 'linear', 'units': '', 'value_min': -1., 'value_max': 1 } inst.meta['unit_mer_z'] = { 'long_name': 'Meridional direction along IVM-z', 'desc': 'Unit vector for the geomagnetic meridional direction.', 'label': 'Meridional Unit Vector: IVM-Z component', 'axis': 'Meridional Unit Vector: IVM-Z component', 'notes': ('The meridional unit vector is perpendicular to the geomagnetic field ' 'and maps along magnetic field lines to vertical ' 'at the magnetic equator, where positive is up. ' 'The unit vector is expressed here in the IVM coordinate system, ' 'where Z is nadir pointing (towards Earth), ' 'when in standard pointing, X along ram. ' 'Calculated using the corresponding unit vector in ECEF and the orientation ' 'of the IVM also expressed in ECEF (sc_*hat_*).'), 'scale': 'linear', 'units': '', 'value_min': -1., 'value_max': 1 } return
def add_mag_drift_unit_vectors_ecef(inst, lat_label='latitude', long_label='longitude', alt_label='altitude', **kwargs): """Adds unit vectors expressing the ion drift coordinate system organized by the geomagnetic field. Unit vectors are expressed in ECEF coordinates. Parameters ---------- inst : pysat.Instrument Instrument object that will get unit vectors **kwargs Passed along to calculate_mag_drift_unit_vectors_ecef Returns ------- None unit vectors are added to the passed Instrument object with a naming scheme: 'unit_zon_ecef_*' : unit zonal vector, component along ECEF-(X,Y,or Z) 'unit_fa_ecef_*' : unit field-aligned vector, component along ECEF-(X,Y,or Z) 'unit_mer_ecef_*' : unit meridional vector, component along ECEF-(X,Y,or Z) """ # add unit vectors for magnetic drifts in ecef coordinates zvx, zvy, zvz, bx, by, bz, mx, my, mz = OMMBV.calculate_mag_drift_unit_vectors_ecef( inst[lat_label], inst[long_label], inst[alt_label], inst.index, **kwargs) inst['unit_zon_ecef_x'] = zvx inst['unit_zon_ecef_y'] = zvy inst['unit_zon_ecef_z'] = zvz inst['unit_fa_ecef_x'] = bx inst['unit_fa_ecef_y'] = by inst['unit_fa_ecef_z'] = bz inst['unit_mer_ecef_x'] = mx inst['unit_mer_ecef_y'] = my inst['unit_mer_ecef_z'] = mz inst.meta['unit_zon_ecef_x'] = { 'long_name': 'Zonal unit vector along ECEF-x', 'desc': 'Zonal unit vector along ECEF-x', 'label': 'Zonal unit vector along ECEF-x', 'notes': ('Magnetic zonal unit vector expressed using ' 'Earth Centered Earth Fixed (ECEF) basis. ' 'Vector system is calculated by determining the ' 'vector direction that, ' 'when mapped to the apex, is purely horizontal. ' 'This component is along the ECEF-x direction.'), 'axis': 'Zonal unit vector along ECEF-x', 'value_min': -1., 'value_max': 1., } inst.meta['unit_zon_ecef_y'] = { 'long_name': 'Zonal unit vector along ECEF-y', 'desc': 'Zonal unit vector along ECEF-y', 'label': 'Zonal unit vector along ECEF-y', 'notes': ('Magnetic zonal unit vector expressed using ' 'Earth Centered Earth Fixed (ECEF) basis. ' 'Vector system is calculated by determining the ' 'vector direction that, ' 'when mapped to the apex, is purely horizontal. ' 'This component is along the ECEF-y direction.'), 'axis': 'Zonal unit vector along ECEF-y', 'value_min': -1., 'value_max': 1., } inst.meta['unit_zon_ecef_z'] = { 'long_name': 'Zonal unit vector along ECEF-z', 'desc': 'Zonal unit vector along ECEF-z', 'label': 'Zonal unit vector along ECEF-z', 'notes': ('Magnetic zonal unit vector expressed using ' 'Earth Centered Earth Fixed (ECEF) basis. ' 'Vector system is calculated by determining the ' 'vector direction that, ' 'when mapped to the apex, is purely horizontal. ' 'This component is along the ECEF-z direction.'), 'axis': 'Zonal unit vector along ECEF-z', 'value_min': -1., 'value_max': 1., } inst.meta['unit_fa_ecef_x'] = { 'long_name': 'Field-aligned unit vector along ECEF-x', 'desc': 'Field-aligned unit vector along ECEF-x', 'label': 'Field-aligned unit vector along ECEF-x', 'notes': ('Field-aligned unit vector expressed using ' 'Earth Centered Earth Fixed (ECEF) basis. ' 'This component is along the ECEF-x direction.'), 'axis': 'Field-aligned unit vector along ECEF-x', 'value_min': -1., 'value_max': 1., } inst.meta['unit_fa_ecef_y'] = { 'long_name': 'Field-aligned unit vector along ECEF-y', 'desc': 'Field-aligned unit vector along ECEF-y', 'label': 'Field-aligned unit vector along ECEF-y', 'notes': ('Field-aligned unit vector expressed using ' 'Earth Centered Earth Fixed (ECEF) basis. ' 'This component is along the ECEF-y direction.'), 'axis': 'Field-aligned unit vector along ECEF-y', 'value_min': -1., 'value_max': 1., } inst.meta['unit_fa_ecef_z'] = { 'long_name': 'Field-aligned unit vector along ECEF-z', 'desc': 'Field-aligned unit vector along ECEF-z', 'label': 'Field-aligned unit vector along ECEF-z', 'notes': ('Field-aligned unit vector expressed using ' 'Earth Centered Earth Fixed (ECEF) basis. ' 'This component is along the ECEF-z direction.'), 'value_min': -1., 'value_max': 1., } inst.meta['unit_mer_ecef_x'] = { 'long_name': 'Meridional unit vector along ECEF-x', 'desc': 'Meridional unit vector along ECEF-x', 'label': 'Meridional unit vector along ECEF-x', 'notes': ('Magnetic meridional unit vector expressed using ' 'Earth Centered Earth Fixed (ECEF) basis. ' 'Vector system is calculated by determining the ' 'magnetic zonal vector direction that, ' 'when mapped to the apex, is purely horizontal. ' 'The meridional vector is perpendicular to the zonal ' 'and field-aligned directions. ' 'This component is along the ECEF-x direction.'), 'axis': 'Meridional unit vector along ECEF-x', 'value_min': -1., 'value_max': 1., } inst.meta['unit_mer_ecef_y'] = { 'long_name': 'Meridional unit vector along ECEF-y', 'desc': 'Meridional unit vector along ECEF-y', 'label': 'Meridional unit vector along ECEF-y', 'notes': ('Magnetic meridional unit vector expressed using ' 'Earth Centered Earth Fixed (ECEF) basis. ' 'Vector system is calculated by determining the ' 'magnetic zonal vector direction that, ' 'when mapped to the apex, is purely horizontal. ' 'The meridional vector is perpendicular to the zonal ' 'and field-aligned directions. ' 'This component is along the ECEF-y direction.'), 'axis': 'Meridional unit vector along ECEF-y', 'value_min': -1., 'value_max': 1., } inst.meta['unit_mer_ecef_z'] = { 'long_name': 'Meridional unit vector along ECEF-z', 'desc': 'Meridional unit vector along ECEF-z', 'label': 'Meridional unit vector along ECEF-z', 'notes': ('Magnetic meridional unit vector expressed using ' 'Earth Centered Earth Fixed (ECEF) basis. ' 'Vector system is calculated by determining the ' 'magnetic zonal vector direction that, ' 'when mapped to the apex, is purely horizontal. ' 'The meridional vector is perpendicular to the zonal ' 'and field-aligned directions. ' 'This component is along the ECEF-z direction.'), 'axis': 'Meridional unit vector along ECEF-z', 'value_min': -1., 'value_max': 1., } return
def load(fnames, tag=None, inst_id=None, obs_long=0., obs_lat=0., obs_alt=0., TLE1=None, TLE2=None, num_samples=None, cadence='1S'): """ Returns data and metadata in the format required by pysat. Generates position of satellite in both geographic and ECEF co-ordinates. Routine is directly called by pysat and not the user. Parameters ---------- fnames : list List of filenames tag : str or NoneType Identifies a particular subset of satellite data (accepts '') (default=None) inst_id : str or NoneType Instrument satellite ID (accepts '') (default=None) obs_long: float Longitude of the observer on the Earth's surface (default=0.) obs_lat: float Latitude of the observer on the Earth's surface (default=0.) obs_alt: float Altitude of the observer on the Earth's surface (default=0.) TLE1 : string or NoneType First string for Two Line Element. Must be in TLE format (default=None) TLE2 : string or NoneType Second string for Two Line Element. Must be in TLE format (default=None) num_samples : int or NoneType Number of samples per day (default=None) cadence : str Uses pandas.frequency string formatting ('1S', etc) (default='1S') Returns ------- data : pandas.DataFrame Object containing satellite data meta : pysat.Meta Object containing metadata such as column names and units Example ------- :: TLE1='1 25544U 98067A 18135.61844383 .00002728 00000-0 48567-4 0 9998' TLE2='2 25544 51.6402 181.0633 0004018 88.8954 22.2246 15.54059185113452' inst = pysat.Instrument('pysat', 'ephem', TLE1=TLE1, TLE2=TLE2) inst.load(2018, 1) """ # TLEs (Two Line Elements for ISS) # format of TLEs is fixed and available from wikipedia... # lines encode list of orbital elements of an Earth-orbiting object # for a given point in time line1 = ''.join(('1 25544U 98067A 18135.61844383 .00002728 00000-0 ', '48567-4 0 9998')) line2 = ''.join(('2 25544 51.6402 181.0633 0004018 88.8954 22.2246 ', '15.54059185113452')) # Use ISS defaults if not provided by user if TLE1 is not None: line1 = TLE1 if TLE2 is not None: line2 = TLE2 if num_samples is None: num_samples = 100 # Extract list of times from filenames and inst_id times, index, dates = ps_meth.generate_times(fnames, num_samples, freq=cadence) # The observer's (ground station) position on the Earth surface site = ephem.Observer() site.lon = str(obs_long) site.lat = str(obs_lat) site.elevation = obs_alt # The first parameter in readtle() is the satellite name sat = ephem.readtle('pysat', line1, line2) output_params = [] for timestep in index: lp = {} site.date = timestep sat.compute(site) # Parameters relative to the ground station lp['obs_sat_az_angle'] = ephem.degrees(sat.az) lp['obs_sat_el_angle'] = ephem.degrees(sat.alt) # Total distance between transmitter and receiver lp['obs_sat_slant_range'] = sat.range # Satellite location (sub-latitude and sub-longitude) lp['glat'] = np.degrees(sat.sublat) lp['glong'] = np.degrees(sat.sublong) # Elevation of satellite in m, converted to km lp['alt'] = sat.elevation / 1000.0 # Get ECEF position of satellite lp['x'], lp['y'], lp['z'] = OMMBV.geodetic_to_ecef(lp['glat'], lp['glong'], lp['alt']) output_params.append(lp) output = pds.DataFrame(output_params, index=index) # Modify input object to include calculated parameters # Put data into DataFrame data = pds.DataFrame({'glong': output['glong'], 'glat': output['glat'], 'alt': output['alt'], 'position_ecef_x': output['x'], 'position_ecef_y': output['y'], 'position_ecef_z': output['z'], 'obs_sat_az_angle': output['obs_sat_az_angle'], 'obs_sat_el_angle': output['obs_sat_el_angle'], 'obs_sat_slant_range': output['obs_sat_slant_range']}, index=index) data.index.name = 'Epoch' return data, meta.copy()
def test_field_line_tracing_against_vitmo(self): """Compare model to http://omniweb.gsfc.nasa.gov/vitmo/cgm_vitmo.html""" # convert position to ECEF ecf_x, ecf_y, ecf_z = OMMBV.geocentric_to_ecef(omni['p_lat'], omni['p_long'], omni['p_alt']) trace_n = [] trace_s = [] date = datetime.datetime(2000, 1, 1) for x, y, z in zip(ecf_x, ecf_y, ecf_z): # trace north and south, take last points trace_n.append( OMMBV.field_line_trace(np.array([x, y, z]), date, 1., 0., step_size=0.5, max_steps=1.E6)[-1, :]) trace_s.append( OMMBV.field_line_trace(np.array([x, y, z]), date, -1., 0., step_size=0.5, max_steps=1.E6)[-1, :]) trace_n = pds.DataFrame(trace_n, columns=['x', 'y', 'z']) trace_n['lat'], trace_n['long'], trace_n['altitude'] = OMMBV.ecef_to_geocentric(trace_n['x'], trace_n['y'], trace_n['z']) trace_s = pds.DataFrame(trace_s, columns=['x', 'y', 'z']) trace_s['lat'], trace_s['long'], trace_s['altitude'] = OMMBV.ecef_to_geocentric(trace_s['x'], trace_s['y'], trace_s['z']) # ensure longitudes are all 0-360 idx, = np.where(omni['n_long'] < 0) omni.ix[idx, 'n_long'] += 360. idx, = np.where(omni['s_long'] < 0) omni.ix[idx, 's_long'] += 360. idx, = np.where(trace_n['long'] < 0) trace_n.ix[idx, 'long'] += 360. idx, = np.where(trace_s['long'] < 0) trace_s.ix[idx, 'long'] += 360. # compute difference between OMNI and local calculation # there is a difference near 0 longitude, ignore this area diff_n_lat = (omni['n_lat'] - trace_n['lat'])[4:-4] diff_n_lon = (omni['n_long'] - trace_n['long'])[4:-4] diff_s_lat = (omni['s_lat'] - trace_s['lat'])[4:-4] diff_s_lon = (omni['s_long'] - trace_s['long'])[4:-4] try: f = plt.figure() plt.plot(omni['n_long'], omni['n_lat'], 'r.', label='omni') plt.plot(omni['s_long'], omni['s_lat'], 'r.', label='_omni') plt.plot(trace_n['long'], trace_n['lat'], 'b.', label='UTD') plt.plot(trace_s['long'], trace_s['lat'], 'b.', label='_UTD') plt.title('Comparison of Magnetic Footpoints for Field Lines through 20 Lat, 550 km') plt.xlabel('Geographic Longitude') plt.ylabel('Geographic Latitude') plt.legend(loc=0) plt.xlim((0, 360.)) plt.savefig('magnetic_footpoint_comparison.pdf') print('Saving magnetic_footpoint_comparison.pdf') except: pass # better than 0.5 km accuracy expected for settings above assert np.all(np.nanstd(diff_n_lat) < .5) assert np.all(np.nanstd(diff_n_lon) < .5) assert np.all(np.nanstd(diff_s_lat) < .5) assert np.all(np.nanstd(diff_s_lon) < .5)
def test_ecef_geodetic_apex_diff_plots(self): """Characterize uncertainty of ECEF and Geodetic transformations""" import matplotlib.pyplot as plt # on_travis = os.environ.get('ONTRAVIS') == 'True' p_lats, p_longs, p_alts = gen_plot_grid_fixed_alt(550.) # data returned are the locations along each direction # the full range of points obtained by iterating over all # recasting alts into a more convenient form for later calculation p_alts = [p_alts[0]] * len(p_longs) # set the date date = datetime.datetime(2000, 1, 1) # memory for results apex_x = np.zeros((len(p_lats), len(p_longs) + 1)) apex_y = np.zeros((len(p_lats), len(p_longs) + 1)) apex_z = np.zeros((len(p_lats), len(p_longs) + 1)) apex_alt = np.zeros((len(p_lats), len(p_longs) + 1)) norm_alt = np.zeros((len(p_lats), len(p_longs) + 1)) # set up multi if self.dc is not None: import itertools targets = itertools.cycle(dc.ids) pending = [] for i, p_lat in enumerate(p_lats): print (i, p_lat) # iterate through target cyclicly and run commands dview.targets = next(targets) pending.append(dview.apply_async(OMMBV.geodetic_to_ecef, np.array([p_lat] * len(p_longs)), p_longs, p_alts)) for i, p_lat in enumerate(p_lats): print ('collecting ', i, p_lat) # collect output x, y, z = pending.pop(0).get() # iterate through target cyclicly and run commands dview.targets = next(targets) pending.append(dview.apply_async(OMMBV.python_ecef_to_geodetic, x, y, z)) for i, p_lat in enumerate(p_lats): print ('collecting 2', i, p_lat) # collect output lat2, lon2, alt2 = pending.pop(0).get() # iterate through target cyclicly and run commands dview.targets = next(targets) pending.append(dview.apply_async(OMMBV.apex_location_info, np.array([p_lat] * len(p_longs)), p_longs, p_alts, [date] * len(p_longs), return_geodetic=True)) pending.append(dview.apply_async(OMMBV.apex_location_info, lat2, lon2, alt2, [date] * len(p_longs), return_geodetic=True)) for i, p_lat in enumerate(p_lats): print ('collecting 3', i, p_lat) x, y, z, _, _, h = pending.pop(0).get() x2, y2, z2, _, _, h2 = pending.pop(0).get() norm_alt[i, :-1] = np.abs(h) apex_x[i, :-1] = np.abs(x2 - x) apex_y[i, :-1] = np.abs(y2 - y) apex_z[i, :-1] = np.abs(z2 - z) apex_alt[i, :-1] = np.abs(h2 - h) else: # single processor case for i, p_lat in enumerate(p_lats): print (i, p_lat) x, y, z = OMMBV.geodetic_to_ecef([p_lat] * len(p_longs), p_longs, p_alts) lat2, lon2, alt2 = OMMBV.ecef_to_geodetic(x, y, z) x2, y2, z2 = OMMBV.geodetic_to_ecef(lat2, lon2, alt2) apex_x[i, :-1] = np.abs(x2 - x) apex_y[i, :-1] = np.abs(y2 - y) apex_z[i, :-1] = np.abs(z2 - z) # account for periodicity apex_x[:, -1] = apex_x[:, 0] apex_y[:, -1] = apex_y[:, 0] apex_z[:, -1] = apex_z[:, 0] apex_alt[:, -1] = apex_alt[:, 0] norm_alt[:, -1] = norm_alt[:, 0] ytickarr = np.array([0, 0.25, 0.5, 0.75, 1]) * (len(p_lats) - 1) xtickarr = np.array([0, 0.2, 0.4, 0.6, 0.8, 1]) * len(p_longs) ytickvals = ['-50', '-25', '0', '25', '50'] try: fig = plt.figure() plt.imshow(np.log10(apex_x), origin='lower') plt.colorbar() plt.yticks(ytickarr, ytickvals) plt.xticks(xtickarr, ['0', '72', '144', '216', '288', '360']) plt.title('Log ECEF-Geodetic Apex Difference (ECEF-x km)') plt.xlabel('Geodetic Longitude (Degrees)') plt.ylabel('Geodetic Latitude (Degrees)') plt.savefig('ecef_geodetic_apex_diff_x.pdf') plt.close() fig = plt.figure() plt.imshow(np.log10(apex_y), origin='lower') plt.colorbar() plt.yticks(ytickarr, ytickvals) plt.xticks(xtickarr, ['0', '72', '144', '216', '288', '360']) plt.title('Log ECEF-Geodetic Apex Difference (ECEF-y km)') plt.xlabel('Geodetic Longitude (Degrees)') plt.ylabel('Geodetic Latitude (Degrees)') plt.savefig('ecef_geodetic_apex_diff_y.pdf') plt.close() fig = plt.figure() plt.imshow(np.log10(apex_z), origin='lower') plt.colorbar() plt.yticks(ytickarr, ytickvals) plt.xticks(xtickarr, ['0', '72', '144', '216', '288', '360']) plt.title('Log ECEF-Geodetic Apex Difference (ECEF-z km)') plt.xlabel('Geodetic Longitude (Degrees)') plt.ylabel('Geodetic Latitude (Degrees)') plt.savefig('ecef_geodetic_apex_diff_z.pdf') plt.close() fig = plt.figure() plt.imshow(np.log10(apex_alt), origin='lower') plt.colorbar() plt.yticks(ytickarr, ytickvals) plt.xticks(xtickarr, ['0', '72', '144', '216', '288', '360']) plt.title('Log ECEF-Geodetic Apex Altitude Difference (km)') plt.xlabel('Geodetic Longitude (Degrees)') plt.ylabel('Geodetic Latitude (Degrees)') plt.savefig('ecef_geodetic_apex_diff_h.pdf') plt.close() fig = plt.figure() plt.imshow(np.log10(apex_alt / norm_alt), origin='lower') plt.colorbar() plt.yticks(ytickarr, ytickvals) plt.xticks(xtickarr, ['0', '72', '144', '216', '288', '360']) plt.title('Log ECEF-Geodetic Apex Normalized Altitude Difference (km)') plt.xlabel('Geodetic Longitude (Degrees)') plt.ylabel('Geodetic Latitude (Degrees)') plt.savefig('ecef_geodetic_apex_norm_diff_h.pdf') plt.close() except: pass
def test_apex_info_accuracy(self): """Characterize performance of apex_location_info as fine_step_size varied""" lats, longs, alts = gen_trace_data_fixed_alt(550.) ecf_x, ecf_y, ecf_z = OMMBV.geodetic_to_ecef(lats, longs, alts) # step size to be tried fine_steps_goal = np.array([25.6, 12.8, 6.4, 3.2, 1.6, 0.8, 0.4, 0.2, 0.1, 0.05, .025, .0125, .00625, .003125, .0015625, .00078125, .000390625, .0001953125, .0001953125 / 2., .0001953125 / 4., .0001953125 / 8., .0001953125 / 16., .0001953125 / 32., .0001953125 / 64., .0001953125 / 128., .0001953125 / 256., .0001953125 / 512., .0001953125 / 1024., .0001953125 / 2048., .0001953125 / 4096.]) date = datetime.datetime(2000, 1, 1) dx = [] dy = [] dz = [] dh = [] # set up multi if self.dc is not None: import itertools targets = itertools.cycle(dc.ids) pending = [] for lat, lon, alt in zip(lats, longs, alts): for steps in fine_steps_goal: # iterate through target cyclicly and run commands dview.targets = next(targets) pending.append(dview.apply_async(OMMBV.apex_location_info, [lat], [lon], [alt], [date], fine_step_size=steps, return_geodetic=True)) out = [] for steps in fine_steps_goal: # collect output x, y, z, _, _, apex_height = pending.pop(0).get() pt = [x[0], y[0], z[0], apex_height[0]] out.append(pt) final_pt = pds.DataFrame(out, columns=['x', 'y', 'z', 'h']) dx.append(np.abs(final_pt.loc[1:, 'x'].values - final_pt.loc[:, 'x'].values[:-1])) dy.append(np.abs(final_pt.loc[1:, 'y'].values - final_pt.loc[:, 'y'].values[:-1])) dz.append(np.abs(final_pt.loc[1:, 'z'].values - final_pt.loc[:, 'z'].values[:-1])) dh.append(np.abs(final_pt.loc[1:, 'h'].values - final_pt.loc[:, 'h'].values[:-1])) else: for lat, lon, alt in zip(lats, longs, alts): out = [] for steps in fine_steps_goal: x, y, z, _, _, apex_height = OMMBV.apex_location_info([lat], [lon], [alt], [date], fine_step_size=steps, return_geodetic=True) pt = [x[0], y[0], z[0], apex_height[0]] out.append(pt) final_pt = pds.DataFrame(out, columns=['x', 'y', 'z', 'h']) dx.append(np.abs(final_pt.loc[1:, 'x'].values - final_pt.loc[:, 'x'].values[:-1])) dy.append(np.abs(final_pt.loc[1:, 'y'].values - final_pt.loc[:, 'y'].values[:-1])) dz.append(np.abs(final_pt.loc[1:, 'z'].values - final_pt.loc[:, 'z'].values[:-1])) dh.append(np.abs(final_pt.loc[1:, 'h'].values - final_pt.loc[:, 'h'].values[:-1])) dx = pds.DataFrame(dx) dy = pds.DataFrame(dy) dz = pds.DataFrame(dz) dh = pds.DataFrame(dh) try: plt.figure() yerrx = np.nanstd(np.log10(dx), axis=0) yerry = np.nanstd(np.log10(dy), axis=0) yerrz = np.nanstd(np.log10(dz), axis=0) yerrh = np.nanstd(np.log10(dh), axis=0) plt.errorbar(np.log10(fine_steps_goal[1:]), np.log10(dx.mean(axis=0)), yerr=yerrx, label='x') plt.errorbar(np.log10(fine_steps_goal[1:]), np.log10(dy.mean(axis=0)), yerr=yerry, label='y') plt.errorbar(np.log10(fine_steps_goal[1:]), np.log10(dz.mean(axis=0)), yerr=yerrz, label='z') plt.errorbar(np.log10(fine_steps_goal[1:]), np.log10(dh.mean(axis=0)), yerr=yerrh, label='h') plt.xlabel('Log Step Size (km)') plt.ylabel('Change in Apex Position (km)') plt.title("Change in Field Apex Position vs Fine Step Size") plt.legend() plt.tight_layout() plt.savefig('apex_location_vs_step_size.pdf') plt.close() except: pass
def test_apex_fine_max_step_diff_plots(self): """Test apex location info for sensitivity to fine_steps parameters""" import matplotlib.pyplot as plt # on_travis = os.environ.get('ONTRAVIS') == 'True' p_lats, p_longs, p_alts = gen_plot_grid_fixed_alt(550.) # data returned are the locations along each direction # the full range of points obtained by iterating over all # recasting alts into a more convenient form for later calculation p_alts = [p_alts[0]] * len(p_longs) # set the date date = datetime.datetime(2000, 1, 1) # memory for results apex_lat = np.zeros((len(p_lats), len(p_longs) + 1)) apex_lon = np.zeros((len(p_lats), len(p_longs) + 1)) apex_alt = np.zeros((len(p_lats), len(p_longs) + 1)) apex_z = np.zeros((len(p_lats), len(p_longs) + 1)) norm_alt = np.zeros((len(p_lats), len(p_longs) + 1)) # set up multi if self.dc is not None: import itertools targets = itertools.cycle(dc.ids) pending = [] for i, p_lat in enumerate(p_lats): print (i, p_lat) # iterate through target cyclicly and run commands dview.targets = next(targets) pending.append(dview.apply_async(OMMBV.apex_location_info, [p_lat] * len(p_longs), p_longs, p_alts, [date] * len(p_longs), fine_max_steps=5, return_geodetic=True)) pending.append(dview.apply_async(OMMBV.apex_location_info, [p_lat] * len(p_longs), p_longs, p_alts, [date] * len(p_longs), fine_max_steps=10, return_geodetic=True)) for i, p_lat in enumerate(p_lats): print ('collecting ', i, p_lat) # collect output x, y, z, _, _, h = pending.pop(0).get() x2, y2, z2, _, _, h2 = pending.pop(0).get() apex_lat[i, :-1] = np.abs(x2 - x) apex_lon[i, :-1] = np.abs(y2 - y) apex_z[i, :-1] = np.abs(z2 - z) apex_alt[i, :-1] = np.abs(h2 - h) else: # single processor case for i, p_lat in enumerate(p_lats): print (i, p_lat) x, y, z, _, _, h = OMMBV.apex_location_info([p_lat] * len(p_longs), p_longs, p_alts, [date] * len(p_longs), fine_max_steps=5, return_geodetic=True) x2, y2, z2, _, _, h2 = OMMBV.apex_location_info([p_lat] * len(p_longs), p_longs, p_alts, [date] * len(p_longs), fine_max_steps=10, return_geodetic=True) norm_alt[i, :-1] = h apex_lat[i, :-1] = np.abs(x2 - x) apex_lon[i, :-1] = np.abs(y2 - y) apex_z[i, :-1] = np.abs(z2 - z) apex_alt[i, :-1] = np.abs(h2 - h) # account for periodicity apex_lat[:, -1] = apex_lat[:, 0] apex_lon[:, -1] = apex_lon[:, 0] apex_z[:, -1] = apex_z[:, 0] apex_alt[:, -1] = apex_alt[:, 0] norm_alt[:, -1] = norm_alt[:, 0] idx, idy, = np.where(apex_lat > 10.) print('Locations with large apex x (ECEF) location differences.', p_lats[idx], p_longs[idx]) ytickarr = np.array([0, 0.25, 0.5, 0.75, 1]) * (len(p_lats) - 1) xtickarr = np.array([0, 0.2, 0.4, 0.6, 0.8, 1]) * len(p_longs) ytickvals = ['-50', '-25', '0', '25', '50'] try: fig = plt.figure() plt.imshow(np.log10(apex_lat), origin='lower') plt.colorbar() plt.yticks(ytickarr, ytickvals) plt.xticks(xtickarr, ['0', '72', '144', '216', '288', '360']) plt.title('Log Apex Location Difference (ECEF-x km)') plt.xlabel('Geodetic Longitude (Degrees)') plt.ylabel('Geodetic Latitude (Degrees)') plt.savefig('apex_loc_max_steps_diff_x.pdf') plt.close() fig = plt.figure() plt.imshow(np.log10(apex_lon), origin='lower') plt.colorbar() plt.yticks(ytickarr, ytickvals) plt.xticks(xtickarr, ['0', '72', '144', '216', '288', '360']) plt.title('Log Apex Location Difference (ECEF-y km)') plt.xlabel('Geodetic Longitude (Degrees)') plt.ylabel('Geodetic Latitude (Degrees)') plt.savefig('apex_loc_max_steps_diff_y.pdf') plt.close() fig = plt.figure() plt.imshow(np.log10(apex_z), origin='lower') plt.colorbar() plt.yticks(ytickarr, ytickvals) plt.xticks(xtickarr, ['0', '72', '144', '216', '288', '360']) plt.title('Log Apex Location Difference (ECEF-z km)') plt.xlabel('Geodetic Longitude (Degrees)') plt.ylabel('Geodetic Latitude (Degrees)') plt.savefig('apex_loc_max_steps_diff_z.pdf') plt.close() fig = plt.figure() plt.imshow(np.log10(apex_alt / norm_alt), origin='lower') plt.colorbar() plt.yticks(ytickarr, ytickvals) plt.xticks(xtickarr, ['0', '72', '144', '216', '288', '360']) plt.title('Log Apex Altitude Normalized Difference (km)') plt.xlabel('Geodetic Longitude (Degrees)') plt.ylabel('Geodetic Latitude (Degrees)') plt.savefig('apex_norm_loc_max_steps_diff_h.pdf') plt.close() fig = plt.figure() plt.imshow(np.log10(apex_alt), origin='lower') plt.colorbar() plt.yticks(ytickarr, ytickvals) plt.xticks(xtickarr, ['0', '72', '144', '216', '288', '360']) plt.title('Log Apex Altitude Normalized Difference (km)') plt.xlabel('Geodetic Longitude (Degrees)') plt.ylabel('Geodetic Latitude (Degrees)') plt.savefig('apex_loc_max_steps_diff_h.pdf') plt.close() except: pass
def test_apex_plots(self): """Plot basic apex parameters""" import matplotlib.pyplot as plt p_lats, p_longs, p_alts = gen_plot_grid_fixed_alt(120.) # data returned are the locations along each direction # the full range of points obtained by iterating over all # recasting alts into a more convenient form for later calculation p_alts = [p_alts[0]] * len(p_longs) # set the date date = datetime.datetime(2000, 1, 1) # memory for results apex_lat = np.zeros((len(p_lats), len(p_longs) + 1)) apex_lon = np.zeros((len(p_lats), len(p_longs) + 1)) apex_alt = np.zeros((len(p_lats), len(p_longs) + 1)) # set up multi if self.dc is not None: import itertools targets = itertools.cycle(dc.ids) pending = [] for i, p_lat in enumerate(p_lats): print (i, p_lat) # iterate through target cyclicly and run commands dview.targets = next(targets) pending.append(dview.apply_async(OMMBV.apex_location_info, [p_lat] * len(p_longs), p_longs, p_alts, [date] * len(p_longs), return_geodetic=True)) for i, p_lat in enumerate(p_lats): print ('collecting ', i, p_lat) # collect output x, y, z, olat, olon, oalt = pending.pop(0).get() apex_lat[i, :-1] = olat apex_lon[i, :-1] = olon apex_alt[i, :-1] = oalt else: # single processor case for i, p_lat in enumerate(p_lats): print (i, p_lat) x, y, z, olat, olon, oalt = OMMBV.apex_location_info([p_lat] * len(p_longs), p_longs, p_alts, [date] * len(p_longs), return_geodetic=True) apex_lat[i, :-1] = olat apex_lon[i, :-1] = olon apex_alt[i, :-1] = oalt # calculate difference between apex longitude and original longitude # values for apex long are -180 to 180, shift to 0 to 360 # process degrees a bit to make the degree difference the most meaningful (close to 0) idx, idy, = np.where(apex_lon < 0.) apex_lon[idx, idy] += 360. idx, idy, = np.where(apex_lon >= 360.) apex_lon[idx, idy] -= 360. apex_lon[:, :-1] -= p_longs idx, idy, = np.where(apex_lon > 180.) apex_lon[idx, idy] -= 360. idx, idy, = np.where(apex_lon <= -180.) apex_lon[idx, idy] += 360. # account for periodicity apex_lat[:, -1] = apex_lat[:, 0] apex_lon[:, -1] = apex_lon[:, 0] apex_alt[:, -1] = apex_alt[:, 0] ytickarr = np.array([0, 0.25, 0.5, 0.75, 1]) * (len(p_lats) - 1) xtickarr = np.array([0, 0.2, 0.4, 0.6, 0.8, 1]) * len(p_longs) ytickvals = ['-25', '-12.5', '0', '12.5', '25'] try: fig = plt.figure() plt.imshow(apex_lat, origin='lower') plt.colorbar() plt.yticks(ytickarr, ytickvals) plt.xticks(xtickarr, ['0', '72', '144', '216', '288', '360']) plt.title('Apex Latitude (Degrees) at 120 km') plt.xlabel('Geodetic Longitude (Degrees)') plt.ylabel('Geodetic Latitude (Degrees)') plt.savefig('apex_lat.pdf') plt.close() fig = plt.figure() plt.imshow(apex_lon, origin='lower') plt.colorbar() plt.yticks(ytickarr, ytickvals) plt.xticks(xtickarr, ['0', '72', '144', '216', '288', '360']) plt.title('Apex Longitude Difference (Degrees) at 120 km') plt.xlabel('Geodetic Longitude (Degrees)') plt.ylabel('Geodetic Latitude (Degrees)') plt.savefig('apex_lon.pdf') plt.close() fig = plt.figure() plt.imshow(np.log10(apex_alt), origin='lower') plt.colorbar() plt.yticks(ytickarr, ytickvals) plt.xticks(xtickarr, ['0', '72', '144', '216', '288', '360']) plt.title('Log Apex Altitude (km) at 120 km') plt.xlabel('Geodetic Longitude (Degrees)') plt.ylabel('Geodetic Latitude (Degrees)') plt.savefig('apex_alt.pdf') plt.close() except: pass
def test_step_along_mag_unit_vector_sensitivity(self, direction, tol=1.E-5): """Characterize apex location uncertainty of neighboring field lines. Parameters ---------- direction : str Step along 'meridional' or 'zonal' directions. """ # Create memory for method locations from method output x = np.zeros((len(self.lats), len(self.longs))) y = x.copy() z = x.copy() h = x.copy() # Second set of outputs x2 = np.zeros((len(self.lats), len(self.longs))) y2 = x2.copy() z2 = x2.copy() h2 = x.copy() dates = [self.date] * len(self.longs) for i, p_lat in enumerate(self.lats): (in_x, in_y, in_z) = OMMBV.trans.geodetic_to_ecef([p_lat] * len(self.longs), self.longs, self.alts) (x[i, :], y[i, :], z[i, :]) = OMMBV.step_along_mag_unit_vector(in_x, in_y, in_z, dates, direction=direction, num_steps=2, step_size=1. / 2.) # Second run (x2[i, :], y2[i, :], z2[i, :]) = OMMBV.step_along_mag_unit_vector(in_x, in_y, in_z, dates, direction=direction, num_steps=1, step_size=1.) for i, p_lat in enumerate(self.lats): # Convert all locations to geodetic coordinates tlat, tlon, talt = OMMBV.trans.ecef_to_geodetic( x[i, :], y[i, :], z[i, :]) # Get apex location (x[i, :], y[i, :], z[i, :], _, _, h[i, :]) = OMMBV.trace.apex_location_info(tlat, tlon, talt, dates, return_geodetic=True) # Repeat process for second set of positions. tlat, tlon, talt = OMMBV.trans.ecef_to_geodetic( x2[i, :], y2[i, :], z2[i, :]) (x2[i, :], y2[i, :], z2[i, :], _, _, h2[i, :]) = OMMBV.trace.apex_location_info(tlat, tlon, talt, dates, return_geodetic=True) # Test within expected tolerance. for item1, item2 in zip([x, y, z, h], [x2, y2, z2, h2]): idx, idy, = np.where(np.abs(item1) >= 1.E-10) np.testing.assert_allclose(item1[idx, idy], item2[idx, idy], rtol=tol) idx, idy, = np.where(np.abs(item1) < 1.E-10) np.testing.assert_allclose(item1[idx, idy], item2[idx, idy], atol=tol) return
def test_apexpy_v_OMMBV(self): """Comparison with apexpy along magnetic equator""" # generate test values glongs = np.arange(99) * 3.6 - 180. glats = np.zeros(99) + 10. alts = np.zeros(99) + 450. # date of run date = self.inst.date # map to the magnetic equator ecef_x, ecef_y, ecef_z, eq_lat, eq_long, eq_z = OMMBV.apex_location_info( glats, glongs, alts, [date] * len(alts), return_geodetic=True) idx = np.argsort(eq_long) eq_long = eq_long[idx] eq_lat = eq_lat[idx] eq_z = eq_z[idx] # get apex basis vectors apex = apexpy.Apex(date=self.inst.date) apex_vecs = apex.basevectors_apex(eq_lat, eq_long, eq_z, coords='geo') apex_fa = apex_vecs[8] apex_mer = apex_vecs[7] apex_zon = apex_vecs[6] # normalize into unit vectors apex_mer[0, :], apex_mer[1, :], apex_mer[ 2, :] = OMMBV.normalize_vector(-apex_mer[0, :], -apex_mer[1, :], -apex_mer[2, :]) apex_zon[0, :], apex_zon[1, :], apex_zon[ 2, :] = OMMBV.normalize_vector(apex_zon[0, :], apex_zon[1, :], apex_zon[2, :]) apex_fa[0, :], apex_fa[1, :], apex_fa[2, :] = OMMBV.normalize_vector( apex_fa[0, :], apex_fa[1, :], apex_fa[2, :]) # calculate mag unit vectors in ECEF coordinates out = OMMBV.calculate_mag_drift_unit_vectors_ecef( eq_lat, eq_long, eq_z, [self.inst.date] * len(eq_z)) zx, zy, zz, fx, fy, fz, mx, my, mz = out # convert into north, east, and up system ze, zn, zu = OMMBV.ecef_to_enu_vector(zx, zy, zz, eq_lat, eq_long) fe, fn, fu = OMMBV.ecef_to_enu_vector(fx, fy, fz, eq_lat, eq_long) me, mn, mu = OMMBV.ecef_to_enu_vector(mx, my, mz, eq_lat, eq_long) # create inputs straight from IGRF igrf_n = [] igrf_e = [] igrf_u = [] for lat, lon, alt in zip(eq_lat, eq_long, eq_z): out = OMMBV.igrf.igrf13syn(0, date.year, 1, alt, np.deg2rad(90 - lat), np.deg2rad(lon)) out = np.array(out) # normalize out /= out[-1] igrf_n.append(out[0]) igrf_e.append(out[1]) igrf_u.append(-out[2]) # dot product of zonal and field aligned dot_apex_zonal = apex_fa[0, :] * apex_zon[0, :] + apex_fa[ 1, :] * apex_zon[1, :] + apex_fa[2, :] * apex_zon[2, :] dot_apex_mer = apex_fa[0, :] * apex_mer[0, :] + apex_fa[ 1, :] * apex_mer[1, :] + apex_fa[2, :] * apex_mer[2, :] dotmagvect_zonal = ze * fe + zn * fn + zu * fu dotmagvect_mer = me * fe + mn * fn + mu * fu assert np.all(dotmagvect_mer < 1.E-8) assert np.all(dotmagvect_zonal < 1.E-8) try: plt.figure() plt.plot(eq_long, np.log10(np.abs(dot_apex_zonal)), label='apex_zonal') plt.plot(eq_long, np.log10(np.abs(dot_apex_mer)), label='apex_mer') plt.plot(eq_long, np.log10(np.abs(dotmagvect_zonal)), label='magv_zonal') plt.plot(eq_long, np.log10(np.abs(dotmagvect_mer)), label='magv_mer') plt.ylabel('Log Dot Product Magnitude') plt.xlabel('Geodetic Longitude (Degrees)') plt.legend() plt.savefig('dot_product.pdf') plt.close() plt.figure() plt.plot(eq_long, fe, label='fa_e', color='r') plt.plot(eq_long, fn, label='fa_n', color='k') plt.plot(eq_long, fu, label='fa_u', color='b') plt.plot(eq_long, apex_fa[0, :], label='apexpy_e', color='r', linestyle='dotted') plt.plot(eq_long, apex_fa[1, :], label='apexpy_n', color='k', linestyle='dotted') plt.plot(eq_long, apex_fa[2, :], label='apexpy_u', color='b', linestyle='dotted') plt.plot(eq_long, igrf_e, label='igrf_e', color='r', linestyle='-.') plt.plot(eq_long, igrf_n, label='igrf_n', color='k', linestyle='-.') plt.plot(eq_long, igrf_u, label='igrf_u', color='b', linestyle='-.') plt.legend() plt.xlabel('Longitude') plt.ylabel('Vector Component') plt.title('Field Aligned Vector') plt.savefig('comparison_field_aligned.pdf') plt.close() f = plt.figure() plt.plot(eq_long, ze, label='zonal_e', color='r') plt.plot(eq_long, zn, label='zonal_n', color='k') plt.plot(eq_long, zu, label='zonal_u', color='b') plt.plot(eq_long, apex_zon[0, :], label='apexpy_e', color='r', linestyle='dotted') plt.plot(eq_long, apex_zon[1, :], label='apexpy_n', color='k', linestyle='dotted') plt.plot(eq_long, apex_zon[2, :], label='apexpy_u', color='b', linestyle='dotted') plt.legend() plt.xlabel('Longitude') plt.ylabel('Vector Component') plt.title('Zonal Vector') plt.savefig('comparison_zonal.pdf') plt.close() f = plt.figure() plt.plot(eq_long, me, label='mer_e', color='r') plt.plot(eq_long, mn, label='mer_n', color='k') plt.plot(eq_long, mu, label='mer_u', color='b') plt.plot(eq_long, apex_mer[0, :], label='apexpy_e', color='r', linestyle='dotted') plt.plot(eq_long, apex_mer[1, :], label='apexpy_n', color='k', linestyle='dotted') plt.plot(eq_long, apex_mer[2, :], label='apexpy_u', color='b', linestyle='dotted') plt.legend() plt.xlabel('Longitude') plt.ylabel('Vector Component') plt.title('Meridional Vector') plt.savefig('comparison_meridional.pdf') plt.close() except: pass
def add_ram_pointing_sc_attitude_vectors(inst): """ Add attitude vectors for spacecraft assuming ram pointing. Presumes spacecraft is pointed along the velocity vector (x), z is generally nadir pointing (positive towards Earth), and y completes the right handed system (generally southward). Parameters ---------- inst : pysat.Instrument Instrument object Returns ------- None Modifies pysat.Instrument object in place to include S/C attitude unit vectors, expressed in ECEF basis. Vectors are named sc_(x,y,z)hat_ecef_(x,y,z). sc_xhat_ecef_x is the spacecraft unit vector along x (positive along velocity vector) reported in ECEF, ECEF x-component. Notes ----- Expects velocity and position of spacecraft in Earth Centered Earth Fixed (ECEF) coordinates to be in the instrument object and named velocity_ecef_* (*=x,y,z) and position_ecef_* (*=x,y,z) Adds attitude vectors for spacecraft in the ECEF basis by calculating the scalar product of each attitude vector with each component of ECEF. """ # Ram pointing is along velocity vector inst['sc_xhat_ecef_x'], inst['sc_xhat_ecef_y'], inst['sc_xhat_ecef_z'] = \ OMMBV.normalize_vector(inst['velocity_ecef_x'], inst['velocity_ecef_y'], inst['velocity_ecef_z']) # Begin with z along Nadir (towards Earth) # if orbit isn't perfectly circular, then the s/c z vector won't # point exactly along nadir. However, nadir pointing is close enough # to the true z (in the orbital plane) that we can use it to get y, # and use x and y to get the real z inst['sc_zhat_ecef_x'], inst['sc_zhat_ecef_y'], inst['sc_zhat_ecef_z'] = \ OMMBV.normalize_vector(-inst['position_ecef_x'], -inst['position_ecef_y'], -inst['position_ecef_z']) # get y vector assuming right hand rule # Z x X = Y inst['sc_yhat_ecef_x'], inst['sc_yhat_ecef_y'], inst['sc_yhat_ecef_z'] = \ OMMBV.cross_product(inst['sc_zhat_ecef_x'], inst['sc_zhat_ecef_y'], inst['sc_zhat_ecef_z'], inst['sc_xhat_ecef_x'], inst['sc_xhat_ecef_y'], inst['sc_xhat_ecef_z']) # Normalize since Xhat and Zhat from above may not be orthogonal inst['sc_yhat_ecef_x'], inst['sc_yhat_ecef_y'], inst['sc_yhat_ecef_z'] = \ OMMBV.normalize_vector(inst['sc_yhat_ecef_x'], inst['sc_yhat_ecef_y'], inst['sc_yhat_ecef_z']) # Strictly, need to recalculate Zhat so that it is consistent with RHS # just created # Z = X x Y inst['sc_zhat_ecef_x'], inst['sc_zhat_ecef_y'], inst['sc_zhat_ecef_z'] = \ OMMBV.cross_product(inst['sc_xhat_ecef_x'], inst['sc_xhat_ecef_y'], inst['sc_xhat_ecef_z'], inst['sc_yhat_ecef_x'], inst['sc_yhat_ecef_y'], inst['sc_yhat_ecef_z']) # Adding metadata inst.meta['sc_xhat_ecef_x'] = { 'units': '', 'desc': ' '.join(('S/C attitude (x-direction, ram) unit vector,', 'expressed in ECEF basis, x-component')) } inst.meta['sc_xhat_ecef_y'] = { 'units': '', 'desc': ' '.join(('S/C attitude (x-direction, ram) unit vector,', 'expressed in ECEF basis, y-component')) } inst.meta['sc_xhat_ecef_z'] = { 'units': '', 'desc': ' '.join(('S/C attitude (x-direction, ram) unit vector,', 'expressed in ECEF basis, z-component')) } inst.meta['sc_zhat_ecef_x'] = { 'units': '', 'desc': ' '.join(('S/C attitude (z-direction, generally nadir) unit', 'vector, expressed in ECEF basis, x-component')) } inst.meta['sc_zhat_ecef_y'] = { 'units': '', 'desc': ' '.join(('S/C attitude (z-direction, generally nadir) unit', 'vector, expressed in ECEF basis, y-component')) } inst.meta['sc_zhat_ecef_z'] = { 'units': '', 'desc': ' '.join(('S/C attitude (z-direction, generally nadir) unit', 'vector, expressed in ECEF basis, z-component')) } inst.meta['sc_yhat_ecef_x'] = { 'units': '', 'desc': ' '.join(('S/C attitude (y-direction, generally south) unit', 'vector, expressed in ECEF basis, x-component')) } inst.meta['sc_yhat_ecef_y'] = { 'units': '', 'desc': ' '.join(('S/C attitude (y-direction, generally south) unit', 'vector, expressed in ECEF basis, y-component')) } inst.meta['sc_yhat_ecef_z'] = { 'units': '', 'desc': ' '.join(('S/C attitude (y-direction, generally south) unit', 'vector, expressed in ECEF basis, z-component')) } # check what magnitudes we get mag = np.sqrt(inst['sc_zhat_ecef_x']**2 + inst['sc_zhat_ecef_y']**2 + inst['sc_zhat_ecef_z']**2) idx, = np.where((mag < .999999999) | (mag > 1.000000001)) if len(idx) > 0: print(mag[idx]) raise RuntimeError(' '.join(('Unit vector generation failure. Not', 'sufficently orthogonal.'))) return