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
def apply_cf_compliant_attrs(ds):
 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'