# -*- coding: utf-8 -*- from __future__ import absolute_import from __future__ import division from __future__ import print_function import os import numpy as np from astropy import units as u from itur import utils from itur.models.itu1144 import bilinear_2D_interpolator,\ bicubic_2D_interpolator from itur.models.itu1511 import topographic_altitude from itur.utils import prepare_input_array, prepare_output_array, dataset_dir,\ load_data, prepare_quantity, memory def __surface_water_vapour_density_836_4_5__(self, lat, lon, p, alt=None): lat_f = lat.flatten() lon_f = lon.flatten() available_p = np.array([0.1, 0.2, 0.3, 0.5, 1, 2, 3, 5, 10, 20, 30, 50, 60, 70, 80, 90, 95, 99]) if p in available_p: p_below = p_above = p pExact = True else: pExact = False idx = available_p.searchsorted(p, side='right') - 1 idx = np.clip(idx, 0, len(available_p) - 1) p_below = available_p[idx] idx = np.clip(idx + 1, 0, len(available_p) - 1) p_above = available_p[idx] R = -(lat_f - 90) // 1.125 C = lon_f // 1.125 lats = np.array([90 - R * 1.125, 90 - (R + 1) * 1.125, 90 - R * 1.125, 90 - (R + 1) * 1.125]) lons = np.mod(np.array([C * 1.125, C * 1.125, (C + 1) * 1.125, (C + 1) * 1.125]), 360) r = - (lat_f - 90) / 1.125 c = lon_f / 1.125 rho_a = self.rho(lats, lons, p_above) VSCH_a = self.VSCH(lats, lons, p_above) altitude_res = topographic_altitude(lats, lons).value.reshape(lats.shape) if alt is None: alt = altitude_res else: alt = alt.flatten() rho_a = rho_a * np.exp(- (alt - altitude_res) * 1.0 / (VSCH_a)) rho_a = (rho_a[0, :] * ((R + 1 - r) * (C + 1 - c)) + rho_a[1, :] * ((r - R) * (C + 1 - c)) + rho_a[2, :] * ((R + 1 - r) * (c - C)) + rho_a[3, :] * ((r - R) * (c - C))) if not pExact: rho_b = self.rho(lats, lons, p_below) VSCH_b = self.VSCH(lats, lons, p_below) rho_b = rho_b * np.exp(- (alt - altitude_res) / (VSCH_b)) rho_b = (rho_b[0, :] * ((R + 1 - r) * (C + 1 - c)) + rho_b[1, :] * ((r - R) * (C + 1 - c)) + rho_b[2, :] * ((R + 1 - r) * (c - C)) + rho_b[3, :] * ((r - R) * (c - C))) # Compute the values of Lred_a if not pExact: rho = rho_b + (rho_a - rho_b) * (np.log(p) - np.log(p_below)) / \ (np.log(p_above) - np.log(p_below)) return rho.reshape(lat.shape) else: return rho_a.reshape(lat.shape) def __total_water_vapour_content_836_4_5__(self, lat, lon, p, alt=None): """ """ lat_f = lat.flatten() lon_f = lon.flatten() available_p = np.array([0.1, 0.2, 0.3, 0.5, 1, 2, 3, 5, 10, 20, 30, 50, 60, 70, 80, 90, 95, 99]) if p in available_p: p_below = p_above = p pExact = True else: pExact = False idx = available_p.searchsorted(p, side='right') - 1 idx = np.clip(idx, 0, len(available_p) - 1) p_below = available_p[idx] idx = np.clip(idx + 1, 0, len(available_p) - 1) p_above = available_p[idx] R = -(lat_f - 90) // 1.125 C = lon_f // 1.125 lats = np.array([90 - R * 1.125, 90 - (R + 1) * 1.125, 90 - R * 1.125, 90 - (R + 1) * 1.125]) lons = np.mod(np.array([C * 1.125, C * 1.125, (C + 1) * 1.125, (C + 1) * 1.125]), 360) r = - (lat_f - 90) / 1.125 c = lon_f / 1.125 V_a = self.V(lats, lons, p_above) VSCH_a = self.VSCH(lats, lons, p_above) altitude_res = topographic_altitude(lats, lons).value.reshape(lats.shape) if alt is None: alt = altitude_res else: alt = alt.flatten() V_a = V_a * np.exp(-(alt - altitude_res) * 1.0 / (VSCH_a)) V_a = (V_a[0, :] * ((R + 1 - r) * (C + 1 - c)) + V_a[1, :] * ((r - R) * (C + 1 - c)) + V_a[2, :] * ((R + 1 - r) * (c - C)) + V_a[3, :] * ((r - R) * (c - C))) if not pExact: V_b = self.V(lats, lons, p_below) VSCH_b = self.VSCH(lats, lons, p_below) V_b = V_b * np.exp(-(alt - altitude_res) * 1.0 / (VSCH_b)) V_b = (V_b[0, :] * ((R + 1 - r) * (C + 1 - c)) + V_b[1, :] * ((r - R) * (C + 1 - c)) + V_b[2, :] * ((R + 1 - r) * (c - C)) + V_b[3, :] * ((r - R) * (c - C))) if not pExact: V = V_b + (V_a - V_b) * (np.log(p) - np.log(p_below)) / \ (np.log(p_above) - np.log(p_below)) return V.reshape(lat.shape) else: return V_a.reshape(lat.shape) class __ITU836(): """Water vapour: surface density and total columnar content Available versions: * P.836-6 (12/17) (Current version) * P.836-5 (09/13) (Superseded) * P.836-4 (10/09) (Superseded) Not available versions: * P.836-0 (03/92) (Superseded) * P.836-1 (08/97) (Superseded) * P.836-2 (02/01) (Superseded) * P.836-3 (11/01) (Superseded) """ # This is an abstract class that contains an instance to a version of the # ITU-R P.836 recommendation. def __init__(self, version=6): if version == 6: self.instance = _ITU836_6() elif version == 5: self.instance = _ITU836_5() elif version == 4: self.instance = _ITU836_4() else: raise ValueError( 'Version {0} is not implemented for the ITU-R P.836 model.' .format(version)) self._V = {} self._VSCH = {} self._rho = {} self._topo_alt = None @property def __version__(self): return self.instance.__version__ def surface_water_vapour_density(self, lat, lon, p, alt): fcn = np.vectorize(self.instance.surface_water_vapour_density, excluded=[0, 1, 3], otypes=[np.ndarray]) return np.array(fcn(lat, lon, p, alt).tolist()) def total_water_vapour_content(self, lat, lon, p, alt): fcn = np.vectorize(self.instance.total_water_vapour_content, excluded=[0, 1, 3], otypes=[np.ndarray]) return np.array(fcn(lat, lon, p, alt).tolist()) class _ITU836_6(): def __init__(self): self.__version__ = 6 self.year = 2017 self.month = 12 self.link = 'https://www.itu.int/rec/R-REC-P.836-6-201712-I/en' self._V = {} self._VSCH = {} self._rho = {} self._topo_alt = None def V(self, lat, lon, p): if not self._V: 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/v6_V_%s.txt') lats = load_data(os.path.join(dataset_dir, '836/v6_Lat.txt')) lons = load_data(os.path.join(dataset_dir, '836/v6_Lon.txt')) for p_loads in ps: vals = load_data(d_dir % (str(p_loads).replace('.', ''))) self._V[float(p_loads)] = bilinear_2D_interpolator( lats, lons, vals) return self._V[float(p)]( np.array([lat.ravel(), lon.ravel()]).T).reshape(lat.shape) def VSCH(self, lat, lon, p): if not self._VSCH: 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/v6_VSCH_%s.txt') lats = load_data(os.path.join(dataset_dir, '836/v6_Lat.txt')) lons = load_data(os.path.join(dataset_dir, '836/v6_Lon.txt')) for p_loads in ps: vals = load_data(d_dir % (str(p_loads).replace('.', ''))) self._VSCH[float(p_loads)] = bilinear_2D_interpolator( lats, lons, vals) return self._VSCH[float(p)]( 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/v6_RHO_%s.txt') lats = load_data(os.path.join(dataset_dir, '836/v6_Lat.txt')) lons = load_data(os.path.join(dataset_dir, '836/v6_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 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 surface_water_vapour_density(self, lat, lon, p, alt=None): """ """ lat_f = lat.flatten() lon_f = lon.flatten() available_p = np.array([0.1, 0.2, 0.3, 0.5, 1, 2, 3, 5, 10, 20, 30, 50, 60, 70, 80, 90, 95, 99]) if p in available_p: p_below = p_above = p pExact = True else: pExact = False idx = available_p.searchsorted(p, side='right') - 1 idx = np.clip(idx, 0, len(available_p) - 1) p_below = available_p[idx] idx = np.clip(idx + 1, 0, len(available_p) - 1) p_above = available_p[idx] R = -(lat_f - 90) // 1.125 C = lon_f // 1.125 lats = np.array([90 - R * 1.125, 90 - (R + 1) * 1.125, 90 - R * 1.125, 90 - (R + 1) * 1.125]) lons = np.mod(np.array([C * 1.125, C * 1.125, (C + 1) * 1.125, (C + 1) * 1.125]), 360) r = - (lat_f - 90) / 1.125 c = lon_f / 1.125 rho_a = self.rho(lats, lons, p_above) VSCH_a = self.VSCH(lats, lons, p_above) altitude_res = self.topo_alt(lats, lons) if alt is None: alt = altitude_res else: alt = alt.flatten() rho_a = rho_a * np.exp(- (alt - altitude_res) * 1.0 / (VSCH_a)) rho_a = (rho_a[0, :] * ((R + 1 - r) * (C + 1 - c)) + rho_a[1, :] * ((r - R) * (C + 1 - c)) + rho_a[2, :] * ((R + 1 - r) * (c - C)) + rho_a[3, :] * ((r - R) * (c - C))) if not pExact: rho_b = self.rho(lats, lons, p_below) VSCH_b = self.VSCH(lats, lons, p_below) rho_b = rho_b * np.exp(- (alt - altitude_res) / (VSCH_b)) rho_b = (rho_b[0, :] * ((R + 1 - r) * (C + 1 - c)) + rho_b[1, :] * ((r - R) * (C + 1 - c)) + rho_b[2, :] * ((R + 1 - r) * (c - C)) + rho_b[3, :] * ((r - R) * (c - C))) # Compute the values of Lred_a if not pExact: rho = rho_b + (rho_a - rho_b) * (np.log(p) - np.log(p_below)) / \ (np.log(p_above) - np.log(p_below)) return rho.reshape(lat.shape) else: return rho_a.reshape(lat.shape) def total_water_vapour_content(self, lat, lon, p, alt=None): """ """ lat_f = lat.flatten() lon_f = lon.flatten() available_p = np.array([0.1, 0.2, 0.3, 0.5, 1, 2, 3, 5, 10, 20, 30, 50, 60, 70, 80, 90, 95, 99]) if p in available_p: p_below = p_above = p pExact = True else: pExact = False idx = available_p.searchsorted(p, side='right') - 1 idx = np.clip(idx, 0, len(available_p) - 1) p_below = available_p[idx] idx = np.clip(idx + 1, 0, len(available_p) - 1) p_above = available_p[idx] R = -(lat_f - 90) // 1.125 C = lon_f // 1.125 lats = np.array([90 - R * 1.125, 90 - (R + 1) * 1.125, 90 - R * 1.125, 90 - (R + 1) * 1.125]) lons = np.mod(np.array([C * 1.125, C * 1.125, (C + 1) * 1.125, (C + 1) * 1.125]), 360) r = - (lat_f - 90) / 1.125 c = lon_f / 1.125 V_a = self.V(lats, lons, p_above) VSCH_a = self.VSCH(lats, lons, p_above) altitude_res = self.topo_alt(lats, lons) if alt is None: alt = altitude_res else: alt = alt.flatten() V_a = V_a * np.exp(-(alt - altitude_res) * 1.0 / (VSCH_a)) V_a = (V_a[0, :] * ((R + 1 - r) * (C + 1 - c)) + V_a[1, :] * ((r - R) * (C + 1 - c)) + V_a[2, :] * ((R + 1 - r) * (c - C)) + V_a[3, :] * ((r - R) * (c - C))) if not pExact: V_b = self.V(lats, lons, p_below) VSCH_b = self.VSCH(lats, lons, p_below) V_b = V_b * np.exp(-(alt - altitude_res) * 1.0 / (VSCH_b)) V_b = (V_b[0, :] * ((R + 1 - r) * (C + 1 - c)) + V_b[1, :] * ((r - R) * (C + 1 - c)) + V_b[2, :] * ((R + 1 - r) * (c - C)) + V_b[3, :] * ((r - R) * (c - C))) if not pExact: V = V_b + (V_a - V_b) * (np.log(p) - np.log(p_below)) / \ (np.log(p_above) - np.log(p_below)) return V.reshape(lat.shape) else: return V_a.reshape(lat.shape) class _ITU836_5(): def __init__(self): self.__version__ = 5 self.year = 2013 self.month = 9 self.link = 'https://www.itu.int/rec/R-REC-P.836-5-201309-I/en' self._V = {} self._VSCH = {} self._rho = {} def V(self, lat, lon, p): if not self._V: 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/v5_V_%s.txt') lats = load_data(os.path.join(dataset_dir, '836/v5_Lat.txt')) lons = load_data(os.path.join(dataset_dir, '836/v5_Lon.txt')) for p_loads in ps: vals = load_data(d_dir % (str(p_loads).replace('.', ''))) self._V[float(p_loads)] = bilinear_2D_interpolator( lats, lons, vals) return self._V[float(p)]( np.array([lat.ravel(), lon.ravel()]).T).reshape(lat.shape) def VSCH(self, lat, lon, p): if not self._VSCH: 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/v5_VSCH_%s.txt') lats = load_data(os.path.join(dataset_dir, '836/v5_Lat.txt')) lons = load_data(os.path.join(dataset_dir, '836/v5_Lon.txt')) for p_loads in ps: vals = load_data(d_dir % (str(p_loads).replace('.', ''))) self._VSCH[float(p_loads)] = bilinear_2D_interpolator( lats, lons, vals) return self._VSCH[float(p)]( 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/v5_RHO_%s.txt') lats = load_data(os.path.join(dataset_dir, '836/v5_Lat.txt')) lons = load_data(os.path.join(dataset_dir, '836/v5_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 surface_water_vapour_density(self, lat, lon, p, alt=None): """ """ return __surface_water_vapour_density_836_4_5__(self, lat, lon, p, alt) def total_water_vapour_content(self, lat, lon, p, alt=None): """ """ return __total_water_vapour_content_836_4_5__(self, lat, lon, p, alt=None) class _ITU836_4(): def __init__(self): self.__version__ = 4 self.year = 2009 self.month = 10 self.link = 'https://www.itu.int/rec/R-REC-P.836-4-200910-S/en' self._V = {} self._VSCH = {} self._rho = {} def V(self, lat, lon, p): if not self._V: 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_V_%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._V[float(p_loads)] =\ bilinear_2D_interpolator(lats, lons, vals) return self._V[float(p)]( np.array([lat.ravel(), lon.ravel()]).T).reshape(lat.shape) def VSCH(self, lat, lon, p): if not self._VSCH: 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_VSCH_%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._VSCH[float(p_loads)] =\ bilinear_2D_interpolator(lats, lons, vals) return self._VSCH[float(p)]( 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) # The procedure to compute the surface water vapour density and the # total water vapour content is similar to the ones in recommendation # ITU-P R.836-5. def surface_water_vapour_density(self, lat, lon, p, alt=None): return __surface_water_vapour_density_836_4_5__(self, lat, lon, p, alt) def total_water_vapour_content(self, lat, lon, p, alt=None): """ """ return __total_water_vapour_content_836_4_5__(self, lat, lon, p, alt=None) __model = __ITU836() def change_version(new_version): """ Change the version of the ITU-R P.836 recommendation currently being used. Parameters ---------- new_version : int Number of the version to use. Available versions: * P.836-6 (12/17) (Current version) * P.836-5 (09/13) (Superseded) * P.836-4 (10/09) (Superseded) Not available versions: * P.836-0 (03/92) (Superseded) * P.836-1 (08/97) (Superseded) * P.836-2 (02/01) (Superseded) * P.836-3 (11/01) (Superseded) """ global __model __model = __ITU836(new_version) utils.memory.clear() def get_version(): """ Obtain the version of the ITU-R P.836 recommendation currently being used. """ global __model return __model.__version__ @memory.cache def surface_water_vapour_density(lat, lon, p, alt=None): """ Method to compute the surface water vapour density along a path at any desired location on the surface of the Earth. Parameters ---------- lat : number, sequence, or numpy.ndarray Latitudes of the receiver points lon : number, sequence, or numpy.ndarray Longitudes of the receiver points p : number Percentage of time exceeded for p% of the average year alt : number, sequence, or numpy.ndarray Altitude of the receivers. If None, use the topographical altitude as described in recommendation ITU-R P.1511 Returns ------- rho: Quantity Surface water vapour density (g/m3) References ---------- [1] Water vapour: surface density and total columnar content https://www.itu.int/rec/R-REC-P.836/en """ global __model type_output = type(lat) lat = prepare_input_array(lat) lon = prepare_input_array(lon) lon = np.mod(lon, 360) alt = prepare_input_array(alt) alt = prepare_quantity(alt, u.km, 'Altitude of the receivers') val = __model.surface_water_vapour_density(lat, lon, p, alt) return prepare_output_array(val, type_output) * u.g / u.m**3 @memory.cache def total_water_vapour_content(lat, lon, p, alt=None): """ Method to compute the total water vapour content along a path at any desired location on the surface of the Earth. Parameters ---------- lat : number, sequence, or numpy.ndarray Latitudes of the receiver points lon : number, sequence, or numpy.ndarray Longitudes of the receiver points p : number Percentage of time exceeded for p% of the average year alt : number, sequence, or numpy.ndarray Altitude of the receivers. If None, use the topographical altitude as described in recommendation ITU-R P.1511 Returns ------- V: Quantity Total water vapour content (kg/m2) References ---------- [1] Water vapour: surface density and total columnar content https://www.itu.int/rec/R-REC-P.836/en """ global __model type_output = type(lat) lat = prepare_input_array(lat) lon = prepare_input_array(lon) lon = np.mod(lon, 360) alt = prepare_input_array(alt) alt = prepare_quantity(alt, u.km, 'Altitude of the receivers') val = __model.total_water_vapour_content(lat, lon, p, alt) return prepare_output_array(val, type_output) * u.kg / u.m**2