gnssvod

These are the main user-facing functions of gnssvod.

gnssvod.preprocess(filepattern: dict, orbit: bool = True, interval: str | Timedelta | None = None, keepvars: list | None = None, outputdir: dict | None = None, overwrite: bool = False, encoding: None | Literal['default'] | dict = 'default', outputresult: bool = False, aux_path: str | None = None, approx_position: list[float] = None) dict[Any, list[Observation]][source]

Reads and processes structured lists of RINEX observation files.

Parameters:
  • filepattern (dict) –

    Dictionary mapping station names to UNIX-style patterns matching RINEX observation files. For example:

    filepattern={'station1':'/path/to/files/of/station1/*O'}
    

  • orbit (bool, optional) – If True, downloads orbit solutions and calculates Azimuth and Elevation parameters. If False, no additional GNSS parameters are calculated.

  • interval (str, pandas.Timedelta, or None, optional) – If None, observations are returned at their original sampling rate. If a string or pandas.Timedelta is provided, observations are resampled (averaged) over that interval.

  • keepvars (list of str or None, optional) – List of columns to keep after processing, reducing the size of saved data. If None, all columns are kept.

  • outputdir (dict or None, optional) – Dictionary mapping station names to folders where preprocessed data should be saved. Dictionary keys must match the filepattern argument. Data are saved as NetCDF files, reusing the original filenames. If None, data are not saved.

  • overwrite (bool, optional) – If False (default), files that already exist in the output directory are skipped.

  • encoding (None, str, or dict, optional) –

    Controls compression and encoding options when saving NetCDF files.

    • None: no variable encodings applied.

    • ”default”: applies default encoding to SNR, VOD, Azimuth, and Elevation variables.

      Default encoding is:

      {
          "dtype": "int16",
          "scale_factor": 0.1,
          "zlib": True,
          "_FillValue": -9999,
      }
      
    • dict: per-variable encodings that are passed to xarray.Dataset.to_netcdf() for fine-grained control by the user.

  • outputresult (bool, optional) – If True and outputdir is None, observation objects are returned as a dictionary.

  • aux_path (str or None, optional) – Directory for auxiliary orbit and clock files. If None, a temporary directory is created and cleaned up after processing.

  • approx_position (list of float, optional) – Cartesian coordinates [X, Y, Z] of the antenna. Used if source RINEX files lack “APPROX POSITION XYZ” and orbit is True. To convert geographic coordinates (lat, lon, h) to Cartesian use gnssvod.geodesy.ell2cart().

Returns:

If outputresult is True, returns a dictionary with one key per station name. Each value is a list of GNSS observation objects read from the input RINEX files.

Return type:

dict or None

Examples

filepattern = {
    "station1": "/path/to/files/of/station1/*O",
    "station2": "/path/to/files/of/station2/*O"
}

interval = "15s"

keepvars = ["S1*", "S2*", "Azimuth", "Elevation"]

outputdir = {
    "station1": "/path/where/to/save/preprocessed/data",
    "station2": "/path/where/to/save/preprocessed/data"
}

output = preprocess(
    filepattern=filepattern,
    orbit=True,
    interval=interval,
    keepvars=keepvars,
    outputdir=outputdir
    outputresult=True
)
gnssvod.gather_stations(filepattern: dict, pairings: dict, timeintervals: IntervalIndex | None = None, keepvars: list | None = None, outputdir: dict | None = None, encoding: None | Literal['default'] | dict = None, outputresult: bool = False) dict[Any, DataFrame][source]

Merge observations from different sites according to specified pairing rules.

The returned dataframe contains a new index level corresponding to each site, with keys given by station names.

Parameters:
  • filepattern (dict) –

    Dictionary mapping station names to UNIX-style file patterns used to locate preprocessed NetCDF observation files. For example:

    filepattern={'station1':'/path/to/files/of/station1/*.nc','station2':'/path/to/files/of/station2/*.nc'}
    

  • pairings (dict) –

    Dictionary mapping case names to tuples of station names indicating which stations should be gathered together. For example:

    pairings={'case1':('station1','station2')}
    

    If data is saved, the case name is used as the output filename.

  • timeintervals (None or pandas.IntervalIndex, optional) – Time interval(s) over which data are sequentially gathered (see example below). Sequential processing avoids loading and pairing too much data at once. If outputdir is not None, the interval frequency also defines how data are saved (e.g. daily files). If None, all available files are used.

  • keepvars (list of str or None, optional) – List of column names to keep after gathering. Helps reduce the size of the dataset when saving. If None, no columns are removed.

  • outputdir (dict or None, optional) –

    Dictionary mapping case names to output directories where gathered data should be saved. For example:

    outputdir={'case1':'/path/where/to/save/data'}
    

    Data are saved as NetCDF files. The dictionary must be consistent with the pairings argument. If None, data are not saved.

  • encoding (None, str, or dict, optional) –

    Controls compression and encoding options when saving NetCDF files.

    • None: no variable encodings are applied.

    • "default": applies a default encoding to SNR, Azimuth, and Elevation variables. The default encoding is:

      {
          "dtype": "int16",
          "scale_factor": 0.1,
          "zlib": True,
          "_FillValue": -9999,
      }
      
    • dict: per-variable encodings passed directly to xarray.Dataset.to_netcdf(), allowing fine-grained customization.

  • outputresult (bool, optional) – If True, observation objects are also returned as a dictionary.

Returns:

If outputresult is True, returns a dictionary with one key per case. Each value is a pandas.DataFrame containing the paired data.

Return type:

dict or None

Examples

filepattern = {
    "station1": "/path/to/files/of/station1/*.nc",
    "station2": "/path/to/files/of/station2/*.nc",
}

pairings = {
    "case1": ("station1", "station2")
}

timeintervals = pd.interval_range(
    start=pd.Timestamp("2018-01-01"),
    periods=8,
    freq="D"
)

keepvars = ["S1", "S2", "Azimuth", "Elevation"]

outputdir = {
    "case1": "/path/where/to/save/data"
}

result = gather_stations(
    filepattern=filepattern,
    pairings=pairings,
    timeintervals=timeintervals,
    keepvars=keepvars,
    outputdir=outputdir,
    outputresult=True
)
gnssvod.hemibuild(angular_resolution, cutoff=0)[source]

Build an equi-angular hemispheric grid.

Constructs a hemispheric partition where grid cells have approximately equal angular area. The grid is organized into concentric elevation rings, each subdivided azimuthally.

The function returns a Hemi object containing grid geometry and helper utilities.

Parameters:
  • angular_resolution (float) – Angular diameter (degrees) of the zenith cell. This defines the target angular size of all grid cells and controls overall grid density.

  • cutoff (float, optional) –

    Minimum elevation angle (degrees) included in the grid.

    • 0 (default) builds a full hemisphere down to the horizon.

    • Higher values exclude low-elevation cells.

Returns:

Hemispheric grid object containing:

  • Cell center coordinates

  • Cell edge geometry

  • Elevation and azimuth bin definitions

  • Cell ID mappings

  • Helper methods (patches(), add_CellID())

Return type:

Hemi

Examples

# Build a hemispheric grid
hemi = hemibuild(angular_resolution=10)

# Access the patches for plotting
patches = hemi.patches()

# Assign grid cell IDs to observation dataframe
df_with_cells = hemi.add_CellID(df_obs, aziname='Azimuth', elename='Elevation')

References

Beckers, B., & Beckers, P. (2012). A general rule for disk and hemisphere partition into equal-area cells. Computational Geometry, 45(7), 275–283.

class gnssvod.Hemi(angular_resolution, grid, elelims, azilims, CellIDs)[source]

Hemispheric polar grid object.

This class stores the geometry of an equi-angular hemispheric grid and provides utilities to work with hemispheric binning of GNSS observations (e.g., assigning measurements to grid cells or generating plotting patches).

Objects of this class are typically created using hemibuild().

angular_resolution

Angular diameter of the zenith cell (degrees).

Type:

float

ncells

Total number of grid cells.

Type:

int

grid

DataFrame describing grid cell geometry. Contain the columns:

  • azi : cell center azimuth (degrees)

  • ele : cell center elevation (degrees)

  • azimin / azimax : azimuthal cell edges (degrees)

  • elemin / elemax : elevation cell edges (degrees)

Type:

pandas.DataFrame

coords

Subset of grid containing cell center coordinates (columns azi and ele).

Type:

pandas.DataFrame

elelims

Elevation band limits.

Type:

numpy.ndarray

azilims

Azimuthal bin edges for each elevation band.

Type:

list of numpy.ndarray

CellIDs

Cell IDs per elevation band.

Type:

list of numpy.ndarray

Examples

# Build a hemispheric grid
hemi = hemibuild(angular_resolution=10)

# Access the patches for plotting
patches = hemi.patches()

# Assign grid cell IDs to observation dataframe
df_with_cells = hemi.add_CellID(df_obs, aziname='Azimuth', elename='Elevation')
patches()[source]

Generate matplotlib patches for hemispheric grid cells.

Creates rectangular patches in polar projection space that can be used to visualize the hemispheric grid.

Returns:

Series of matplotlib.patches.Rectangle objects indexed by CellID.

Return type:

pandas.Series

Notes

Elevation is transformed to polar coordinates using:

r = 90° - elevation

This representation is suitable for hemispheric sky plots.

add_CellID(df: DataFrame, aziname: str = 'Azimuth', elename: str = 'Elevation', idname: str = 'CellID', drop: bool = True)[source]

Assign hemispheric grid cell IDs to observations.

Maps each observation to the corresponding hemispheric grid cell based on its azimuth and elevation coordinates.

Parameters:
  • df (pandas.DataFrame) – Input dataframe containing azimuth and elevation observations.

  • aziname (str, optional) – Name of the azimuth column in df (degrees). Default is 'Azimuth'.

  • elename (str, optional) – Name of the elevation column in df (degrees). Default is 'Elevation'.

  • idname (str, optional) – Name of the output column containing assigned CellIDs. Default is 'CellID'.

  • drop (bool, optional) –

    Controls handling of observations that cannot be assigned to a grid cell:

    • True (default): rows without CellID are dropped.

    • False: rows are preserved and assigned NaN.

Returns:

DataFrame with an added CellID column.

  • If drop=True → only rows with valid CellIDs.

  • If drop=False → all rows retained.

Return type:

pandas.DataFrame

Notes

  • Azimuth values are normalized to the range [0°, 360°].

  • Observations with missing azimuth or elevation are ignored.

  • Elevation binning is performed first, followed by azimuthal binning within each elevation band.

gnssvod.calc_vod(filepattern: str, pairings: dict[str, tuple[str, str]], bands: dict[str, list[str]]) dict[str, DataFrame][source]

Calculate Vegetation Optical Depth (VOD) from processed GNSS observations.

This function combines multiple NetCDF files containing paired GNSS receiver observations (typically generated with gnssvod.gather_stations()) and computes VOD for user-defined frequency bands.

Each band corresponds to a list of observation types (e.g., ‘S1’, ‘S1X’, ‘S1C’) across satellites. The function merges all available signals in the band to produce a single VOD estimate per band, increasing coverage.

Parameters:
  • filepattern (str) – UNIX-style pattern to locate preprocessed NetCDF files for a case. Example: ‘/path/to/files/of/case1/*.nc’

  • pairings (dict) – Dictionary mapping case names to tuples of station names, indicating the reference station and the ground station, respectively. Example: {‘Laeg1’: (‘Laeg2_Twr’, ‘Laeg1_Grnd’)}

  • bands (dict) –

    Dictionary mapping VOD band names to lists of observation types to merge. For instance, {‘VOD_L1’: [‘S1’, ‘S1X’, ‘S1C’]}.

    The function combines all matched observation types across satellites.

Returns:

Dictionary mapping case names to pandas.DataFrame objects. Each DataFrame contains the original measurements along with additional columns for each VOD band.

Return type:

dict

Example

files = "/path/to/gathered/data/*.nc"

pairings = {
    "case1": ("Ref1", "Grnd1"),
    "case2": ("Ref1", "Grnd2"),
}

bands = {
    "VOD_L1": ["S1", "S1X"],
    "VOD_L2": ["S2", "S2X"],
}

vod_results = calc_vod(files, pairings, bands)