Example 3 - stripy interpolation on the sphere#

SSRFPACK is a Fortran 77 software package that constructs a smooth interpolatory or approximating surface to data values associated with arbitrarily distributed points on the surface of a sphere. It employs automatically selected tension factors to preserve shape properties of the data and avoid overshoot and undershoot associated with steep gradients.

The next three examples demonstrate the interface to SSRFPACK provided through stripy

Notebook contents#

The next example is Ex4-Gradients


Define two different meshes#

Create a fine and a coarse mesh without common points

import stripy as stripy

cmesh = stripy.spherical_meshes.triangulated_cube_mesh(refinement_levels=2)
fmesh = stripy.spherical_meshes.icosahedral_mesh(refinement_levels=2, include_face_points=True)

print(cmesh.npoints)
print(fmesh.npoints)
98
482
help(cmesh.interpolate)
Help on method interpolate in module stripy.spherical:

interpolate(lons, lats, zdata, order=1, grad=None, sigma=None, threads=1) method of stripy.spherical_meshes.triangulated_cube_mesh instance
    Base class to handle nearest neighbour, linear, and cubic interpolation.
    Given a triangulation of a set of nodes on the unit sphere, along with data
    values at the nodes, this method interpolates (or extrapolates) the value
    at a given longitude and latitude.

    Args:
        lons : float / array of floats, shape (l,)
            longitudinal coordinate(s) on the sphere
        lats : float / array of floats, shape (l,)
            latitudinal coordinate(s) on the sphere
        zdata : array of floats, shape (n,)
            value at each point in the triangulation
            must be the same size of the mesh
        order : int (default=1)
            order of the interpolatory function used

            - `order=0` = nearest-neighbour
            - `order=1` = linear
            - `order=3` = cubic

        sigma : array of floats, shape (6n-12)
            precomputed array of spline tension factors from
            `get_spline_tension_factors(zdata, tol=1e-3, grad=None)`
            (only used in cubic interpolation)

        threads : int or 'auto', optional; default : 1
            Number of threads to use for linear and nearest-neighbour
            interpolation (N.B. multi-threaded cubic interpolation is
            not supported).
            By default, only a single thread will be used. Use
            `threads='auto'` to attempt to automatically determine how
            many threads to use based on the size of the input and output
            data.
            Negative values count backwards, such that -1 is equivalent to
            `multiprocessing.cpu_count()`, -2 to `cpu_count() - 1`, etc.

    Returns:
        zi : float / array of floats, shape (l,)
            interpolated value(s) at (lons, lats)
        err : int / array of ints, shape (l,)
            whether interpolation (0), extrapolation (1) or error (other)
%matplotlib inline

import cartopy
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import numpy as np



def mesh_fig(mesh, meshR, name):

    fig = plt.figure(figsize=(10, 10), facecolor="none")
    ax  = plt.subplot(111, projection=ccrs.Orthographic(central_longitude=0.0, central_latitude=0.0, globe=None))
    ax.coastlines(color="lightgrey")
    ax.set_global()

    generator = mesh
    refined   = meshR

    lons0 = np.degrees(generator.lons)
    lats0 = np.degrees(generator.lats)

    lonsR = np.degrees(refined.lons)
    latsR = np.degrees(refined.lats)

    lst = generator.lst
    lptr = generator.lptr


    ax.scatter(lons0, lats0, color="Red",
                marker="o", s=100.0, transform=ccrs.PlateCarree())

    ax.scatter(lonsR, latsR, color="DarkBlue",
                marker="o", s=30.0, transform=ccrs.PlateCarree())

    segs = refined.identify_segments()

    for s1, s2 in segs:
        ax.plot( [lonsR[s1], lonsR[s2]],
                 [latsR[s1], latsR[s2]], 
                 linewidth=0.5, color="black", transform=ccrs.Geodetic())

    # fig.savefig(name, dpi=250, transparent=True)
    
    return

mesh_fig(cmesh,  fmesh, "Two grids" )
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[3], line 1
----> 1 get_ipython().run_line_magic('matplotlib', 'inline')
      3 import cartopy
      4 import cartopy.crs as ccrs

File ~/.local/lib/python3.12/site-packages/IPython/core/interactiveshell.py:2482, in InteractiveShell.run_line_magic(self, magic_name, line, _stack_depth)
   2480     kwargs['local_ns'] = self.get_local_scope(stack_depth)
   2481 with self.builtin_trap:
-> 2482     result = fn(*args, **kwargs)
   2484 # The code below prevents the output from being displayed
   2485 # when using magics with decorator @output_can_be_silenced
   2486 # when the last Python token in the expression is a ';'.
   2487 if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):

File ~/.local/lib/python3.12/site-packages/IPython/core/magics/pylab.py:103, in PylabMagics.matplotlib(self, line)
     98     print(
     99         "Available matplotlib backends: %s"
    100         % _list_matplotlib_backends_and_gui_loops()
    101     )
    102 else:
--> 103     gui, backend = self.shell.enable_matplotlib(args.gui)
    104     self._show_matplotlib_backend(args.gui, backend)

File ~/.local/lib/python3.12/site-packages/IPython/core/interactiveshell.py:3667, in InteractiveShell.enable_matplotlib(self, gui)
   3664     import matplotlib_inline.backend_inline
   3666 from IPython.core import pylabtools as pt
-> 3667 gui, backend = pt.find_gui_and_backend(gui, self.pylab_gui_select)
   3669 if gui != None:
   3670     # If we have our first gui selection, store it
   3671     if self.pylab_gui_select is None:

File ~/.local/lib/python3.12/site-packages/IPython/core/pylabtools.py:338, in find_gui_and_backend(gui, gui_select)
    321 def find_gui_and_backend(gui=None, gui_select=None):
    322     """Given a gui string return the gui and mpl backend.
    323 
    324     Parameters
   (...)
    335     'WXAgg','Qt4Agg','module://matplotlib_inline.backend_inline','agg').
    336     """
--> 338     import matplotlib
    340     if _matplotlib_manages_backends():
    341         backend_registry = matplotlib.backends.registry.backend_registry

ModuleNotFoundError: No module named 'matplotlib'

Analytic function#

Define a relatively smooth function that we can interpolate from the coarse mesh to the fine mesh and analyse

def analytic(lons, lats, k1, k2):
     return np.cos(k1*lons) * np.sin(k2*lats)

coarse_afn = analytic(cmesh.lons, cmesh.lats, 5.0, 2.0)
fine_afn   = analytic(fmesh.lons, fmesh.lats, 5.0, 2.0)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[4], line 4
      1 def analytic(lons, lats, k1, k2):
      2      return np.cos(k1*lons) * np.sin(k2*lats)
----> 4 coarse_afn = analytic(cmesh.lons, cmesh.lats, 5.0, 2.0)
      5 fine_afn   = analytic(fmesh.lons, fmesh.lats, 5.0, 2.0)

Cell In[4], line 2, in analytic(lons, lats, k1, k2)
      1 def analytic(lons, lats, k1, k2):
----> 2      return np.cos(k1*lons) * np.sin(k2*lats)

NameError: name 'np' is not defined

The analytic function on the different samplings#

It is helpful to be able to view a mesh in 3D to verify that it is an appropriate choice. Here, for example, is the icosahedron with additional points in the centroid of the faces.

This produces triangles with a narrow area distribution. In three dimensions it is easy to see the origin of the size variations.

import k3d
plot = k3d.plot(camera_auto_fit=False, grid_visible=False, 
                menu_visibility=False, axes_helper=False )

findices = fmesh.simplices.astype(np.uint32)
cindices = cmesh.simplices.astype(np.uint32)
fpoints = np.column_stack(fmesh.points.T).astype(np.float32)
cpoints = np.column_stack(cmesh.points.T).astype(np.float32)

plot   += k3d.mesh(fpoints, findices, wireframe=False, color=0xBBBBBB,
                   flat_shading=True, opacity=1.0 )


plot   += k3d.points(fpoints, point_size=0.01,color=0xFF0000)




plot   += k3d.points(cpoints, point_size=0.02,color=0x00FF00)

plot.display()
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[5], line 1
----> 1 import k3d
      2 plot = k3d.plot(camera_auto_fit=False, grid_visible=False, 
      3                 menu_visibility=False, axes_helper=False )
      5 findices = fmesh.simplices.astype(np.uint32)

ModuleNotFoundError: No module named 'k3d'

Interpolation from coarse to fine#

The interpolate method of the sTriangulation takes arrays of longitude, latitude points (in radians) and an array of data on the mesh vertices. It returns an array of interpolated values and a status array that states whether each value represents an interpolation, extrapolation or neither (an error condition). The interpolation can be nearest-neighbour (order=0), linear (order=1) or cubic spline (order=3).

interp_c2f1, err = cmesh.interpolate(fmesh.lons, fmesh.lats, order=1, zdata=coarse_afn)
interp_c2f3, err = cmesh.interpolate(fmesh.lons, fmesh.lats, order=3, zdata=coarse_afn)

err_c2f1 = interp_c2f1-fine_afn
err_c2f3 = interp_c2f3-fine_afn
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[6], line 1
----> 1 interp_c2f1, err = cmesh.interpolate(fmesh.lons, fmesh.lats, order=1, zdata=coarse_afn)
      2 interp_c2f3, err = cmesh.interpolate(fmesh.lons, fmesh.lats, order=3, zdata=coarse_afn)
      4 err_c2f1 = interp_c2f1-fine_afn

NameError: name 'coarse_afn' is not defined
interp_c2f1.max()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[7], line 1
----> 1 interp_c2f1.max()

NameError: name 'interp_c2f1' is not defined
import k3d
plot = k3d.plot(camera_auto_fit=False, grid_visible=False, 
                menu_visibility=True, axes_helper=False )

findices = fmesh.simplices.astype(np.uint32)
cindices = cmesh.simplices.astype(np.uint32)
fpoints = np.column_stack(fmesh.points.T).astype(np.float32)
cpoints = np.column_stack(cmesh.points.T).astype(np.float32)


plot   += k3d.mesh(fpoints, findices, wireframe=False, attribute=interp_c2f1,
                   color_map=k3d.colormaps.basic_color_maps.CoolWarm, 
                   name="1st order interpolant",
                   flat_shading=False, opacity=1.0  )


plot   += k3d.mesh(fpoints, findices, wireframe=False, attribute=interp_c2f3,
                   color_map=k3d.colormaps.basic_color_maps.CoolWarm, 
                   name="3rd order interpolant",
                   flat_shading=False, opacity=1.0  )


plot   += k3d.mesh(fpoints, findices, wireframe=False, attribute=err_c2f1,
                   color_map=k3d.colormaps.basic_color_maps.CoolWarm, 
                   name="1st order error",
                   flat_shading=False, opacity=1.0  )


plot   += k3d.mesh(fpoints, findices, wireframe=False, attribute=err_c2f3,
                   color_map=k3d.colormaps.basic_color_maps.CoolWarm, 
                   name="3rd order error",
                   flat_shading=False, opacity=1.0  )



plot   += k3d.points(fpoints, point_size=0.01,color=0x779977)


plot.display()
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[8], line 1
----> 1 import k3d
      2 plot = k3d.plot(camera_auto_fit=False, grid_visible=False, 
      3                 menu_visibility=True, axes_helper=False )
      5 findices = fmesh.simplices.astype(np.uint32)

ModuleNotFoundError: No module named 'k3d'

Interpolate to grid#

Interpolating to a grid is useful for exporting maps of a region. The interpolate_to_grid method interpolates mesh data to a regular grid defined by the user. Values outside the convex hull are extrapolated.

interpolate_to_grid is a convenience function that yields identical results to interpolating over a meshed grid using the interpolate method.

resX = 200
resY = 100

extent_globe = np.radians([-180,180,-90,90])

grid_lon = np.linspace(extent_globe[0], extent_globe[1], resX)
grid_lat = np.linspace(extent_globe[2], extent_globe[3], resY)

grid_z1 = fmesh.interpolate_to_grid(grid_lon, grid_lat, interp_c2f3)

# compare with `interpolate` method
grid_loncoords, grid_latcoords = np.meshgrid(grid_lon, grid_lat)

grid_z2, ierr = fmesh.interpolate(grid_loncoords.ravel(), grid_latcoords.ravel(), interp_c2f3, order=3)
grid_z2 = grid_z2.reshape(resY,resX)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[9], line 4
      1 resX = 200
      2 resY = 100
----> 4 extent_globe = np.radians([-180,180,-90,90])
      6 grid_lon = np.linspace(extent_globe[0], extent_globe[1], resX)
      7 grid_lat = np.linspace(extent_globe[2], extent_globe[3], resY)

NameError: name 'np' is not defined
fig = plt.figure(figsize=(15, 10), facecolor="none")

ax1  = plt.subplot(121, projection=ccrs.Mercator())
ax1.coastlines()
ax1.set_global()
ax1.imshow(grid_z1, extent=np.degrees(extent_globe), cmap='RdBu', transform=ccrs.PlateCarree())

ax2  = plt.subplot(122, projection=ccrs.Mercator())
ax2.coastlines()
ax2.set_global()
ax2.imshow(grid_z2, extent=np.degrees(extent_globe), cmap='RdBu', transform=ccrs.PlateCarree())
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[10], line 1
----> 1 fig = plt.figure(figsize=(15, 10), facecolor="none")
      3 ax1  = plt.subplot(121, projection=ccrs.Mercator())
      4 ax1.coastlines()

NameError: name 'plt' is not defined

The next example is Ex4-Gradients