--- file_format: mystnb kernelspec: name: python3 --- # Limited-area HEALPix The HEALPix grid is designed to cover an entire sphere, be it Space or the Earth. This raises the question of whether HEALPix can also be used to store regional cutouts, as is often the case in weather forecasting or regional climate modelling. In this article we will try to outline some technical issues and suggest ways around them. ```{code-cell} ipython3 import cartopy.crs as ccrs import easygems.healpix as egh import healpix as hp import matplotlib.pyplot as plt import numpy as np import xarray as xr ``` Define two helper functions to create a cartopy map with regional extent and plot boxes on it. ```{code-cell} ipython3 def get_axis_europe(): """Create cartyop.GeoAxis with an extent centered around Europe.""" _, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) ax.coastlines() ax.set_extent([-40, 60, 20, 65]) return ax def plot_extent_box(ax, extent, edgecolor="m", linewidth=2): """Add a rectangular patch around an extent to a cartopy.GeoAxis.""" return ax.add_patch( plt.Rectangle( xy=[extent[0], extent[2]], width=extent[1] - extent[0], height=extent[3] - extent[2], facecolor="none", edgecolor=edgecolor, linewidth=linewidth, transform=ccrs.PlateCarree(), ) ) ``` ## Querying grid points In a first step, we will define a simple lat/lon box over Europe as our region of interest. ```{code-cell} ipython3 extent = [-20, 40, 30, 60] # Europe ax = get_axis_europe() plot_extent_box(ax, extent); ``` Next, we can use existing HEALPix libraries to compute lat/lon coordinates at a given order (zoom level). These coordinates can then be compared again our boundaries to query a regional cutout. ```{code-cell} ipython3 # Compute HEALPix lat/lon coordinates for a given order (zoom level) order = zoom = 8 nside = hp.order2nside(order) npix = hp.nside2npix(nside) hp_lon, hp_lat = hp.pix2ang(nside, np.arange(npix), nest=True, lonlat=True) hp_lon = (hp_lon + 180) % 360 - 180 # Find all grid points within the defined extent icell, = np.where( (hp_lon > extent[0]) & (hp_lon < extent[1]) & (hp_lat > extent[2]) & (hp_lat < extent[3]) ) # Plot the selected points ax = get_axis_europe() plot_extent_box(ax, extent) region = np.full_like(hp_lon, fill_value=np.nan) # Full HEALPix with NaNs region[icell] = 1 # Set selected values to 1 egh.healpix_show(region); ``` It worked! However, a technical problem with these slices is that they distort the grid index ordering[^1]. ## Maintaining the grid index ordering [^1]: You can have a look at [this article about ICON's grid](../../Processing/playing_with_triangles/grid_index_ordering) if you are not familiar with the topic. The nested HEALPix grid ordering ensures that geographical proximity of data is mapped to proximity in the array. However, this relationship is broken when we cut along simple lines on the map. Fortunately, we can maintain the proximity by keeping full chunks that follow the $4^n$ relation. We will use the `get_full_chunks()` convenience function provided by `easygems`. This function takes an array of indices and a chunk size and returns all indices of grid points in chunks that are touched. ```{code-cell} ipython3 # Get indices of **full chunks** ichunk = egh.get_full_chunks(icell, chunksize=4**5) # Plot the selected points ax = get_axis_europe() plot_extent_box(ax, extent) region = np.full_like(hp_lon, fill_value=np.nan) # Full HEALPix with NaNs region[ichunk] = 1 # Set selected values to 1 egh.healpix_show(region); ``` ## Setting the Coordinate References System As for full HEALPix grids, it is good practice to store map projection information in the [Coordinate Reference Systems (CRS)](https://cfconventions.org/Data/cf-conventions/cf-conventions-1.13/cf-conventions.html#grid-mappings-and-projections). The CF conventions presribe to store the grid parameters for the **global grid**. This way downstream libraries will be able to compute the full grid. ```{code-cell} ipython3 crs = xr.DataArray( name="crs", attrs={ "grid_mapping_name": "healpix", "refinement_level": 2**zoom, "indexing_scheme": "nested", }, ) crs ``` In addition, the dataset **must** contain the global indices of the original HEALPix grid. This allows the original location of any selected grid point to be diagnosed. Here is a complete example of a limited-area HEALPix dataset: ```{code-cell} ipython3 # Construct global HEALPix dataset with proper `cell` **coordinate** and CRS information. ds = xr.Dataset( coords={ "crs": crs, "lat": (("cell",), hp_lat), "lon": (("cell",), hp_lon), "cell": (("cell",), np.arange(npix), {"standard_name": "healpix_index"}), } ) # Select the limited-area while maintaining global index information (and chunks) ds.isel(cell=ichunk) ```