xopr.cf_units
CF-compliant metadata and units for polar radar datasets.
This module provides utilities for applying Climate and Forecast (CF) metadata conventions to polar radar echogram datasets. The CF conventions ensure that radar data includes standardized attributes for coordinates, data variables, and global metadata, making the datasets more interoperable and self-describing.
The primary function applies CF-1.8 compliant attributes to xarray Datasets, including:
- Standard names and units for coordinates (time, two-way travel time)
- Physical units and descriptions for data variables (radar power, GPS position, etc.)
- Global attributes for dataset provenance and spatial/temporal coverage
Notes
This is a work in progress and the data structures are definitely not fully CF-compliant yet.
@private Not intended for external use.
1""" 2CF-compliant metadata and units for polar radar datasets. 3 4This module provides utilities for applying Climate and Forecast (CF) metadata 5conventions to polar radar echogram datasets. The CF conventions ensure that 6radar data includes standardized attributes for coordinates, data variables, 7and global metadata, making the datasets more interoperable and self-describing. 8 9The primary function applies CF-1.8 compliant attributes to xarray Datasets, 10including: 11- Standard names and units for coordinates (time, two-way travel time) 12- Physical units and descriptions for data variables (radar power, GPS position, etc.) 13- Global attributes for dataset provenance and spatial/temporal coverage 14 15Notes 16----- 17This is a work in progress and the data structures are definitely not fully CF-compliant yet. 18 19@private 20Not intended for external use. 21""" 22 23import numpy as np 24 25 26def apply_cf_compliant_attrs(ds): 27 """ 28 Apply CF-compliant units and attributes to radar echogram dataset. 29 30 This function adds Climate and Forecast (CF) metadata conventions version 1.8 31 to a polar radar echogram dataset. It applies standardized attributes to 32 coordinates, data variables, and global metadata to ensure the dataset is 33 self-describing and interoperable with CF-compliant tools. 34 35 The function modifies attributes for the following coordinate and data variables 36 (if present in the dataset): 37 38 Coordinates: 39 - slow_time: Time along flight track (standard_name='time') 40 - twtt: Two-way travel time from radar to target 41 42 Data Variables: 43 - Bottom: Two-way travel time to detected bottom surface 44 - Data: Radar echo power in linear scale 45 - Elevation: Platform elevation above WGS84 ellipsoid 46 - Heading: Platform heading angle from north 47 - Latitude: GPS latitude in WGS84 48 - Longitude: GPS longitude in WGS84 49 - Pitch: Platform pitch angle (positive nose up) 50 - Roll: Platform roll angle (positive right wing down) 51 - Surface: Two-way travel time to detected surface 52 53 Parameters 54 ---------- 55 ds : xarray.Dataset 56 Input radar echogram dataset containing radar data and navigation variables. 57 The original dataset is not modified; a copy is created. 58 59 Returns 60 ------- 61 xarray.Dataset 62 Copy of the input dataset with CF-1.8 compliant attributes applied to 63 coordinates, data variables, and global metadata. Includes geospatial 64 bounds and temporal coverage in global attributes. 65 66 Notes 67 ----- 68 - The function creates a copy of the input dataset to avoid modifying the original 69 - Only variables present in the input dataset will have attributes applied 70 - Global attributes include geospatial bounds and time coverage computed from data 71 - Radar echo power units are currently set to '1' (dimensionless) pending calibration 72 73 Examples 74 -------- 75 >>> import xarray as xr 76 >>> from xopr.cf_units import apply_cf_compliant_attrs 77 >>> ds = xr.open_dataset('radar_echogram.nc') 78 >>> ds_cf = apply_cf_compliant_attrs(ds) 79 >>> print(ds_cf['Latitude'].attrs['units']) 80 'degrees_north' 81 """ 82 83 # Create a copy to avoid modifying the original dataset 84 ds_cf = ds.copy() 85 86 # Define CF-compliant attributes for coordinates 87 coordinate_attrs = { 88 'slow_time': { 89 #'units': 'seconds since 1970-01-01T00:00:00Z', 90 'standard_name': 'time', 91 'long_name': 'slow time', 92 'comment': 'Time coordinate for radar pulse transmission along flight track' 93 }, 94 'twtt': { 95 'units': 's', 96 'standard_name': 'time', 97 'long_name': 'two-way travel time', 98 'comment': 'Two-way travel time from radar to target and back' 99 } 100 } 101 102 # Define CF-compliant attributes for data variables 103 data_var_attrs = { 104 'Bottom': { 105 'units': 's', 106 'long_name': 'bottom surface two-way travel time', 107 'comment': 'Two-way travel time to detected bottom surface. NaN where bottom not detected.', 108 '_FillValue': np.nan 109 }, 110 'Data': { 111 'units': '1', # TODO: Appropriate units for radar data -- can we calibrate to watts? 112 'long_name': 'radar echo power', 113 'comment': 'Radar echo power in linear scale', 114 'coordinates': 'slow_time twtt' 115 }, 116 'Elevation': { 117 'units': 'm', 118 'standard_name': 'height_above_reference_ellipsoid', 119 'long_name': 'platform elevation above WGS84 ellipsoid', 120 'comment': 'GPS-derived elevation of radar platform above WGS84 reference ellipsoid' 121 }, 122 'Heading': { 123 'units': 'radians', 124 'standard_name': 'platform_orientation', 125 'long_name': 'platform heading angle', 126 'comment': 'Platform heading angle in radians from north, clockwise positive', 127 'valid_min': -np.pi, 128 'valid_max': np.pi, 129 'valid_range': [-np.pi, np.pi], 130 }, 131 'Latitude': { 132 'units': 'degrees_north', 133 'standard_name': 'latitude', 134 'long_name': 'platform latitude', 135 'comment': 'GPS-derived latitude of radar platform in WGS84 coordinate system', 136 'valid_min': -90.0, 137 'valid_max': 90.0, 138 'valid_range': [-90.0, 90.0] 139 }, 140 'Longitude': { 141 'units': 'degrees_east', 142 'standard_name': 'longitude', 143 'long_name': 'platform longitude', 144 'comment': 'GPS-derived longitude of radar platform in WGS84 coordinate system', 145 'valid_min': -180.0, 146 'valid_max': 180.0, 147 'valid_range': [-180.0, 180.0] 148 }, 149 'Pitch': { 150 'units': 'radians', 151 'standard_name': 'platform_pitch_angle', 152 'long_name': 'platform pitch angle', 153 'comment': 'Platform pitch angle in radians, positive nose up', 154 'valid_min': -np.pi/2, 155 'valid_max': np.pi/2, 156 'valid_range': [-np.pi/2, np.pi/2] 157 }, 158 'Roll': { 159 'units': 'radians', 160 'standard_name': 'platform_roll_angle', 161 'long_name': 'platform roll angle', 162 'comment': 'Platform roll angle in radians, positive right wing down', 163 'valid_min': -np.pi, 164 'valid_max': np.pi, 165 'valid_range': [-np.pi, np.pi] 166 }, 167 'Surface': { 168 'units': 's', 169 'long_name': 'surface two-way travel time', 170 'comment': 'Two-way travel time to detected surface. Zero indicates surface at platform level.', 171 'valid_min': 0.0 172 } 173 } 174 175 # Apply coordinate attributes 176 for coord_name, attrs in coordinate_attrs.items(): 177 if coord_name in ds_cf.coords: 178 ds_cf[coord_name].attrs.update(attrs) 179 180 # Apply data variable attributes 181 for var_name, attrs in data_var_attrs.items(): 182 if var_name in ds_cf.data_vars: 183 ds_cf[var_name].attrs.update(attrs) 184 185 # Add global attributes for CF compliance 186 global_attrs = { 187 'Conventions': 'CF-1.8', 188 'title': 'Radar Echogram Data', 189 'institution': 'Open Polar Radar (OPR)', 190 'source': 'Airborne/ground-based radar sounder', 191 'history': f'Converted to CF-compliant format on {np.datetime64("now").astype(str)}', 192 'references': 'https://gitlab.com/englacial/xopr', 193 'comment': 'Polar radar echogram data with CF-compliant metadata', 194 'geospatial_lat_min': float(ds_cf.Latitude.min()) if 'Latitude' in ds_cf else None, 195 'geospatial_lat_max': float(ds_cf.Latitude.max()) if 'Latitude' in ds_cf else None, 196 'geospatial_lon_min': float(ds_cf.Longitude.min()) if 'Longitude' in ds_cf else None, 197 'geospatial_lon_max': float(ds_cf.Longitude.max()) if 'Longitude' in ds_cf else None, 198 'time_coverage_start': str(ds_cf.slow_time.min().values) if 'slow_time' in ds_cf else None, 199 'time_coverage_end': str(ds_cf.slow_time.max().values) if 'slow_time' in ds_cf else None 200 } 201 202 # Remove None values from global attributes 203 global_attrs = {k: v for k, v in global_attrs.items() if v is not None} 204 205 # Update global attributes 206 ds_cf.attrs.update(global_attrs) 207 208 return ds_cf
27def apply_cf_compliant_attrs(ds): 28 """ 29 Apply CF-compliant units and attributes to radar echogram dataset. 30 31 This function adds Climate and Forecast (CF) metadata conventions version 1.8 32 to a polar radar echogram dataset. It applies standardized attributes to 33 coordinates, data variables, and global metadata to ensure the dataset is 34 self-describing and interoperable with CF-compliant tools. 35 36 The function modifies attributes for the following coordinate and data variables 37 (if present in the dataset): 38 39 Coordinates: 40 - slow_time: Time along flight track (standard_name='time') 41 - twtt: Two-way travel time from radar to target 42 43 Data Variables: 44 - Bottom: Two-way travel time to detected bottom surface 45 - Data: Radar echo power in linear scale 46 - Elevation: Platform elevation above WGS84 ellipsoid 47 - Heading: Platform heading angle from north 48 - Latitude: GPS latitude in WGS84 49 - Longitude: GPS longitude in WGS84 50 - Pitch: Platform pitch angle (positive nose up) 51 - Roll: Platform roll angle (positive right wing down) 52 - Surface: Two-way travel time to detected surface 53 54 Parameters 55 ---------- 56 ds : xarray.Dataset 57 Input radar echogram dataset containing radar data and navigation variables. 58 The original dataset is not modified; a copy is created. 59 60 Returns 61 ------- 62 xarray.Dataset 63 Copy of the input dataset with CF-1.8 compliant attributes applied to 64 coordinates, data variables, and global metadata. Includes geospatial 65 bounds and temporal coverage in global attributes. 66 67 Notes 68 ----- 69 - The function creates a copy of the input dataset to avoid modifying the original 70 - Only variables present in the input dataset will have attributes applied 71 - Global attributes include geospatial bounds and time coverage computed from data 72 - Radar echo power units are currently set to '1' (dimensionless) pending calibration 73 74 Examples 75 -------- 76 >>> import xarray as xr 77 >>> from xopr.cf_units import apply_cf_compliant_attrs 78 >>> ds = xr.open_dataset('radar_echogram.nc') 79 >>> ds_cf = apply_cf_compliant_attrs(ds) 80 >>> print(ds_cf['Latitude'].attrs['units']) 81 'degrees_north' 82 """ 83 84 # Create a copy to avoid modifying the original dataset 85 ds_cf = ds.copy() 86 87 # Define CF-compliant attributes for coordinates 88 coordinate_attrs = { 89 'slow_time': { 90 #'units': 'seconds since 1970-01-01T00:00:00Z', 91 'standard_name': 'time', 92 'long_name': 'slow time', 93 'comment': 'Time coordinate for radar pulse transmission along flight track' 94 }, 95 'twtt': { 96 'units': 's', 97 'standard_name': 'time', 98 'long_name': 'two-way travel time', 99 'comment': 'Two-way travel time from radar to target and back' 100 } 101 } 102 103 # Define CF-compliant attributes for data variables 104 data_var_attrs = { 105 'Bottom': { 106 'units': 's', 107 'long_name': 'bottom surface two-way travel time', 108 'comment': 'Two-way travel time to detected bottom surface. NaN where bottom not detected.', 109 '_FillValue': np.nan 110 }, 111 'Data': { 112 'units': '1', # TODO: Appropriate units for radar data -- can we calibrate to watts? 113 'long_name': 'radar echo power', 114 'comment': 'Radar echo power in linear scale', 115 'coordinates': 'slow_time twtt' 116 }, 117 'Elevation': { 118 'units': 'm', 119 'standard_name': 'height_above_reference_ellipsoid', 120 'long_name': 'platform elevation above WGS84 ellipsoid', 121 'comment': 'GPS-derived elevation of radar platform above WGS84 reference ellipsoid' 122 }, 123 'Heading': { 124 'units': 'radians', 125 'standard_name': 'platform_orientation', 126 'long_name': 'platform heading angle', 127 'comment': 'Platform heading angle in radians from north, clockwise positive', 128 'valid_min': -np.pi, 129 'valid_max': np.pi, 130 'valid_range': [-np.pi, np.pi], 131 }, 132 'Latitude': { 133 'units': 'degrees_north', 134 'standard_name': 'latitude', 135 'long_name': 'platform latitude', 136 'comment': 'GPS-derived latitude of radar platform in WGS84 coordinate system', 137 'valid_min': -90.0, 138 'valid_max': 90.0, 139 'valid_range': [-90.0, 90.0] 140 }, 141 'Longitude': { 142 'units': 'degrees_east', 143 'standard_name': 'longitude', 144 'long_name': 'platform longitude', 145 'comment': 'GPS-derived longitude of radar platform in WGS84 coordinate system', 146 'valid_min': -180.0, 147 'valid_max': 180.0, 148 'valid_range': [-180.0, 180.0] 149 }, 150 'Pitch': { 151 'units': 'radians', 152 'standard_name': 'platform_pitch_angle', 153 'long_name': 'platform pitch angle', 154 'comment': 'Platform pitch angle in radians, positive nose up', 155 'valid_min': -np.pi/2, 156 'valid_max': np.pi/2, 157 'valid_range': [-np.pi/2, np.pi/2] 158 }, 159 'Roll': { 160 'units': 'radians', 161 'standard_name': 'platform_roll_angle', 162 'long_name': 'platform roll angle', 163 'comment': 'Platform roll angle in radians, positive right wing down', 164 'valid_min': -np.pi, 165 'valid_max': np.pi, 166 'valid_range': [-np.pi, np.pi] 167 }, 168 'Surface': { 169 'units': 's', 170 'long_name': 'surface two-way travel time', 171 'comment': 'Two-way travel time to detected surface. Zero indicates surface at platform level.', 172 'valid_min': 0.0 173 } 174 } 175 176 # Apply coordinate attributes 177 for coord_name, attrs in coordinate_attrs.items(): 178 if coord_name in ds_cf.coords: 179 ds_cf[coord_name].attrs.update(attrs) 180 181 # Apply data variable attributes 182 for var_name, attrs in data_var_attrs.items(): 183 if var_name in ds_cf.data_vars: 184 ds_cf[var_name].attrs.update(attrs) 185 186 # Add global attributes for CF compliance 187 global_attrs = { 188 'Conventions': 'CF-1.8', 189 'title': 'Radar Echogram Data', 190 'institution': 'Open Polar Radar (OPR)', 191 'source': 'Airborne/ground-based radar sounder', 192 'history': f'Converted to CF-compliant format on {np.datetime64("now").astype(str)}', 193 'references': 'https://gitlab.com/englacial/xopr', 194 'comment': 'Polar radar echogram data with CF-compliant metadata', 195 'geospatial_lat_min': float(ds_cf.Latitude.min()) if 'Latitude' in ds_cf else None, 196 'geospatial_lat_max': float(ds_cf.Latitude.max()) if 'Latitude' in ds_cf else None, 197 'geospatial_lon_min': float(ds_cf.Longitude.min()) if 'Longitude' in ds_cf else None, 198 'geospatial_lon_max': float(ds_cf.Longitude.max()) if 'Longitude' in ds_cf else None, 199 'time_coverage_start': str(ds_cf.slow_time.min().values) if 'slow_time' in ds_cf else None, 200 'time_coverage_end': str(ds_cf.slow_time.max().values) if 'slow_time' in ds_cf else None 201 } 202 203 # Remove None values from global attributes 204 global_attrs = {k: v for k, v in global_attrs.items() if v is not None} 205 206 # Update global attributes 207 ds_cf.attrs.update(global_attrs) 208 209 return ds_cf
Apply CF-compliant units and attributes to radar echogram dataset.
This function adds Climate and Forecast (CF) metadata conventions version 1.8 to a polar radar echogram dataset. It applies standardized attributes to coordinates, data variables, and global metadata to ensure the dataset is self-describing and interoperable with CF-compliant tools.
The function modifies attributes for the following coordinate and data variables (if present in the dataset):
Coordinates:
- slow_time: Time along flight track (standard_name='time')
- twtt: Two-way travel time from radar to target
Data Variables:
- Bottom: Two-way travel time to detected bottom surface
- Data: Radar echo power in linear scale
- Elevation: Platform elevation above WGS84 ellipsoid
- Heading: Platform heading angle from north
- Latitude: GPS latitude in WGS84
- Longitude: GPS longitude in WGS84
- Pitch: Platform pitch angle (positive nose up)
- Roll: Platform roll angle (positive right wing down)
- Surface: Two-way travel time to detected surface
Parameters
- ds (xarray.Dataset): Input radar echogram dataset containing radar data and navigation variables. The original dataset is not modified; a copy is created.
Returns
- xarray.Dataset: Copy of the input dataset with CF-1.8 compliant attributes applied to coordinates, data variables, and global metadata. Includes geospatial bounds and temporal coverage in global attributes.
Notes
- The function creates a copy of the input dataset to avoid modifying the original
- Only variables present in the input dataset will have attributes applied
- Global attributes include geospatial bounds and time coverage computed from data
- Radar echo power units are currently set to '1' (dimensionless) pending calibration
Examples
>>> import xarray as xr
>>> from xopr.cf_units import apply_cf_compliant_attrs
>>> ds = xr.open_dataset('radar_echogram.nc')
>>> ds_cf = apply_cf_compliant_attrs(ds)
>>> print(ds_cf['Latitude'].attrs['units'])
'degrees_north'