"""pyart.aux_io.knmi_h5====================Routines for reading ODIM_H5 files... autosummary:: :toctree: generated/ read_knmi_grid_h5 _get_knmi_data"""importdatetimeimportnumpyasnptry:importh5py_H5PY_AVAILABLE=TrueexceptImportError:_H5PY_AVAILABLE=Falsetry:importpyproj_PYPROJ_AVAILABLE=TrueexceptImportError:_PYPROJ_AVAILABLE=Falsefrom..aux_io.odim_h5import_to_str,proj4_to_dictfrom..configimportFileMetadata,get_fillvaluefrom..core.gridimportGridfrom..exceptionsimportMissingOptionalDependencyfrom..io.commonimport_test_arguments,make_time_unit_strfrom..utilimportma_broadcast_toKNMI_H5_FIELD_NAMES={"RAINFALL_RATE_[MM/H]":"radar_estimated_rain_rate","ACCUMULATION_[MM]":"rainfall_accumulation","QUALITY_[-]":"signal_quality_index","ADJUSTMENT_FACTOR_[DB]":"adjustment_factor",}
[docs]defread_knmi_grid_h5(filename,field_names=None,additional_metadata=None,file_field_names=False,exclude_fields=None,include_fields=None,time_ref="end",**kwargs):""" Read a KNMI_H5 grid file. Parameters ---------- filename : str Name of the field_name file to read. field_names : dict, optional Dictionary mapping field_name field names to radar field names. If a data type found in the file does not appear in this dictionary or has a value of None it will not be placed in the radar.fields dictionary. A value of None, the default, will use the mapping defined in the Py-ART configuration file. additional_metadata : dict of dicts, optional Dictionary of dictionaries to retrieve metadata from during this read. This metadata is not used during any successive file reads unless explicitly included. A value of None, the default, will not introduct any addition metadata and the file specific or default metadata as specified by the Py-ART configuration file will be used. file_field_names : bool, optional True to use the MDV data type names for the field names. If this case the field_names parameter is ignored. The field dictionary will likely only have a 'data' key, unless the fields are defined in `additional_metadata`. exclude_fields : list or None, optional List of fields to exclude from the radar object. This is applied after the `file_field_names` and `field_names` parameters. Set to None to include all fields specified by include_fields. include_fields : list or None, optional List of fields to include from the radar object. This is applied after the `file_field_names` and `field_names` parameters. Set to None to include all fields not specified by exclude_fields. time_ref : str Time reference in the /what/time attribute. Can be either 'start', 'mid' or 'end'. If 'start' the attribute is expected to be the starttime of the scan, if 'mid', the middle time, if 'end' the endtime. Returns ------- grid : Grid Grid object containing data from ODIM_H5 file. """# check that h5py is availableifnot_H5PY_AVAILABLE:raiseMissingOptionalDependency("h5py is required to use read_odim_h5 but is not installed")# test for non empty kwargs_test_arguments(kwargs)# create metadata retrieval objectiffield_namesisNone:field_names=KNMI_H5_FIELD_NAMESfilemetadata=FileMetadata("odim_h5",field_names,additional_metadata,file_field_names,exclude_fields,include_fields,)withh5py.File(filename,"r")ashfile:# determine the number of datasets by the number of groups which# begin with imagedatasets=[kforkinhfileifk.startswith("image")]datasets.sort(key=lambdax:int(x[5:]))# latitude, longitude and altitudex=filemetadata("x")y=filemetadata("y")z=filemetadata("z")geo_group=hfile["geographic"].attrsmap_proj=hfile["geographic"]["map_projection"].attrs# projection definitionprojection=map_proj["projection_proj4_params"]# 0, 49.3621, 0, 55.9736, 10.8565, 55.389, 9.0093, 48.8953geo_product_corners=geo_group["geo_product_corners"]if_PYPROJ_AVAILABLE:wgs84=pyproj.CRS.from_epsg(4326)try:# pyproj doens't like bytearraysprojection=projection.decode("utf-8")exceptException:pass# projection = f'{projection} +units=km'projection=("+proj=stere +lat_0=90 +lon_0=0.0 +lat_ts=60.0"" +a=6378137 +b=6356752 +x_0=0 +y_0=0")coordTrans=pyproj.Transformer.from_crs(wgs84,projection)xstart,ystart=coordTrans.transform(geo_product_corners[1],geo_product_corners[0])xend,yend=coordTrans.transform(geo_product_corners[5],geo_product_corners[4])xvec=np.linspace(xstart,xend,geo_group["geo_number_columns"][0])yvec=np.linspace(ystart,yend,geo_group["geo_number_rows"][0])else:xvec=np.linspace(geo_product_corners[5],geo_product_corners[1],geo_group["geo_number_columns"][0],)yvec=np.linspace(geo_product_corners[0],geo_product_corners[4],geo_group["geo_number_rows"][0],)x["data"]=xvecy["data"]=yvecz["data"]=np.array([0],dtype="float64")# metadatametadata=filemetadata("metadata")# metadata['source'] = _to_str(hfile['what'].attrs['source'])metadata["original_container"]="knmi_h5"# metadata['odim_conventions'] = _to_str(hfile.attrs['Conventions'])# h_what = hfile['what'].attrs# metadata['version'] = _to_str(h_what['version'])# metadata['source'] = _to_str(h_what['source'])# Get the MeteoSwiss-specific data# try:# h_how2 = hfile['how']['MeteoSwiss'].attrs# except KeyError:# # if no how group exists mock it with an empty dictionary# h_how2 = {}# if 'radar' in h_how2:# metadata['radar'] = h_how2['radar']# try:# ds1_how = hfile[datasets[0]]['how'].attrs# except KeyError:# # if no how group exists mock it with an empty dictionary# ds1_how = {}# if 'system' in ds1_how:# metadata['system'] = ds1_how['system']# if 'software' in ds1_how:# metadata['software'] = ds1_how['software']# if 'sw_version' in ds1_how:# metadata['sw_version'] = ds1_how['sw_version']dset_list=[]field_list=[]knmi_list=[]forknmi_fieldinfield_names.keys():fordsetindatasets:dset_field=_to_str(hfile[dset].attrs["image_geo_parameter"])ifknmi_field==dset_field:dset_list.append(dset)field_list.append(field_names[knmi_field])knmi_list.append(knmi_field)breakif(knmi_field=="RAINFALL_RATE_[MM/H]"anddset_field=="ACCUMULATION_[MM]"):dset_list.append(dset)field_list.append(field_names[knmi_field])knmi_list.append(knmi_field)breakfields={}forknmi_field,field_name,dsetinzip(knmi_list,field_list,dset_list):fdata=_get_knmi_data(hfile[dset],knmi_field,hfile[dset]["calibration"].attrs)field_dic=filemetadata(field_name)# grid object expects a 3D fieldny=geo_group["geo_number_rows"][0]nx=geo_group["geo_number_columns"][0]field_dic["data"]=ma_broadcast_to(fdata[::-1,:],(1,ny,nx))field_dic["_FillValue"]=get_fillvalue()fields[field_name]=field_dicifnotfields:# warn(f'No fields could be retrieved from file')returnNone_time=filemetadata("time")origin_latitude=filemetadata("origin_latitude")origin_longitude=filemetadata("origin_longitude")origin_altitude=filemetadata("origin_altitude")# format 12-OCT-2023;08:00:04.000start_time=hfile["overview"].attrs["product_datetime_start"]start_time=datetime.datetime.strptime(_to_str(start_time),"%d-%b-%Y;%H:%M:%S.000")end_time=hfile["overview"].attrs["product_datetime_end"]end_time=datetime.datetime.strptime(_to_str(end_time),"%d-%b-%Y;%H:%M:%S.000")_time["data"]=[0]iftime_ref=="mid":mid_delta=(end_time-start_time)/2mid_ts=start_time+mid_delta_time["units"]=make_time_unit_str(mid_ts)eliftime_ref=="end":_time["units"]=make_time_unit_str(end_time)else:_time["units"]=make_time_unit_str(start_time)projection=proj4_to_dict(projection)if"lat_0"inprojection:origin_latitude["data"]=np.array([projection["lat_0"]])else:origin_latitude["data"]=np.array([0.0])if"lon_0"inprojection:origin_longitude["data"]=np.array([projection["lat_0"]])else:origin_longitude["data"]=np.array([0.0])origin_altitude["data"]=np.array([0.0])# radar variablesradar_latitude=Noneradar_longitude=Noneradar_altitude=Noneradar_name=Noneradar_time=NonereturnGrid(_time,fields,metadata,origin_latitude,origin_longitude,origin_altitude,x,y,z,projection=projection,radar_latitude=radar_latitude,radar_longitude=radar_longitude,radar_altitude=radar_altitude,radar_name=radar_name,radar_time=radar_time,)
def_get_knmi_data(group,knmi_field,calibration):"""Get KNMI data from an HDF5 group."""nodata=calibration["calibration_missing_data"]undetect=calibration["calibration_out_of_image"]formula=_to_str(calibration["calibration_formulas"]).split("=")[1]# b'GEO = 0.500000 * PV + -32.000000'gain=float(formula.split("*")[0])offset=float(formula.split("+")[1])# mask raw dataraw_data=group["image_data"][:]data=np.ma.masked_values(raw_data,nodata)data=np.ma.masked_values(data,undetect)data=data*gain+offsetifknmi_field=="RAINFALL_RATE_[MM/H]":data*=12.0returndata