Meshing ETOPO1¶
In this notebook we:
Find the land surface in a region by filtering ETOPO1
Create a DM object for all the data points
Save the mesh to HDF5 file
import quagmire
from quagmire.tools import meshtools
from osgeo import gdal
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
%matplotlib inline
from scipy.ndimage.filters import gaussian_filter
## Define region of interest (here NZ)
japan_bounds = (129.0, 30.0, 148.0, 47.0)
australia_bounds = (110, -45, 160, -10)
tasmania_bounds = (144, -44, 149, -40)
new_zealand_bounds = (166, -47.5, 179, -34)
## Read ETOPO1 data from online service
import xarray
(left, bottom, right, top) = new_zealand_bounds
map_extent = ( left, right, bottom, top)
etopo_dataset = "http://thredds.socib.es/thredds/dodsC/ancillary_data/bathymetry/ETOPO1_Bed_g_gmt4.nc"
etopo_data = xarray.open_dataset(etopo_dataset)
regional_data = etopo_data.sel(x=slice(left,right), y=slice(bottom, top))
lons = regional_data.coords.get('x')
lats = regional_data.coords.get('y')
vals = regional_data['z']
x,y = np.meshgrid(lons.data, lats.data)
height = vals.data
## Here is how to clip the original tif and save a new file
# opts = gdal.WarpOptions(outputBounds=japan_bounds)
# gdalobj = gdal.Warp(destNameOrDestDS="JapanEtopo1.tif", srcDSOrSrcDSTab="../../../ETOPO1/ETOPO1_Ice_c_geotiff.tif", options=opts )
# print("Japan - ", gdalobj.RasterXSize, gdalobj.RasterYSize)
# gdalobj = None # this closes the file and saves the data
# opts = gdal.WarpOptions(outputBounds=australia_bounds)
# gdalobj = gdal.Warp(destNameOrDestDS="AustraliaEtopo1.tif", srcDSOrSrcDSTab="../../../ETOPO1/ETOPO1_Ice_c_geotiff.tif", options=opts )
# print("Australia - ", gdalobj.RasterXSize, gdalobj.RasterYSize)
# gdalobj = None # this closes the file and saves the data
# opts = gdal.WarpOptions(outputBounds=tasmania_bounds)
# gdalobj = gdal.Warp(destNameOrDestDS="TasmaniaEtopo1.tif", srcDSOrSrcDSTab="../../../ETOPO1/ETOPO1_Ice_c_geotiff.tif", options=opts )
# print("Tasmania - ", gdalobj.RasterXSize, gdalobj.RasterYSize)
# gdalobj = None # this closes the file and saves the data
# opts = gdal.WarpOptions(outputBounds=new_zealand_bounds)
# gdalobj = gdal.Warp(destNameOrDestDS="NZEtopo1.tif", srcDSOrSrcDSTab="../../../ETOPO1/ETOPO1_Ice_c_geotiff.tif", options=opts )
# print("NZ - ", gdalobj.RasterXSize, gdalobj.RasterYSize)
# gdalobj = None # this closes the file and saves the data
# file = "NZEtopo1.tif"
# ds = gdal.Open(file)
# band = ds.GetRasterBand(1)
# height = band.ReadAsArray()
# [cols, rows] = height.shape
# left, hres, n0, top, n1, vres = ds.GetGeoTransform()
# right = left+rows*hres
# bottom = top+cols*vres
# x,y = np.meshgrid(np.arange(left, right, hres), np.arange(top, bottom, vres))
# map_extent = ( left, right, bottom, top)
## Plot the heights as a contour map
import cartopy.crs as ccrs
import cartopy.feature as cfeature
coastline = cfeature.NaturalEarthFeature('physical', 'coastline', '10m',
edgecolor=(1.0,0.8,0.0),
facecolor="none")
ocean = cfeature.NaturalEarthFeature('physical', 'ocean', '10m',
edgecolor="green",
facecolor="blue")
lakes = cfeature.NaturalEarthFeature('physical', 'lakes', '10m',
edgecolor="green",
facecolor="#4488FF")
rivers = cfeature.NaturalEarthFeature('physical', 'rivers_lake_centerlines', '10m',
edgecolor="#4488FF",
facecolor="blue")
plt.figure(figsize=(15, 10))
ax = plt.subplot(111, projection=ccrs.PlateCarree())
ax.set_extent(map_extent)
ax.add_feature(coastline, edgecolor="black", linewidth=0.5, zorder=3)
ax.add_feature(lakes, edgecolor="black", linewidth=1, zorder=3)
ax.add_feature(rivers , facecolor="none", linewidth=1, zorder=3)
plt.imshow(height, extent=map_extent, transform=ccrs.PlateCarree(),
cmap='terrain', origin='lower', vmin=-400., vmax=2000.)
## Filter out the points we don't want at all
points = height > -100
m1s = height[points]
x1s = x[points]
y1s = y[points]
submarine = m1s < 0.0
subaerial = m1s >= 0.0
fig = plt.figure(figsize=(15, 10))
ax = plt.subplot(111, projection=ccrs.PlateCarree())
ax.set_extent(map_extent)
plt.imshow(height, extent=map_extent, transform=ccrs.PlateCarree(),
cmap='terrain', origin='lower', vmin=-400., vmax=2000.)
ax.scatter(x1s[submarine], y1s[submarine], s=0.1, color="Blue", transform=ccrs.Geodetic())
#ax.scatter(x1s[subaerial], y1s[subaerial], s=0.1, color="Red", transform=ccrs.Geodetic())
fig.show()
## triangulate
import stripy
mesh0 = stripy.cartesian.Triangulation(x1s, y1s, permute=True, tree=True)
d, n = mesh0.nearest_vertices(mesh0.points[submarine][:,0], mesh0.points[submarine][:,1], k=25)
candidates = np.where(np.any(subaerial[n],axis=1))
keepers = n[candidates,0]
boundary = np.zeros_like(subaerial)
boundary[keepers] = True
fig = plt.figure(figsize=(15, 10))
ax = plt.subplot(111, projection=ccrs.PlateCarree())
ax.set_extent(map_extent)
ax.scatter(x1s[submarine], y1s[submarine], s=0.01, color="Blue", transform=ccrs.Geodetic() )
ax.scatter(x1s[subaerial], y1s[subaerial], s=0.01, color="Red", transform=ccrs.Geodetic() )
ax.scatter(x1s[boundary], y1s[boundary], s=0.25, color="Green",transform=ccrs.Geodetic() )
fig.show()
## Now re-define the various arrays on this subset of the data
points2 = np.zeros_like(points)
points2[points] = subaerial | boundary
m2s = height[points2]
x2s = x[points2]
y2s = y[points2]
bmask2 = subaerial[subaerial | boundary]
fig = plt.figure(figsize=(15, 10))
ax = plt.subplot(111, projection=ccrs.PlateCarree())
ax.set_extent(map_extent)
ax.scatter(x2s[~bmask2], y2s[~bmask2], s=0.05, color="blue", transform=ccrs.Geodetic())
ax.scatter(x2s[ bmask2], y2s[ bmask2], s=0.01, color="Red", transform=ccrs.Geodetic())
fig.show()
3. Create the DM¶
The points are now read into a DM
DM = meshtools.create_DMPlex_from_points(x2s, y2s, bmask2, refinement_levels=0)
mesh = quagmire.QuagMesh(DM, verbose=True, permute=True, downhill_neighbours=2)
x2r = mesh.tri.x
y2r = mesh.tri.y
simplices = mesh.tri.simplices
bmaskr = mesh.bmask
with mesh.deform_topography():
mesh.topography.data = m2s
low_points1 = mesh.identify_low_points()
low_point_coords1 = mesh.coords[low_points1]
print(low_points1.shape)
cumulative_flow_1 = mesh.upstream_integral_fn(mesh.topography**2).evaluate(mesh)
topography_1 = mesh.topography.data[:]
logflow = np.log10(1.0e-3+cumulative_flow_1)
logflow.min(), logflow.max()
logflow = np.log10(1.0e-3+cumulative_flow_1)
flows_img1 = logflow.min() * np.ones_like(points2)
flows_img1[points2] = logflow
plt.figure(figsize=(15, 10))
ax = plt.subplot(111, projection=ccrs.PlateCarree())
ax.set_extent(map_extent)
ax.add_feature(coastline, edgecolor="black", linewidth=1, zorder=3)
ax.add_feature(lakes, edgecolor="black", linewidth=1, zorder=3)
ax.add_feature(rivers , edgecolor="black", facecolor="none", linewidth=1, zorder=3)
# ax.scatter(x2s[~bmask2], y2s[~bmask2], color="#660000", s=.1)
plt.imshow(flows_img1, extent=map_extent, transform=ccrs.PlateCarree(),
cmap='Blues', origin='lower', vmin=0.0, vmax=4.0)
plt.savefig("Flowpath-Wex1-1.png", dpi=250)
Low-point -filling algorithm¶
Most effective seems to be a little local patching followed by some iterations of the swamp fill. Repeat as necessary and check periodically to see what is actually happening.
for repeat in range(0,2):
mesh.low_points_local_patch_fill(its=3, smoothing_steps=2)
topography_2 = mesh.topography.data[:]
cumulative_flow_2 = mesh.upstream_integral_fn(mesh.topography**2).evaluate(mesh)
low_points2 = mesh.identify_low_points()
low_point_coords2 = mesh.coords[low_points2]
print("Low points - {}".format(low_points2.shape))
for i in range(0,10):
mesh.low_points_swamp_fill(ref_height=0.0, saddles=True)
# In parallel, we can't break if ANY processor has work to do (barrier / sync issue)
low_points3 = mesh.identify_global_low_points()
print("{} : {}".format(i,low_points3[0]))
if low_points3[0] == 0:
break
cumulative_flow_3 = mesh.upstream_integral_fn(mesh.topography**2).evaluate(mesh)
topography_3 = mesh.topography.data[:]
low_points3 = mesh.identify_low_points()
print("Low points - {}".format(low_points3.shape))
hdiff = height.copy()
hdiff[points2] = mesh.topography.data - height[points2]
hdiff[~points2] = 0.0
outflows = mesh.identify_outflow_points()
logflow = np.log10(1.0e-3+cumulative_flow_3)
flows_img3 = logflow.min() * np.ones_like(points2)
flows_img3[points2] = logflow
plt.figure(figsize=(15, 10))
ax = plt.subplot(111, projection=ccrs.PlateCarree())
ax.set_extent(map_extent)
ax.add_feature(coastline, edgecolor="black", linewidth=1, zorder=3)
ax.add_feature(lakes, edgecolor="black", facecolor="none", linewidth=1, zorder=3)
ax.add_feature(rivers , edgecolor="Yellow", facecolor="none", linewidth=1, zorder=3)
plt.imshow(flows_img3, extent=map_extent, transform=ccrs.PlateCarree(),
cmap='Blues', origin='lower', vmin=0.0, vmax=4.0)
ax.scatter(x2s[outflows], y2s[outflows], color="Green", s=1.0)
ax.scatter(x2s[low_points3], y2s[low_points3], color="Red", s=5.0)
plt.imshow(hdiff, extent=map_extent, transform=ccrs.PlateCarree(),
cmap='Greens', origin='lower', vmin=0.0, vmax=200, alpha=0.25)
plt.savefig("WEx1-Flowpath-3.png", dpi=250)
# ## We can also dump the modified topography to a geotiff matching the original
# ## NOTE the vertical resolution of the ETOPO geotiff is 1m (int16) - not enough to
# ## record the changes necessary for the hydrological flow connections.
# hnew = np.zeros_like(height, dtype=float)
# hnew[points2] = mesh.topography.data
# hnew[~points2] = height[~points2]
# arr_out = hnew
# ds = gdal.Open(file)
# driver = gdal.GetDriverByName("GTiff")
# outdata = driver.Create("WEx1-ETOPO1-quagmire.tif", rows, cols, 1, gdal.GDT_Float32)
# outdata.SetGeoTransform(ds.GetGeoTransform()) ##sets same geotransform as input
# outdata.SetProjection(ds.GetProjection()) ##sets same projection as input
# outdata.GetRasterBand(1).WriteArray(arr_out)
# outdata.FlushCache() ##saves to disk!!
# outdata = None
# band=None
# ds=None
## verify
ds = gdal.Open("WEx1-ETOPO1-quagmire.tif")
band = ds.GetRasterBand(1)
height = band.ReadAsArray()
[cols, rows] = height.shape
left, hres, n0, top, n1, vres = ds.GetGeoTransform()
right = left+rows*hres
bottom = top+cols*vres
x,y = np.meshgrid(np.arange(left, right, hres), np.arange(top, bottom, vres))
map_extent = ( left, right, bottom, top)
point_mask = height > -10.0
xs = x[point_mask]
ys = y[point_mask]
heights = height[point_mask]
points = np.column_stack([xs, ys])
submarine = (heights < 0.0 )
subaerial = (heights >= 0.0 )
DM1 = meshtools.create_DMPlex_from_points(xs, ys, bmask=subaerial, refinement_levels=0)
mesh1 = quagmire.QuagMesh(DM1, verbose=True, permute=False, downhill_neighbours=2)
with mesh1.deform_topography():
mesh1.topography.data = heights
mesh1.identify_low_points().shape
99 = 1
## The hdf5 functionality may not work for conda installations due to limitations in the available petsc builds.
5. Save to HDF5¶
Save the mesh to an HDF5 file so that it can be visualised in Paraview or read into Quagmire another time. There are two ways to do this:
Using the
save_DM_to_hdf5
function in meshtools, orDirectly from trimesh interface using
save_mesh_to_hdf5
method.
Remember to execute petsc_gen_xdmf.py austopo.h5
to create the XML file structure necessary to visualise the mesh in paraview.
filename = 'NZTopo.h5'
mesh.save_mesh_to_hdf5(filename)
mesh.save_field_to_hdf5(filename, height=mesh.height,
height0=raw_heights,
slope=mesh.slope,
flow1=np.sqrt(flowpaths1),
flow2=np.sqrt(flowpaths2),
lakes = lakes)
# to view in Paraview
meshtools.generate_xdmf(filename)