def add(config_key, dset): delta_pos = list() for _ in dset.for_each_suffix("station"): if position.is_position(dset.site_pos): sta_delta = position.PositionDelta(np.zeros((dset.num_obs, 3)), system="gcrs", ref_pos=dset.site_pos) elif position.is_posvel(dset.site_pos): sta_delta = position.PosVelDelta(np.zeros((dset.num_obs, 6)), system="gcrs", ref_pos=dset.site_pos) if config_key not in dset.fields: return list(sta_delta) pos_fields = [ f for f in dset[config_key]._fields if f.endswith(dset.default_field_suffix) ] for field in pos_fields: sta_delta += dset[config_key][field] delta_pos.append(sta_delta) return delta_pos
def ocean_pole_tides_station(dset, opt): """Calculate the ocean pole tide corrections for a station Ocean pole tide corrections are returned in meters in the Geocentric Celestial Reference System for each observation. Args: dset: A Dataset containing model data. version: Version number of conventional mean pole model opt: Apriori ocean pole tide coefficients Returns: Numpy array with ocean tide corrections in meters. """ eop = apriori.get("eop", time=dset.time) # Constants in IERS Conventions equation 7.29 H_p = np.sqrt(8 * np.pi / 15) * constant.omega ** 2 * constant.a ** 4 / constant.GM K = 4 * np.pi * constant.G * constant.a * constant.rho_w * H_p / (3 * constant.g_E) # @todo make these global? related to love numbers somehow gamma_2_R = 0.6870 gamma_2_I = 0.0036 # Equation (7.24) IERS Conventions 2010 m_1 = (eop.x - eop.x_pole) * Unit.arcsec2rad m_2 = (eop.y_pole - eop.y) * Unit.arcsec2rad lat, lon, _ = dset.site_pos.pos.llh.T u_enu_R = np.array( [opt["u_e_R"](lon, lat, grid=False), opt["u_n_R"](lon, lat, grid=False), opt["u_r_R"](lon, lat, grid=False)] ) u_enu_I = np.array( [opt["u_e_I"](lon, lat, grid=False), opt["u_n_I"](lon, lat, grid=False), opt["u_r_I"](lon, lat, grid=False)] ) # Equation (7.29) IERS Conventions 2010 denu = (K * ((m_1 * gamma_2_R + m_2 * gamma_2_I) * u_enu_R + (m_1 * gamma_2_R - m_2 * gamma_2_I) * u_enu_I)).T if position.is_position(dset.site_pos): pos_correction = position.PositionDelta(denu, system="enu", ref_pos=dset.site_pos, time=dset.time) elif position.is_posvel(dset.site_pos): # set velocity to zero denu = np.concatenate((denu, np.zeros(denu.shape)), axis=1) pos_correction = position.PosVelDelta(denu, system="enu", ref_pos=dset.site_pos, time=dset.time) else: log.fatal(f"dset.site_pos{dset.default_field_suffix} is not a PositionArray or PosVelArray.") return pos_correction.gcrs
def solid_pole_tides_station(dset): """Calculate the solid pole tide corrections for a station Solid pole tide corrections are returned in meters in the Geocentric Celestial Reference System for each observation. Args: dset: A Dataset containing model data. version: Version number of conventional mean pole model Returns: Numpy array with ocean tide corrections in meters. """ eop = apriori.get("eop", time=dset.time) # Equation (7.24) IERS Conventions 2010 m_1 = eop.x - eop.x_pole m_2 = eop.y_pole - eop.y lat, lon, _ = dset.site_pos.pos.llh.T theta = np.pi / 2 - lat coslon, sinlon = np.cos(lon), np.sin(lon) # Equation (7.26) IERS Conventions 2010 dup = -33 * np.sin(2 * theta) * (m_1 * coslon + m_2 * sinlon) * Unit.mm2m dsouth = -9 * np.cos(2 * theta) * (m_1 * coslon + m_2 * sinlon) * Unit.mm2m deast = 9 * np.cos(theta) * (m_1 * sinlon + m_2 * coslon) * Unit.mm2m # Correction in topocentric (east, north, up) coordinates denu = np.vstack((deast, -dsouth, dup)).T if position.is_position(dset.site_pos): pos_correction = position.PositionDelta(denu, system="enu", ref_pos=dset.site_pos, time=dset.time) elif position.is_posvel(dset.site_pos): # set velocity to zero denu = np.concatenate((denu, np.zeros(denu.shape)), axis=1) pos_correction = position.PosVelDelta(denu, system="enu", ref_pos=dset.site_pos, time=dset.time) else: log.fatal( f"dset.site_pos{dset.default_field_suffix} is not a PositionArray or PosVelArray." ) return pos_correction.gcrs
def solid_tides_station(dset, correction_cache): """Calculate the solid tide corrections for a station Solid tide corrections are returned in meters in the Geocentric Celestial Reference System for each observation. Args: dset: A Dataset containing model data. Returns: Numpy array with solid tide corrections in meters. """ eph = apriori.get("ephemerides", time=dset.time) dxyz = np.zeros((dset.num_obs, 3)) obs_dt = dset.time.utc.datetime hour_of_day = dset.time.utc.jd_frac * 24 sun_itrs = eph.pos_itrs("sun") moon_itrs = eph.pos_itrs("moon") # Calculate correction for obs in range(dset.num_obs): cache_key = (dset.station[obs], obs_dt[obs]) if cache_key in correction_cache: dxyz[obs] = correction_cache[cache_key] else: dxyz[obs] = iers.dehanttideinel( dset.site_pos.pos[obs], obs_dt[obs].year, obs_dt[obs].month, obs_dt[obs].day, hour_of_day[obs], sun_itrs[obs], moon_itrs[obs], ) correction_cache[cache_key] = dxyz[obs] if position.is_position(dset.site_pos): pos_correction = position.PositionDelta(dxyz, system="trs", ref_pos=dset.site_pos, time=dset.time) elif position.is_posvel(dset.site_pos): # set velocity to zero dxyz = np.concatenate((dxyz, np.zeros(dxyz.shape)), axis=1) pos_correction = position.PosVelDelta(dxyz, system="trs", ref_pos=dset.site_pos, time=dset.time) else: log.fatal(f"dset.site_pos{dset.default_field_suffix} is not a PositionArray or PosVelArray.") return pos_correction.gcrs
def non_tidal_atmospheric_loading_station(ntapl, dset): """Apply non tidal atmospheric loading displacements for a station field. Corrections are returned in meters in the Geocentric Celestial Reference System for each observation. Args: dset: A Dataset containing model data Returns: Numpy array: GCRS corrections in meters. """ lat, lon, _ = dset.site_pos.pos.llh.T try: dup = ntapl["up"](dset.time, lon, lat) deast = ntapl["east"](dset.time, lon, lat) dnorth = ntapl["north"](dset.time, lon, lat) except KeyError: log.warn( f"No non-tidal atmospheric loading available for {dset.rundate}") dup = np.zeros(len(dset.time)) deast = np.zeros(len(dset.time)) dnorth = np.zeros(len(dset.time)) denu = np.stack((deast, dnorth, dup), axis=1) if position.is_position(dset.site_pos): pos_correction = position.PositionDelta(denu, system="enu", ref_pos=dset.site_pos, time=dset.time) elif position.is_posvel(dset.site_pos): # set velocity to zero denu = np.concatenate((denu, np.zeros(denu.shape)), axis=1) pos_correction = position.PosVelDelta(denu, system="enu", ref_pos=dset.site_pos, time=dset.time) else: log.fatal( f"dset.site_pos{dset.default_field_suffix} is not a PositionArray or PosVelArray." ) return pos_correction.gcrs
def atmospheric_tides_station(dset): """Calculate the atmospheric tides corrections for a station Atmospheric tides corrections are returned in meters in the Geocentric Celestial Reference System for each observation. Args: dset: A Dataset containing model data Returns: Numpy array with atmospheric tide corrections in meters. """ coeff = apriori.get("atmospheric_tides") use_cmc = config.tech.atmospheric_tides_cmc.bool # S1 has a period of 1 cycle/day, S2 has a period of 2 cycle/day omega_1 = 2 * np.pi omega_2 = 4 * np.pi # Time argument is fraction of UT1 day, see [2]. t = dset.time.ut1.jd_frac lat, lon, _ = dset.site_pos.pos.llh.T # Equation 7.19a and 7.19b from IERS Conventions 2010 de = ( coeff["A_d1_e"](lon, lat, grid=False) * np.cos(omega_1 * t) + coeff["B_d1_e"](lon, lat, grid=False) * np.sin(omega_1 * t) + coeff["A_d2_e"](lon, lat, grid=False) * np.cos(omega_2 * t) + coeff["B_d2_e"](lon, lat, grid=False) * np.sin(omega_2 * t) ) dn = ( coeff["A_d1_n"](lon, lat, grid=False) * np.cos(omega_1 * t) + coeff["B_d1_n"](lon, lat, grid=False) * np.sin(omega_1 * t) + coeff["A_d2_n"](lon, lat, grid=False) * np.cos(omega_2 * t) + coeff["B_d2_n"](lon, lat, grid=False) * np.sin(omega_2 * t) ) du = ( coeff["A_d1_u"](lon, lat, grid=False) * np.cos(omega_1 * t) + coeff["B_d1_u"](lon, lat, grid=False) * np.sin(omega_1 * t) + coeff["A_d2_u"](lon, lat, grid=False) * np.cos(omega_2 * t) + coeff["B_d2_u"](lon, lat, grid=False) * np.sin(omega_2 * t) ) denu = np.vstack([de, dn, du]).T * Unit.mm2m if position.is_position(dset.site_pos): pos_correction = position.PositionDelta(denu, system="enu", ref_pos=dset.site_pos, time=dset.time) elif position.is_posvel(dset.site_pos): # set velocity to zero denu = np.concatenate((denu, np.zeros(denu.shape)), axis=1) pos_correction = position.PosVelDelta(denu, system="enu", ref_pos=dset.site_pos, time=dset.time) else: log.fatal(f"dset.site_pos{dset.default_field_suffix} is not a PositionArray or PosVelArray.") # Add center of mass corrections if use_cmc: # Equation (7.20) in [1] coeff_cmc = apriori.get("atmospheric_tides_cmc") cmc = ( coeff_cmc["A1"][None, :] * np.cos(omega_1 * t)[:, None] + coeff_cmc["B1"][None, :] * np.sin(omega_1 * t)[:, None] + coeff_cmc["A2"][None, :] * np.cos(omega_2 * t)[:, None] + coeff_cmc["B2"][None, :] * np.sin(omega_2 * t)[:, None] ) if position.is_position(dset.site_pos): cmc_correction = position.PositionDelta(cmc, system="trs", ref_pos=dset.site_pos, time=dset.time) elif position.is_posvel(dset.site_pos): # set velocity to zero cmc = np.concatenate((cmc, np.zeros(cmc.shape)), axis=1) cmc_correction = position.PosVelDelta(cmc, system="trs", ref_pos=dset.site_pos, time=dset.time) pos_correction = pos_correction.trs + cmc_correction.trs return pos_correction.gcrs
def ocean_tides_station(dset, amplitudes, phases, correction_cache): """Calculate the ocean tide corrections for a station Ocean tide corrections are returned in meters in the Geocentric Celestial Reference System for each observation. Args: dset: A Dataset containing model data. Returns: Numpy array with ocean tide corrections in meters. """ denu = np.zeros((dset.num_obs, 3)) use_cmc = config.tech.ocean_tides_cmc.bool # Calculate correction for obs, site_id in enumerate(dset.site_id): if site_id not in amplitudes: # Warn about missing Ocean Tides Coefficients if site_id in _WARNED_MISSING: continue station = dset.unique("station", site_id=site_id)[0] log.error( f"Missing ocean loading coefficients for site id {site_id!r} ({station}). Correction set to zero." ) _WARNED_MISSING.add(site_id) continue cache_key = (dset.station[obs], dset.time.utc.datetime[obs]) if cache_key in correction_cache: denu[obs] = correction_cache[cache_key] else: epoch = [float(t) for t in dset.time.utc[obs].yday.split(":")] dup, dsouth, dwest = iers.hardisp(epoch, amplitudes[site_id], phases[site_id], 1, 1.0) # Correction in topocentric (east, north, up) coordinates denu[obs] = np.array([-dwest[0], -dsouth[0], dup[0]]) correction_cache[cache_key] = denu[obs] if position.is_position(dset.site_pos): pos_correction = position.PositionDelta(denu, system="enu", ref_pos=dset.site_pos, time=dset.time) elif position.is_posvel(dset.site_pos): # set velocity to zero denu = np.concatenate((denu, np.zeros(denu.shape)), axis=1) pos_correction = position.PosVelDelta(denu, system="enu", ref_pos=dset.site_pos, time=dset.time) else: log.fatal( f"dset.site_pos{dset.default_field_suffix} is not a PositionArray or PosVelArray." ) # Center of mass corrections if use_cmc: coeff_cmc = apriori.get("ocean_tides_cmc") in_phase = coeff_cmc["in_phase"] cross_phase = coeff_cmc["cross_phase"] cmc = np.zeros((dset.num_obs, 3)) for obs, time in enumerate(dset.time.utc): year, doy = time.datetime.year, float( time.datetime.strftime("%j")) + time.mjd_frac angle = iers.arg2(year, doy)[:, None] cmc[obs] += np.sum(in_phase * np.cos(angle) + cross_phase * np.sin(angle), axis=0) if position.is_position(dset.site_pos): cmc_correction = position.PositionDelta(cmc, system="trs", ref_pos=dset.site_pos, time=dset.time) elif position.is_posvel(dset.site_pos): # set velocity to zero cmc = np.concatenate((cmc, np.zeros(cmc.shape)), axis=1) cmc_correction = position.PosVelDelta(cmc, system="trs", ref_pos=dset.site_pos, time=dset.time) pos_correction = pos_correction.trs + cmc_correction return pos_correction.gcrs
def eccentricity_vector_station(ecc, dset): """Calculate the eccentricity vector for a station. Corrections are returned in meters in the Geocentric Celestial Reference System for each observation. Args: dset: A Dataset containing model data Returns: Numpy array: GCRS corrections in meters. """ if position.is_position(dset.site_pos): ecc_vector = position.PositionDelta(np.zeros((dset.num_obs, 3)), system="enu", ref_pos=dset.site_pos, time=dset.time) elif position.is_posvel(dset.site_pos): ecc_vector = position.PosVelDelta(np.zeros((dset.num_obs, 6)), system="enu", ref_pos=dset.site_pos, time=dset.time) else: log.fatal( f"dset.site_pos{dset.default_field_suffix} is not a PositionArray or PosVelArray." ) fieldnames = config.tech.eccentricity.identifier.list fielddata = [dset[field] for field in fieldnames] if len(fieldnames) > 1: keys = set(tuple(zip(*fielddata))) else: keys = fielddata[0].tolist() for key in keys: if len(fieldnames) > 1: filters = dict(zip(fieldnames, key)) else: filters = dict(zip(fieldnames, [key])) if key not in ecc: ecc_vector[dset.filter(**filters), 0:3] = np.zeros(3) if key in _WARNED_MISSING: continue log.warn( f"Missing eccentricity data for {key}. Vector set to zero.") _WARNED_MISSING.add(key) continue if ecc[key]["coord_type"] == "ENU": ecc_vector[dset.filter(**filters), 0:3] = ecc[key]["vector"] ecc_vector = ecc_vector.trs for key in keys: if len(fieldnames) > 1: filters = dict(zip(fieldnames, key)) else: filters = dict(zip(fieldnames, [key])) if key not in ecc: ecc_vector[dset.filter(**filters), 0:3] = np.zeros(3) continue if ecc[key]["coord_type"] == "XYZ": ecc_vector[dset.filter(**filters), 0:3] = ecc[key]["vector"] return ecc_vector.gcrs
def satellite_phase_center_offset( self, dset: "Dataset", sys_freq: Union[None, Dict[str, Dict[str, str]]] = None ) -> "PosVelDeltaArray": """Determine satellite phase center offset correction vectors given in ITRS Satellite phase center offset (PCO) corrections are frequency dependent. The argument 'sys_freq' defines, which frequencies for a given GNSS should be used. If two frequencies for a GNSS are given, then the PCO is determined as ionospheric-free linear combination. If 'sys_freq' is not defined as input argument, then 'sys_freq' is generated based on the given observation types in dataset 'dset'. Args: dset: Model data. sys_freq: Dictionary with frequency or frequency combination given for GNSS identifier: sys_freq = { <sys_id>: <freq> } (e.g. sys_freq = {'E': 'E1', 'G': 'L1_L2'} ) Returns: Satellite phase center offset correction vectors given in ITRS in meter """ # GNSS Freq number GNSS freq # L<num>/C<num> # ___________________________________________ # C (BeiDou): 2 'B1' # 7 'B2' # 6 'B3' # G (GPS): 1 'L1' # 2 'L2' # 5 'L5' # R (GLONASS): 1 'G1' # 2 'G2' # 3 'G3' # E (Galileo): 1 'E1' # 8 'E5 (E5a+E5b)' # 5 'E5a' # 7 'E5b' # 6 'E6' # I (IRNSS): 5 'L5' # 9 'S' # J (QZSS): 1 'L1' # 2 'L2' # 5 'L5' # 6 'LEX' # S (SBAS): 1 'L1' # 5 'L5' obstype_to_gnss_freq = { "C": { "2": "B1", "7": "B2", "6": "B3" }, "E": { "1": "E1", "8": "E5", "5": "E5a", "7": "E5b", "6": "E6" }, "G": { "1": "L1", "2": "L2", "5": "L5" }, "I": { "5": "L5", "9": "S" }, "J": { "1": "L1", "2": "L2", "5": "L5", "6": "LEX" }, "R": { "1": "G1", "2": "G2", "3": "G3" }, "S": { "1": "L1", "5": "L5" }, } correction = position.PosVelDelta(val=np.zeros((dset.num_obs, 6)), system="yaw", ref_pos=dset.sat_posvel, time=dset.time) used_date = None # Get GNSS frequency based on observation type if sys_freq is None: sys_freq = dict() obstypes = dset.meta["obstypes"] for sys in obstypes: freqs = {o[1] for o in obstypes[sys]} sys_freq[sys] = "_".join(f"{obstype_to_gnss_freq[sys][f]}" for f in sorted(freqs)) # Loop over all satellites given in RINEX observation file and configuration file for sat in dset.unique("satellite"): # Skip satellites, which are not given in ANTEX file if sat not in self.data: log.warn( f"Satellite {sat} is not given in ANTEX file {self.file_path}. That means no satellite antenna " f"phase center offset correction can be applied for satellite {sat}." ) continue # Get array with information about, when observation are available for the given satellite (indicated by True) idx = dset.filter(satellite=sat) # Get used date used_date = self._used_date(sat, dset.analysis["rundate"]) if used_date is None: continue # Add PCO to Dataset meta pco_sat = self.get_pco_sat(sat, sys_freq, used_date) dset.meta.setdefault("pco_sat", dict()).update({sat: pco_sat.tolist()}) correction[idx] = np.repeat(np.append(pco_sat, np.zeros(3))[None, :], np.sum(idx), axis=0) # Transform PCO given in satellite body-fixed reference frame (for GPS and Galileo assumed to be aligned # to yaw-steering reference frame) to ITRS return correction.trs