Resampling raster data

Resampling data is a very common operation when building a Modflow model. Usually it is used to project data from one grid onto the other. There are many different ways to do this. This notebook shows some examples of resampling methods that are incorporated in the nlmod package. These methods rely heavily on resampling methods in packages such as rioxarray and scipy.interpolate.

import geopandas as gpd
import matplotlib.pyplot as plt
import numpy as np
import xarray as xr
from matplotlib.colors import Normalize
from scipy.interpolate import RectBivariateSpline
from shapely.geometry import LineString, Point

import nlmod
from nlmod import resample
nlmod.util.get_color_logger("INFO")
nlmod.show_versions()
Python version     : 3.11.14
NumPy version      : 2.4.4
Xarray version     : 2026.4.0
Matplotlib version : 3.10.9
Flopy version      : 3.10.0

nlmod version      : 0.11.3dev

Grid types

Two different gridtypes are supported in nlmod:

  • structured grids where the cellsize is fixed for all cells

  • vertex grids where the cellsize differs locally. These grids are usually created using local grid refinement algorithms.

In this notebook we define a few xarray DataArrays of structured and vertex grids. We use these grids in the next section to show the resampling functions in nlmod.

structured grid

This structured grid consits of 3 x 3 cells, and has random numbers between 0 and 9.

ds = nlmod.get_ds([950, 1250, 20050, 20350], delr=100)
rng = np.random.default_rng(123)
ds["data"] = ("y", "x"), rng.random((len(ds.y), len(ds.x))) * 10

norm=Normalize(0, 10)
fig, ax = plt.subplots()
ax.set_aspect("equal")
ds["data"].plot(ax=ax, lw=0.1, edgecolor="k", norm=norm);
INFO:nlmod.dims.base.to_model_ds:resample layer model data to structured modelgrid
../_images/f123fd7169893d3ebe9c2e9de89a7436e3525849a64eecdd43a1fb908eeafff5.png

structured grid with nan value

ds["data_nan"] = ds["data"].copy()
ds["data_nan"].data[0, 1] = np.nan

fig, ax = plt.subplots()
ax.set_aspect("equal")
ds["data_nan"].plot(ax=ax, lw=0.1, edgecolor="k", norm=norm);
../_images/8ca54a772569d76aaf669b5f49965bcc157af2dd8c8f6d5d86972f7c1ccc463b.png

vertex grid

dsv = nlmod.grid.refine(
    ds, refinement_features=[([Point(1200, 20200)], "point", 1)], model_ws="07_resampling"
)
dsv["data"] = "icell2d", rng.random(len(dsv.data)) * 10

fig, ax = plt.subplots()
ax.set_aspect("equal")
pc = nlmod.plot.data_array(dsv["data"], ds=dsv, edgecolor="k", norm=norm)
plt.colorbar(pc);
INFO:nlmod.dims.grid.refine:create vertex grid using gridgen
INFO:nlmod.dims.grid.ds_to_gridprops:resample model Dataset to vertex modelgrid
../_images/c7d1220d184ea1888ec4f694209c0e2970379458f31ed4bc03030bb9de677431.png

vertex grid with nan

dsv["data_nan"] = dsv["data"].copy()
dsv["data_nan"][7] = np.nan

fig, ax = plt.subplots()
ax.set_aspect("equal")
nlmod.plot.data_array(dsv["data_nan"], ds=dsv, edgecolor="k", norm=norm);
../_images/59404c53fcea41e7b0789539e4318d2dfacd758a02212ee662323e362af1d32f.png

Structured grid to fine structured grid

# generate a finer model dataset
ds_fine = nlmod.get_ds(extent=[950.0, 1250.0, 20050.0, 20350.0], delr=50.0)
INFO:nlmod.dims.base.to_model_ds:resample layer model data to structured modelgrid
def compare_structured_data_arrays(da1, da2, method, edgecolor="k"):
    fig, axes = plt.subplots(ncols=2, figsize=(12, 6))
    da1.plot(ax=axes[0], edgecolor=edgecolor, norm=norm)
    axes[0].set_aspect("equal")
    axes[0].set_title("original grid")
    da2.plot(ax=axes[1], edgecolor=edgecolor, norm=norm)
    axes[1].set_aspect("equal")
    axes[1].set_title(f"resampled grid, method {method}")

Without NaNs

for method in ["nearest", "linear", "cubic", "average", "min"]:
    struc_out = resample.structured_da_to_ds(ds["data"], ds_fine, method=method)
    compare_structured_data_arrays(ds["data"], struc_out, method)
../_images/c049a615d95a13ed9ae1b23545844ef79c66893cf287973345b25634fa2608d5.png ../_images/4f01d19861eade6307072a8b443cfda14df7e5a18a721038b37f8eb09e1a81ed.png ../_images/b00ca2b119c35287b2172e433ceb6253be737a86b9e563f799ee919d0cc86470.png ../_images/6dd4da1af2be3e447faf94e098dfdc587dbc28b2dd37c3d6d4bedc6352eea7a0.png ../_images/85f2d086256f9de6c628beb6f1036f8048f94e37014c3965c55c2ee79499ddfc.png

With NaNs

for method in ["nearest", "linear", "cubic", "average", "mode"]:
    struc_out = resample.structured_da_to_ds(ds["data_nan"], ds_fine, method=method)
    compare_structured_data_arrays(ds["data_nan"], struc_out, method)
../_images/56db1912cd9db04fe2dcb41d7240ccfac623cbbc6f71b3c5832fed3b1af77d63.png ../_images/0601ae35a5e41030cf1611a67c080e368a285c0fd4e01fd5b18d57fe7a5d5864.png ../_images/4fe011d6a5c4b38ede9ee6118c7df8338c20540d64905c8d7de88151b12437a3.png ../_images/dee42af975d78fb23b1a9a73ecc3f3ff102aff644dece2d82fda1668bd6dd894.png ../_images/c0d59798111cf5b01ab9767a12dfe23ddb96685657bfbbdf5a287a04fab11b3f.png

Rectangular Bivariate Spline

Note: not yet included as a method in nlmod

interp_spline = RectBivariateSpline(
    ds.x.values,
    ds.y.values[::-1],
    ds["data"].values[::-1],
    ky=min(3, len(ds.y) - 1),
    kx=min(3, len(ds.x) - 1),
)
arr_out = interp_spline(ds_fine.x, ds_fine.y[::-1], grid=True)[::-1]
struc_out = xr.DataArray(
    arr_out, dims=("y", "x"), coords={"x": ds_fine.x, "y": ds_fine.y}
)
compare_structured_data_arrays(ds["data"], struc_out, "Rectangular Bivariate Spline")
../_images/ad78f1186bee137dd37810fa924311601278f49f3d8b8106231efaaf2d6e3e31.png

Rectangular Bivariate Spline with nans

Note: not yet included as a method in nlmod

interp_spline = RectBivariateSpline(
    ds.x.values,
    ds.y.values[::-1],
    ds["data_nan"].values[::-1],
    ky=min(3, len(ds.y) - 1),
    kx=min(3, len(ds.x) - 1),
)
arr_out = interp_spline(ds_fine.x, ds_fine.y[::-1], grid=True)[::-1]
struc_out = xr.DataArray(
    arr_out, dims=("y", "x"), coords={"x": ds_fine.x, "y": ds_fine.y}
)
compare_structured_data_arrays(
    ds["data_nan"], struc_out, "Rectangular Bivariate Spline"
)
../_images/01733b59853886f3244eac771c7f3430d4e6125df7f0a79e1f43bab9bade36bb.png

Structured grid to locally refined grid

def compare_struct_to_vertex(struc2d, res_vertex2d_n, dsv, method):
    fig, axes = plt.subplots(ncols=2, figsize=(12, 6))
    struc2d.plot(ax=axes[0], edgecolor="k", norm=norm)
    axes[0].set_aspect("equal")
    axes[0].set_title("structured grid")

    pc = nlmod.plot.data_array(
        res_vertex2d_n, ds=dsv, ax=axes[1], edgecolor="k", norm=norm
    )
    plt.colorbar(pc)
    axes[1].set_aspect("equal")
    axes[1].set_title(f"locally refined grid, method {method}")

WIthout NaNs

for method in ["nearest", "linear", "cubic"]:
    res_vertex2d_n = resample.structured_da_to_ds(ds["data"], dsv, method=method)
    compare_struct_to_vertex(ds["data"], res_vertex2d_n, dsv, method)
../_images/01fd031ce3be1682fc05676eab6fe27b7302e3d6c3b221c02091ad8e4ed883a9.png ../_images/b66e7462e43ef5c26afbd8966708646a7da3280c6e60a8b5825df940ceb7cc4a.png ../_images/e6f5343e11abe5535889a2e8d89f318b5bb827618713d2fbe1657e688c50d536.png

Locally refined grid to structured grid

def compare_vertex_to_struct(vertex1, dsv, struc_out_n, method):
    fig, axes = plt.subplots(ncols=2, figsize=(12, 6))
    pc = nlmod.plot.data_array(vertex1, ds=dsv, ax=axes[0], edgecolor="k", norm=norm)
    plt.colorbar(pc)
    axes[0].set_title("original")
    axes[0].set_aspect("equal")
    struc_out_n.plot(ax=axes[1], edgecolor="k", norm=norm)
    axes[1].set_title(f"resampled, method {method}")
    axes[1].set_aspect("equal")

Without NaNs

for method in ["nearest", "linear", "cubic"]:
    struc_out_n = resample.vertex_da_to_ds(dsv["data"], ds=ds, method=method)
    compare_vertex_to_struct(dsv["data"], dsv, struc_out_n, method)
../_images/87a0bbed76b97a4f52c8370e9af9510f6653b6835de5620eeb9a3203ade3b968.png ../_images/e950d51db744ada7df7554bc1a6fbb30d1cfb975ea590ea9c98c34afbe2658ba.png ../_images/f0b353490b552b0d8a36061deab44fbbeb5f5ad0602448637e17b2419db311a0.png

With NaNs

for method in ["nearest", "linear", "cubic"]:
    struc_out_n = resample.vertex_da_to_ds(dsv["data_nan"], ds=ds, method=method)
    compare_vertex_to_struct(dsv["data_nan"], dsv, struc_out_n, method)
../_images/0cbc8b32b1be646dd7710aa4385007e918ce6ee922c9b9acab19d6e71baaf518.png ../_images/5f5c88077de160e7e1586d731bf62a827d33e662628f08ae216db0fba1590397.png ../_images/350f5b522afca70f3465951df08a086cf13de42627fa1ac4fdd39ea36bd714b8.png

Fill nan values

Structured grid

for method in ["nearest", "linear"]:
    struc2d_nan_filled = resample.fillnan_da_structured_grid(
        ds["data_nan"], method=method
    )
    compare_structured_data_arrays(ds["data_nan"], struc2d_nan_filled, method)
../_images/41175043802078bc5300961803f5a9d3d0dbc05b2d0a75266e4520d8ac874458.png ../_images/6c15fd6191a054689e9ec9b2f091b58b309cfe8545ca93e3634812092326b0c2.png

vertex grid

def compare_vertex_arrays(vertex1, vertex2, dsv, method):
    fig, axes = plt.subplots(ncols=2, figsize=(12, 6))
    pc = nlmod.plot.data_array(vertex1, ds=dsv, ax=axes[0], edgecolor="k", norm=norm)
    plt.colorbar(pc)
    axes[0].set_title("original")
    axes[0].set_aspect("equal")
    pc = nlmod.plot.data_array(vertex2, ds=dsv, ax=axes[1], edgecolor="k", norm=norm)
    plt.colorbar(pc)
    axes[1].set_title(f"resampled, method {method}")
    axes[1].set_aspect("equal")
for method in ["nearest", "linear"]:
    vertex1_nan_filled = resample.fillnan_da_vertex_grid(
        dsv["data_nan"], ds=dsv, method=method
    )
    compare_vertex_arrays(dsv["data_nan"], vertex1_nan_filled, dsv, method)
../_images/0f4f9709f3261175c1a9681d9d0c3098204d6ee9da31b25a0ad24faef81f4b6c.png ../_images/90ab311d0b850d2e317cec5a524b52a236ffc7d7daef62cacf1718b8d7c34a0c.png

Real world example

In this example we will resample the values of the dutch Digital Terrain Model (DTM) from AHN4 to a structured grid and a vertex grid, using several methods. First we will download the AHN-information.

extent = [133000, 134000, 402000, 403000]
ahn = nlmod.read.ahn.download_ahn4(extent)
Downloading tiles of AHN4:   0%|          | 0/1 [00:00<?, ?it/s]
Downloading tiles of AHN4: 100%|██████████| 1/1 [00:04<00:00,  4.17s/it]

Transform ahn data to structured grid

We create a dummy dataset with a structured grid, to which we will resample the AHN-data.

# create an empty model dataset
ds_ahn = nlmod.get_ds(extent, delr=100.0, layer=1)
INFO:nlmod.dims.base.to_model_ds:resample layer model data to structured modelgrid
norm = Normalize(ahn.min(), ahn.max())
for method in ["nearest", "linear", "average", "min", "max"]:
    ahn_res = nlmod.resample.structured_da_to_ds(ahn, ds_ahn, method=method)

    fig, axes = nlmod.plot.get_map(extent, ncols=2, figsize=(12, 6))
    pc = nlmod.plot.data_array(ahn, ax=axes[0], norm=norm)
    nlmod.plot.colorbar_inside(pc, ax=axes[0])
    axes[0].set_aspect("equal")
    axes[0].set_title("original grid")
    pc = nlmod.plot.data_array(ahn_res, ds_ahn, ax=axes[1], edgecolor="k", norm=norm)
    nlmod.plot.colorbar_inside(pc, ax=axes[1])
    axes[1].set_aspect("equal")
    axes[1].set_title(f"resampled grid, method {method}")
../_images/4a593eb3818e5435ed63d14610bd0ab10a857e4b9651720863e5b449357f2acc.png ../_images/87e2ed7d443f0dc9a3c26c825a02b385e96e0f1cfd17342910896274fee7d02c.png ../_images/535900a90ac375ffeb6b5db91584d7bba930318b23f1add0f5d406b96a1d65e7.png ../_images/eefdfbd20fa34f6adaa9e09785ced8beac06e9268c587f846afa8c119e3d28e1.png ../_images/01b43fbc91042cc567f410535ec29382f7cc5879ab3648cd7b71bd03230a43e4.png

Transform ahn data to vertex grid

We create a vertex grid by refining the cells along a line from the southwest to the northeast.

gdf = gpd.GeoDataFrame(
    geometry=[LineString([(extent[0], extent[2]), (extent[1], extent[3])]).buffer(10.0)]
)
dsv = nlmod.grid.refine(ds_ahn, model_ws="07_resampling", refinement_features=[(gdf, 1)])
INFO:nlmod.dims.grid.refine:create vertex grid using gridgen
INFO:nlmod.dims.grid.ds_to_gridprops:resample model Dataset to vertex modelgrid
for method in ["nearest", "average", "min", "max"]:
    ahn_res = nlmod.resample.structured_da_to_ds(ahn, dsv, method=method)

    fig, axes = nlmod.plot.get_map(extent, ncols=2, figsize=(12, 6))
    pc = nlmod.plot.data_array(ahn, ax=axes[0], norm=norm)
    nlmod.plot.colorbar_inside(pc, ax=axes[0])
    axes[0].set_aspect("equal")
    axes[0].set_title("original grid")
    pc = nlmod.plot.data_array(ahn_res, dsv, ax=axes[1], edgecolor="k", norm=norm)
    nlmod.plot.colorbar_inside(pc, ax=axes[1])
    axes[1].set_aspect("equal")
    axes[1].set_title(f"resampled grid, method {method}")
../_images/0ed7ba71a629a82c424b5c5d8f16d50db74e68b3b30d2718d3ae5ad0c13c9490.png ../_images/ae4282ceef01cab764fed1f285ff14848a874bb75cda449f81e4b21ce2e29f98.png ../_images/1cf99d2a4913026e37d81e9e27d3298648e9406f9cacc6e6cf3ace542e2d6d3a.png ../_images/ab28f6a5d31a7f3962d3d986ce79df0ce19c6232c43fa2166a42d18d4fd297c4.png