diff --git a/core/core.py b/core/core.py new file mode 100644 index 0000000000000000000000000000000000000000..a76210123ad9f011129fdd43c729361ad2671a0a --- /dev/null +++ b/core/core.py @@ -0,0 +1,255 @@ +def read_lon_lat(fname: str): + """ + Read geographical position for hdf4 file. + Adapted from https://gitlab.phaidra.org/climate/icon_2.3.0_ice-mp_rad_vis/-/blob/master/CloudSat/CloudSat_read.py + + Input: + - fname: full path of hdf file (string) + Output: + - data: xarray dataset with longitude and latitude in deg + """ + import numpy as np + from pyhdf import HDF, VS + import xarray as xr + h = HDF.HDF(fname) + vs = h.vstart() + # latitude + latid = vs.attach(vs.find('Latitude')) + latid.setfields('Latitude') + nrecs, _, _, _, _ = latid.inquire() + latitude = np.array(latid.read(nRec=nrecs)).squeeze() + latid.detach() + # longitude + lonid = vs.attach(vs.find('Longitude')) + lonid.setfields('Longitude') + nrecs, _, _, _, _ = lonid.inquire() + longitude = np.array(lonid.read(nRec=nrecs)).squeeze() + lonid.detach() + # construct xarray dataset following the dimensions and coordinates of 2B-FLXHR-LIDAR + y = np.arange(0,np.size(longitude))+0.5 + da_lon = xr.DataArray(longitude, name="LON", dims=("y"), coords={"y": y}) + da_lat = xr.DataArray(latitude, name="LAT", dims=("y"), coords={"y": y}) + ds = xr.merge([da_lon, da_lat]) + return ds + + +def read_surfaceheightbin(fname: str): + """ + Read surface height bin. Can be used to mask out values near the surface. + + Input: + - fname: full path of hdf file (string) + Output: + - data: xarray dataset with surface height bin + """ + import numpy as np + from pyhdf import HDF, VS + import xarray as xr + h = HDF.HDF(fname) + vs = h.vstart() + varid = vs.attach(vs.find('SurfaceHeightBin')) + varid.setfields('SurfaceHeightBin') + nrecs, _, _, _, _ = varid.inquire() + var = np.array(varid.read(nRec=nrecs)).squeeze() + varid.detach() + # return xarray dataset following the dimensions and coordinates of 2B-FLXHR-LIDAR + y = np.arange(0,np.size(var))+0.5 + da_var = xr.DataArray(var, name="SurfaceHeightBin", dims=("y"), coords={"y": y}) + return da_var.to_dataset() + + +def read_time(fname: str): + """ + Read time. + + Input: + - fname: full path of hdf file (string) + Output: + - data: xarray dataset with time in datetime64 format + """ + import numpy as np + from pyhdf import HDF, VS + import xarray as xr + from astropy.time import Time + h = HDF.HDF(fname) + vs = h.vstart() + # TAI_start, seconds since 00:00:00 1 Jan 1993 + varid = vs.attach(vs.find('TAI_start')) + varid.setfields('TAI_start') + nrecs, _, _, _, _ = varid.inquire() + TAI_start = np.array(varid.read(nRec=nrecs)).squeeze() + varid.detach() + # Profile time + varid = vs.attach(vs.find('Profile_time')) + varid.setfields('Profile_time') + nrecs, _, _, _, _ = varid.inquire() + Profile_time = np.array(varid.read(nRec=nrecs)).squeeze() + varid.detach() + # calculate time using astropy and change to datetim64 format + tanom = Time(TAI_start + Profile_time, format="unix_tai", scale="utc") + tbase = Time('1993-01-01 00:00:00', scale='utc') # TAI time for CloudSat starts not in 1970 but 1993 + time = Time(tanom.value+tbase.tai.unix_tai, format="unix_tai", scale="utc") + time = time.datetime64 + # construct xarray dataset following the dimensions and coordinates of 2B-FLXHR-LIDAR + y = np.arange(0,np.size(time))+0.5 + ds = xr.DataArray(time, name="TIME", dims=("y"), coords={"y": y}).to_dataset() + return ds + + +def read_height(fname: str): + """ + Read 2d height array for hdf4 file. + + Input: + - fname: full path of hdf file (string) + Output: + - height: xarray dataset with height + """ + import numpy as np + from pyhdf.SD import SD, SDC + import xarray as xr + hdf = SD(fname, SDC.READ) + height = hdf.select('Height') + height = height[:,:] + # construct xarray dataset following the dimensions and coordinates of 2B-FLXHR-LIDAR + x = np.arange(0,np.size(height[0]))+0.5 + y = np.arange(0,np.size(height[:,0]))+0.5 + da_height = xr.DataArray(height, name="HGT", dims=("y", "xfull"), coords={"y": y, "xfull": x}) + return da_height.to_dataset() + + +def read_toaswin(fname: str): + """ + Read TOA incoming solar radiation. + + Input: + - fname: full path of hdf file (string) + Output: + - toaswin: xarray dataset with toaswin + """ + from pyhdf import HDF, VS + import xarray as xr + import numpy as np + h = HDF.HDF(fname) + vs = h.vstart() + varid = vs.attach(vs.find('FD_TOA_IncomingSolar')) + varid.setfields('FD_TOA_IncomingSolar') + nrecs, _, _, _, _ = varid.inquire() + toaswin = np.array(varid.read(nRec=nrecs)).squeeze() + varid.detach() + # set missing value to nan and rescale + missval = toaswin.min() + toaswin = 0.1*np.where(toaswin==missval, np.nan, toaswin) + # construct xarray dataset following the dimensions and coordinates of 2B-FLXHR-LIDAR + y = np.arange(0,np.size(toaswin))+0.5 + da = xr.DataArray(toaswin, name="TOASWIN", dims=("y"), coords={"y": y}) + return da.to_dataset() + + +def read_fluxes(fname: str): + """ + Reads radiative fluxes from 2B-FLXHR-LIDAR file. + + Input: + - fname: full path of hdf file (string) + Output: + - xarray dataset with vertically-resolved net longwave and shortwave fluxes. + """ + import rioxarray as rxr + import xarray as xr + import numpy as np + + data = rxr.open_rasterio(fname) + + # all-sky fluxes + FD = data[0]["FD"] + missval = FD.min().values + FD = 0.1*xr.where(FD==missval, np.nan, FD) + FU = data[0]["FU"] + missval = FU.min().values + FU = 0.1*xr.where(FU==missval, np.nan, FU) + FN = (FD - FU).rename("FN") + + # clear-sky fluxes + FD_NC = data[0]["FD_NC"] + missval = FD_NC.min().values + FD_NC = 0.1*xr.where(FD_NC==missval, np.nan, FD_NC) + FU_NC = data[0]["FU_NC"] + missval = FU_NC.min().values + FU_NC = 0.1*xr.where(FU_NC==missval, np.nan, FU_NC) + FN_NC = (FD_NC - FU_NC).rename("FN_NC") + + # return all fluxes in one xarray dataset + return xr.merge([FN, FN_NC]) + + +def read_plevthickness(fname: str): + """ + Calculates pressure level thickness from ECMWF-AUX file. + + Input: + - fname: full path of hdf file (string) + Output: + - xarray dataset with pressure level thickness DP in Pa. + """ + import rioxarray as rxr + import xarray as xr + import numpy as np + + data = rxr.open_rasterio(fname) + P = data["Pressure"].squeeze() + missval = P.min().values + P = xr.where(P==missval, np.nan, P) + DP = 0.5*(P.shift(x=-1) - P.shift(x=+1)) + DP = DP.rename("DP") + return DP + + +def compute_heatingrates(data): + """ + Compute all-sky and clear-sky heating rates from fluxes. Shortwave heating is rescale by the daily-mean + insolation. + + Input: + - data: xarray dataset with net all-sky (FN) and clear-sky fluxes (FN_NC), TOA incoming shortwave (TOASWIN), pressure level thickness (DP) + latitude (LAT) and time (TIME) + Output: + - output: xarray dataset with all-sky (HR) and clear-sky heating rates (HR_NC) in K/day + for rays with zero or nan TOA SW IN, the shortwave heating rate will be nan + """ + import numpy as np + import xarray as xr + import pandas as pd + import sys + sys.path.append("/jetfs/home/avoigt/cloudsat-calipso-heating-rates/solar/") + import insolation + cp = 1004; + g = 9.81; + HR = -g * data["FN"].diff("x", label="lower") / (cp*data["DP"]) * 86400 + HR_NC = -g * data["FN_NC"].diff("x", label="lower") / (cp*data["DP"]) * 86400 + + # rename + HR = HR.rename("HR").rename({"x": "xfull"}) + HR_NC = HR_NC.rename("HR_NC").rename({"x": "xfull"}) + + # split into shortwave and longwave heating + HRSW = HR.isel(band=0) + HRLW = HR.isel(band=1) + HRSW_NC = HR_NC.isel(band=0) + HRLW_NC = HR_NC.isel(band=1) + + # rescale shortwave heating rates with daily mean insolation + SWIN = data["TOASWIN"] # instantaneous shortwave down at TOA + # set SWIN below 50 Wm-2 to nan to remove rays that are close to sunset/sunrise + SWIN = xr.where(SWIN<50, np.nan, SWIN) + DOY = pd.to_datetime(data.TIME).dayofyear + SWIN_daily = insolation.daily_insolation(data.LAT, day=DOY) + + HRSW = HRSW * SWIN_daily/SWIN + HRSW_NC = HRSW_NC * SWIN_daily/SWIN + + # combine shortwave and longwave again into one dataarray + HR[0] = HRSW; HR[1] = HRLW + HR_NC[0] = HRSW_NC; HR_NC[1] = HRLW_NC + + return xr.merge([HR, HR_NC])