Working with HEALPix Data#

In this notebook, we showcase how UXarray can be used with HEALPix data.

  • A brief overview into HEALPix

  • Representing a HEALPix grid in the UGRID conventions

  • Loading data that resides on HEALPix Grids

import cartopy.crs as ccrs

import uxarray as ux

What is HEALPix?#

The Hierarchical Equal Area isoLatitude Pixelisation (HEALPix) algorithm is a method for the pixelisation of the 2-sphere. It has three defining qualities.

  • The sphere is hierarchically tessellated into curvilinear quadrilaterals

  • Areas of all pixels at a given resolution are identical

  • Pixels are distributed on lines of constant latitude

Note

For more information on HEALPix, you can refer to this excellent overview in DKRZ’s Easy Gems documentation.

Representing a HEALPix Grid in the UGRID Conventions#

Most datasets loaded into UXarray come with a dedicated grid file. However, for HEALPix datasets, the underlying grid structure is inferred from the number of cells.

For a HEALPix grid in a nested layout, the number of cells is defined by:

\[ N_{\text{cells}} = 12 \cdot z^4, \]

where z is the zoom level. A higher zoom level increases the effective resolution of the grid.

The ux.Grid.from_healpix(zoom) classmethod can be used to construct a ux.Grid instance representing a HEALPix grid in the UGRID conventions.

ux.Grid.from_healpix(zoom=3)
<uxarray.Grid>
Original Grid Type: HEALPix
Grid Dimensions:
  * n_face: 768
Grid Coordinates (Spherical):
  * face_lon: (768,)
  * face_lat: (768,)
Grid Coordinates (Cartesian):
Grid Connectivity Variables:
Grid Descriptor Variables:

By default, our Grid will only contain the face coordinate values, which represent the HEALPix pixel locations. We can see this above with only the face_lon and face_lat variables populated.

However, much of UXarray functionality requires the boundaries of each cell to be defined. This is represented using the following variables:

  • node_lon: Longitude of the corners of each cell

  • node_lat: Latitude of the corners of each cell

  • face_node_connectivity: The indices of the nodes that surround each face

An optional parameter, pixels_only, is provided which can be set to True if you require the boundaries to be computed upfront.

ux.Grid.from_healpix(zoom=3, pixels_only=False)
<uxarray.Grid>
Original Grid Type: HEALPix
Grid Dimensions:
  * n_node: 770
  * n_face: 768
  * n_max_face_nodes: 4
Grid Coordinates (Spherical):
  * node_lon: (770,)
  * node_lat: (770,)
  * face_lon: (768,)
  * face_lat: (768,)
Grid Coordinates (Cartesian):
Grid Connectivity Variables:
  * face_node_connectivity: (768, 4)
Grid Descriptor Variables:

However, even if you load in a HEALPix grid specifying that you do not want the connectivity upfront, they can still be constructed when desired because of UXarray’s internal design.

ux.Grid.from_healpix(zoom=3, pixels_only=True).face_node_connectivity
<xarray.DataArray 'face_node_connectivity' (n_face: 768, n_max_face_nodes: 4)> Size: 25kB
array([[212, 760, 378, 697],
       [651, 586, 760, 212],
       [760, 455, 536, 378],
       ...,
       [580, 593, 699, 671],
       [699, 590, 138, 188],
       [593, 325, 590, 699]], shape=(768, 4))
Dimensions without coordinates: n_face, n_max_face_nodes
Attributes:
    cf_role:      face_node_connectivity
    long name:    Maps every face to its corner nodes.
    start_index:  0
    _FillValue:   -9223372036854775808

Grid-Agnostic Functionality#

Once loaded in, UXarray does not care about the underlying grid format, as it represents all formats in its unified UGRID-like convention. This means that all existing functionality is supported through the same UXarray inferface. Below are basic examples of plotting & remapping.

Plotting#

By using UXarray’s plotting functionality, we can observe how increasing the zoom level leads to a higher-resolution grid.

(
    ux.Grid.from_healpix(zoom=2).plot(
        periodic_elements="split", projection=ccrs.Orthographic(), title="zoom=2"
    )
    + ux.Grid.from_healpix(zoom=3).plot(
        periodic_elements="split", projection=ccrs.Orthographic(), title="zoom=3"
    )
    + ux.Grid.from_healpix(zoom=4).plot(
        periodic_elements="split", projection=ccrs.Orthographic(), title="zoom=4"
    )
).cols(1)

Remapping#

Below is a simple example of remapping UGRID data residing on a CSne30 grid to a HEALPix grid with a zoom level of 5.

source_uxds = ux.open_dataset(
    "../../test/meshfiles/ugrid/outCSne30/outCSne30.ug",
    "../../test/meshfiles/ugrid/outCSne30/outCSne30_vortex.nc",
)

# source data & grid
source_uxds["psi"].plot(
    cmap="inferno", projection=ccrs.Orthographic(), title="Source Data"
)

# destination grid
hp_grid = ux.Grid.from_healpix(zoom=5)

Before remapping, we can plot our Source and Destination grids.

source_uxds.uxgrid.plot(
    projection=ccrs.Orthographic(), title="Source Grid (CSne30)"
) + hp_grid.plot(projection=ccrs.Orthographic(), title="Destination Grid (HEALPix)")

We can now perform our remapping. In this case, we apply a simple nearest neighbor remapping.

psi_hp = source_uxds["psi"].remap.nearest_neighbor(destination_grid=hp_grid)
psi_hp
<xarray.UxDataArray 'psi' (n_face: 12288)> Size: 98kB
array([0.631794, 0.634404, 0.666049, ..., 1.424628, 1.437251, 1.437746],
      shape=(12288,))
Dimensions without coordinates: n_face

Our original data variable now resides on our HEALPix grid.

psi_hp.plot(cmap="inferno", projection=ccrs.Orthographic(), title="Remapped Data")

We can now chain together a few functions to convert our output from a UXarray Dataset to a NetCDF file, with the grid represented in the HEALPix format.

  • to_dataset(): Converts the ux.UxDataArray to a ux.UxDataset

  • to_xarray(grid_format="HEALPix"): Converts our ux.UxDataset to a xr.Dataset encoded in the HEALPix format.

  • to_netcdf("psi_healpix.nc"): Saves our dataset to disk as a NetCDF file.

psi_hp.to_dataset().to_xarray(grid_format="HEALPix").to_netcdf("psi_healpix.nc")

Loading HEALPix Data#

In the remapping example above, we successfully remapped a data variable onto a HEALPix grid and saved it as a NetCDF file.

UXarray extends Xarray’s Dataset and DataArray classes to provide grid-aware implementations.

Using the example we generated above, we can load in data that is stored in the HEALPix format using the UxDataset.from_healpix() classmethod.

uxds = ux.UxDataset.from_healpix("psi_healpix.nc")
uxds
<xarray.UxDataset> Size: 98kB
Dimensions:  (n_face: 12288)
Dimensions without coordinates: n_face
Data variables:
    psi      (n_face) float64 98kB ...

The interface above looks almost identical to what you would see if you loaded in the file directly with Xarray.

import xarray as xr

xr.open_dataset("psi_healpix.nc")
<xarray.Dataset> Size: 98kB
Dimensions:  (cell: 12288)
Dimensions without coordinates: cell
Data variables:
    psi      (cell) float64 98kB ...

However, there are a few noticeable differences and additions that streamline UXarray’s implementation and provide more context on what grid the data resides on:

  1. The “Show Grid Information” tab provides information on the underlying Grid object that is attached via the .uxgrid attribute

  2. The zoom for HEALPix grids is automatically determined from the cells dimension, with an appropriate Grid constructed from that 3The dimensions are renamed to match the UGRID conventions, with cells being renamed to n_face for the case of HEALPix grids

We can then directly access our Grid using the .uxgrid attribute, allowing for grid-specific functionallity as described in the sections above.

uxds.uxgrid
<uxarray.Grid>
Original Grid Type: HEALPix
Grid Dimensions:
  * n_face: 12288
Grid Coordinates (Spherical):
  * face_lon: (12288,)
  * face_lat: (12288,)
Grid Coordinates (Cartesian):
Grid Connectivity Variables:
Grid Descriptor Variables:

Future Work#

Support for the HEALPix format is still in its early stages. We welcome your suggestions and contributions! To get started, please check out our Contributors Guide.