Skip to content

Commit da9e861

Browse files
authored
Merge pull request #876 from andrewdnolan/moderate_refinement
Enable MALI Greenland mesh overlap with ocean mesh, through a buffer defined by a geojson file
2 parents 70a4a41 + f98e08e commit da9e861

6 files changed

Lines changed: 125 additions & 5 deletions

File tree

compass/landice/mesh.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,61 @@ def set_rectangular_geom_points_and_edges(xmin, xmax, ymin, ymax):
189189
return geom_points, geom_edges
190190

191191

192+
def clip_mesh_to_bounding_box(mask_ds, base_ds, bounding_box):
193+
"""
194+
Set cells to culled if they lay outside the bounding box
195+
196+
Parameters
197+
----------
198+
mask_ds: xr.Dataset
199+
mask dataset, generated by ``compute_mpas_region_mask``
200+
base_ds: xr.Dataset
201+
unculled mesh dataset
202+
bounding_box: list of 4 ints
203+
Bounding box [x_min, x_max, y_min, y_max] to cull mesh outside of
204+
205+
Returns
206+
-------
207+
mask_ds: xarray.Dataset
208+
mask dataset with updated masks based on bounding box
209+
"""
210+
211+
if len(bounding_box) != 4:
212+
msg = f"bounding box must be len 4, instead is len {len(bounding_box)}"
213+
raise ValueError(msg)
214+
215+
x_min, x_max, y_min, y_max = bounding_box
216+
217+
if (x_max < x_min) or (y_max < y_min):
218+
msg = "Bounding box must be ordered: [x_min, x_max, y_min, y_max]"
219+
msg += (
220+
f"\n x_max < x_min ({x_max:.2f} < {x_min:.2f})"
221+
if x_max < x_min else ""
222+
)
223+
msg += (
224+
f"\n y_max < y_min ({y_max:.2f} < {y_min:.2f})"
225+
if y_max < y_min else ""
226+
)
227+
raise ValueError(msg)
228+
229+
for loc in ["Cell", "Edge", "Vertex"]:
230+
mask_var = f"region{loc}Masks"
231+
232+
if mask_var not in mask_ds:
233+
continue
234+
235+
mask = (
236+
(base_ds[f"x{loc}"] < x_min) |
237+
(base_ds[f"x{loc}"] > x_max) |
238+
(base_ds[f"y{loc}"] < y_min) |
239+
(base_ds[f"y{loc}"] > y_max)
240+
)
241+
242+
mask_ds[mask_var] = xarray.where(mask, 0, mask_ds[mask_var])
243+
244+
return mask_ds
245+
246+
192247
def set_cell_width(self, section_name, thk, bed=None, vx=None, vy=None,
193248
dist_to_edge=None, dist_to_grounding_line=None,
194249
flood_fill_iStart=None, flood_fill_jStart=None):
@@ -618,7 +673,7 @@ def build_cell_width(self, section_name, gridded_dataset,
618673
def build_mali_mesh(self, cell_width, x1, y1, geom_points,
619674
geom_edges, mesh_name, section_name,
620675
gridded_dataset, projection, geojson_file=None,
621-
cores=1):
676+
cores=1, bounding_box=None):
622677
"""
623678
Create the MALI mesh based on final cell widths determined by
624679
:py:func:`compass.landice.mesh.build_cell_width()`, using Jigsaw and
@@ -672,8 +727,18 @@ def build_mali_mesh(self, cell_width, x1, y1, geom_points,
672727
673728
cores : int, optional
674729
The number of cores to use for mask creation
730+
731+
bounding_box : array_like of float, shape (4,), optional
732+
Bounding box [x_min, x_max, y_min, y_max] to cull mesh outside of
675733
"""
676734

735+
if bounding_box is not None and geojson_file is None:
736+
msg = (
737+
"Bounding box clipping can only be applied to an existing cull"
738+
"mask. You must provide a geojson file for this to work."
739+
)
740+
raise ValueError(msg)
741+
677742
logger = self.logger
678743
section = self.config[section_name]
679744

@@ -735,6 +800,10 @@ def build_mali_mesh(self, cell_width, x1, y1, geom_points,
735800
dsMesh = xarray.open_dataset('grid_preCull.nc')
736801
if geojson_file is not None:
737802
mask = xarray.open_dataset('mask.nc')
803+
804+
if bounding_box is not None:
805+
mask = clip_mesh_to_bounding_box(mask, dsMesh, bounding_box)
806+
738807
else:
739808
mask = None
740809

compass/landice/tests/greenland/greenland_only_outline_45km_buffer_latlon_singlepart.geojson

Lines changed: 8 additions & 0 deletions
Large diffs are not rendered by default.

compass/landice/tests/greenland/mesh.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,17 @@ def __init__(self, test_case):
5454
target='greenland_2km_2024_01_29.epsg3413.nc',
5555
database='')
5656

57-
# no setup() method is needed
57+
def setup(self):
58+
"""
59+
Add the configured geojson cull-mask file as an input.
60+
"""
61+
section = self.config['greenland']
62+
geojson_filename = section.get('geojson_filename')
63+
64+
self.add_input_file(filename=geojson_filename,
65+
package='compass.landice.tests.greenland',
66+
target=geojson_filename,
67+
database=None)
5868

5969
def run(self):
6070
"""
@@ -71,10 +81,13 @@ def run(self):
7181
data_path = section_gis.get('data_path')
7282
measures_filename = section_gis.get("measures_filename")
7383
bedmachine_filename = section_gis.get("bedmachine_filename")
84+
geojson_filename = section_gis.get('geojson_filename')
7485

7586
measures_dataset = os.path.join(data_path, measures_filename)
7687
bedmachine_dataset = os.path.join(data_path, bedmachine_filename)
7788

89+
bounding_box = self._get_bedmachine_bounding_box(bedmachine_dataset)
90+
7891
section_name = 'mesh'
7992

8093
source_gridded_dataset_1km = 'greenland_1km_2024_01_29.epsg3413.icesheetonly.nc' # noqa: E501
@@ -91,8 +104,10 @@ def run(self):
91104
build_mali_mesh(
92105
self, cell_width, x1, y1, geom_points, geom_edges,
93106
mesh_name=self.mesh_filename, section_name=section_name,
94-
gridded_dataset=source_gridded_dataset_1km,
95-
projection=src_proj, geojson_file=None)
107+
gridded_dataset=source_gridded_dataset_1km, projection=src_proj,
108+
geojson_file=geojson_filename,
109+
bounding_box=bounding_box,
110+
)
96111

97112
# Create scrip file for the newly generated mesh
98113
logger.info('creating scrip file for destination mesh')
@@ -161,3 +176,14 @@ def run(self):
161176
ds["observedThicknessTendencyUncertainty"] = dHdtErr
162177
# Write the data to disk
163178
ds.to_netcdf(self.mesh_filename, 'a')
179+
180+
def _get_bedmachine_bounding_box(self, bedmachine_filepath):
181+
182+
ds = xr.open_dataset(bedmachine_filepath)
183+
184+
x_min = ds.x1.min()
185+
x_max = ds.x1.max()
186+
y_min = ds.y1.min()
187+
y_max = ds.y1.max()
188+
189+
return [x_min, x_max, y_min, y_max]

compass/landice/tests/greenland/mesh_gen/mesh_gen.cfg

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ use_bed = True
5353
# (default value is for Perlmutter)
5454
data_path = /global/cfs/cdirs/fanssie/standard_datasets/GIS_datasets/
5555

56+
# geojson used to create the cull mask in mesh generation
57+
geojson_filename = greenland_only_outline_45km_buffer_latlon_singlepart.geojson
58+
5659
# filename of the BedMachine thickness and bedTopography dataset
5760
# (default value is for Perlmutter)
5861
bedmachine_filename = BedMachineGreenland-v6_edits_floodFill_extrap.nc

docs/developers_guide/landice/api.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ greenland
222222
run_model.RunModel.run
223223

224224
mesh.Mesh
225+
mesh.Mesh.setup
225226
mesh.Mesh.run
226227

227228
mesh_gen.MeshGen
@@ -491,6 +492,7 @@ Landice Framework
491492

492493
mesh.add_bedmachine_thk_to_ais_gridded_data
493494
mesh.clean_up_after_interp
495+
mesh.clip_mesh_to_bounding_box
494496
mesh.gridded_flood_fill
495497
mesh.interp_gridded2mali
496498
mesh.mpas_flood_fill

docs/developers_guide/landice/framework.rst

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ thickness to the final MALI mesh.
5353
:py:func:`compass.landice.mesh.clean_up_after_interp()` performs some final
5454
clean up steps after interpolation for the AIS mesh case.
5555

56+
:py:func:`compass.landice.mesh.clip_mesh_to_bounding_box()` updates an
57+
existing MPAS region mask so cells, edges, and vertices outside a bounding
58+
box ``[x_min, x_max, y_min, y_max]`` are culled. This is useful when a
59+
``geojson_file`` supplies the initial cull mask but the final culled mesh
60+
should also be limited to the valid extent of an input gridded dataset.
61+
The bounding box must be ordered as ``[x_min, x_max, y_min, y_max]``.
62+
5663
:py:func:`compass.landice.mesh.gridded_flood_fill()` applies a flood-fill algorithm
5764
to the gridded dataset in order to separate the ice sheet from peripheral ice.
5865

@@ -87,7 +94,12 @@ based on user-defined density functions and config options.
8794
cell widths determined by py:func:`compass.landice.mesh.build_cell_width()`, using Jigsaw
8895
and MPAS-Tools functions. Culls the mesh based on config options, interpolates
8996
all available fields from the gridded dataset to the MALI mesh using the bilinear
90-
method, and marks domain boundaries as Dirichlet cells.
97+
method, and marks domain boundaries as Dirichlet cells. When a ``geojson_file``
98+
is supplied, :py:func:`compass.landice.mesh.build_mali_mesh()` can also take an
99+
optional ``bounding_box`` argument to clip the cull mask to
100+
``[x_min, x_max, y_min, y_max]`` before culling. This additional clipping is
101+
applied to the mask, not to the rectangular geometry passed to
102+
:py:func:`compass.landice.mesh.set_rectangular_geom_points_and_edges()`.
91103

92104
:py:func:`compass.landice.mesh.make_region_masks()` creates region masks using regions
93105
defined in Geometric Features repository. It is only used by the ``antarctica``

0 commit comments

Comments
 (0)