Grid types

Met.3D represents atmospheric data as grids. All grid types share a common abstract interface, but differ in how they store and compute vertical coordinates. This page describes the class hierarchy, the shared MStructuredGrid API, and the specialisations for each vertical coordinate system.

Source code: src/data/abstractgrid.h, src/data/structuredgrid.h, src/radar/radargrid.h.

Class hierarchy

MAbstractGrid          (abstract interface; holds level type)
 ├── MStructuredGrid   (regular lon/lat horizontal grid; flat data array)
 │    ├── MRegularLonLatLnPGrid                 (log-pressure levels)
 │    ├── MRegularLonLatStructuredPressureGrid  (pressure levels)
 │    ├── MRegularLonLatGrid                    (2-D surface field, SINGLE_LEVEL)
 │    ├── MLonLatHybridSigmaPressureGrid        (model levels, hybrid σ-p)
 │    └── MLonLatAuxiliaryPressureGrid          (arbitrary 3-D pressure field)
 └── MRadarGrid        (radar volume scans; elevation / azimuth / range)

All grid objects also inherit MWeatherPredictionMetaData, which carries the NWP metadata attached to the field (variable name, init time, valid time, ensemble member, data source ID).

Grid objects have potentially a huge memory footprint and should therefore be owned and managed by the memory manager (see Memory management). Pipeline modules producing grids as data items are automatically memory managed. Try to avoid creating non-memory managed grids as much as possible.

Vertical level types

The MVerticalLevelType enum (src/data/abstractgrid.h) identifies the vertical coordinate system of a grid. It is the primary discriminator used throughout the pipeline and the actor system:

Enum value

Meaning

SINGLE_LEVEL

2-D surface field (no vertical dimension).

PRESSURE_LEVELS_3D

Constant pressure levels (e.g. 850, 500, 200 hPa).

LOG_PRESSURE_LEVELS_3D

Levels uniformly spaced in log(pressure) space.

HYBRID_SIGMA_PRESSURE_3D

Model levels in hybrid sigma-pressure coordinates (ECMWF IFS, ICON, COSMO).

AUXILIARY_PRESSURE_3D

Levels whose pressure is stored in a separate 3-D auxiliary field.

POTENTIAL_VORTICITY_2D

2-D PV surface (special case for isentropic/PV-level data). Deprecated.

MISC_LEVELS_3D

Level types that do not fit any other category.

RADAR_LEVELS_3D

Radar elevation angles; used by MRadarGrid.

EMPTY_LEVELS_3D

Placeholder for an empty/null grid.

The enum value is stored in every grid object and exposed via MAbstractGrid::getLevelType().

The MStructuredGrid API

MStructuredGrid is the base class for all non-radar grids. It provides a unified API so that code working with atmospheric fields does not need to know the concrete grid type.

Dimensions and coordinate arrays

unsigned int getNumLevels() const;   // k dimension (vertical)
unsigned int getNumLats()   const;   // j dimension
unsigned int getNumLons()   const;   // i dimension

const double* getLevels() const;     // vertical coordinate values
const double* getLats()   const;     // latitude values  [degrees]
const double* getLons()   const;     // longitude values [degrees]

What levels[k] means depends on the concrete subclass (pressure in hPa, log(pressure), hybrid level index, etc.).

Data access

Data is stored in a flat float array; level (k) is the slowest-varying dimension, longitude (i) the fastest:

float getValue(unsigned int k, unsigned int j, unsigned int i) const;
void  setValue(unsigned int k, unsigned int j, unsigned int i, float v);

// Convenience: direct linear index
float getValue(unsigned int n) const;
void  setValue(unsigned int n, float v);

// Raw pointer (e.g. for GPU upload)
const float* getData() const;

Pressure queries

The virtual pressure methods are the main abstraction point. Each subclass overrides them to implement its own coordinate formula:

// Pressure at grid point (k, j, i), in hPa.
virtual float getPressure(unsigned int k, unsigned int j, unsigned int i) const;

// Pressure at the cell interfaces (used for mass-weighted integrals, etc.)
virtual float getBottomInterfacePressure(unsigned int k, unsigned int j, unsigned int i);
virtual float getTopInterfacePressure   (unsigned int k, unsigned int j, unsigned int i);

// Pressure range of the whole data volume.
virtual float getTopDataVolumePressure_hPa   (bool useCachedValue = true) const;
virtual float getBottomDataVolumePressure_hPa(bool useCachedValue = true) const;

Interpolation

MStructuredGrid provides a family of interpolation methods. The key vertical piece that subclasses must implement is interpolateGridColumnToPressure(); the public interpolateValue() calls it internally after resolving the horizontal position:

// Full trilinear interpolation to (lon, lat, p_hPa).
float interpolateValue(float lon, float lat, float p_hPa);

// Bilinear interpolation on a single level k.
float interpolateValueOnLevel(float lon, float lat, unsigned int k) const;

// Sample all levels at (lon, lat) in one pass — more efficient than
// calling interpolateValueOnLevel() nlevs times.
bool interpolateValuesInVerticalColumn(float lon, float lat, float* out);

// Extract a vertical profile as (value, pressure) pairs.
QVector<QVector2D> extractVerticalProfile(float lon, float lat);

Methods subclasses must override

To create a new MStructuredGrid subclass, implement:

  • getPressure(k, j, i): the core pressure formula.

  • interpolateGridColumnToPressure(j, i, p_hPa): vertical interpolation for a single (j, i) column.

  • levelPressureAtLonLat_hPa(lon, lat, k): pressure on level k at a geographic position (needed for extractVerticalProfile).

  • findLevel(j, i, p_hPa): binary search or similar.

  • getTopDataVolumePressure_hPa() / getBottomDataVolumePressure_hPa().

Concrete MStructuredGrid subclasses

MRegularLonLatStructuredPressureGrid

Level type: PRESSURE_LEVELS_3D

Standard pressure level data (e.g. ECMWF ERA5 on pressure levels, NCEP reanalysis). levels[k] stores pressure directly in hPa, with index 0 typically at the top (lowest pressure).

Pressure formula: p(k, j, i) = levels[k]

Additionally provides getPressureTexCoordTexture1D(), a 1-D GPU texture that maps normalised log(pressure) to the texture coordinate of the data volume. This is used by the volume raycaster for efficient pressure-to-texture-space lookups.

MRegularLonLatLnPGrid

Level type: LOG_PRESSURE_LEVELS_3D

Like the pressure level grid, but levels are uniformly spaced in log(pressure) space. levels[k] stores ln(p) in hPa.

Pressure formula: p(k, j, i) = exp(levels[k])

Vertical interpolation is linear in ln(p), which matches the typical vertical structure of atmospheric variables and avoids compression artefacts near the surface.

MRegularLonLatGrid

Level type: SINGLE_LEVEL

2-D surface field. Internally nlevs = 1, but the class provides dedicated 2-D index helpers:

float getValue(unsigned int j, unsigned int i) const;
void  setValue(unsigned int j, unsigned int i, float v);

Typical uses: surface pressure, 2-metre temperature, precipitation, boundary layer height, geopotential at the surface.

MLonLatHybridSigmaPressureGrid

Level type: HYBRID_SIGMA_PRESSURE_3D

Model-level data from NWP models that use hybrid sigma-pressure vertical coordinates (e.g., ECMWF IFS). The pressure at a grid point depends on the surface pressure at that column:

\[p(k, j, i) = a_k + b_k \cdot p_\text{sfc}(j, i)\]

where \(a_k\) (in hPa) and \(b_k\) (dimensionless) are the hybrid coefficients for level k, and \(p_\text{sfc}\) is a companion MRegularLonLatGrid held in the surfacePressure member.

Cell interface pressures use a separate set of half-level coefficients \(a^i_k\) / \(b^i_k\), computed from the level-centre coefficients by computeInterfaceCoefficients().

Because pressure varies horizontally, vertical interpolation cannot use a simple 1-D lookup. interpolateGridColumnToPressure() performs a binary search within each column. For GPU rendering, getPressureTexCoordTexture2D() provides a 2-D texture (one row per lat/lon column) mapping normalised pressure to texture coordinates.

Key accessors:

float getPressure(unsigned int k, unsigned int j, unsigned int i) const;

MDataLease<MRegularLonLatGrid> getSurfacePressureGrid() const;
float getSurfacePressure(unsigned int j, unsigned int i) const;

double* getAkCoeffs() const;   // array of nlevs values, in hPa
double* getBkCoeffs() const;   // array of nlevs values, dimensionless

MLonLatAuxiliaryPressureGrid

Level type: AUXILIARY_PRESSURE_3D

The most flexible grid type. Pressure at every grid point is read from a separate 3-D pressure field of the same grid size:

p(k, j, i) = auxPressureField_hPa->getValue(k, j, i)

This is used for any level type that cannot be expressed with a simple formula, for example output that has already been interpolated to arbitrary 3-D coordinates, or data on isentropic (potential temperature) surfaces.

A special self-referential mode exists for the case where the grid itself is the pressure field (i.e. the variable being stored is pressure). In that case markAsSelfReferential() is called and getPressureGrid() returns this.

Key accessors:

MDataView<MLonLatAuxiliaryPressureGrid> getPressureGrid();
void exchangeAuxiliaryPressureGrid(MDataLease<MLonLatAuxiliaryPressureGrid> newField);
bool isSelfReferential() const;

Vertical interpolation requires a binary search within each (j, i) column of the auxiliary pressure field, since pressure values are arbitrary.

Radar grid

MRadarGrid (src/radar/radargrid.h) represents weather radar volume scans. It does not derive from MStructuredGrid because its geometry is fundamentally different: data is organised by elevation angle, azimuth ray, and range bin rather than by latitude, longitude, and vertical levels.

Structure of a radar volume

A volume scan consists of several elevation angles (typically 5-18). Each elevation is a MRadarElevation object containing:

  • elevationAngle: antenna tilt in degrees above horizontal.

  • nrays: number of azimuth rays in this scan.

  • nbins: number of range bins per ray.

  • binDistance, rangeStart: spacing and offset to the first bin, in metres.

  • rayAngles: per-ray azimuth angles (degrees).

  • data: flat QVector<float> of size nrays * nbins.

  • cartesianCoordinates: pre-computed 3-D positions for each data point.

The radar station position (lon, lat, height) is stored on the MRadarGrid object itself.

Data access

// Value at a specific elevation / ray / bin index.
float getValue(int elIdx, int rayIdx, int binIdx) const;

// Number of elevations, rays and bins in a given elevation.
int getNumberOfElevations() const;
int getNrays(unsigned int elIdx) const;
int getNbins (unsigned int elIdx) const;

Coordinate transformations

Because radar data lives in spherical coordinates centred on the antenna, the class provides methods to convert between radar and geographic space:

// Radar (elevation angle °, azimuth °, range m) → geographic (lon °, lat °, height m)
QVector3D radToLonLatMetreFromValues(float elev, float azimuth, float range) const;

// Radar → pressure coordinates (lon °, lat °, p hPa)
QVector3D radToLonLatPressure_hPa_FromValues(float elev, float azimuth, float range);

interpolateValue(lon, lat, p_hPa) performs the inverse transformation to find the radar bin closest to a given geographic position.

Horizontal section extraction

For 2-D cross-sections at a given height (in metres), calculateHorizontalRadarSection() extracts the relevant data points and their lon/lat coordinates, which can then be passed to the GPU as textures via getHSecLonLatTexture() / getHSecDataTexture().