Skip to content

Commit 5173d93

Browse files
authored
add optional custom grid for merging gridded data (OGGM#1779)
* add optional custom grid for merging gridded data * fix bug when reprojecting relatively small grid to a larger one * fix bug when merging 3D data * added whats-new
1 parent 628fbdf commit 5173d93

File tree

5 files changed

+79
-10
lines changed

5 files changed

+79
-10
lines changed

docs/whats-new.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ Enhancements
4747
- New standard glacier directories now use a new reference lookup
4848
table to decide on the topo data to use (:pull:`1781`).
4949
By `Fabien Maussion <https://github.com/fmaussion>`_
50+
- Added the possibility to provide a custom grid in
51+
``workflow.merge_gridded_data``. If no grid is provided, the default is to
52+
merge all grids of the provided gdirs (:pull:`1779`).
53+
By `Patrick Schmitt <https://github.com/pat-schmitt>`_
5054

5155
Bug fixes
5256
~~~~~~~~~

oggm/core/gis.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1992,6 +1992,8 @@ def reproject_gridded_data_variable_to_grid(gdir,
19921992
total_before = (np.nansum(data.values, axis=sum_axis) *
19931993
ds.salem.grid.dx ** 2)
19941994

1995+
total_before_is_zero = np.isclose(total_before, 0, atol=1e-6)
1996+
19951997
if smooth_radius != 0:
19961998
if smooth_radius is None:
19971999
smooth_radius = np.rint(cfg.PARAMS['smooth_window'] /
@@ -2008,12 +2010,40 @@ def reproject_gridded_data_variable_to_grid(gdir,
20082010

20092011
total_after = (np.nansum(r_data, axis=sum_axis) *
20102012
target_grid.dx ** 2)
2013+
total_after_is_zero = np.isclose(total_after, 0, atol=1e-6)
2014+
2015+
# if a relatively small grid is reprojected into a larger grid, it
2016+
# could happen that no data is assigned at all
2017+
no_data_after_but_before = np.logical_and(total_after_is_zero,
2018+
~total_before_is_zero)
2019+
if np.any(no_data_after_but_before):
2020+
# we just assign the maximum value to one grid point and use the
2021+
# factor for conserving the total value
2022+
def _assign_max_value(data_provided, data_target):
2023+
j_max, i_max = np.unravel_index(
2024+
np.nanargmax(data_provided.values), data_provided.shape)
2025+
oi_max, oj_max = target_grid.center_grid.transform(
2026+
i_max, j_max, crs=gdir.grid.center_grid, nearest=True)
2027+
data_target[oj_max, oi_max] = data_provided[j_max, i_max]
2028+
return data_target
2029+
2030+
if r_data.ndim == 3:
2031+
for i in range(r_data.shape[0]):
2032+
if no_data_after_but_before[i]:
2033+
r_data[i, :, :] = _assign_max_value(data[i, :, :],
2034+
r_data[i, :, :])
2035+
else:
2036+
r_data = _assign_max_value(data, r_data)
2037+
2038+
# and recalculate the total after again
2039+
total_after = (np.nansum(r_data, axis=sum_axis) *
2040+
target_grid.dx ** 2)
20112041

20122042
# only preserve total if there is some data before
20132043
with warnings.catch_warnings():
20142044
# Divide by zero is fine
20152045
warnings.filterwarnings("ignore", category=RuntimeWarning)
2016-
factor = np.where(np.isclose(total_before, 0, atol=1e-6),
2046+
factor = np.where(total_before_is_zero,
20172047
0., total_before / total_after)
20182048

20192049
if len(data.dims) == 3:

oggm/sandbox/distribute_2d.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@ def distribute_thickness_from_simulation(gdir,
490490
def merge_simulated_thickness(gdirs,
491491
output_folder=None,
492492
output_filename=None,
493+
output_grid=None,
493494
simulation_filesuffix='',
494495
years_to_merge=None,
495496
keep_dem_file=False,
@@ -514,6 +515,9 @@ def merge_simulated_thickness(gdirs,
514515
output_folder
515516
output_filename : str
516517
Default is gridded_simulation_merged{simulation_filesuffix}.
518+
output_grid : salem.gis.Grid
519+
You can provide a custom grid on which the distributed data should be
520+
merged on.
517521
simulation_filesuffix : str
518522
the filesuffix of the gridded_simulation file
519523
years_to_merge : list | xr.DataArray | None
@@ -559,6 +563,7 @@ def _calc_bedrock_topo(fp):
559563
gdirs,
560564
output_folder=output_folder,
561565
output_filename=f"{output_filename}_topo_data",
566+
output_grid=output_grid,
562567
input_file='gridded_data',
563568
input_filesuffix='',
564569
included_variables=['glacier_ext',
@@ -607,6 +612,7 @@ def _calc_bedrock_topo(fp):
607612
gdirs,
608613
output_folder=output_folder,
609614
output_filename=f"{output_filename}_{year}_{month:02d}",
615+
output_grid=output_grid,
610616
input_file='gridded_simulation',
611617
input_filesuffix=simulation_filesuffix,
612618
included_variables=[('simulated_thickness',
@@ -632,6 +638,7 @@ def _calc_bedrock_topo(fp):
632638
gdirs,
633639
output_folder=output_folder,
634640
output_filename=output_filename,
641+
output_grid=output_grid,
635642
input_file=['gridded_data', 'gridded_simulation'],
636643
input_filesuffix=['', simulation_filesuffix],
637644
included_variables=[['glacier_ext',

oggm/tests/test_workflow.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,28 @@ def test_merge_gridded_data():
414414
assert_allclose(df['inv_volume_km3'].sum(), inv_volume_gridded_merged,
415415
rtol=1e-6)
416416

417+
# test providing a custom grid
418+
default_grid = utils.combine_grids(gdirs)
419+
custom_grid_dict = default_grid.to_dict()
420+
custom_grid_dict['nxny'] = (custom_grid_dict['nxny'][0] - 2,
421+
custom_grid_dict['nxny'][1] - 2)
422+
custom_grid = salem.gis.Grid.from_dict(custom_grid_dict)
423+
424+
ds_merged_custom = workflow.merge_gridded_data(
425+
gdirs,
426+
output_grid=custom_grid,
427+
included_variables='distributed_thickness',
428+
reset=True)
429+
430+
assert ds_merged_custom.salem.grid.nx < ds_merged.salem.grid.nx
431+
assert ds_merged_custom.salem.grid.ny < ds_merged.salem.grid.ny
432+
433+
# total volume should be the same with a custom grid
434+
inv_volume_gridded_merged_custom = (ds_merged_custom.distributed_thickness.sum() *
435+
ds_merged_custom.salem.grid.dx ** 2) * 1e-9
436+
assert_allclose(inv_volume_gridded_merged_custom, inv_volume_gridded_merged,
437+
rtol=1e-6)
438+
417439

418440
@pytest.mark.slow
419441
@pytest.mark.graphic

oggm/workflow.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,7 @@ def _recursive_merging(gdirs, gdir_main, glcdf=None, dem_source=None,
848848
@global_task(log)
849849
def merge_gridded_data(gdirs, output_folder=None,
850850
output_filename='gridded_data_merged',
851+
output_grid=None,
851852
input_file='gridded_data',
852853
input_filesuffix='',
853854
included_variables='all',
@@ -884,6 +885,10 @@ def merge_gridded_data(gdirs, output_folder=None,
884885
should be stored. Default is cfg.PATHS['working_dir']
885886
output_filename : str
886887
The name for the resulting file. Default is 'gridded_data_merged'.
888+
output_grid : salem.gis.Grid
889+
You can provide a custom grid on which the gridded data should be
890+
merged on. If None, a combined grid of all gdirs will be constructed.
891+
Default is None.
887892
input_file : str or list
888893
The file(s) which should be merged. If a list is provided the data of
889894
all files is merged into the same dataset. Default is 'gridded_data'.
@@ -970,9 +975,10 @@ def merge_gridded_data(gdirs, output_folder=None,
970975
# into a list of lists
971976
included_variables = [included_variables]
972977

973-
# create a combined salem.Grid object, which serves as canvas/boundaries of
974-
# the combined glacier region
975-
combined_grid = utils.combine_grids(gdirs)
978+
if output_grid is None:
979+
# create a combined salem.Grid object, which serves as canvas/boundaries of
980+
# the combined glacier region
981+
output_grid = utils.combine_grids(gdirs)
976982

977983
if add_topography:
978984
# ok, lets get a DEM and add it to the final file
@@ -982,11 +988,11 @@ def merge_gridded_data(gdirs, output_folder=None,
982988
else:
983989
dem_source = None
984990
dem_gdir = gdirs[0]
985-
gis.get_dem_for_grid(combined_grid, output_folder,
991+
gis.get_dem_for_grid(output_grid, output_folder,
986992
source=dem_source, gdir=dem_gdir)
987993
# unwrapped is needed to execute process_dem without the entity_task
988994
# overhead (this would need a valid gdir)
989-
gis.process_dem.unwrapped(gdir=None, grid=combined_grid,
995+
gis.process_dem.unwrapped(gdir=None, grid=output_grid,
990996
fpath=output_folder,
991997
output_filename=output_filename)
992998
if not keep_dem_file:
@@ -999,7 +1005,7 @@ def merge_gridded_data(gdirs, output_folder=None,
9991005
if os.path.exists(fpath):
10001006
os.remove(fpath)
10011007

1002-
with gis.GriddedNcdfFile(grid=combined_grid, fpath=output_folder,
1008+
with gis.GriddedNcdfFile(grid=output_grid, fpath=output_folder,
10031009
basename=output_filename) as nc:
10041010

10051011
# adding the data of one file after another to the merged dataset
@@ -1036,9 +1042,9 @@ def merge_gridded_data(gdirs, output_folder=None,
10361042
dim_lengths = []
10371043
for dim in dims:
10381044
if dim == 'y':
1039-
dim_lengths.append(combined_grid.ny)
1045+
dim_lengths.append(output_grid.ny)
10401046
elif dim == 'x':
1041-
dim_lengths.append(combined_grid.nx)
1047+
dim_lengths.append(output_grid.nx)
10421048
else:
10431049
if slice_of_var is not None:
10441050
# only keep selected part of the variable
@@ -1088,7 +1094,7 @@ def merge_gridded_data(gdirs, output_folder=None,
10881094

10891095
kwargs_reproject = dict(
10901096
variable=var,
1091-
target_grid=combined_grid,
1097+
target_grid=output_grid,
10921098
filename=in_file,
10931099
filesuffix=in_filesuffix,
10941100
use_glacier_mask=use_glacier_mask,

0 commit comments

Comments
 (0)