How to Choose a Cell Size for an Interpolated Surface

Problem statement

The output cell size is the one parameter of an interpolation that has no right answer in the data, so people default to whatever looks smooth β€” usually far finer than the samples support.

It is easy to measure what that costs. Five hundred samples over a 105 kmΒ² area, interpolated to grids of different cell sizes, scored against the real elevation surface:

cell size    output cells      RMSE
     10 m     1,045,233     54.01 m
     15 m       464,548     54.00 m
     30 m       116,137     53.96 m
     60 m        28,864     53.90 m
    120 m         7,216     53.89 m
    250 m         1,638     54.17 m

The 10 m grid has 145 times as many cells as the 120 m grid and is very slightly less accurate. Every one of those extra million cells is interpolation, not information.

Quick answer

Start from the mean sample spacing and go one step finer, no more:

import numpy as np


def suggested_cell_size(n_samples, area_m2, factor=0.5):
    """Mean sample spacing, halved. Finer than this adds cells, not information."""
    spacing = np.sqrt(area_m2 / n_samples)
    cell = spacing * factor
    print(f"  {n_samples} samples over {area_m2 / 1e6:.1f} kmΒ²")
    print(f"  mean spacing        {spacing:7.0f} m")
    print(f"  suggested cell size {cell:7.0f} m")
    print(f"  grid would be       {area_m2 / cell ** 2:,.0f} cells")
    return cell
  500 samples over 104.5 kmΒ²
  mean spacing            457 m
  suggested cell size     228 m
  grid would be         2,009 cells
RMSE flat at about 54 metres across cell sizes from 10 m to 250 m while the cell count falls from a million to under two thousand.
Accuracy is flat across a 25Γ— range of cell sizes. Only the file size changes.

Step-by-step solution

1. Compute the mean sample spacing

spacing = np.sqrt(area / n_samples)

This is the spacing a perfect grid of the same size would have. With 500 samples over 104.5 kmΒ² it is 457 m β€” the natural resolution of this survey, regardless of what grid you write it to.

2. Pick a cell size at or below half that spacing

Half the spacing is a reasonable convention: fine enough that the grid does not itself introduce visible blockiness, coarse enough that you are not storing a hundred cells per observation.

At 457 m spacing that gives about 230 m cells. If that sounds shockingly coarse, that is the correct reaction to what 500 samples over 105 kmΒ² actually supports.

3. Do not confuse cell size with accuracy

The temptation is to reason "a 10 m DEM is better than a 100 m DEM, so I should output 10 m". That holds when the measurements are at 10 m. It does not hold when the measurements are 457 m apart and the 10 m grid is an interpolation.

The measured RMSE moved by 0.28 m β€” half a percent β€” across a 25Γ— range of cell sizes. Meanwhile the file grew by a factor of 640.

4. Match the neighbours' grid if you will combine layers

If the surface will be stacked with other rasters, matching their grid saves a resampling step and prevents alignment bugs. That is often a better reason to pick a cell size than any property of the samples.

Pick the coarsest resolution among the layers you will combine, and snap the origin so pixel corners line up. See How to resample satellite bands to a common grid for the alignment mechanics.

5. Consider what happens downstream

Some operations impose their own constraint:

  • Zonal statistics over small polygons need cells small enough that each polygon contains several. A 230 m cell is useless for field-scale parcels.
  • Slope and aspect are extremely sensitive to cell size, because they are derivatives.
  • Visual products benefit from finer cells even when accuracy does not, because contour lines and hillshades look ragged on coarse grids.

Those are legitimate reasons to go finer. "The map looks smoother" is a legitimate reason too β€” as long as nobody reads the fine grid as fine data.

Sample spacing of 457 metres against output grids at 10 m, 30 m and 120 m, showing how many cells fall between two observations.
At 10 m cells there are 45 cells between adjacent observations. Forty-four of them are the model talking.

Code examples

Example 1 β€” a cell-size sweep on your own data

import numpy as np
from scipy.spatial import cKDTree


def cell_size_sweep(points, values, bounds, truth_fn,
                    candidates=(10, 25, 50, 100, 250, 500), power=2, k=12):
    """Interpolate at several cell sizes and score each against known truth."""
    left, bottom, right, top = bounds
    tree = cKDTree(points)

    for cell in candidates:
        width = int((right - left) / cell)
        height = int((top - bottom) / cell)
        xs = left + (np.arange(width) + 0.5) * cell
        ys = top - (np.arange(height) + 0.5) * cell
        gx, gy = np.meshgrid(xs, ys)
        grid = np.column_stack([gx.ravel(), gy.ravel()])

        d, i = tree.query(grid, k=min(k, len(points)))
        d = d.reshape(len(grid), -1); i = i.reshape(len(grid), -1)
        w = 1.0 / np.maximum(d, 1e-12) ** power
        predicted = (w * values[i]).sum(1) / w.sum(1)

        truth = truth_fn(grid)
        rmse = float(np.sqrt(np.mean((predicted - truth) ** 2)))
        print(f"  {cell:4d} m  {len(grid):10,} cells  RMSE {rmse:7.2f}  "
              f"{len(grid) * 4 / 1e6:7.1f} MB as float32")

You need truth to run this, which means a pilot on a dense dataset. Do it once for your variable and landscape and the answer transfers.

Example 2 β€” deriving the cell size from the variogram instead

import numpy as np


def cell_size_from_variogram(nugget, sill, rng, n_samples, area_m2):
    """Two independent limits on useful resolution."""
    spacing = np.sqrt(area_m2 / n_samples)

    # 1. sampling limit: you cannot resolve detail finer than the spacing
    sampling_limit = spacing / 2

    # 2. structure limit: features shorter than about a tenth of the range
    #    are not represented in the model at all
    structure_limit = rng / 10

    chosen = max(min(sampling_limit, structure_limit), 1.0)
    print(f"  sampling limit  {sampling_limit:7.0f} m  (spacing {spacing:.0f} m)")
    print(f"  structure limit {structure_limit:7.0f} m  (range {rng:.0f} m)")
    print(f"  suggested       {chosen:7.0f} m")

    if nugget / (nugget + sill) > 0.3:
        print(f"  ! nugget is {nugget / (nugget + sill):.0%} of the sill β€” much of "
              "the variation is unresolvable at any cell size")
    return chosen
  sampling limit      228 m  (spacing 457 m)
  structure limit     454 m  (range 4537 m)
  suggested           228 m

The two limits usually disagree, and the smaller one wins. Here sampling is the binding constraint β€” the structure extends over kilometres, so it is the survey, not the phenomenon, that sets the resolution.

Example 3 β€” writing a coarse surface without a coarse-looking map

import numpy as np
import rasterio
from rasterio.enums import Resampling


def write_with_display_overview(path, surface, profile, display_cell=None):
    """Store at the honest resolution; resample only for display, and say so."""
    with rasterio.open(path, "w", **profile) as dst:
        dst.write(surface, 1)
        dst.build_overviews([2, 4, 8], Resampling.average)
        dst.update_tags(
            resolution_note="stored at the resolution the sampling supports; "
                            "any finer rendering is interpolation for display",
        )

    if display_cell:
        factor = profile["transform"].a / display_cell
        print(f"  display copy would be {factor:.0f}x finer β€” "
              "render it, do not store it as data")

If a stakeholder wants a smooth 10 m map, produce it as a rendering rather than as a raster of numbers. The moment a fine grid exists as data, someone will run zonal statistics on it and quote the result to three decimal places.

Explanation

Why accuracy is flat across cell sizes

An interpolated value at a location depends only on the samples near it. Two adjacent cells of a fine grid have almost the same nearest samples and almost the same weights, so they get almost the same value.

Refining the grid therefore does not add information; it adds cells that are nearly copies of their neighbours. The RMSE stays put because the surface is the same surface, sampled more densely.

The very slight worsening at the extremes has two separate causes. At 250 m, cells become large enough that a single value is a poor summary of the ground it covers. At 10 m, the surface faithfully reproduces the bullseyes around each sample, which are artefacts β€” a coarser grid averages some of them away.

Why fine grids are actively harmful

Three costs, none of them obvious at the moment you type the number:

  • Storage and speed. 145Γ— the cells is 145Γ— the bytes and roughly 145Γ— the time for every downstream operation, forever.
  • False precision. A 10 m raster invites 10 m questions. Someone will extract a value for a single parcel and treat it as a measurement of that parcel.
  • Hidden artefacts. Bullseyes and triangulation facets are much more visible at fine resolution β€” which is at least honest, but they will be read as terrain.

Why the sampling limit is usually the binding one

Two things limit useful resolution: how densely you sampled, and how fine the real structure is. The measurement above has a variogram range of 4,537 m β€” the phenomenon varies over kilometres β€” while the sample spacing is 457 m.

So the survey is comfortably dense relative to the structure, and the resolution is set by the sampling. That is the normal situation. The reverse β€” structure finer than your samples can see β€” is the case where no cell size helps, because the variation is inside the nugget.

Why this matters more than the interpolation method

The method choice was worth 25% here (kriging against IDW). The cell size choice is worth 0% in accuracy and a factor of 640 in file size.

Getting the method wrong costs you some accuracy. Getting the cell size wrong costs you nothing measurable and everything in credibility, because the output claims a resolution the data cannot support.

Four legitimate drivers of cell size: the decision, the other layers, the delivery budget and the sample spacing.
Accuracy is not on the list, because across the range anyone would consider it does not move.

Edge cases or notes

  • sqrt(area / n) assumes even spacing. For clustered samples use the 95th-percentile gap instead; it is a much larger number.
  • Snap the origin to a multiple of the cell size so grids at different resolutions share pixel corners.
  • Slope and aspect are derivatives and change substantially with cell size β€” do not compute them from an over-fine interpolated grid.
  • Zonal statistics need several cells per polygon. If that forces a fine grid, the honest conclusion may be that the sampling cannot answer the question.
  • Store coarse, render fine. Overviews and display resampling do not lie about resolution; a fine raster does.
  • Match neighbouring layers if the surface will be stacked β€” often the strongest argument.
  • A large nugget caps useful resolution regardless of sampling density.
  • Record the sample spacing in the file tags, so the next person can see what the grid is standing on.

FAQ

What cell size should I use for an interpolated surface?

Around half the mean sample spacing, sqrt(area / n) / 2. With 500 samples over 105 kmΒ² that is about 230 m.

Does a finer grid make the surface more accurate?

No. Across cell sizes from 10 m to 250 m the RMSE moved by 0.28 m β€” half a percent β€” while the cell count changed by a factor of 640.

Why does my 10 m interpolated raster look so much better?

Because it is smoother to look at. Smoothness is a property of the rendering, not of the information; the 10 m grid has 45 cells between adjacent observations and 44 of them are model output.

Should I match the cell size of my other rasters?

Usually yes. Alignment saves a resampling step and prevents a whole class of bugs, and it is often a better criterion than anything in the samples themselves.

What if I need fine cells for zonal statistics?

Then check whether the sampling supports the question. If polygons are smaller than the sample spacing, a finer grid gives you a number per polygon but not information per polygon.

How does the variogram help choose a cell size?

It gives a second limit: features shorter than about a tenth of the range are not in the model. Take the smaller of that and half the sample spacing.

Can I store coarse and display fine?

Yes, and you should. Build overviews and let the renderer interpolate. That keeps the data honest while the map looks the way people expect.