def calc_L_n_Campbell(T_C, T_S, L_dn, lai, emisVeg, emisGrd, x_LAD=1): ''' Net longwave radiation for soil and canopy layers Estimates the net longwave radiation for soil and canopy layers unisg based on equation 2a from [Kustas1999]_ and incorporated the effect of the Leaf Angle Distribution based on [Campbell1998]_ Parameters ---------- T_C : float Canopy temperature (K). T_S : float Soil temperature (K). L_dn : float Downwelling atmospheric longwave radiation (w m-2). lai : float Effective Leaf (Plant) Area Index. emisVeg : float Broadband emissivity of vegetation cover. emisGrd : float Broadband emissivity of soil. x_LAD: float, optional x parameter for the ellipsoidal Leaf Angle Distribution function, use x_LAD=1 for a spherical LAD. Returns ------- L_nC : float Net longwave radiation of canopy (W m-2). L_nS : float Net longwave radiation of soil (W m-2). References ---------- .. [Kustas1999] Kustas and Norman (1999) Evaluation of soil and vegetation heat flux predictions using a simple two-source model with radiometric temperatures for partial canopy cover, Agricultural and Forest Meteorology, Volume 94, Issue 1, Pages 13-29, http://dx.doi.org/10.1016/S0168-1923(99)00005-2. ''' # calculate long wave emissions from canopy, soil and sky L_C = emisVeg * met.calc_stephan_boltzmann(T_C) L_C[np.isnan(L_C)] = 0 L_S = emisGrd * met.calc_stephan_boltzmann(T_S) L_S[np.isnan(L_S)] = 0 # Calculate the canopy spectral properties _, albl, _, taudl = calc_spectra_Cambpell(lai, np.zeros(emisVeg.shape), 1.0 - emisVeg, np.zeros(emisVeg.shape), 1.0 - emisGrd, x_lad=x_LAD, lai_eff=None) # calculate net longwave radiation divergence of the soil L_nS = emisGrd * taudl * L_dn + emisGrd * (1.0 - taudl) * L_C - L_S L_nC = (1 - albl) * (1.0 - taudl) * (L_dn + L_S) - 2.0 * (1.0 - taudl) * L_C L_nC[np.isnan(L_nC)] = 0 L_nS[np.isnan(L_nS)] = 0 return np.asarray(L_nC), np.asarray(L_nS)
def calc_L_n_Kustas(T_C, T_S, L_dn, LAI, emisVeg, emisGrd, x_LAD=1): ''' Net longwave radiation for soil and canopy layers Estimates the net longwave radiation for soil and canopy layers unisg based on equation 2a from [Kustas1999]_ and incorporated the effect of the Leaf Angle Distribution based on [Campbell1998]_ Parameters ---------- T_C : float Canopy temperature (K). T_S : float Soil temperature (K). L_dn : float Downwelling atmospheric longwave radiation (w m-2). LAI : float Effective Leaf (Plant) Area Index. emisVeg : float Broadband emissivity of vegetation cover. emisGrd : float Broadband emissivity of soil. x_LAD: float, optional x parameter for the ellipsoidal Leaf Angle Distribution function, use x_LAD=1 for a spherical LAD. Returns ------- L_nC : float Net longwave radiation of canopy (W m-2). L_nS : float Net longwave radiation of soil (W m-2). References ---------- .. [Kustas1999] Kustas and Norman (1999) Evaluation of soil and vegetation heat flux predictions using a simple two-source model with radiometric temperatures for partial canopy cover, Agricultural and Forest Meteorology, Volume 94, Issue 1, Pages 13-29, http://dx.doi.org/10.1016/S0168-1923(99)00005-2. ''' # Integrate to get the diffuse transmitance taud = 0 for angle in range(0, 90, 5): akd = calc_K_be_Campbell(angle, x_LAD) # Eq. 15.4 taub = np.exp(-akd * LAI) taud = taud + taub * np.cos(np.radians(angle)) * \ np.sin(np.radians(angle)) * np.radians(5) taud = 2.0 * taud # D I F F U S E C O M P O N E N T S # Diffuse light canopy reflection coefficients for a deep canopy akd = -np.log(taud) / LAI ameanl = np.asarray(emisVeg) taudl = np.exp(-np.sqrt(ameanl) * akd * LAI) # Eq 15.6 # calculate long wave emissions from canopy, soil and sky L_C = emisVeg * met.calc_stephan_boltzmann(T_C) L_S = emisGrd * met.calc_stephan_boltzmann(T_S) # calculate net longwave radiation divergence of the soil L_nS = taudl * L_dn + (1.0 - taudl) * L_C - L_S L_nC = (1.0 - taudl) * (L_dn + L_S - 2.0 * L_C) return np.asarray(L_nC), np.asarray(L_nS)
def calc_longwave_irradiance(ea, t_a_k, p=1013.25, z_T=2.0, h_C=2.0): '''Longwave irradiance Estimates longwave atmospheric irradiance from clear sky. By default there is no lapse rate correction unless air temperature measurement height is considerably different than canopy height, (e.g. when using NWP gridded meteo data at blending height) Parameters ---------- ea : float atmospheric vapour pressure (mb). t_a_k : float air temperature (K). p : float air pressure (mb) z_T: float air temperature measurement height (m), default 2 m. h_C: float canopy height (m), default 2 m, Returns ------- L_dn : float Longwave atmospheric irradiance (W m-2) above the canopy ''' lapse_rate = met.calc_lapse_rate_moist(t_a_k, ea, p) t_a_surface = t_a_k - lapse_rate * (h_C - z_T) emisAtm = calc_emiss_atm(ea, t_a_surface) L_dn = emisAtm * met.calc_stephan_boltzmann(t_a_surface) return np.asarray(L_dn)
def calc_longwave_irradiance(ea, T_A_K, p=1013.25, z_T=2.0): '''Longwave irradiance Estimates longwave atmospheric irradiance from clear sky. Parameters ---------- ea : float atmospheric vapour pressure (mb). T_A_K : float air temperature (K). p : float air pressure (mb) z_T: float air temperature measurement height (m), default 2 m. Returns ------- L_dn : float Longwave atmospheric irradiance (W m-2) ''' lapse_rate = met.calc_lapse_rate_moist(T_A_K, ea, p) T_A_surface = T_A_K - z_T * lapse_rate emisAtm = calc_emiss_atm(ea, T_A_surface) L_dn = emisAtm * met.calc_stephan_boltzmann(T_A_surface) return np.asarray(L_dn)
def calc_longwave_irradiance(ea, T_A_K, z_T=2.0): '''Longwave irradiance Estimates longwave atmospheric irradiance from clear sky. Parameters ---------- ea : float atmospheric vapour pressure (mb). T_A_K : float air temperature (K). z_T: float air temperature measurement height (m), default 2 m. Returns ------- L_dn : float Longwave atmospheric irradiance (W m-2) ''' # Assume dry adiabatic lapse rate of air temperature. T_A_surface = T_A_K - z_T * 0.0098 emisAtm = calc_emiss_atm(ea, T_A_surface) L_dn = emisAtm * met.calc_stephan_boltzmann(T_A_surface) return np.asarray(L_dn)
def METRIC(Tr_K, T_A_K, u, ea, p, Sn, L_dn, emis, z_0M, d_0, z_u, z_T, cold_pixel, hot_pixel, LE_cold, LE_hot=0, use_METRIC_resistance=True, calcG_params=[[1], 0.35], UseL=False, UseDEM=False): '''Calulates bulk fluxes using METRIC model Parameters ---------- Tr_K : float Radiometric composite temperature (Kelvin). T_A_K : float Air temperature (Kelvin). u : float Wind speed above the canopy (m s-1). ea : float Water vapour pressure above the canopy (mb). p : float Atmospheric pressure (mb), use 1013 mb by default. S_n : float Solar irradiance (W m-2). L_dn : float Downwelling longwave radiation (W m-2) emis : float Surface emissivity. z_0M : float Aerodynamic surface roughness length for momentum transfer (m). d_0 : float Zero-plane displacement height (m). z_u : float Height of measurement of windspeed (m). z_T : float Height of measurement of air temperature (m). cold_pixel : tuple pixel coordinates (row, col) for the cold endmember hot_pixel : tuple pixel coordinates (row, col) for the hot endmember calcG_params : list[list,float or array], optional Method to calculate soil heat flux,parameters. * [[1],G_ratio]: default, estimate G as a ratio of Rn_S, default Gratio=0.35. * [[0],G_constant] : Use a constant G, usually use 0 to ignore the computation of G. * [[2,Amplitude,phase_shift,shape],time] : estimate G from Santanello and Friedl with G_param list of parameters (see :func:`~TSEB.calc_G_time_diff`). UseL : Optional[float] If included, its value will be used to force the Moning-Obukhov stability length. Returns ------- flag : int Quality flag, see Appendix for description. Ln : float Net longwave radiation (W m-2) LE : float Latent heat flux (W m-2). H : float Sensible heat flux (W m-2). G : float Soil heat flux (W m-2). R_A : float Aerodynamic resistance to heat transport (s m-1). u_friction : float Friction velocity (m s-1). L : float Monin-Obuhkov length (m). n_iterations : int number of iterations until convergence of L. References ---------- ''' # Convert input scalars to numpy arrays and check parameters size Tr_K = np.asarray(Tr_K) (T_A_K, u, ea, p, Sn, L_dn, emis, z_0M, d_0, z_u, z_T, LE_cold, LE_hot, calcG_array) = map(tseb._check_default_parameter_size, [ T_A_K, u, ea, p, Sn, L_dn, emis, z_0M, d_0, z_u, z_T, LE_cold, LE_hot, calcG_params[1] ], [Tr_K] * 14) # Create the output variables [Ln, LE, H, G, R_A, iterations] = [np.zeros(Tr_K.shape) + np.NaN for i in range(6)] flag = np.zeros(Tr_K.shape, dtype=np.byte) # iteration of the Monin-Obukhov length if isinstance(UseL, bool): # Initially assume stable atmospheric conditions and set variables for L = np.zeros(Tr_K.shape) + np.inf max_iterations = ITERATIONS else: # We force Monin-Obukhov lenght to the provided array/value L = np.ones(Tr_K.shape) * UseL max_iterations = 1 # No iteration if isinstance(UseDEM, bool): Tr_datum = np.asarray(Tr_K) Ta_datum = np.asarray(T_A_K) else: gamma_w = met.calc_lapse_rate_moist(T_A_K, ea, p) Tr_datum = Tr_K + gamma_w * UseDEM Ta_datum = T_A_K + gamma_w * UseDEM # Calculate the general parameters rho = met.calc_rho(p, ea, T_A_K) # Air density c_p = met.calc_c_p(p, ea) # Heat capacity of air rho_datum = met.calc_rho(p, ea, Ta_datum) # Air density # Calc initial Monin Obukhov variables u_friction = MO.calc_u_star(u, z_u, L, d_0, z_0M) u_friction = np.maximum(u_friction_min, u_friction) z_0H = res.calc_z_0H(z_0M, kB=kB) # Calculate Net radiation Ln = emis * L_dn - emis * met.calc_stephan_boltzmann(Tr_K) Rn = np.asarray(Sn + Ln) # Compute Soil Heat Flux i = np.ones(Rn.shape, dtype=bool) G[i] = tseb.calc_G([calcG_params[0], calcG_array], Rn, i) # Get cold and hot variables Rn_endmembers = np.array([Rn[cold_pixel], Rn[hot_pixel]]) G_endmembers = np.array([G[cold_pixel], G[hot_pixel]]) LE_endmembers = np.array([LE_cold[cold_pixel], LE_hot[hot_pixel]]) u_friction_endmembers = np.array( [u_friction[cold_pixel], u_friction[hot_pixel]]) u_endmembers = np.array([u[cold_pixel], u[hot_pixel]]) z_u_endmembers = np.array([z_u[cold_pixel], z_u[hot_pixel]]) Ta_datum_endmembers = np.array([Ta_datum[cold_pixel], Ta_datum[hot_pixel]]) z_T_endmembers = np.array([z_T[cold_pixel], z_T[hot_pixel]]) rho_datum_endmembers = np.array( [rho_datum[cold_pixel], rho_datum[hot_pixel]]) c_p_endmembers = np.array([c_p[cold_pixel], c_p[hot_pixel]]) d_0_endmembers = np.array([d_0[cold_pixel], d_0[hot_pixel]]) z_0M_endmembers = np.array([z_0M[cold_pixel], z_0M[hot_pixel]]) z_0H_endmembers = np.array([z_0H[cold_pixel], z_0H[hot_pixel]]) H_endmembers = calc_H_residual(Rn_endmembers, G_endmembers, LE=LE_endmembers) # ============================================================================== # HOT and COLD PIXEL ITERATIONS FOR MONIN-OBUKHOV LENGTH TO CONVERGE # ============================================================================== # Initially assume stable atmospheric conditions and set variables for L_old = np.ones(2) L_diff = np.ones(2) * float('inf') for iteration in range(max_iterations): if np.all(L_diff < L_thres): break if isinstance(UseL, bool): # Recaulculate L and the difference between iterations L_endmembers = MO.calc_L(u_friction_endmembers, Ta_datum_endmembers, rho_datum_endmembers, c_p_endmembers, H_endmembers, LE_endmembers) L_diff = np.fabs(L_endmembers - L_old) / np.fabs(L_old) L_old = np.array(L_endmembers) L_old[np.fabs(L_old) == 0] = 1e-36 u_friction_endmembers = MO.calc_u_star(u_endmembers, z_u_endmembers, L_endmembers, d_0_endmembers, z_0M_endmembers) u_friction_endmembers = np.maximum(u_friction_min, u_friction_endmembers) # Hot and Cold aerodynamic resistances if use_METRIC_resistance is True: R_A_params = { "z_T": np.array([2.0, 2.0]), "u_friction": u_friction_endmembers, "L": L_endmembers, "d_0": np.array([0.0, 0.0]), "z_0H": np.array([0.1, 0.1]) } else: R_A_params = { "z_T": z_T_endmembers, "u_friction": u_friction_endmembers, "L": L_endmembers, "d_0": d_0_endmembers, "z_0H": z_0H_endmembers } R_A_endmembers, _, _ = tseb.calc_resistances(tseb.KUSTAS_NORMAN_1999, {"R_A": R_A_params}) # Calculate the temperature gradients dT_endmembers = calc_dT(H_endmembers, R_A_endmembers, rho_datum_endmembers, c_p_endmembers) # dT constants # Note: the equations for a and b in the Allen 2007 paper (eq 50 and 51) appear to be wrong. dT_b = (dT_endmembers[1] - dT_endmembers[0]) / (Tr_datum[hot_pixel] - Tr_datum[cold_pixel]) dT_a = dT_endmembers[1] - dT_b * Tr_datum[hot_pixel] # Apply the constant to the whole image dT = dT_a + dT_b * Tr_datum # Allen 2007 eq. 29 # ============================================================================== # ITERATIONS FOR MONIN-OBUKHOV LENGTH AND H TO CONVERGE # ============================================================================== # Initially assume stable atmospheric conditions and set variables for L_queue = deque([np.ones(dT.shape)], 6) L_converged = np.asarray(np.zeros(Tr_K.shape)).astype(bool) L_diff_max = np.inf i = np.ones(dT.shape, dtype=bool) start_time = time.time() loop_time = time.time() for n_iterations in range(max_iterations): iterations[i] = n_iterations if np.all(L_converged): break current_time = time.time() loop_duration = current_time - loop_time loop_time = current_time total_duration = loop_time - start_time print( "Iteration: %d, non-converged pixels: %d, max L diff: %f, total time: %f, loop time: %f" % (n_iterations, np.sum(~L_converged[i]), L_diff_max, total_duration, loop_duration)) i = ~L_converged if use_METRIC_resistance is True: R_A_params = { "z_T": np.array([2.0, 2.0]), "u_friction": u_friction[i], "L": L[i], "d_0": np.array([0.0, 0.0]), "z_0H": np.array([0.1, 0.1]) } else: R_A_params = { "z_T": z_T[i], "u_friction": u_friction[i], "L": L[i], "d_0": d_0[i], "z_0H": z_0H[i] } R_A[i], _, _ = tseb.calc_resistances(tseb.KUSTAS_NORMAN_1999, {"R_A": R_A_params}) H[i] = calc_H(dT[i], rho[i], c_p[i], R_A[i]) LE[i] = Rn[i] - G[i] - H[i] if isinstance(UseL, bool): # Now L can be recalculated and the difference between iterations # derived L[i] = MO.calc_L(u_friction[i], T_A_K[i], rho[i], c_p[i], H[i], LE[i]) u_friction[i] = MO.calc_u_star(u[i], z_u[i], L[i], d_0[i], z_0M[i]) u_friction[i] = np.asarray( np.maximum(u_friction_min, u_friction[i])) # We check convergence against the value of L from previous iteration but as well # against values from 2 or 3 iterations back. This is to catch situations (not # infrequent) where L oscillates between 2 or 3 steady state values. L_new = np.array(L) L_new[L_new == 0] = 1e-36 L_queue.appendleft(L_new) i = ~L_converged L_converged[i] = _L_diff(L_queue[0][i], L_queue[1][i]) < L_thres L_diff_max = np.max(_L_diff(L_queue[0][i], L_queue[1][i])) if len(L_queue) >= 4: i = ~L_converged L_converged[i] = np.logical_and( _L_diff(L_queue[0][i], L_queue[2][i]) < L_thres, _L_diff(L_queue[1][i], L_queue[3][i]) < L_thres) if len(L_queue) == 6: i = ~L_converged L_converged[i] = np.logical_and.reduce( (_L_diff(L_queue[0][i], L_queue[3][i]) < L_thres, _L_diff(L_queue[1][i], L_queue[4][i]) < L_thres, _L_diff(L_queue[2][i], L_queue[5][i]) < L_thres)) flag, Ln, LE, H, G, R_A, u_friction, L, iterations = map( np.asarray, (flag, Ln, LE, H, G, R_A, u_friction, L, iterations)) return flag, Ln, LE, H, G, R_A, u_friction, L, iterations
def run_TSEB_local_image(self): ''' Runs TSEB for all the pixel in an image''' #====================================== # Process the input # Create an input dictionary in_data = dict() if 'subset' in self.p: subset = ast.literal_eval(self.p['subset']) else: subset = [] # Open the LST data according to the model try: fid = gdal.Open(self.p['T_R1'], gdal.GA_ReadOnly) prj = fid.GetProjection() geo = fid.GetGeoTransform() if subset: T_R1 = fid.GetRasterBand(1).ReadAsArray( subset[0], subset[1], subset[2], subset[3]) geo = [ geo[0] + subset[0] * geo[1], geo[1], geo[2], geo[3] + subset[1] * geo[5], geo[4], geo[5] ] else: T_R1 = fid.GetRasterBand(1).ReadAsArray() dims = np.shape(T_R1) # In case of TSEB_PT or DTD models save noon temperature if self.model_type == 'TSEB_PT' or self.model_type == 'DTD': in_data['T_R1'] = T_R1 # In case of TSEB_2T save both component temperatures if self.model_type == 'TSEB_2T': in_data['T_C'] = T_R1 if subset: in_data['T_S'] = fid.GetRasterBand(2).ReadAsArray( subset[0], subset[1], subset[2], subset[3]) else: in_data['T_S'] = fid.GetRasterBand(2).ReadAsArray() fid = None except: print('Error reading sunrise LST file ' + str(self.p['T_R0'])) fid = None return # In case of DTD also need to read the sunrise/night LST if self.model_type == 'DTD': success, in_data['T_R0'] = self._open_GDAL_image( self.p['T_R0'], dims, 'Sunrise LST', subset) if not success: return # Read the image mosaic and get the LAI success, in_data['LAI'] = self._open_GDAL_image( self.p['LAI'], dims, 'Leaf Area Index', subset) if not success: return # Read the image View Zenith Angle success, in_data['VZA'] = self._open_GDAL_image( self.p['VZA'], dims, 'View Zenith Angle', subset) if not success: return # Read the fractional cover data success, in_data['f_c'] = self._open_GDAL_image( self.p['f_c'], dims, 'Fractional Cover', subset) if not success: return # Read the Canopy Height data success, in_data['h_C'] = self._open_GDAL_image( self.p['h_C'], dims, 'Canopy Height', subset) if not success: return # Read the canopy witdth ratio success, in_data['w_C'] = self._open_GDAL_image( self.p['w_C'], dims, 'Canopy Width Ratio', subset) if not success: return # Read the Green fraction success, in_data['f_g'] = self._open_GDAL_image( self.p['f_g'], dims, 'Green Fraction', subset) if not success: return # Read landcover success, in_data['landcover'] = self._open_GDAL_image( self.p['landcover'], dims, 'Landcover', subset) if not success: return # Read leaf angle distribution success, in_data['x_LAD'] = self._open_GDAL_image( self.p['x_LAD'], dims, 'Leaf Angle Distribution', subset) if not success: return # Read initial alpha_PT success, in_data['alpha_PT'] = self._open_GDAL_image( self.p['alpha_PT'], dims, 'Initial alpha_PT', subset) if not success: return # Read spectral properties success, in_data['rho_vis_C'] = self._open_GDAL_image( self.p['rho_vis_C'], dims, 'Leaf PAR Reflectance', subset) if not success: return success, in_data['tau_vis_C'] = self._open_GDAL_image( self.p['tau_vis_C'], dims, 'Leaf PAR Transmitance', subset) if not success: return success, in_data['rho_nir_C'] = self._open_GDAL_image( self.p['rho_nir_C'], dims, 'Leaf NIR Reflectance', subset) if not success: return success, in_data['tau_nir_C'] = self._open_GDAL_image( self.p['tau_nir_C'], dims, 'Leaf NIR Transmitance', subset) if not success: return success, in_data['rho_vis_S'] = self._open_GDAL_image( self.p['rho_vis_S'], dims, 'Soil PAR Reflectance', subset) if not success: return success, in_data['rho_nir_S'] = self._open_GDAL_image( self.p['rho_nir_S'], dims, 'Soil NIR Reflectance', subset) if not success: return success, in_data['emis_C'] = self._open_GDAL_image( self.p['emis_C'], dims, 'Leaf Emissivity', subset) if not success: return success, in_data['emis_S'] = self._open_GDAL_image( self.p['emis_S'], dims, 'Soil Emissivity', subset) if not success: return # Calculate illumination conditions success, lat = self._open_GDAL_image(self.p['lat'], dims, 'Latitude', subset) if not success: return success, lon = self._open_GDAL_image(self.p['lon'], dims, 'Longitude', subset) if not success: return success, stdlon = self._open_GDAL_image(self.p['stdlon'], dims, 'Standard Longitude', subset) if not success: return success, in_data['time'] = self._open_GDAL_image( self.p['time'], dims, 'Time', subset) if not success: return success, doy = self._open_GDAL_image(self.p['DOY'], dims, 'DOY', subset) if not success: return in_data['SZA'], in_data['SAA'] = met.calc_sun_angles( lat, lon, stdlon, doy, in_data['time']) # Wind speed success, in_data['u'] = self._open_GDAL_image(self.p['u'], dims, 'Wind speed', subset) if not success: return # Vapour pressure success, in_data['ea'] = self._open_GDAL_image(self.p['ea'], dims, 'Vapour pressure', subset) if not success: return # Air pressure success, in_data['p'] = self._open_GDAL_image(self.p['p'], dims, 'Pressure', subset) # If pressure was not provided then estimate it based of altitude if not success: success, alt = self._open_GDAL_image(self.p['alt'], dims, 'Altitude', subset) if success: in_data['p'] = met.calc_pressure(alt) else: return success, in_data['S_dn'] = self._open_GDAL_image( self.p['S_dn'], dims, 'Shortwave irradiance', subset) if not success: return # Wind speed measurement height success, in_data['z_u'] = self._open_GDAL_image( self.p['z_u'], dims, 'Wind speed height', subset) if not success: return # Air temperature mesurement height success, in_data['z_T'] = self._open_GDAL_image( self.p['z_T'], dims, 'Air temperature height', subset) if not success: return # Leaf width success, in_data['leaf_width'] = self._open_GDAL_image( self.p['leaf_width'], dims, 'Leaf Width', subset) if not success: return # Soil roughness success, in_data['z0_soil'] = self._open_GDAL_image( self.p['z0_soil'], dims, 'Soil Roughness', subset) if not success: return # Incoming long wave radiation success, in_data['T_A1'] = self._open_GDAL_image( self.p['T_A1'], dims, 'Air Temperature', subset) if not success: return if self.model_type == 'DTD': success, in_data['T_A0'] = self._open_GDAL_image( self.p['T_A0'], dims, 'Air Temperature at time 0', subset) if not success: return success, in_data['L_dn'] = self._open_GDAL_image( self.p['L_dn'], dims, 'Longwave irradiance', subset) # If longwave irradiance was not provided then estimate it based on air # temperature and humidity if not success: emisAtm = rad.calc_emiss_atm(in_data['ea'], in_data['T_A1']) in_data['L_dn'] = emisAtm * met.calc_stephan_boltzmann( in_data['T_A1']) # Open the processing maks and get the id for the cells to process if self.p['input_mask'] != '0': success, mask = self._open_GDAL_image(self.p['input_mask'], dims, 'input mask', subset) if not success: print( "Please set input_mask=0 for processing the whole image.") return # Otherwise create mask from landcover array else: mask = np.ones(dims) mask[np.logical_or.reduce((in_data['landcover'] == res.WATER, in_data['landcover'] == res.URBAN, in_data['landcover'] == res.SNOW))] = 0 # Get the Soil Heat flux if G_form includes the option of measured G # Constant G or constant ratio of soil reaching radiation if self.G_form[0][0] == 0 or self.G_form[0][0] == 1: success, self.G_form[1] = self._open_GDAL_image( self.G_form[1], dims, 'G', subset) if not success: return # Santanello and Friedls G elif self.G_form[0][0] == 2: # Set the time in the G_form flag to compute the Santanello and # Friedl G self.G_form[1] = in_data['time'] # Set the Kustas and Norman resistance parameters if self.resistance_form == 0: success, self.res_params['KN_b'] = self._open_GDAL_image( self.p['KN_b'], dims, 'Resistance parameter b', subset) if not success: return success, self.res_params['KN_c'] = self._open_GDAL_image( self.p['KN_c'], dims, 'Resistance parameter c', subset) if not success: return success, self.res_params['KN_C_dash'] = self._open_GDAL_image( self.p['KN_C_dash'], dims, 'Resistance parameter C\'', subset) if not success: return #====================================== # Run the chosen model out_data = self.run_TSEB(in_data, mask) #====================================== # Save output files # Output variables saved in images self.fields = ('H1', 'LE1', 'R_n1', 'G1') # Ancillary output variables self.anc_fields = ('H_C1', 'LE_C1', 'LE_partition', 'T_C1', 'T_S1', 'R_ns1', 'R_nl1', 'delta_R_n1', 'u_friction', 'L', 'R_S1', 'R_x1', 'R_A1', 'flag') outdir = dirname(self.p['output_file']) if not exists(outdir): mkdir(outdir) self._write_raster_output(self.p['output_file'], out_data, geo, prj, self.fields) outputfile = splitext(self.p['output_file'])[0] + '_ancillary' + \ splitext(self.p['output_file'])[1] self._write_raster_output(outputfile, out_data, geo, prj, self.anc_fields) print('Saved Files') return in_data, out_data
def run_TSEB_point_series_array(self): ''' Runs TSEB for all the dates in point time-series''' def compose_date(years, months=1, days=1, weeks=None, hours=None, minutes=None, seconds=None, milliseconds=None, microseconds=None, nanoseconds=None): ''' Taken from http://stackoverflow.com/questions/34258892/converting-year-and-day-of-year-into-datetime-index-in-pandas''' years = np.asarray(years) - 1970 months = np.asarray(months) - 1 days = np.asarray(days) - 1 types = ('<M8[Y]', '<m8[M]', '<m8[D]', '<m8[W]', '<m8[h]', '<m8[m]', '<m8[s]', '<m8[ms]', '<m8[us]', '<m8[ns]') vals = (years, months, days, weeks, hours, minutes, seconds, milliseconds, microseconds, nanoseconds) return sum( np.asarray(v, dtype=t) for t, v in zip(types, vals) if v is not None) #====================================== # Process the input # Read input data from CSV file in_data = pd.read_csv(self.p['input_file'], delim_whitespace=True) in_data.index = compose_date(years=in_data['year'], days=in_data['DOY'], hours=in_data['time'], minutes=in_data['time'] % 1 * 60) # Check if all the required columns are present if not self._required_data_present(in_data): return None, None # Fill in data fields which might not be in the input file if 'SZA' not in in_data.columns: sza, _ = met.calc_sun_angles(self.p['lat'], self.p['lon'], self.p['stdlon'], in_data['DOY'], in_data['time']) in_data['SZA'] = sza if 'SAA' not in in_data.columns: _, saa = met.calc_sun_angles(self.p['lat'], self.p['lon'], self.p['stdlon'], in_data['DOY'], in_data['time']) in_data['SAA'] = saa if 'p' not in in_data.columns: # Estimate barometric pressure from the altitude if not included in the table in_data['p'] = met.calc_pressure(self.p['alt']) if 'f_c' not in in_data.columns: # Fractional cover in_data['f_c'] = self.p['f_c'] # Use default value if 'w_C' not in in_data.columns: # Canopy width to height ratio in_data['w_C'] = self.p['w_C'] # Use default value if 'f_g' not in in_data.columns: # Green fraction in_data['f_g'] = self.p['f_g'] # Use default value if 'rho_vis_C' not in in_data.columns: in_data['rho_vis_C'] = self.p['rho_vis_C'] if 'tau_vis_C' not in in_data.columns: in_data['tau_vis_C'] = self.p['tau_vis_C'] if 'rho_nir_C' not in in_data.columns: in_data['rho_nir_C'] = self.p['rho_nir_C'] if 'tau_nir_C' not in in_data.columns: in_data['tau_nir_C'] = self.p['tau_nir_C'] if 'rho_vis_S' not in in_data.columns: in_data['rho_vis_S'] = self.p['rho_vis_S'] if 'rho_nir_S' not in in_data.columns: in_data['rho_nir_S'] = self.p['rho_nir_S'] if 'emis_C' not in in_data.columns: in_data['emis_C'] = self.p['emis_C'] if 'emis_S' not in in_data.columns: in_data['emis_S'] = self.p['emis_S'] # Fill in other data fields from the parameter file in_data['landcover'] = self.p['landcover'] in_data['z_u'] = self.p['z_u'] in_data['z_T'] = self.p['z_T'] in_data['leaf_width'] = self.p['leaf_width'] in_data['z0_soil'] = self.p['z0_soil'] in_data['alpha_PT'] = self.p['alpha_PT'] in_data['x_LAD'] = self.p['x_LAD'] # Incoming long wave radiation if 'L_dn' not in in_data.columns: # If longwave irradiance was not provided then estimate it based on air # temperature and humidity emisAtm = rad.calc_emiss_atm(in_data['ea'], in_data['T_A1']) in_data['L_dn'] = emisAtm * met.calc_stephan_boltzmann( in_data['T_A1']) # Get the Soil Heat flux if G_form includes the option of measured G dims = in_data['LAI'].shape if self.G_form[0][0] == 0: # Constant G if 'G' in in_data.columns: self.G_form[1] = in_data['G'] elif self.G_form[0][0] == 1: self.G_form[1] = np.ones(dims) * self.G_form[1] elif self.G_form[0][0] == 2: # Santanello and Friedls G # Set the time in the G_form flag to compute the Santanello and # Friedl G self.G_form[1] = in_data['time'] # Set the Kustas and Norman resistance parameters if self.resistance_form == 0: self.res_params['KN_b'] = np.ones(dims) * self.p['KN_b'] self.res_params['KN_c'] = np.ones(dims) * self.p['KN_c'] self.res_params['KN_C_dash'] = np.ones(dims) * self.p['KN_C_dash'] #====================================== # Run the chosen model out_data = self.run_TSEB(in_data) out_data = pd.DataFrame(data=np.stack(out_data.values()).T, index=in_data.index, columns=out_data.keys()) #====================================== # Save output file # Output Headers outputTxtFieldNames = [ 'Year', 'DOY', 'Time', 'LAI', 'f_g', 'VZA', 'SZA', 'SAA', 'L_dn', 'Rn_model', 'Rn_sw_veg', 'Rn_sw_soil', 'Rn_lw_veg', 'Rn_lw_soil', 'T_C', 'T_S', 'T_AC', 'LE_model', 'H_model', 'LE_C', 'H_C', 'LE_S', 'H_S', 'G_model', 'R_S', 'R_x', 'R_A', 'u_friction', 'L', 'Skyl', 'z_0M', 'd_0', 'flag' ] # Create the ouput directory if it doesn't exist outdir = dirname(self.p['output_file']) if not exists(outdir): mkdir(outdir) # Write the data csvData = pd.concat([ in_data[[ 'year', 'DOY', 'time', 'LAI', 'f_g', 'VZA', 'SZA', 'SAA', 'L_dn' ]], out_data[[ 'R_n1', 'Sn_C1', 'Sn_S1', 'Ln_C1', 'Ln_S1', 'T_C1', 'T_S1', 'T_AC1', 'LE1', 'H1', 'LE_C1', 'H_C1', 'LE_S1', 'H_S1', 'G1', 'R_S1', 'R_x1', 'R_A1', 'u_friction', 'L', 'Skyl', 'z_0M', 'd_0', 'flag' ]] ], axis=1) csvData.to_csv(self.p['output_file'], sep='\t', index=False, header=outputTxtFieldNames) print('Done') return in_data, out_data
def process_local_image(self): ''' Runs pyMETRIC for all the pixel in an image''' #====================================== # Process the input # Create an input dictionary in_data = dict() if 'subset' in self.p: subset = ast.literal_eval(self.p['subset']) else: subset = [] # Open the LST data according to the model try: fid = gdal.Open(self.p['T_R1'], gdal.GA_ReadOnly) prj = fid.GetProjection() geo = fid.GetGeoTransform() if subset: in_data['T_R1'] = fid.GetRasterBand(1).ReadAsArray( subset[0], subset[1], subset[2], subset[3]) geo = [ geo[0] + subset[0] * geo[1], geo[1], geo[2], geo[3] + subset[1] * geo[5], geo[4], geo[5] ] else: in_data['T_R1'] = fid.GetRasterBand(1).ReadAsArray() dims = np.shape(in_data['T_R1']) except: print('Error reading LST file ' + str(self.p['T_R1'])) fid = None return # Read the image mosaic and get the LAI success, in_data['LAI'] = self._open_GDAL_image( self.p['LAI'], dims, 'Leaf Area Index', subset) if not success: return # Read the image mosaic and get the Vegetation Index success, in_data['VI'] = self._open_GDAL_image(self.p['VI'], dims, 'Vegetation Index', subset) if not success: return # Read the fractional cover data success, in_data['f_c'] = self._open_GDAL_image( self.p['f_c'], dims, 'Fractional Cover', subset) if not success: return # Read the Canopy Height data success, in_data['h_C'] = self._open_GDAL_image( self.p['h_C'], dims, 'Canopy Height', subset) if not success: return # Read the canopy witdth ratio success, in_data['w_C'] = self._open_GDAL_image( self.p['w_C'], dims, 'Canopy Width Ratio', subset) if not success: return # Read landcover success, in_data['landcover'] = self._open_GDAL_image( self.p['landcover'], dims, 'Landcover', subset) if not success: return # Read leaf angle distribution success, in_data['x_LAD'] = self._open_GDAL_image( self.p['x_LAD'], dims, 'Leaf Angle Distribution', subset) if not success: return # Read digital terrain model success, in_data['alt'] = self._open_GDAL_image( self.p['alt'], dims, 'Digital Terrain Model', subset) if not success: return # Read spectral properties success, in_data['rho_vis_C'] = self._open_GDAL_image( self.p['rho_vis_C'], dims, 'Leaf PAR Reflectance', subset) if not success: return success, in_data['tau_vis_C'] = self._open_GDAL_image( self.p['tau_vis_C'], dims, 'Leaf PAR Transmitance', subset) if not success: return success, in_data['rho_nir_C'] = self._open_GDAL_image( self.p['rho_nir_C'], dims, 'Leaf NIR Reflectance', subset) if not success: return success, in_data['tau_nir_C'] = self._open_GDAL_image( self.p['tau_nir_C'], dims, 'Leaf NIR Transmitance', subset) if not success: return success, in_data['rho_vis_S'] = self._open_GDAL_image( self.p['rho_vis_S'], dims, 'Soil PAR Reflectance', subset) if not success: return success, in_data['rho_nir_S'] = self._open_GDAL_image( self.p['rho_nir_S'], dims, 'Soil NIR Reflectance', subset) if not success: return success, in_data['emis_C'] = self._open_GDAL_image( self.p['emis_C'], dims, 'Leaf Emissivity', subset) if not success: return success, in_data['emis_S'] = self._open_GDAL_image( self.p['emis_S'], dims, 'Soil Emissivity', subset) if not success: return # Calculate illumination conditions success, lat = self._open_GDAL_image(self.p['lat'], dims, 'Latitude', subset) if not success: return success, lon = self._open_GDAL_image(self.p['lon'], dims, 'Longitude', subset) if not success: return success, stdlon = self._open_GDAL_image(self.p['stdlon'], dims, 'Standard Longitude', subset) if not success: return success, in_data['time'] = self._open_GDAL_image( self.p['time'], dims, 'Time', subset) if not success: return success, doy = self._open_GDAL_image(self.p['DOY'], dims, 'DOY', subset) if not success: return in_data['SZA'], in_data['SAA'] = met.calc_sun_angles( lat, lon, stdlon, doy, in_data['time']) del lat, lon, stdlon, doy # Wind speed success, in_data['u'] = self._open_GDAL_image(self.p['u'], dims, 'Wind speed', subset) if not success: return # Vapour pressure success, in_data['ea'] = self._open_GDAL_image(self.p['ea'], dims, 'Vapour pressure', subset) if not success: return # Air pressure success, in_data['p'] = self._open_GDAL_image(self.p['p'], dims, 'Pressure', subset) # If pressure was not provided then estimate it based of altitude if not success: success, alt = self._open_GDAL_image(self.p['alt'], dims, 'Altitude', subset) if success: in_data['p'] = met.calc_pressure(alt) else: return success, in_data['S_dn'] = self._open_GDAL_image( self.p['S_dn'], dims, 'Shortwave irradiance', subset) if not success: return # Wind speed measurement height success, in_data['z_u'] = self._open_GDAL_image( self.p['z_u'], dims, 'Wind speed height', subset) if not success: return # Air temperature mesurement height success, in_data['z_T'] = self._open_GDAL_image( self.p['z_T'], dims, 'Air temperature height', subset) if not success: return # Soil roughness success, in_data['z0_soil'] = self._open_GDAL_image( self.p['z0_soil'], dims, 'Soil Roughness', subset) if not success: return # Incoming long wave radiation success, in_data['T_A1'] = self._open_GDAL_image( self.p['T_A1'], dims, 'Air Temperature', subset) if not success: return success, in_data['L_dn'] = self._open_GDAL_image( self.p['L_dn'], dims, 'Longwave irradiance', subset) # If longwave irradiance was not provided then estimate it based on air # temperature and humidity if not success: emisAtm = rad.calc_emiss_atm(in_data['ea'], in_data['T_A1']) in_data['L_dn'] = emisAtm * met.calc_stephan_boltzmann( in_data['T_A1']) # Open the processing maks and get the id for the cells to process if self.p['input_mask'] != '0': success, mask = self._open_GDAL_image(self.p['input_mask'], dims, 'input mask', subset) if not success: print( "Please set input_mask=0 for processing the whole image.") return # Otherwise create mask from landcover array else: mask = np.ones(dims) mask[np.logical_or.reduce((in_data['landcover'] == res.WATER, in_data['landcover'] == res.URBAN, in_data['landcover'] == res.SNOW))] = 0 mask[np.logical_or(~np.isfinite(in_data['VI']), ~np.isfinite(in_data['T_R1']))] = 0 # Open the bare soil ET if str(self.p['ET_bare_soil']) != '0': success, in_data['ET_bare_soil'] = self._open_GDAL_image( self.p['ET_bare_soil'], dims, 'ET_bare_soil', subset) if not success: print( "Please set ET_bare_soil=0 for assuming zero ET for bare soil." ) return # Otherwise assume ET soil = 0 else: in_data['ET_bare_soil'] = np.zeros(dims) # Get the Soil Heat flux if G_form includes the option of measured G # Constant G or constant ratio of soil reaching radiation if self.G_form[0][0] == 0 or self.G_form[0][0] == 1: success, self.G_form[1] = self._open_GDAL_image( self.G_form[1], dims, 'G', subset) if not success: return # Santanello and Friedls G elif self.G_form[0][0] == 2: # Set the time in the G_form flag to compute the Santanello and # Friedl G self.G_form[1] = in_data['time'] # ASCE G ratios elif self.G_form[0][0] == 4: self.G_form[1] = (0.04, 0.10) elif self.G_form[0][0] == 5: self.G_form[1] = (0.04, 0.10) del in_data['time'] #====================================== # Run the chosen model out_data = self.run_METRIC(in_data, mask) #====================================== # Save output files # Output variables saved in images self.fields = ('R_n1', 'H1', 'LE1', 'G1') # Ancillary output variables self.anc_fields = ('R_ns1', 'R_nl1', 'u_friction', 'L', 'R_A1', 'flag', 'ET0_datum') outdir = dirname(self.p['output_file']) if not exists(outdir): mkdir(outdir) geo_father = [] if 'subset_output' in self.p: geo_father = geo subset, geo = self._get_subset(self.p["subset_output"], prj, geo) dims = (subset[3], subset[2]) for field in self.fields: out_data[field] = out_data[field][subset[1]:subset[1] + subset[3], subset[0]:subset[0] + subset[2]] for field in self.anc_fields: out_data[field] = out_data[field][subset[1]:subset[1] + subset[3], subset[0]:subset[0] + subset[2]] if dims[0] <= 0 or dims[1] <= 0: print('No valid extent for creating ouput') return in_data, out_data self._write_raster_output(self.p['output_file'], out_data, geo, prj, self.fields, geo_father=geo_father) outputfile = splitext(self.p['output_file'])[0] + '_ancillary' + \ splitext(self.p['output_file'])[1] self._write_raster_output(outputfile, out_data, geo, prj, self.anc_fields, geo_father=geo_father) print('Saved Files') return in_data, out_data