Giter VIP home page Giter VIP logo

s5p-lno2's Introduction

S5P-LNO2

DOI

Core code for the TROPOMI lightning NO2 (LNO2) retrievals. Some useful Jupyter Notebooks are available at S5P-LNO2-Notebook.

Main workflow

Users need to prepare the Input Data first and then run the scripts one by one.

Please feel free to modify the settings.txt file for your own research.

workflow

Detailed explanations of main scripts:

  1. Select lightning-swaths with both lightning and high NO2, and save them to nc files. [s5p_lno2_main.py]
  2. Link the consecutive lightning-swath cases and save variables (case No., filename, and mask label) to csv files. [s5p_lno2_cases.py]
  3. Combine the generated csv file into fresh_lightning_cases.csv or nolightning_cases.csv. [s5p_lno2_cases_sum.py]
  4. (Optional) Plot linked variables and save them as images to filter cases manually [s5p_lno2_plot.py]. We are still thinking how to convert the manual part into an auto one.
  5. Extract TM5 no2_vmr and temperature profiles for consecutive lightning-swaths. [s5p_lno2_tm5_extract.py]
  6. Calculate lightning variables (AMFs, SCD_Bkgd, tropopause_pressure, lno2vis, lno2_geo and lno2) and save them to one netcdf file called "S5P_LNO2.nc" [s5p_lno2_product.py]
  7. Calculate lightning NO2 production efficiency and save all useful variables to one CSV file called "S5P_LNO2_PE.csv". [s5p_lno2_pe_lifetime.py]

Input Data

The used data are listed below.

The input paths are shown in parentheses. Please feel free to modify them in settings.txt.

  1. TROPOMI (<s5p_dir>/<yyymm>/S5P_**__L2__NO2____)

    There're three main methods of downloading the TROPOMI NO2 L2 data:

  2. ERA5 (<era5_dir>/era5_<yyyymm>.nc)

    The pressure level (200 - 700 hPa) ERA5 data (u and v) are used to predict the transport of lightning air in the upper troposphere.

    Note that for the TROPOMI data on the first day of month, we need the ERA5 data on the last day of the previous month.

    e.g. S5P...20220201... needs era5_202201.nc which at least has the data on 2022-01-31.

  3. Lightning Data (<lightning_dir>/<yyymm>/<yyyymmdd>.csv)

    The lightning data should be saved in CSV format and have at least three fields: timestamp, longitude, and latitude.

  4. VIIRS S-NPP fire product

    The archive VIIRS S-NPP data is available from FIRMS.

Other useful functions

  1. Regrid TROPOMI product (s5p_regrid.py and s5p_regrid_combine.py)

    The harp tool is convient for regridding TROPOMI L2 data to Lon/Lat grids with filters.

  2. Calculate the TROPOMI pixel areas (s5p_pixel_area.py)

​ Usually the areas are same for different swaths. We calculated all possible areas and saved it into one netCDF file for usage.

  1. Calculate lightning within TROPOMI swaths (swath_lightning.py)

​ It is useful to know how many lightning happened inside the swaths before the TROPOMI overpass.

  1. Grid lightning NO2 product (s5p_lno2_grid.py)
  2. Generate daily and summertime GLD360 data (gld360_daily.py and gld360_summer.py)

Outputs

1. L2 product with new variables (netCDF file)

(Click to expand) s5p_lno2_main.py adds lightning/fire data and lightning masks to simplified L2 product.

Varname Group Units Description
time S5P days since <yyyy-mm-dd> time using proleptic gregorian calendar
latitude S5P degrees_north pixel center latitude
longitude S5P degrees_east pixel center longitude
air_mass_factor_clear S5P 1 Air mass factor for the cloud-free part of the scene
air_mass_factor_cloudy S5P 1 Air mass factor for the cloud-covered part of the scene
air_mass_factor_stratosphere S5P 1 Stratospheric air mass factor
air_mass_factor_total S5P 1 Total air mass factor
air_mass_factor_troposphere S5P 1 Tropospheric air mass factor
Apparent_scene_pressure S5P Pa Scene pressure from the cloud product
assembled_lat_bounds S5P degrees_north assembled_latitude_bounds calculated by Satpy
assembled_lon_bounds S5P degrees_east assembled_longitude_bounds calculated by Satpy
Averaging_kernel S5P 1 Averaging kernel
cloud_albedo_crb S5P 1 Cloud albedo in the cloud product
cloud_fraction_crb_nitrogendioxide_window S5P 1 Cloud fraction at 440 nm for NO2 retrieval
cloud_pressure_crb S5P Pa Cloud optical centroid pressure
cloud_radiance_fraction_nitrogendioxide_window S5P 1 Cloud radiance fraction at 440 nm for NO2 retrieval
Geolocation_flags S5P 1 Some flags (see ATBD)
lightning_mask S5P 1 <0: labeled lightning with fire;
0: no lightning;
>0: labeled lightning without fire
nitrogendioxide_ghost_column S5P mol m-2 Ghost column NO2: modelled NO2 column below the cloud top
nitrogendioxide_segmentation S5P 1 0: no high NO2;
>=1: labeled high NO2
nitrogendioxide_slant_column_density S5P mol m-2 Stratospheric vertical column of nitrogen dioxide, derived from the TM5-MP vertical profiles
nitrogendioxide_stratospheric_column S5P mol m-2 Stratospheric vertical column of nitrogen dioxide, derived from the TM5-MP vertical profile
nitrogendioxide_total_column S5P mol m-2 Total vertical column of nitrogen dioxide derived from the total slant column and TM5 profile in stratosphere and troposphere
nitrogendioxide_tropospheric_column S5P mol m-2 Tropospheric vertical column of nitrogen dioxide
processing_quality_flags S5P 1 Processing quality flags (See ATBD)
qa_value S5P 1 Quality value
scene_albedo S5P 1 Scene albedo in the cloud product
snow_ice_flag S5P 1 Snow-ice mask (See ATBD)
solar_azimuth_angle S5P degree
clockwise from the North (East = 90, South = 180, West = 270)
Solar azimuth angle at the ground pixel location on the reference ellipsoid.
solar_zenith_angle S5P degree
measured away from the vertical
Solar zenith angle at the ground pixel location on the reference ellipsoid.
surface_albedo_nitrogendioxide_window S5P 1 Surface albedo in the NO2 fit window
surface_pressure S5P Pa Surface pressure
time_utc S5P 1 Time of observation as ISO 8601 date-time string
tm5_constant_a S5P Pa TM5 hybrid A coefficient at upper and lower interface levels
tm5_constant_b S5P Pa TM5 hybrid B coefficient at upper and lower interface levels
tm5_tropopause_layer_index S5P 1 TM5 layer index of the highest layer in the tropopause
viewing_azimuth_angle S5P degree
measured clockwise from the North (East = 90, South = 180, West = 270)
Satellite azimuth angle at the ground pixel location on the reference ellipsoid.
viewing_zenith_angle S5P degree
measured away from the vertical
Zenith angle of the satellite at the ground pixel location on the reference ellipsoid.
cluster_label Lightning 1 Clustered lightning labeled by DBSCAN
time Lightning minutes since
longitude Lightning degrees_east Longitude of lightning
latitude Lightning degrees_north Latitude of lightning
delta Lightning minute The time difference between detected lightning and TROPOMI overpass time
level Lightning hPa Pressure levels used for lightning NO2 air parcel
longitude_pred Lightning degrees_east Longitude of lightning at different pressure levels predicted by ERA5 data
latitude_pred Lightning degrees_north Latitude of lightning at different pressure levels predicted by ERA5 data
lightning_label Lightning 1 Lightning label paired with lightning mask
time Fire 1
longitude Fire degrees_north Longitude of fire
latitude Fire degrees_north Longitude of fire
type Fire 1 Fire type

2. Lightning NO2 production and lifetime (netCDF file)

The lightning no2 data (S5P_LNO2_production.nc and S5P_LNO2_lifetime.nc) includes the products of netCDF file and other new retrievals (see S5P-WRFCHEM, but we use a priori NO2 profiles assuming guassian distributions instead of WRF--Chem results).

(Click to expand) Data Structure

- Case 1
	-	Swath xxx
		- S5P
		 - .......... (official S5P data as shown above)
		 - scdTrop
		 - lightning_mask
		 - area
		 - amfTrop
		 - amfTropVis
		 - swClr
		 - swCld
		 - avgKernel
		 - no2apriori
		 - no2Trop
		 - no2TropVis
		 - scdClr
		 - scdCld
		 - vcdGnd
		 - tropopause_pressure
		 - plevels
		 - scdBkgd
		 - amflno2
		 - lno2_mask
		 - lno2vis
		 - lno2geo
		 - lno2
		- Lightning
	- Swath xxx
	...
- Case 2
.....

3. Lightning NO2 production and lifetime (csv file)

The csv files contain two kinds of variable:

  • Data from previous netcdf files.
  • Lightning NO2 production efficiency based on consecutive swaths.

csv head: time,case,swath,longitude,latitude,region,nlightning,area,apparent_scene_pressure,no2,lno2geo,lno2vis,lno2,pe_lno2geo,pe_lno2vis,pe_lno2

Publications

Zhang et al., Spaceborne observations of lightning NO2 in the Arctic, Environ. Sci. Technol.

s5p-lno2's People

Contributors

zxdawn avatar

Stargazers

 avatar

Watchers

 avatar

s5p-lno2's Issues

wrong retrieval of cloud pressure

Comparing the figures of two consecutive orbits, we find that the cloud pressure of the previous seems wrong. The left part is mostly around 400 hPa.

image

image

Error of plotting linked files

Traceback (most recent call last):
  File "/student/zhangxin/papers/Arctic/S5P_LNOx/main/s5p_lnox_plot_tracks.py", line 406, in <module>
    main()
  File "/student/zhangxin/papers/Arctic/S5P_LNOx/main/s5p_lnox_plot_tracks.py", line 370, in main
    pool.starmap(plot_swaths, enumerate(consecutive_swaths))
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/multiprocessing/pool.py", line 372, in starmap
    return self._map_async(func, iterable, starmapstar, chunksize).get()
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/multiprocessing/pool.py", line 771, in get
    raise self._value
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/multiprocessing/pool.py", line 125, in worker
    result = (True, func(*args, **kwds))
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/multiprocessing/pool.py", line 51, in starmapstar
    return list(itertools.starmap(args[0], args[1]))
  File "/student/zhangxin/papers/Arctic/S5P_LNOx/main/s5p_lnox_plot_tracks.py", line 345, in plot_swaths
    trajectories = get_tracks(features)
  File "/student/zhangxin/papers/Arctic/S5P_LNOx/main/s5p_lnox_plot_tracks.py", line 123, in get_tracks
    trajectories = pred.link_df(deepcopy(features.to_dataframe()),
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/site-packages/trackpy/predict.py", line 93, in link_df
    return self.wrap_single(linking.link_df_iter, *args, **kw)
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/site-packages/trackpy/predict.py", line 79, in wrap_single
    return pandas_concat(linking_fcn(*([features_iter, ] + args), **kw))
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/site-packages/trackpy/utils.py", line 296, in _pandas_concat_post_023
    return pd.concat(*args, **kwargs)
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/site-packages/pandas/util/_decorators.py", line 311, in wrapper
    return func(*args, **kwargs)
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/site-packages/pandas/core/reshape/concat.py", line 294, in concat
    op = _Concatenator(
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/site-packages/pandas/core/reshape/concat.py", line 351, in __init__
    raise ValueError("No objects to concatenate")
ValueError: No objects to concatenate

Support gridded lightning density data

The default input lightning data is point data (time, longitude, and latitude). We can add the support of gridded density data, such as count=XXX in 10 minutes and 0.1*0.1 degree grid.

Add NO2 mask to output

The mask by the re-segmentation masked NO2 should be saved to output for checking relationships among lightning, lightning NO2, lightning NO2 mask, LNO2, and NO2VIS.

convection_mask, scd_no2_bkgd, lno2_vis, lno2_geo = calc_lno2vis(scd_no2, scd_no2_norm, ds_mask, ds_amf, threshold, alpha_bkgd, features, crf_min)

[Feature] Add varnames for AMF calculation

The current code just reads the necessary variables for analyzing NO2 and cloud.

Users may use our previous tool (S5P-WRFChem) to calculate AMF based on our products.

vnames = ['time_utc', 'qa_value',
'processing_quality_flags', 'geolocation_flags',
# 'latitude', 'longitude',
# 'latitude_bounds', 'longitude_bounds',
'assembled_lon_bounds', 'assembled_lat_bounds',
'nitrogendioxide_slant_column_density',
'nitrogendioxide_tropospheric_column', 'nitrogendioxide_stratospheric_column',
'nitrogendioxide_total_column', 'nitrogendioxide_ghost_column',
'cloud_pressure_crb', 'cloud_radiance_fraction_nitrogendioxide_window',
'cloud_fraction_crb_nitrogendioxide_window',
'air_mass_factor_stratosphere', 'air_mass_factor_troposphere',
'air_mass_factor_total',
'air_mass_factor_clear', 'air_mass_factor_cloudy',
'scene_albedo', 'snow_ice_flag', 'solar_azimuth_angle', 'solar_zenith_angle',
'viewing_azimuth_angle', 'viewing_zenith_angle',
'surface_albedo_nitrogendioxide_window', 'surface_pressure', 'apparent_scene_pressure',
'tm5_constant_a', 'tm5_constant_b', 'tm5_tropopause_layer_index', 'averaging_kernel',
]

Skip empty dataset for Plot function

Traceback (most recent call last):
  File "/student/zhangxin/papers/Arctic/S5P_LNOx/main/s5p_lnox_plot_tracks.py", line 228, in <module>                                             
    main()         
  File "/student/zhangxin/papers/Arctic/S5P_LNOx/main/s5p_lnox_plot_tracks.py", line 212, in main                                                 
    pool.map(plot_data, filelist)
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/multiprocessing/pool.py", line 364, in map                            
    return self._map_async(func, iterable, mapstar, chunksize).get()     
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/multiprocessing/pool.py", line 771, in get                            
    raise self._value
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/multiprocessing/pool.py", line 125, in worker                         
    result = (True, func(*args, **kwds))                                 
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/multiprocessing/pool.py", line 48, in mapstar                         
    return list(map(*args))
  File "/student/zhangxin/papers/Arctic/S5P_LNOx/main/s5p_lnox_plot_tracks.py", line 200, in plot_data                                            
    plot_func(ds_tropomi, ds_lightning)                                  
  File "/student/zhangxin/papers/Arctic/S5P_LNOx/main/s5p_lnox_plot_tracks.py", line 173, in plot_func                                            
    lon_min = min([n for n in ds_lightning['longitude'] if n>0]).values  
ValueError: min() arg is an empty sequence

Add stratospheric variables to output

The changes in the stratospheric NO2 column between consecutive orbits can affect the tropospheric NO2 calculation. Thus, it's better to add the output of stratospheric variables: nitrogendioxide_stratospheric_column and SCD_Strato (has been added).

Pair lightning labels generated by DBSCAN with lightning mask

The DBSCAN lightning labels have both clean and polluted lightning, while the lightning mask has distinguished them and grouped them using wind data:

for index, label in enumerate(labels):
# iterate each label and update the mask
dfq = ds.sel(lightning_label=label)
lon_lat = dfq[['longitude_pred', 'latitude_pred']].stack(all=("level", "lightning_label"))
lightning_points = lon_lat.to_array().transpose('all', ...)
mask = in_hull(pixel_points, lightning_points).reshape(scn['nitrogendioxide_tropospheric_column'].shape, order='F')
# get the overplapped label
overlapped_label = np.delete(np.unique(lightning_mask.where(xr.DataArray(mask, dims=['y', 'x']), 0)), 0)
if len(overlapped_label) == 0:
if kind == 'clean':
# clean lightning 1abel: 1, 2, ....
lightning_mask = xr.where(mask, index+1, lightning_mask)
elif kind == 'polluted':
# polluted lightning 1abel: -1, -2, ....
lightning_mask = xr.where(mask, -index-1, lightning_mask)
elif len(overlapped_label) == 1:
# set to the only overlapped value
if kind == 'clean':
lightning_mask = xr.where(mask, overlapped_label[0], lightning_mask)
elif kind == 'polluted':
lightning_mask = xr.where(mask, overlapped_label[0], lightning_mask)
else:
# get the minimum label
min_label = np.min(overlapped_label)
# set the mask where lightning happens to minimum label
lightning_mask = xr.where(mask, min_label, lightning_mask)
# update overlapped masks with minimum min label
for rest_label in np.delete(overlapped_label, np.where(overlapped_label == min_label)):
lightning_mask = lightning_mask.where(lightning_mask != rest_label, min_label)

Because the number of lighting labels represents the lightning count, it should be updated with the lightning mask. Otherwise, we don't know how much lightning happened in each lightning mask.

Cluster of lightning isn't large enough

Problem

The observed lightning flashes are clustered first and then classified into clean and polluted clusters.

# cluster lightning data
df_cluster, cluster_labels, hulls = get_cluster(df_lightning)
if df_cluster.empty:
save_data(scn, vnames, cfg, output_file, mask)
logging.info(' '*4+f'No lightning clusters are found for {os.path.basename(filename)}')
return
else:
# classify lightning clusters into clean and polluted (fire) categories
clean_cluster, polluted_cluster = classify_lightning(df_cluster, df_viirs, cluster_labels, hulls)
if not clean_cluster.empty:
logging.info(' '*4+'Calculate the transported clean lighting cluster ...')
clean_cluster = pred_cluster(clean_cluster, t_overpass, ds_era5, wind_levels, cfg)

However, that would split storms using the default 40 km

# search for 40km around each lightning dots
epsilon = 40/kms_per_radian
logging.info(' '*4 + f'Cluster lightning by DBSCAN ...')
db = DBSCAN(eps=epsilon, min_samples=2, algorithm='ball_tree', metric='haversine').fit(np.radians(coords))
cluster_labels = db.labels_

This method works well for isolated storms like this:
The LNO2 were produced and transported with wind:
S5P_PAL__L2__NO2____20190810T212136_20190810T230306_09456_01_020301_1
S5P_PAL__L2__NO2____20190811T004435_20190811T022605_09458_01_020301_1

However, there're some storms that occurred closely but were still far from 40 km. The LNO2 produced by them could mix together:
S5P_PAL__L2__NO2____20190610T051322_20190610T065452_08581_01_020301_1
S5P_PAL__L2__NO2____20190610T170352_20190610T184522_08588_01_020301_1

Solution

It's better to calculated the transported air containing LNO2 first, and then cluster and classify it.

Lightning NO2 air tracer cross 180E

Although #26 supports resampling the lightning mask values to lightning location (lightning_label), there're some missing values.

Lightning mask

image

lightning label

image

The lightning mask is wrong ... We need to fix it.

Export linked cases including fresh lightning automatically

There's one lightning threshold at the current stage:
At least 20 lightning in the cluster

df_cluster = df_cluster[df_cluster.label.isin(v.index[v.gt(20)])]

We can add another one: at least xx lightning during the 100-min period before the TROPOMI overpass time. This can make sure there's enough fresh lightning happens before TROPOMI overpasses for the calculation of lightning NO2.

Better log of multiprocessing exception

Because pandarallel creates embedded Pools, it is not supported in the multiprocessing pool.

That's why we use ProcessPoolExecutor instead:

from concurrent.futures import ProcessPoolExecutor as Pool

with Pool(max_workers=int(cfg['num_pool'])) as pool:
# data process in parallel
# we don't use multiprocessing.Pool because it's non-daemonic
# https://stackoverflow.com/a/61470465/7347925
try:
pool.map(functools.partial(process_data, cfg=cfg), filelist)
except Exception as exc:
logging.info(exc)

However, the Exception isn't printed at all ...

Swtich to cloud

It's better to switch from the local computer to the cloud.

I found the free method of combing repl.it and uptimerobot.com.

[Feature] Better NO2 segmentation

def segmentation(scn, min_threshold=4e14, max_threshold=1e15, step_threshold=2e14):
'''Segmentation the NO2 field by tobac
Tobac: https://gmd.copernicus.org/articles/12/4551/2019/
'''
logging.info(' '*4 + 'Segmentation of NO2 ...')
# get the finite no2 data
no2_finite = get_finite_scn(scn) # unit: molec./cm2
# Detect the features and get the masks using tobac
masks_scn = feature_mask(no2_finite, min_threshold=min_threshold,
max_threshold=max_threshold, step_threshold=step_threshold)
return masks_scn

The threshold is hardcoded. A better method is to use the monthly average NO2 as the minimum value of the threshold.

Because we are developing S5P-LNO2 for Arctic lightning NO2 (background NO2 is quite low), this doesn't matter at the current stage.

Please feel free to PR for making it work for polluted regions!

Loading correct ERA5 data for TROPOMI data on the first day of month

def calc_wind(row, coords, u, v, time_pred, lon_column, lat_column):
'''Calculate the wspd and wdir'''
# interpolate the u and v fields
interp_points = [xr.DataArray(time_pred).astype('float').values, row[lat_column], row[lon_column]]
u_interp = interpn(coords, u, interp_points)
v_interp = interpn(coords, v, interp_points)

Sometimes, the error will raise if any lightning happened before the first day of a month:

 File "/student/zhangxin/papers/Arctic/S5P_LNOx/main/s5p_lnox_functions.py", line 216, in calc_wind
    u_interp = interpn(coords, u, interp_points)
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/site-packages/scipy/interpolate/interpolate.py", line 2698, in interpn
    raise ValueError("One of the requested xi is out of bounds "
ValueError: One of the requested xi is out of bounds in dimension 0

So, we need to load the correct ERA5 data here:

# read hourly era5 data
ds_era5 = xr.open_dataset(os.path.join(cfg['era5_dir'],
f"era5_{scn['nitrogendioxide_tropospheric_column'].time.dt.strftime('%Y%m').values}.nc")
)

Two options:

  • Load two months' ERA5 data
  • Append the last day of the previous month to the current ERA5 data

Find consecutive swaths by filenames

To make #24 easier and quicker, we can find the consecutive filenames of outputs from s5p_lnox_main.py.

There are two applications:

  • Extracting the TM5 profile data for useful swath data instead of all data (s5p_lnox_tm5_extract.py)
  • Grouped swaths can be used directly for analysis of lightning NO2 production (s5p_lnox_main.py)

Add TROPOMI pixel area file

Because we need the TROPOMI pixel area info for calculating the summation of NO2, it's useful to support the accurate area instead of using the first pixel's. Note that the pixel area is constant for different swaths, though

Along-track there are 3245 or 3246 scanlines (4172 or 4173 after the along-track pixel size reduction) in regular radiance orbits.

Scaled colorbar for tracked NO2 VCD

As mentioned in Marais et al. (2021):

they impose a modest threshold to only use TROPOMI tropospheric columns > 4 × 10^13 molec. cm−2 to mimic the detection limits of the instruments (Gomez et al., 2014)

It's 6.6^-7 mol/m2. It's better to change the constant 0~4e-5 colorbar to 0-max_value for tracked NO2 VCD.

ax = axs[6]
ds_lightning_mask['nitrogendioxide_tropospheric_column'].plot(**plot_set,
vmin=0, vmax=4e-5,
cmap='Thermal',
cbar_kwargs={"label": "[mol m$^{-2}$]"},
ax=ax)
ax.format(title='Tracked VCD NO$_2$')

Slow prediction the cluster of lightning

Although I use Pool for predicting the cluster of lightning, it's still slow.

Because the next time step's location relies on previous location and u/v wind, I can't accelerate it (at least for my current Python ability) ... Leave it here, in case someone or I come up with a better idea.

Avoid looping pressure levels for the transport of lightning cluster

for level in wind_levels:
# get the wind at the pressure level
u = ds_era5['u'].sel(level=level).values
v = ds_era5['v'].sel(level=level).values
# using the wind info to predict the location at the TROPOMI overpass time
df_cluster = df_cluster.parallel_apply(lambda row: predict_loc(row, level, coords, u, v), axis=1)

The looping will recreate the pandarallel worker. That will waster some time.

Use VCD_bkgd instead of SCD_bkgd

scd_no2_bkgd = dist.model['CII_max_alpha'] * (scd_no2.max() - scd_no2.min()) + scd_no2.min()
lno2_vis = (ds_mask['SCD_Trop'] - scd_no2_bkgd) / ds_amf['amfTropVis']
lno2_geo = (ds_mask['SCD_Trop'] - scd_no2_bkgd) / ds_mask['amf_geo']

Using SCD_bkgd only works when SCD_bkgd / AMF_trop = VCD_bkgd. But I suppose it's not true. We need to try to use VCD_bkgd directly and compare the results.

Support linking NO2 masks

It's better to use tobac to link the masks automatically instead of using the plot function to check it by ourselves.

Clip lon/lat to ERA5

  File "/student/zhangxin/papers/Arctic/S5P_LNOx/main/s5p_lnox_utils.py", line 250, in <lambda>
    df_cluster = df_cluster.parallel_apply(lambda row: predict_loc(row, level, coords, u, v), axis=1)
  File "/student/zhangxin/papers/Arctic/S5P_LNOx/main/s5p_lnox_functions.py", line 242, in predict_loc
    wspd, wdir = calc_wind(row, coords, u, v, time_pred, lon_column, lat_column)
  File "/student/zhangxin/papers/Arctic/S5P_LNOx/main/s5p_lnox_functions.py", line 214, in calc_wind
    u_interp = interpn(coords, u, interp_points)
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/site-packages/scipy/interpolate/interpolate.py", line 2698, in interpn
    raise ValueError("One of the requested xi is out of bounds "
ValueError: One of the requested xi is out of bounds in dimension 2

# interpolate the u and v fields
interp_points = [xr.DataArray(time_pred).astype('float').values, row[lat_column], row[lon_column]]
u_interp = interpn(coords, u, interp_points)
v_interp = interpn(coords, v, interp_points)

Swtich from apparent_scene_pressure to cloud_pressure

After the discussion with Ronald van der A, the correct/accurate way to calculate VCD NO2 is using cloud pressure.

lno2_tropo_cloud[i, j] = integrate_lno2(ptropo.values[i, j], pasp.values[i, j], compute_c)

Besides, the lightning NO2 retrieval should use the customed AMF_LNO2 instead of using the ratio of integration.

for i in range(ptropo.shape[0]):
for j in range(ptropo.shape[1]):
lno2_tropo_cloud[i, j] = integrate_lno2(ptropo.values[i, j], pasp.values[i, j], compute_c)
lno2_tropo_sfc[i, j] = integrate_lno2(ptropo.values[i, j], psfc.values[i, j], compute_c)
# # ---- bak: xarray version which is slower ---
# lno2_tropo_cloud = xr.apply_ufunc(integrate_lno2_xr,
# ptropo.chunk({'y': ptropo.sizes['y'],
# 'x': ptropo.sizes['x']}),
# pasp,
# peak_pressure, peak_width, vectorize=True,
# dask="parallelized", output_dtypes=[ptropo.dtype])
# lno2_tropo_sfc = xr.apply_ufunc(integrate_lno2_xr,
# ptropo.chunk({'y': ptropo.sizes['y'],
# 'x': ptropo.sizes['x']}),
# psfc,
# peak_pressure, peak_width, vectorize=True,
# dask="parallelized", output_dtypes=[ptropo.dtype])
lno2 = lno2_vis.where(convection_mask) / (lno2_tropo_cloud/lno2_tropo_sfc)

Empty S5P-LNO2 product

I got this error when extract TM5 profiles to products generated by s5p_lnox_main.py

Adding NO2 and Temperature profiles to  /student/zhangxin/papers/Arctic/S5P_LNOx/main/output_gld360/clean_lightning/202107/S5P_PAL__L2__NO2____20210712T000148_20210712T014317_19403_02_020301.nc
Traceback (most recent call last):
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/site-packages/xarray/backends/netCDF4_.py", line 180, in _nc4_require_group
    ds = ds.groups[key]
KeyError: 'S5P'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/student/zhangxin/papers/Arctic/S5P_LNOx/main/s5p_lnox_tm5_extract.py", line 48, in <module>
    with xr.open_dataset(file, group='S5P') as ds:
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/site-packages/xarray/backends/api.py", line 495, in open_dataset
    backend_ds = backend.open_dataset(
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/site-packages/xarray/backends/netCDF4_.py", line 553, in open_dataset
    store = NetCDF4DataStore.open(
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/site-packages/xarray/backends/netCDF4_.py", line 382, in open
    return cls(manager, group=group, mode=mode, lock=lock, autoclose=autoclose)
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/site-packages/xarray/backends/netCDF4_.py", line 330, in __init__
    self.format = self.ds.data_model
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/site-packages/xarray/backends/netCDF4_.py", line 391, in ds
    return self._acquire()
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/site-packages/xarray/backends/netCDF4_.py", line 386, in _acquire
    ds = _nc4_require_group(root, self._group, self._mode)
  File "/public/home/zhangxin/new/miniconda3/envs/knmi_arctic/lib/python3.9/site-packages/xarray/backends/netCDF4_.py", line 186, in _nc4_require_group
    raise OSError(f"group not found: {key}", e)
OSError: [Errno group not found: S5P] 'S5P'

Two solutions:

  1. Skip saving the product if it's empty
  2. Skip extracting if it's empty

Add geo center and land/ocean flag

It's useful to calculate the geo center which can be used to know where the linked convection is. Then, we can distinguish the calculated PEs is over land or ocean.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.