Region Manifold Extraction

Region Manifold Extraction#

Introduction#

Cubed-Sphere

Fig. 4: Cubed-Sphere#

In this tutorial we use unstructured cubed-sphere sample data from the pantry to explore mesh regional extraction using a geodesic manifold.

Let’s Go!#

The Met Office is migrating to a unstructured cubed-sphere quadrilateral-cell mesh, which is a gridded cube projected onto a sphere i.e., there are 6 panels on the sphere, see Fig. 4:.

Each cubed-sphere is defined by the number of cells squared within each panel. In this tutorial we’ll use a C48 cubed-sphere, so there are 48 * 48 cells per panel, and 6 * 48 * 48 cells in a mesh overall.

First, let’s import geovista and generate some LFRic model cubed-sphere sample data using the lfric() and lfric_sst() convenience functions:

1import geovista as gv
2from geovista.pantry.meshes import lfric, lfric_sst

As the lfric() and lfric_sst() functions both return a pyvista.PolyData we can simply use the pyvista.DataSet.plot method to render them:

1c48 = lfric(resolution="c48")
2c48.plot(show_edges=True)
Hide code cell output
../_images/d755b8171a7d4971fe17bf69e88ed1129ed1c68f11ae44dffa1704ee0857dee4.png

Now create the same C48 cubed-sphere resolution mesh, but this time with cells populated with Sea Surface Temperature (SST) data. Note that the land masses are masked, as this is an oceanographic dataset. Masked cells within the mesh will have nan values:

1c48_sst = lfric_sst()
2c48_sst.plot(show_edges=True)
Hide code cell output
../_images/1c9fb1440d5a064b6902d091dd323ee695007e66ce5a04c297aa5b64e1e8c4f3.png

Our challenge is to extract all cubed-sphere cells within the panel covering the Americas.

We can achieve this using the geovista.geodesic.panel() function.

The panel() function is a convenience which allows you to extract cells from a mesh within 1 of the 6 named cubed-sphere panels i.e., africa, americas, antarctic, arctic, asia, and pacific.

It returns a geovista.geodesic.BBox instance, which defines a geodesic bounding-box manifold for the requested spatial region.

So let’s go ahead and create the BBox for the americas cubed-sphere panel and use its mesh() method to visualise the manifold:

1from geovista.geodesic import panel
2
3americas_bbox = panel("americas")
4americas_bbox.mesh.plot(color="orange")
Hide code cell output
../_images/49685a0bbf25e0a0449446ca775f5af1380065325e3892ea5b8a122ecf304364.png

Note that, the americas_bbox is constructed from geodesic lines i.e., great circles, and is a 3-D manifold i.e., a water-tight geometric structure. As such, we can then use it to extract points/cells from any underlying mesh.

Before doing that, first let’s render the americas_bbox and the c48_sst mesh together so that we can see their relationship to one another and convince ourselves that the americas_bbox instance is indeed covering the correct panel of the cubed-sphere:

1plotter = gv.GeoPlotter()
2
3plotter.add_mesh(c48_sst, show_edges=True)
4plotter.add_mesh(americas_bbox.mesh, color="orange")
5
6plotter.view_yz()
7plotter.camera.zoom(1.2)
8plotter.add_axes()
9plotter.show()
Hide code cell output
../_images/db05b4d7ac7d7bdea9636923e9a2c479076e2b58aa5cad97a9ee5a25d91b6268.png

Seems to be right on the money! 👍

However, let’s apply some opacity to the c48_sst mesh so that we can see through the surface and view the americas_bbox from a different angle.

We’ll also use the view_poi() method to perform some cartographic camera controls on the rendered scene. Namey, move the camera to 30°E longitude.

1plotter = gv.GeoPlotter()
2plotter.add_mesh(c48_sst, opacity=0.3)
3plotter.add_mesh(americas_bbox.mesh, color="orange")
4
5plotter.view_poi(x=30)
6
7plotter.camera.zoom(1.2)
8plotter.add_axes()
9plotter.show()
Hide code cell output
../_images/738175cdf185fe28b825f7a77545ef4806273d239dcab632cb08e22a2c6a6489.png

Rather than viewing the entire bounding-box, sometimes it’s more helpful to see only the boundary where the manifold intersects the surface of the mesh that it’s enclosing.

We can achieve this by using the geovista.geodesic.BBox.boundary() method:

1plotter = gv.GeoPlotter()
2plotter.add_mesh(c48_sst, show_edges=True)
3
4plotter.add_mesh(americas_bbox.boundary(), color="orange", line_width=5)
5
6plotter.view_xz()
7plotter.camera.zoom(1.2)
8plotter.add_axes()
9plotter.show()
Hide code cell output
../_images/938a18521389eedf8833655fd51c9bdfcecdeb81abf26897b9a0fc57923c3f6b.png

Okay, so let’s finally use the americas_bbox to extract cells from the c48_sst mesh by using the geovista.geodesic.BBox.enclosed() method:

1region = americas_bbox.enclosed(c48_sst)

The extracted region returned by enclosed() is a pyvista.PolyData. Under the hood enclosed() uses the pyvista.DataSetFilters.select_enclosed_points() method to achieve this.

You may want to experiment with the preference kwarg of the enclosed() method to see the impact on the end result.

Anyways, let’s go ahead and see the extracted region, which should represent all the cells from the c48_sst mesh within the americas bounding-box:

1plotter = gv.GeoPlotter()
2
3plotter.add_mesh(region, show_edges=True)
4
5plotter.view_xz()
6plotter.add_axes()
7plotter.show()
Hide code cell output
../_images/03ec9a185acb4a41055cab869e98ba0b2cbde1a6d237c291113bba4575595c48.png

Now bring this all together by rendering the extracted region along with a Natural Earth texture mapped base layer and coastlines:

 1clim = (271, 303)
 2
 3plotter = gv.GeoPlotter()
 4plotter.add_mesh(region, show_edges=True, clim=clim)
 5
 6plotter.add_base_layer(texture=gv.natural_earth_hypsometric())
 7plotter.add_coastlines(resolution="10m")
 8
 9plotter.view_xz()
10plotter.camera.zoom(1.2)
11plotter.show_axes()
12plotter.show()
Hide code cell output
../_images/988b4c4404c11e0f3097bda9f322f23f8c6ebcb797542eb665a72452a6810f11.png

As we’re not particularly interested in the masked land masses, we can easily remove them using the pyvista.DataSetFilters.threshold() method available on the region PolyData instance.

By default, the threshold() method will remove cells with nan values from the active scalars array on the mesh, which is just what we need:

1sea_region = region.threshold()

Now let’s respin the render, but with the sea_region mesh and add some S.I. units to the scalar bar:

 1plotter = gv.GeoPlotter()
 2
 3sargs = {"title": f"{sea_region.active_scalars_name} / K"}
 4plotter.add_mesh(sea_region, show_edges=True, clim=clim, scalar_bar_args=sargs)
 5
 6plotter.add_base_layer(texture=gv.natural_earth_hypsometric())
 7plotter.add_coastlines(resolution="10m")
 8plotter.view_xz()
 9plotter.camera.zoom(1.2)
10plotter.show_axes()
11plotter.show()
Hide code cell output
../_images/0e28e39d9b5fc24fc452e8dba12ec43fd2c59ba90a04eadd20529549800d40f0.png

Extension#

geovista offers basic support for Cylindrical and Pseudo-Cylindrical cartographic projections. We’re working on maturing this capability, and later extending to other classes of projections, such as Azimuthal and Conic.

To exercise this, let’s transform our sea_region mesh to a Robinson projection using a PROJ string to define the Coordinate Reference System (CRS):

1crs = "+proj=robin lon_0=-90"

We pass the crs as a kwarg to the GeoPlotter and render the projected scene:

1plotter = gv.GeoPlotter(crs=crs)
2plotter.add_mesh(sea_region, show_edges=True, clim=clim)
3plotter.add_base_layer(texture=gv.natural_earth_hypsometric())
4plotter.add_coastlines(resolution="10m")
5plotter.view_xy()
6plotter.camera.zoom(1.5)
7plotter.show_axes()
8plotter.show()
Hide code cell output
../_images/cb597edfea4b9f04c7048bd187c7d18f2ad22375698aac065aa1e455de83ef58.png

Note that, the base layer and coastlines are also automatically transformed to the target projection.

geovista also has an understanding of cartopy CRS’s. So let’s use cartopy to create a Mollweide CRS:

1import cartopy.crs as ccrs
2
3crs = ccrs.Mollweide(central_longitude=-90)

Before we use this CRS, let’s invert the sea_region i.e., find all cells not enclosed by the americas_bbox manifold. We can easily do this using the outside kwarg of enclosed():

1outside = americas_bbox.enclosed(c48_sst, outside=True)
2sea_outside = outside.threshold()

Now let’s see the final projected result:

1plotter = gv.GeoPlotter(crs=crs)
2plotter.add_mesh(sea_outside, show_edges=True, clim=clim)
3plotter.add_base_layer(texture=gv.natural_earth_hypsometric())
4plotter.add_coastlines(resolution="10m")
5plotter.view_xy()
6plotter.camera.zoom(1.5)
7plotter.show_axes()
8plotter.show()
Hide code cell output
../_images/f6699449578f9698bd0c872872037866333d27a011ad363ec822a16471cf1508.png

Summary#

Key learning points of this tutorial: