def N_wet(self, lat, lon): if not self._N_wet: vals = load_data(os.path.join(dataset_dir, '453/v12_ESANWET.txt')) lats = load_data(os.path.join(dataset_dir, '453/v12_ESALAT.txt')) lons = load_data(os.path.join(dataset_dir, '453/v12_ESALON.txt')) self._N_wet = bilinear_2D_interpolator(lats, lons, vals) return self._N_wet(np.array([lat.ravel(), lon.ravel()]).T).reshape(lat.shape)
def Pclw(self, lat, lon): if not self._Pclw: vals = load_data(os.path.join(dataset_dir, '840/v6_Pclw.txt')) lats = load_data(os.path.join(dataset_dir, '840/v6_Lat.txt')) lons = load_data(os.path.join(dataset_dir, '840/v6_Lon.txt')) self._Pclw = bilinear_2D_interpolator(lats, lons, vals) return self._Pclw( np.array([lat.ravel(), lon.ravel()]).T).reshape(lat.shape)
def temperature(self, lat, lon): if not self._temperature: vals = load_data(os.path.join(dataset_dir, '1510/v0_Temp.txt')) lats = load_data(os.path.join(dataset_dir, '1510/v0_Lat.txt')) lons = load_data(os.path.join(dataset_dir, '1510/v0_Lon.txt')) self._temperature = bicubic_2D_interpolator(np.flipud(lats), lons, np.flipud(vals)) return self._temperature( np.array([lat.ravel(), lon.ravel()]).T).reshape(lat.shape)
def sigma(self, lat, lon): if not self._sigma: vals = load_data(os.path.join(dataset_dir, '840/v4_WRED_LOGNORMAL_STDEV.txt')) lats = load_data(os.path.join(dataset_dir, '840/v6_Lat.txt')) lons = load_data(os.path.join(dataset_dir, '840/v6_Lon.txt')) self._sigma = bilinear_2D_interpolator(lats, lons, vals) return self._sigma( np.array([lat.ravel(), lon.ravel()]).T).reshape(lat.shape)
def temperature(self, lat, lon): if not self._temperature: vals = load_data(os.path.join(dataset_dir, '1510/v1_T_Annual.txt')) lats = load_data(os.path.join(dataset_dir, '1510/v1_Lat.txt')) lons = load_data(os.path.join(dataset_dir, '1510/v1_Lon.txt')) self._temperature = bilinear_2D_interpolator( np.flipud(lats), lons, np.flipud(vals)) lon[lon > 180] = lon[lon > 180] - 360 return self._temperature( np.array([lat.ravel(), lon.ravel()]).T).reshape(lat.shape)
def altitude(self, lat, lon): if not self._altitude: vals = load_data( os.path.join(dataset_dir, '1511/v1_TOPO_0DOT5.txt')) lats = load_data(os.path.join(dataset_dir, '1511/v1_Lat.txt')) lons = load_data(os.path.join(dataset_dir, '1511/v1_Lon.txt')) self._altitude = bicubic_2D_interpolator(np.flipud(lats), lons, np.flipud(vals)) return self._altitude(np.array([lat.ravel(), lon.ravel()]).T).reshape(lat.shape)
def topo_alt(self, lat, lon): if self._topo_alt is None: d_dir = os.path.join(dataset_dir, '836/v6_TOPO_0DOT5.txt') lats = load_data(os.path.join(dataset_dir, '836/v6_TOPOLAT.txt')) lons = load_data(os.path.join(dataset_dir, '836/v6_TOPOLON.txt')) vals = load_data(d_dir) self._topo_alt = bicubic_2D_interpolator(np.flipud(lats), lons, np.flipud(vals)) return self._topo_alt( np.array([lat.ravel(), lon.ravel()]).T).reshape(lat.shape)
def Beta(self, lat, lon): if not self._Beta: vals = load_data( os.path.join(dataset_dir, '837/ESARAIN_BETA_v5.txt')) lats = load_data( os.path.join(dataset_dir, '837/ESARAIN_LAT_v5.txt')) lons = load_data( os.path.join(dataset_dir, '837/ESARAIN_LON_v5.txt')) self._Beta = bilinear_2D_interpolator(lats, lons, vals) return self._Beta(np.array([lat.ravel(), lon.ravel()]).T).reshape(lat.shape)
def isoterm_0(self, lat, lon): if not self._zero_isoterm_data: vals = load_data(os.path.join(dataset_dir, '839/v4_ESA0HEIGHT.txt')) lats = load_data(os.path.join(dataset_dir, '839/v4_ESALAT.txt')) lons = load_data(os.path.join(dataset_dir, '839/v4_ESALON.txt')) self._zero_isoterm_data = bilinear_2D_interpolator( lats, lons, vals) return self._zero_isoterm_data(np.array([lat.ravel(), lon.ravel() ]).T).reshape(lat.shape)
def R001(self, lat, lon): if not self._R001: lats = load_data(os.path.join(dataset_dir, '837/v7_LAT_R001.txt')) lons = load_data(os.path.join(dataset_dir, '837/v7_LON_R001.txt')) vals = load_data(os.path.join(dataset_dir, '837/v7_R001.txt')) self._R001 = bilinear_2D_interpolator(np.flipud(lats), lons, np.flipud(vals)) # In this recommendation the longitude is encoded with format -180 to # 180 whereas we always use 0 - 360 encoding lon = np.array(lon) lon[lon > 180] = lon[lon > 180] - 360 return self._R001(np.array([lat.ravel(), lon.ravel()]).T).reshape(lat.shape)
def rho(self, lat, lon, p): if not self._rho: ps = [0.1, 0.2, 0.3, 0.5, 1, 2, 3, 5, 10, 20, 30, 50, 60, 70, 80, 90, 95, 99] d_dir = os.path.join(dataset_dir, '836/v4_RHO_%s.txt') lats = load_data(os.path.join(dataset_dir, '836/v4_Lat.txt')) lons = load_data(os.path.join(dataset_dir, '836/v4_Lon.txt')) for p_loads in ps: vals = load_data(d_dir % (str(p_loads).replace('.', ''))) self._rho[float(p_loads)] =\ bilinear_2D_interpolator(lats, lons, vals) return self._rho[float(p)]( np.array([lat.ravel(), lon.ravel()]).T).reshape(lat.shape)
def month_temperature(self, lat, lon, m): if not self._month_temperature: lats = load_data(os.path.join(dataset_dir, '1510/v1_Lat.txt')) lons = load_data(os.path.join(dataset_dir, '1510/v1_Lon.txt')) for _m in self.__months: vals = load_data(os.path.join(dataset_dir, '1510/v1_T_Month{0:02d}.txt') .format(_m)) self._month_temperature[_m] = bilinear_2D_interpolator( np.flipud(lats), lons, np.flipud(vals)) lon[lon > 180] = lon[lon > 180] - 360 return self._month_temperature[m]( np.array([lat.ravel(), lon.ravel()]).T).reshape(lat.shape)
def Lred(self, lat, lon, p): if not self._Lred: ps = [0.1, 0.2, 0.3, 0.5, 1, 2, 3, 5, 10, 20, 30, 50, 60, 70, 80, 90, 95] d_dir = os.path.join(dataset_dir, '840/v7_Lred_%s.txt') lats = load_data(os.path.join(dataset_dir, '840/v7_Lat.txt')) lons = load_data(os.path.join(dataset_dir, '840/v7_Lon.txt')) for p_load in ps: vals = load_data(d_dir % (str(p_load).replace('.', ''))) self._Lred[float(p_load)] = bilinear_2D_interpolator( lats, lons, vals) return self._Lred[float(p)]( np.array([lat.ravel(), lon.ravel()]).T).reshape(lat.shape)
def s_a(self, lat, lon): """ Standard deviation of terrain heights (m) within a 110 km × 110 km area with a 30 s resolution (e.g. the Globe “gtopo30” data). The value for the mid-path may be obtained from an area roughness with 0.5 × 0.5 degree resolution of geographical coordinates using bi-linear interpolation. """ if not self._s_a: vals = load_data(os.path.join(dataset_dir, '530/v16_gtopo_30.txt')) lats = load_data(os.path.join(dataset_dir, '530/v16_lat.txt')) lons = load_data(os.path.join(dataset_dir, '530/v16_lon.txt')) self._Pr6 = bilinear_2D_interpolator(lats, lons, vals) return self._Pr6(np.array([lat.ravel(), lon.ravel()]).T).reshape(lat.shape)
def Mt(self, lat, lon, m): if not self._Mt: lats = load_data(os.path.join(dataset_dir, '837/v7_LAT_MT.txt')) lons = load_data(os.path.join(dataset_dir, '837/v7_LON_MT.txt')) for _m in self.months: vals = load_data( os.path.join(dataset_dir, '837/v7_MT_Month{0:02d}.txt').format(_m)) self._Mt[_m] = bilinear_2D_interpolator( np.flipud(lats), lons, np.flipud(vals)) # In this recommendation the longitude is encoded with format -180 to # 180 whereas we always use 0 - 360 encoding lon = np.array(lon) lon[lon > 180] = lon[lon > 180] - 360 return self._Mt[m](np.array([lat.ravel(), lon.ravel()]).T).reshape(lat.shape)
def N_wet(self, lat, lon, p): if not self._N_wet: ps = [ 0.1, 0.2, 0.3, 0.5, 1, 2, 3, 5, 10, 20, 30, 50, 60, 70, 80, 90, 95, 99 ] d_dir = os.path.join(dataset_dir, '453/v13_NWET_Annual_%s.txt') lats = load_data(os.path.join(dataset_dir, '453/v13_LAT_N.txt')) lons = load_data(os.path.join(dataset_dir, '453/v13_LON_N.txt')) for p_loads in ps: vals = load_data(d_dir % (str(p_loads).replace('.', ''))) self._N_wet[float(p_loads)] = bilinear_2D_interpolator( np.flipud(lats), lons, np.flipud(vals)) lon[lon > 180] = lon[lon > 180] - 360 return self._N_wet[float(p)](np.array([lat.ravel(), lon.ravel() ]).T).reshape(lat.shape)
def DN65(self, lat, lon, p): if not self._DN65: ps = [ 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 98, 99, 99.5, 99.8, 99.9 ] d_dir = os.path.join(dataset_dir, '453/v12_DN65m_%02dd%02d_v1.txt') lats = load_data(os.path.join(dataset_dir, '453/v12_lat0d75.txt')) lons = load_data(os.path.join(dataset_dir, '453/v12_lon0d75.txt')) for p_loads in ps: int_p = p_loads // 1 frac_p = round((p_loads % 1.0) * 100) vals = load_data(d_dir % (int_p, frac_p)) self._DN65[float(p_loads)] = bilinear_2D_interpolator( lats, lons, vals) return self._DN65[float(p)](np.array([lat.ravel(), lon.ravel() ]).T).reshape(lat.shape)
class _ITU676_10(): tmp = load_data(os.path.join(dataset_dir, '676/v10_lines_oxygen.txt'), skip_header=1) f_ox = tmp[:, 0] a1 = tmp[:, 1] a2 = tmp[:, 2] a3 = tmp[:, 3] a4 = tmp[:, 4] a5 = tmp[:, 5] a6 = tmp[:, 6] tmp = load_data(os.path.join(dataset_dir, '676//v10_lines_water_vapour.txt'), skip_header=1) f_wv = tmp[:, 0] b1 = tmp[:, 1] b2 = tmp[:, 2] b3 = tmp[:, 3] b4 = tmp[:, 4] b5 = tmp[:, 5] b6 = tmp[:, 6] def __init__(self): self.__version__ = 10 self.year = 2013 self.month = 9 self.link = 'https://www.itu.int/rec/R-REC-P.676-10-201309-S/en' @classmethod def gammaw_approx(self, f, P, rho, T): rp = P / 1013 rt = 288 / (T) eta1 = 0.955 * rp * rt**0.68 + 0.006 * rho eta2 = 0.735 * rp * rt**0.50 + 0.0353 * rt**4 * rho def g(f, fi): return 1 + ((f - fi) / (f + fi))**2 gammaw = ( (3.98 * eta1 * np.exp(2.23 * (1 - rt))) / ((f - 22.235)**2 + 9.42 * eta1**2) * g(f, 22.0) + (11.96 * eta1 * np.exp(0.70 * (1 - rt))) / ((f - 183.310)**2 + 11.14 * eta1**2) + (0.081 * eta1 * np.exp(6.44 * (1 - rt))) / ((f - 321.226)**2 + 6.29 * eta1**2) + (3.660 * eta1 * np.exp(1.60 * (1 - rt))) / ((f - 325.153)**2 + 9.22 * eta1**2) + (25.37 * eta1 * np.exp(1.09 * (1 - rt))) / ((f - 380.000)**2) + (17.40 * eta1 * np.exp(1.46 * (1 - rt))) / ((f - 448.000)**2) + (844.6 * eta1 * np.exp(0.17 * (1 - rt))) / ((f - 557.000)**2) * g(f, 557.0) + (290.0 * eta1 * np.exp(0.41 * (1 - rt))) / ((f - 752.000)**2) * g(f, 752.0) + (8.3328e4 * eta2 * np.exp(0.99 * (1 - rt))) / ((f - 1780.00)**2) * g(f, 1780.0)) * f**2 * rt**2.5 * rho * 1e-4 return gammaw @classmethod def gamma0_approx(self, f, P, rho, T): rp = P / 1013.0 rt = 288.0 / (T) def phi(rp, rt, a, b, c, d): return np.power(rp, a) * np.power(rt, b) * np.exp(c * (1 - rp) + d * (1 - rt)) # Dry air attenuation (gamma0) computation as in Section 1 of Annex 2 # of [1] delta = -0.00306 * phi(rp, rt, 3.211, -14.94, 1.583, -16.37) xi1 = phi(rp, rt, 0.0717, -1.8132, 0.0156, -1.6515) xi2 = phi(rp, rt, 0.5146, -4.6368, -0.1921, -5.7416) xi3 = phi(rp, rt, 0.3414, -6.5851, 0.2130, -8.5854) xi4 = phi(rp, rt, -0.0112, 0.0092, -0.1033, -0.0009) xi5 = phi(rp, rt, 0.2705, -2.7192, -0.3016, -4.1033) xi6 = phi(rp, rt, 0.2445, -5.9191, 0.0422, -8.0719) xi7 = phi(rp, rt, -0.1833, 6.5589, -0.2402, 6.131) gamma54 = 2.192 * phi(rp, rt, 1.8286, -1.9487, 0.4051, -2.8509) gamma58 = 12.59 * phi(rp, rt, 1.0045, 3.5610, 0.1588, 1.2834) gamma60 = 15.00 * phi(rp, rt, 0.9003, 4.1335, 0.0427, 1.6088) gamma62 = 14.28 * phi(rp, rt, 0.9886, 3.4176, 0.1827, 1.3429) gamma64 = 6.819 * phi(rp, rt, 1.4320, 0.6258, 0.3177, -0.5914) gamma66 = 1.908 * phi(rp, rt, 2.0717, -4.1404, 0.4910, -4.8718) def fcn_le_54(): return (((7.2 * rt**2.8) / (f**2 + 0.34 * rp**2 * rt**1.6) + (0.62 * xi3) / ((54 - f)**(1.16 * xi1) + 0.83 * xi2)) * f**2 * rp**2 * 1e-3) def fcn_le_60(): return (np.exp( np.log(gamma54) / 24.0 * (f - 58) * (f - 60) - np.log(gamma58) / 8.0 * (f - 54) * (f - 60) + np.log(gamma60) / 12.0 * (f - 54) * (f - 58))) def fcn_le_62(): return (gamma60 + (gamma62 - gamma60) * (f - 60) / 2.0) def fcn_le_66(): return (np.exp( np.log(gamma62) / 8.0 * (f - 64) * (f - 66) - np.log(gamma64) / 4.0 * (f - 62) * (f - 66) + np.log(gamma66) / 8.0 * (f - 62) * (f - 64))) def fcn_le_120(): return ((3.02e-4 * rt**3.5 + (0.283 * rt**3.8) / ((f - 118.75)**2 + 2.91 * rp**2 * rt**1.6) + (0.502 * xi6 * (1 - 0.0163 * xi7 * (f - 66))) / ((f - 66)**(1.4346 * xi4) + 1.15 * xi5)) * f**2 * rp**2 * 1e-3) def fcn_rest(): return (((3.02e-4) / (1 + 1.9e-5 * f**1.5) + (0.283 * rt**0.3) / ((f - 118.75)**2 + 2.91 * rp**2 * rt**1.6)) * f**2 * rp**2 * rt**3.5 * 1e-3 + delta) gamma0 = \ np.where( f <= 54, fcn_le_54(), np.where( np.logical_and(54 < f, f <= 60), fcn_le_60(), np.where( np.logical_and(60 < f, f <= 62), fcn_le_62(), np.where( np.logical_and(62 < f, f <= 66), fcn_le_66(), np.where( np.logical_and(66 < f, f <= 120), fcn_le_120(), fcn_rest()))))) return gamma0 @classmethod def gamma0_exact(self, f, p, rho, T): return __gamma0_exact__676_9_11__(self, f, p, rho, T) @classmethod def gammaw_exact(self, f, p, rho, T): return __gammaw_exact__676_9_11__(self, f, p, rho, T) @classmethod def gamma_exact(self, f, p, rho, T): return (self.gamma0_exact(f, p, rho, T) + self.gammaw_exact(f, p, rho, T)) @classmethod def gaseous_attenuation_approximation(self, f, el, rho, P, T): """ T goes in Kelvin """ if np.any(f > 350): warnings.warn( RuntimeWarning( 'The approximated method to computes ' 'the gaseous attenuation in recommendation ITU-P 676-11 ' 'is only recommended for frequencies below 350GHz')) if np.any(5 > el) or np.any(np.mod(el, 90) < 5): warnings.warn( RuntimeWarning( 'The approximated method to compute ' 'the gaseous attenuation in recommendation ITU-P 676-11 ' 'is only recommended for elevation angles between' '5 and 90 degrees')) # Water vapour attenuation (gammaw) computation as in Section 1 of # Annex 2 of [1] gamma0 = self.gamma0_approx(f, P, rho, T) gammaw = self.gammaw_approx(f, P, rho, T) return gamma0, gammaw @classmethod def slant_inclined_path_equivalent_height(self, f, p): """ """ rp = p / 1013.0 t1 = (4.64) / (1 + 0.066 * rp**-2.3) * \ np.exp(- ((f - 59.7) / (2.87 + 12.4 * np.exp(-7.9 * rp)))**2) t2 = (0.14 * np.exp(2.21 * rp)) / \ ((f - 118.75)**2 + 0.031 * np.exp(2.2 * rp)) t3 = (0.0114) / (1 + 0.14 * rp**-2.6) * f * \ (-0.0247 + 0.0001 * f + 1.61e-6 * f**2) / \ (1 - 0.0169 * f + 4.1e-5 * f**2 + 3.2e-7 * f**3) h0 = (6.1) / (1 + 0.17 * rp**-1.1) * (1 + t1 + t2 + t3) h0 = np.where(f < 70, np.minimum(h0, 10.7 * rp**0.3), h0) sigmaw = (1.013) / (1 + np.exp(-8.6 * (rp - 0.57))) hw = 1.66 * (1 + (1.39 * sigmaw) / ((f - 22.235)**2 + 2.56 * sigmaw) + (3.37 * sigmaw) / ((f - 183.31)**2 + 4.69 * sigmaw) + (1.58 * sigmaw) / ((f - 325.1)**2 + 2.89 * sigmaw)) return h0, hw @classmethod def gaseous_attenuation_terrestrial_path(self, r, f, el, rho, P, T, mode='approx'): """ """ if mode == 'approx': gamma0, gammaw = self.gaseous_attenuation_approximation( f, el, rho, P, T) return (gamma0 + gammaw) * r else: gamma = self.gamma_exact(f, P, rho, T) return gamma * r @classmethod def gaseous_attenuation_slant_path(self, f, el, rho, P, T, V_t=None, h=None, mode='approx'): """ """ if mode == 'approx': gamma0, gammaw = self.gaseous_attenuation_approximation( f, el, rho, P, T) e = rho * T / 216.7 h0, hw = self.slant_inclined_path_equivalent_height(f, P + e) # Use the zenit water-vapour method if the values of V_t # and h are provided if V_t is not None and h is not None: Aw = self.zenit_water_vapour_attenuation( None, None, None, f, V_t, h) else: Aw = gammaw * hw A0 = gamma0 * h0 return (A0 + Aw) / np.sin(np.deg2rad(el)) else: delta_h = 0.0001 * np.exp((np.arange(1, 923) - 1) / 100) h_n = np.cumsum(delta_h) T_n = standard_temperature(h_n).to(u.K).value press_n = standard_pressure(h_n).value rho_n = standard_water_vapour_density(h_n, rho_0=rho).value e = rho * T / 216.7 n_n = radio_refractive_index(press_n, e, T).value n_ratio = np.pad(n_n[1:], (0, 1), mode='edge') / n_n r_n = 6371 + h_n b = np.pi / 2 - np.deg2rad(el) Agas = 0 for t, press, rho, r, delta, n_r in zip(T_n, press_n, rho_n, r_n, delta_h, n_ratio): a = -r * np.cos(b) + 0.5 * np.sqrt( 4 * r**2 * np.cos(b)**2 + 8 * r * delta + 4 * delta**2) a_cos_arg = np.clip((-a**2 - 2 * r * delta - delta**2) / (2 * a * r + 2 * a * delta), -1, 1) alpha = np.pi - np.arccos(a_cos_arg) gamma = self.gamma_exact(f, press, rho, t) Agas += a * gamma b = np.arcsin(n_r * np.sin(alpha)) return Agas @classmethod def gaseous_attenuation_inclined_path(self, f, el, rho, P, T, h1, h2, mode='approx'): """ """ if h1 > 10 or h2 > 10: raise ValueError('Both the transmitter and the receiver must be at' 'altitude of less than 10 km above the sea level.' 'Current altitude Tx: %.2f km, Rx: %.2f km' % (h1, h2)) if mode == 'approx': rho = rho * np.exp(h1 / 2) gamma0, gammaw = self.gaseous_attenuation_approximation( f, el, rho, P, T) else: gamma0 = self.gamma_exact(f, P, rho, T) gammaw = 0 e = rho * T / 216.7 h0, hw = self.slant_inclined_path_equivalent_height(f, P + e) if 5 < el and el < 90: h0_p = h0 * (np.exp(-h1 / h0) - np.exp(-h2 / h0)) hw_p = hw * (np.exp(-h1 / hw) - np.exp(-h2 / hw)) return (gamma0 * h0_p + gammaw * hw_p) / np.sin(np.deg2rad(el)) else: def F(x): return 1 / (0.661 * x + 0.339 * np.sqrt(x**2 + 5.51)) el1 = el el2 = -el Re = 8500 # TODO: change to ITU-R P 834 def xi(eli, hi): return np.tan(np.deg2rad(eli) * np.sqrt((Re + hi) / h0)) def xi_p(eli, hi): return np.tan(np.deg2rad(eli) * np.sqrt((Re + hi) / hw)) def eq_33(h_num, h_den, el, x): return np.sqrt(Re + h_num) * F(x) * \ np.exp(-h_num / h_den) / np.cos(np.deg2rad(el)) A = gamma0 * np.sqrt(h0) * (eq_33(h1, h0, el1, xi(el1, h1)) - eq_33(h2, h0, el2, xi(el2, h2))) +\ gammaw * np.sqrt(hw) * (eq_33(h1, hw, el1, xi_p(el1, h1)) - eq_33(h2, hw, el2, xi_p(el2, h2))) return A @classmethod def zenit_water_vapour_attenuation(self, lat, lon, p, f, V_t=None, h=None): f_ref = 20.6 # [GHz] p_ref = 780 # [hPa] if V_t is None: V_t = total_water_vapour_content(lat, lon, p, h).value rho_ref = V_t / 4 # [g/m3] t_ref = 14 * np.log(0.22 * V_t / 4) + 3 # [Celsius] gammaw_approx_vect = np.vectorize(self.gammaw_approx) return (0.0173 * V_t * gammaw_approx_vect(f, p_ref, rho_ref, t_ref + 273) / gammaw_approx_vect(f_ref, p_ref, rho_ref, t_ref + 273))
class _ITU676_11(): tmp = load_data(os.path.join(dataset_dir, '676/v11_lines_oxygen.txt'), skip_header=1) f_ox = tmp[:, 0] a1 = tmp[:, 1] a2 = tmp[:, 2] a3 = tmp[:, 3] a4 = tmp[:, 4] a5 = tmp[:, 5] a6 = tmp[:, 6] tmp = load_data(os.path.join(dataset_dir, '676//v11_lines_water_vapour.txt'), skip_header=1) f_wv = tmp[:, 0] b1 = tmp[:, 1] b2 = tmp[:, 2] b3 = tmp[:, 3] b4 = tmp[:, 4] b5 = tmp[:, 5] b6 = tmp[:, 6] idx_approx = np.zeros_like(b1, dtype=bool).squeeze() asterisk_rows = [0, 3, 4, 5, 7, 12, 20, 24, 34] idx_approx[np.array(asterisk_rows)] = True def __init__(self): self.__version__ = 11 self.year = 2017 self.month = 12 self.link = 'https://www.itu.int/rec/R-REC-P.676-11-201712-S/en' @classmethod def gammaw_approx(self, f, p, rho, T): # T in Kelvin # e : water vapour partial pressure in hPa (total barometric pressure # ptot = p + e) theta = 300 / T e = rho * T / 216.7 f_wv = self.f_wv[self.idx_approx] b1 = self.b1[self.idx_approx] b2 = self.b2[self.idx_approx] b3 = self.b3[self.idx_approx] b4 = self.b4[self.idx_approx] b5 = self.b5[self.idx_approx] b6 = self.b6[self.idx_approx] D_f_wv = b3 * 1e-4 * (p * theta**b4 + b5 * e * theta**b6) F_i_wv = f / f_wv * ((D_f_wv) / ((f_wv - f)**2 + D_f_wv**2) + (D_f_wv) / ((f_wv + f)**2 + D_f_wv**2)) Si_wv = b1 * 1e-1 * e * theta**3.5 * np.exp(b2 * (1 - theta)) N_pp_wv = Si_wv * F_i_wv N_pp = N_pp_wv.sum() gamma = 0.1820 * f * N_pp # Eq. 1 [dB/km] return gamma @classmethod def gamma0_approx(self, f, p, rho, T): # T in Kelvin # e : water vapour partial pressure in hPa (total barometric pressure # ptot = p + e) theta = 300 / T e = rho * T / 216.7 f_ox = self.f_ox D_f_ox = self.a3 * 1e-4 * (p * (theta**(0.8 - self.a4)) + 1.1 * e * theta) delta_ox = (self.a5 + self.a6 * theta) * 1e-4 * (p + e) * theta**0.8 F_i_ox = f / f_ox * ((D_f_ox - delta_ox * (f_ox - f)) / ((f_ox - f)**2 + D_f_ox**2) + (D_f_ox - delta_ox * (f_ox + f)) / ((f_ox + f)**2 + D_f_ox**2)) Si_ox = self.a1 * 1e-7 * p * theta**3 * np.exp(self.a2 * (1 - theta)) N_pp_ox = Si_ox * F_i_ox d = 5.6e-4 * (p + e) * theta**0.8 N_d_pp = f * p * theta**2 * \ (6.14e-5 / (d * (1 + (f / d)**2)) + 1.4e-12 * p * theta**1.5 / (1 + 1.9e-5 * f**1.5)) N_pp = N_pp_ox.sum() + N_d_pp gamma = 0.1820 * f * N_pp # Eq. 1 [dB/km] return gamma @classmethod def gamma0_exact(self, f, p, rho, T): return __gamma0_exact__676_9_11__(self, f, p, rho, T) @classmethod def gammaw_exact(self, f, p, rho, T): return __gammaw_exact__676_9_11__(self, f, p, rho, T) @classmethod def gamma_exact(self, f, p, rho, T): return (self.gamma0_exact(f, p, rho, T) + self.gammaw_exact(f, p, rho, T)) @classmethod def gaseous_attenuation_approximation(self, f, el, rho, P, T): """ T goes in Kelvin """ if np.any(f > 350): warnings.warn( RuntimeWarning( 'The approximated method to computes ' 'the gaseous attenuation in recommendation ITU-P 676-11 ' 'is only recommended for frequencies below 350GHz')) if np.any(5 > el) or np.any(np.mod(el, 90) < 5): warnings.warn( RuntimeWarning( 'The approximated method to compute ' 'the gaseous attenuation in recommendation ITU-P 676-11 ' 'is only recommended for elevation angles between' '5 and 90 degrees')) # Water vapour attenuation (gammaw) computation as in Section 1 of # Annex 2 of [1] gamma0 = self.gamma0_approx(f, P, rho, T) gammaw = self.gammaw_approx(f, P, rho, T) return gamma0, gammaw @classmethod def slant_inclined_path_equivalent_height(self, f, p): """ """ rp = p / 1013.25 t1 = 4.64 / (1 + 0.066 * rp**-2.3) * \ np.exp(- ((f - 59.7) / (2.87 + 12.4 * np.exp(-7.9 * rp)))**2) t2 = (0.14 * np.exp(2.12 * rp)) / \ ((f - 118.75)**2 + 0.031 * np.exp(2.2 * rp)) t3 = 0.0114 / (1 + 0.14 * rp**-2.6) * f * \ (-0.0247 + 0.0001 * f + 1.61e-6 * f**2) / \ (1 - 0.0169 * f + 4.1e-5 * f**2 + 3.2e-7 * f**3) h0 = 6.1 / (1 + 0.17 * rp**-1.1) * (1 + t1 + t2 + t3) h0 = np.where(f < 70, np.minimum(h0, 10.7 * rp**0.3), h0) sigmaw = 1.013 / (1 + np.exp(-8.6 * (rp - 0.57))) hw = 1.66 * (1 + (1.39 * sigmaw) / ((f - 22.235)**2 + 2.56 * sigmaw) + (3.37 * sigmaw) / ((f - 183.31)**2 + 4.69 * sigmaw) + (1.58 * sigmaw) / ((f - 325.1)**2 + 2.89 * sigmaw)) return h0, hw @classmethod def gaseous_attenuation_terrestrial_path(self, r, f, el, rho, P, T, mode='approx'): """ """ if mode == 'approx': gamma0, gammaw = self.gaseous_attenuation_approximation( f, el, rho, P, T) return (gamma0 + gammaw) * r else: gamma = self.gamma_exact(f, P, rho, T) return gamma * r @classmethod def gaseous_attenuation_slant_path(self, f, el, rho, P, T, V_t=None, h=None, mode='approx'): """ """ if mode == 'approx': gamma0, gammaw = self.gaseous_attenuation_approximation( f, el, rho, P, T) e = rho * T / 216.7 h0, hw = self.slant_inclined_path_equivalent_height(f, P + e) # Use the zenit water-vapour method if the values of V_t # and h are provided if V_t is not None and h is not None: Aw = self.zenit_water_vapour_attenuation( None, None, None, f, V_t, h) else: Aw = gammaw * hw A0 = gamma0 * h0 return (A0 + Aw) / np.sin(np.deg2rad(el)) else: delta_h = 0.0001 * np.exp((np.arange(0, 923)) / 100) h_n = np.cumsum(delta_h) T_n = standard_temperature(h_n).to(u.K).value press_n = standard_pressure(h_n).value rho_n = standard_water_vapour_density(h_n, rho_0=rho).value e = rho * T / 216.7 n_n = radio_refractive_index(press_n, e, T).value n_ratio = np.pad(n_n[1:], (0, 1), mode='edge') / n_n r_n = 6371 + h_n b = np.pi / 2 - np.deg2rad(el) Agas = 0 for t, press, rho, r, delta, n_r in zip(T_n, press_n, rho_n, r_n, delta_h, n_ratio): a = -r * np.cos(b) + 0.5 * np.sqrt( 4 * r**2 * np.cos(b)**2 + 8 * r * delta + 4 * delta**2) a_cos_arg = np.clip((-a**2 - 2 * r * delta - delta**2) / (2 * a * r + 2 * a * delta), -1, 1) alpha = np.pi - np.arccos(a_cos_arg) gamma = self.gamma_exact(f, press, rho, t) Agas += a * gamma b = np.arcsin(n_r * np.sin(alpha)) return Agas @classmethod def gaseous_attenuation_inclined_path(self, f, el, rho, P, T, h1, h2, mode='approx'): """ """ if h1 > 10 or h2 > 10: raise ValueError('Both the transmitter and the receiver must be at' 'altitude of less than 10 km above the sea level.' 'Current altitude Tx: %.2f km, Rx: %.2f km' % (h1, h2)) if mode == 'approx': rho = rho * np.exp(h1 / 2) gamma0, gammaw = self.gaseous_attenuation_approximation( f, el, rho, P, T) else: gamma0 = self.gamma0_exact(f, P, rho, T) gammaw = 0 e = rho * T / 216.7 h0, hw = self.slant_inclined_path_equivalent_height(f, P + e) if 5 < el and el < 90: h0_p = h0 * (np.exp(-h1 / h0) - np.exp(-h2 / h0)) hw_p = hw * (np.exp(-h1 / hw) - np.exp(-h2 / hw)) return (gamma0 * h0_p + gammaw * hw_p) / np.sin(np.deg2rad(el)) else: def F(x): return 1 / (0.661 * x + 0.339 * np.sqrt(x**2 + 5.51)) el1 = el el2 = -el Re = 8500 # TODO: change to ITU-R P 834 def xi(eli, hi): return np.tan(np.deg2rad(eli) * np.sqrt((Re + hi) / h0)) def xi_p(eli, hi): return np.tan(np.deg2rad(eli) * np.sqrt((Re + hi) / hw)) def eq_33(h_num, h_den, el, x): return np.sqrt(Re + h_num) * F(x) * \ np.exp(-h_num / h_den) / np.cos(np.deg2rad(el)) A = gamma0 * np.sqrt(h0) * (eq_33(h1, h0, el1, xi(el1, h1)) - eq_33(h2, h0, el2, xi(el2, h2))) +\ gammaw * np.sqrt(hw) * (eq_33(h1, hw, el1, xi_p(el1, h1)) - eq_33(h2, hw, el2, xi_p(el2, h2))) return A @classmethod def zenit_water_vapour_attenuation(self, lat, lon, p, f, V_t=None, h=None): f_ref = 20.6 # [GHz] p_ref = 815 # [hPa] if h is None: h = topographic_altitude(lat, lon).value if V_t is None: V_t = total_water_vapour_content(lat, lon, p, h).value rho_ref = V_t / 3.67 t_ref = 14 * np.log(0.22 * V_t / 3.67) + 3 # [Celsius] a = (0.2048 * np.exp(-((f - 22.43) / 3.097)**2) + 0.2236 * np.exp(-((f - 183.5) / 4.096)**2) + 0.2073 * np.exp(-((f - 325) / 3.651)**2) - 0.113) b = 8.741e4 * np.exp(-0.587 * f) + 312.2 * f**(-2.38) + 0.723 h = np.minimum(h, 4) gammaw_approx_vect = np.vectorize(self.gammaw_approx) Aw_term1 = (0.0176 * V_t * gammaw_approx_vect(f, p_ref, rho_ref, t_ref + 273.15) / gammaw_approx_vect(f_ref, p_ref, rho_ref, t_ref + 273.15)) return np.where(f < 20, Aw_term1, Aw_term1 * (a * h**b + 1))
class _ITU676_9(): tmp = load_data(os.path.join(dataset_dir, '676//v9_lines_oxygen.txt'), skip_header=1) f_ox = tmp[:, 0] a1 = tmp[:, 1] a2 = tmp[:, 2] a3 = tmp[:, 3] a4 = tmp[:, 4] a5 = tmp[:, 5] a6 = tmp[:, 6] tmp = load_data(os.path.join(dataset_dir, '676//v9_lines_water_vapour.txt'), skip_header=1) f_wv = tmp[:, 0] b1 = tmp[:, 1] b2 = tmp[:, 2] b3 = tmp[:, 3] b4 = tmp[:, 4] b5 = tmp[:, 5] b6 = tmp[:, 6] def __init__(self): self.__version__ = 9 self.year = 2012 self.month = 2 self.link = 'https://www.itu.int/rec/R-REC-P.676-9-201202-S/en' # Recommendation ITU-P R.676-9 has most of the methods similar to those # in Recommendation ITU-P R.676-10. def gammaw_approx(self, *args, **kwargs): return _ITU676_10.gammaw_approx(*args, **kwargs) def gamma0_approx(self, *args, **kwargs): return _ITU676_10.gamma0_approx(*args, **kwargs) def gaseous_attenuation_inclined_path(self, *args, **kwargs): return _ITU676_10.gaseous_attenuation_inclined_path(*args, **kwargs) def zenit_water_vapour_attenuation(self, *args, **kwargs): return _ITU676_10.zenit_water_vapour_attenuation(*args, **kwargs) def gaseous_attenuation_approximation(self, *args, **kwargs): return _ITU676_10.gaseous_attenuation_approximation(*args, **kwargs) def slant_inclined_path_equivalent_height(self, *args, **kwargs): return _ITU676_10.slant_inclined_path_equivalent_height( *args, **kwargs) def gaseous_attenuation_terrestrial_path(self, *args, **kwargs): return _ITU676_10.gaseous_attenuation_terrestrial_path(*args, **kwargs) def gaseous_attenuation_slant_path(self, *args, **kwargs): return _ITU676_10.gaseous_attenuation_slant_path(*args, **kwargs) def gamma0_exact(self, f, p, rho, T): return __gamma0_exact__676_9_11__(self, f, p, rho, T) def gammaw_exact(self, f, p, rho, T): return __gammaw_exact__676_9_11__(self, f, p, rho, T) def gamma_exact(self, f, p, rho, T): return (self.gamma0_exact(f, p, rho, T) + self.gammaw_exact(f, p, rho, T))