Source code for pycif.plugins.obsparsers.tropomi_no2.parse

from logging import info, debug
import xarray as xr
import pandas as pd
import numpy as np
import datetime
import netCDF4 as nc
from ....utils.datastores.empty import init_empty


[docs] def do_parse( self, obs_file, **kwargs ): """Parse function for a file from template observations Args: obs_file (str) : Path to input file Returns: pandas.DataFrame : Dataframe from input file df[parameter][station] """ info("Parsing official TROPOMI data " "from observation file: {}".format(obs_file)) ddi, ddf = self.datei, self.datef product_data = xr.open_dataset(obs_file) obs_dates = product_data["time_utc"].values.astype( np.datetime64).astype(datetime.datetime) info(obs_dates.size) if obs_dates.size : if ddi > obs_dates.max() or ddf < obs_dates.min(): debug(f"File {obs_file} with date {obs_dates.min()} " f"is outside simulation time period {ddi} / {ddf}") return init_empty() else : debug(f"Empty {obs_file}") return init_empty() self.data_date_max = obs_dates.max() # Filter out data outside domain longitude = product_data.variables['longitude'].values latitude = product_data.variables['latitude'].values longitude_bounds = product_data.variables["longitude_bounds"].values latitude_bounds = product_data.variables["latitude_bounds"].values # better to use corners to crop domain ... mask_domain = np.ones_like(longitude, dtype=bool) if hasattr(self, "crop_domain"): crop_domain = self.crop_domain mask_domain = ((longitude < crop_domain.xmax) & (longitude > crop_domain.xmin) & (latitude < crop_domain.ymax) & (latitude > crop_domain.ymin)) if mask_domain.sum() == 0: return init_empty() # Check number of levels nlevread = product_data.dims["phony_dim_2"] if nlevread != self.nlayers: raise Exception( f"The file {obs_file} does not have the expected number of " f"averaging kernel layers: {nlevread} instead of " f"{self.nlayers}. \n" f"Please check your files or change the parameter 'nlayers' " f"in the Yaml (expert users only).") # Valid data scanline = product_data.dims["phony_dim_0"] ground_pixel = product_data.dims["phony_dim_1"] obs = product_data.variables['nitrogendioxide_tropospheric_column'].values conv = product_data.variables[ 'nitrogendioxide_tropospheric_column' ].attrs['multiplication_factor_to_convert_to_molecules_percm2'] obs*=conv obserror = product_data.variables[ 'nitrogendioxide_tropospheric_column_precision'].values conv = product_data.variables[ 'nitrogendioxide_tropospheric_column_precision'].attrs['multiplication_factor_to_convert_to_molecules_percm2'] obserror*=conv mask_valid = product_data.variables['qa_value'].values >= self.qa_value #mask_valid = product_data.variables['qa_value'].values >= self.qa_value mask_valid = mask_valid & ~np.isnan(obs) #mask_valid = ~np.isnan(obs) if (mask_domain & mask_valid).sum() == 0: return init_empty() # Filter out data not in simulation window mask_time = (obs_dates >= ddi) & (obs_dates <= ddf) mask_all = mask_time[..., np.newaxis] & mask_valid & mask_domain if mask_all.sum() == 0: return init_empty() # Now process actual data debug(f"Data found in file {obs_file}") list_basic_cols = [ 'date', 'duration', 'station', 'network', 'parameter', 'lon', 'lat', 'obs', 'obserror'] subdata = pd.DataFrame(columns=list_basic_cols) subdata['date'] = np.repeat( obs_dates[..., np.newaxis], ground_pixel, axis=1)[mask_all] subdata = subdata.assign(duration=self.obs_duration) subdata['lon'] = longitude[mask_all] subdata['lat'] = latitude[mask_all] subdata = subdata.assign(station=self.stationID) subdata = subdata.assign(network=self.networkID) subdata = subdata.assign(parameter=self.parameter) subdata["obs"] = obs[mask_all].astype(float) subdata["obserror"] = obserror[mask_all].astype(float) # Load aks and obs detailed_results_grp = product_data aktot = np.float64( detailed_results_grp.variables['averaging_kernel'] .values[mask_all, :] ) # Pressure levels # particular case of TROPOM CH4: create pressure grid from psurf and delta psurf = product_data.variables['surface_pressure'].values a = product_data.variables['tm5_constant_a'].values[:,1].T b = product_data.variables['tm5_constant_b'].values[:,1].T plev = np.array([a[lev] + b[lev]*psurf for lev in range(self.nlayers)]) pavg = np.concatenate((psurf[np.newaxis,:],plev),axis=0) pavg = np.float64(pavg[:,mask_all]).T # Load aks trop_id = product_data.variables['tm5_tropopause_layer_index'].values aktot = product_data.variables['averaging_kernel'].values amftot = product_data.variables['air_mass_factor_total'].values amftrop = product_data.variables['air_mass_factor_troposphere'].values aktrop = np.array([ aktot[:,:,lev]*amftot[:,:]/amftrop[:,:] for lev in range(self.nlayers)]) lat_bounds = latitude_bounds[mask_all,:] lon_bounds = longitude_bounds[mask_all,:] if False: for index, idtrop in np.ndenumerate(trop_id): if np.isnan(idtrop): continue aktrop[int(idtrop):,index[0],index[1]]=0 aktrop = np.float64(aktrop[:,mask_all]).T ds = xr.Dataset({'ak': (['index', 'level'], aktrop), 'pavg0': (['index', 'level_pressure'], pavg), 'lat0': (['index'],lat_bounds[:,0]), 'lat1': (['index'],lat_bounds[:,1]), 'lat2': (['index'],lat_bounds[:,2]), 'lat3': (['index'],lat_bounds[:,3]), 'lon0': (['index'],lon_bounds[:,0]), 'lon1': (['index'],lon_bounds[:,1]), 'lon2': (['index'],lon_bounds[:,2]), 'lon3': (['index'],lon_bounds[:,3])}, coords={'index': subdata.index, 'level': range(self.nlayers), 'level_pressure': range(self.nlayers + 1)}) subdata = subdata.to_xarray() subdata = xr.merge([ds, subdata]) debug(f"Observations after filter: {len(subdata.index)}") return subdata