Analyzing Meshes

Very often there will be specific regions of a set of results that are the most of interest, and in those cases it can be helpful to pull out a particular region's data for visualization in a clearer way. The LineCrossSection and SurfaceCrossSection classes allow taking cross-sections of a mesh (or some other rasterizable object) along poly-lines drawn using the PolyDraw tool, letting you make an x/z or x/z/t plot from data laid out in x/y/z or x/y/z/t (respectively). These classes rasterize the data at a fixed resolution, and then sample the data at that resolution given the underlying polylines. The resolution may be defined in the units of the underlying coordinate system.

In [1]:
import holoviews as hv
import geoviews as gv
import cartopy.crs as ccrs
from colorcet import cm_n

from holoviews import opts

from earthsim.analysis import LineCrossSection, SurfaceCrossSection
from earthsim.io import read_3dm_mesh, read_mesh2d

hv.extension('bokeh')

opts.defaults(
    opts.Curve(height=400, width=400, framewise=True),
    opts.Image(width=500, height=400, colorbar=True, framewise=True),
    opts.NdOverlay(legend_limit=0),
    opts.Path(line_width=3, color='black'),
    opts.RGB(width=500, height=400),
    opts.VLine(color='black')
)
hv.output(holomap='scrubber')

Example 1: A static TriMesh

As a first simple example, we will pass a static TriMesh to the LineCrossSection and display it. We'll pass a couple of sample polylines to start with, and if a live Python process is available we will be able to see the depth along the paths appear on the right, for those paths and any more that are subsequently drawn by the user.

In [2]:
filename = '../data/Chesapeake_and_Delaware_Bays.3dm'

cb_paths = [[(-8523594.,  4588993.), (-8476533.,  4578872.),
             (-8449931.,  4562126.), (-8435409.,  4539747.)],
            [(-8477265.,  4544109.), (-8469725.,  4430387.)]]
            
tris, verts = read_3dm_mesh(filename)
points = gv.operation.project_points(gv.Points(verts, vdims=['z']))
trimesh = hv.TriMesh((tris, points))

sector1 = LineCrossSection(trimesh, cb_paths)
sector1.view()
Out[2]:

Each individual polyline (thick black connected line segments) on the left should result in one curve on the right, with a colored dot on that path corresponding to the color of the curve. The location of the dot will change as you hover in the plot on the right, allowing you to see which value in the curve corresponds to which location along the polyline.

Example 2: A time-varying mesh

LineCrossSection also allows working with time-varying meshes. Here we will modify the static TriMesh data with a time-varying random offset to demonstrate that the LineCrossSection also works for data that is evolving temporally.

In [3]:
sd_paths = [
    [(-13037161.,   3843454.), (-13041435.,   3854275.), (-13045822.,   3857996.),
     (-13048664.,   3857210.), (-13050429.,   3855575.), (-13048385.,   3849452.),
     (-13048027.,   3847896.)],
    [(-13042975.,   3850040.), (-13063919.,   3844636.)]
]

filename = '../data/SanDiego_Mesh/SanDiego.3dm'
tris, verts = read_3dm_mesh(filename, skiprows=2)
points = gv.operation.project_points(gv.Points(verts, vdims=['z'], crs=ccrs.UTM(11)))
trimesh = gv.TriMesh((tris, points))

filename2 = '../data/SanDiego_Mesh/SanDiego_ovl.dat'
dfs = read_mesh2d(filename2)

points = gv.operation.project_points(gv.Points((verts.x, verts.y), crs=ccrs.UTM(11)))

def time_mesh(time):
    depth_points = points.add_dimension('Velocity', 0, dfs[time].values[:, 0], vdim=True)
    return gv.TriMesh((tris, depth_points), crs=ccrs.GOOGLE_MERCATOR)

time_dim = hv.Dimension('Time', values=sorted(dfs.keys()), default=3600)
meshes = hv.DynamicMap(time_mesh, kdims=time_dim)

sector2 = LineCrossSection(meshes, sd_paths, resolution=100)
sector2.view(cmap=cm_n.rainbow_r, shade=True)
Out[3]:


Once Loop Reflect

You can use the scrubber controls to animate both plots, showing both the map-based data and the curve cross sections over time.

Example 3: Sampling a Surface

Instead of sampling individual Curve elements for each time as in example 2, we can take cross-sections across time, returning an Image plotting the sampled value across both distance and time. The SurfaceCrossSection class is a simple subclass of LineCrossSection that overrides the sample method to apply the sampling for all time values and return an Image.

In [4]:
sector3 = SurfaceCrossSection(meshes, sd_paths[:1], resolution=100)
sector3.view(cmap=cm_n.rainbow_r, shade=False)
WARNING:param.Image09404: Image dimension Time is not evenly sampled to relative tolerance of 0.001. Please use the QuadMesh element for irregularly sampled data or set a higher tolerance on hv.config.image_rtol or the rtol parameter in the Image constructor.
Out[4]:


Once Loop Reflect

The scrubber will now only animate the plot on the left, as time has been laid out vertically on the plot on the right.

If you have a running Python process and want to draw your own line, first delete the existing one, because an overlay of non-transparent images will only show the one on top.


Right click to download this notebook from GitHub.