louis-e/arnis · error · Error

land cover grid mismatch

Error message

land cover grid mismatch

What it means

The land cover payload ('APL1') carries its own grid dimensions (gw, gh) in its header; this check asserts they match the DEM dataset's grid (d.gw, d.gh) because the land cover raster is overlaid pixel-for-pixel on the DEM grid. A mismatch means the two rasters cannot be aligned, so the overlay is aborted with this Error.

Source

Thrown at src/gui/js/preview3d.js:414

    if (!modalView || !modalView.map) return;
    const map = modalView.map;
    try {
      if (map.getLayer("landcover")) {
        map.setLayoutProperty("landcover", "visibility", enabled ? "visible" : "none");
        return;
      }
      if (!enabled) return;
      const d = datasets.get(modalView.gen);
      if (!d) return;

      toggle.disabled = true;
      let url = landCoverCache.key === d.bboxText ? landCoverCache.url : null;
      if (!url) {
        const raw = await window.__TAURI__.core.invoke("gui_get_preview_landcover", {
          bboxText: d.bboxText,
        });
        const { buffer, gw, gh } = readPayloadHeader(raw, "APL1");
        if (gw !== d.gw || gh !== d.gh) throw new Error("land cover grid mismatch");
        url = landCoverUrl(new Uint8Array(buffer, 12, gw * gh), gw, gh);
        landCoverCache = { key: d.bboxText, url: url };
      }
      if (!modalView || modalView.map !== map) return;
      map.addSource("landcover", {
        type: "image",
        url: url,
        coordinates: [
          [d.minLng, d.maxLat],
          [d.maxLng, d.maxLat],
          [d.maxLng, d.minLat],
          [d.minLng, d.minLat],
        ],
      });
      map.addLayer(
        {
          id: "landcover",
          type: "raster",

View on GitHub (pinned to 34048924d9)

Solutions

  1. Make the Rust gui_get_preview_landcover command derive its grid dimensions from the same source as the DEM command so gw/gh always match d.gw/d.gh.
  2. Capture the dataset generation/id when starting the land cover fetch and re-validate (or discard) the result if the dataset changed while awaiting.
  3. Invalidate landCoverCache whenever the DEM dataset is regenerated, not only when bboxText changes.
  4. Catch this error in the caller and retry the land cover fetch once after refreshing the dataset dimensions.
  5. Log gw/gh from both payloads on mismatch to quickly identify which side's resolution constant drifted.

Example fix

// before
const raw = await window.__TAURI__.core.invoke("gui_get_preview_landcover", { bboxText: d.bboxText });
const { buffer, gw, gh } = readPayloadHeader(raw, "APL1");
if (gw !== d.gw || gh !== d.gh) throw new Error("land cover grid mismatch");
// after
const genAtStart = d.gen;
const raw = await window.__TAURI__.core.invoke("gui_get_preview_landcover", { bboxText: d.bboxText });
if (d.gen !== genAtStart) return; // dataset regenerated mid-flight
const { buffer, gw, gh } = readPayloadHeader(raw, "APL1");
if (gw !== d.gw || gh !== d.gh) {
  console.error("landcover grid", gw, gh, "vs dem", d.gw, d.gh);
  landCoverCache = { key: null, url: null };
  return;
}
Defensive patterns

Strategy: validation

Validate before calling

const { buffer, gw, gh } = readPayloadHeader(raw, "APL1");
if (gw !== d.gw || gh !== d.gh) {
  console.error("landcover dims", { gw, gh, demGw: d.gw, demGh: d.gh });
  return; // skip overlay instead of throwing
}

Type guard

function dimsMatch(cover, dem) { return cover.gw === dem.gw && cover.gh === dem.gh; }

Try / catch

try {
  await loadLandCover(d);
} catch (e) {
  if (e.message === "land cover grid mismatch") {
    landCoverCache = { key: null, url: null };
    return; // render terrain without overlay
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling gui_get_preview_landcover with the dataset's bboxText and receiving a grid whose width/height differ from d.gw/d.gh — e.g. the Rust backend computes land cover at a different resolution, the bbox text maps to a different cached grid, or the DEM dataset was regenerated at a new resolution while landCoverCache still holds a result keyed to the old bboxText but the newly fetched cover uses old dims (or vice versa).

Common situations: Backend changed its land cover resolution constant without a matching change to the DEM grid size; user panned/resized the selection so bboxText changed and the DEM was re-fetched at new dims while a stale land cover response arrived for the old dims; concurrent generations interleaving async results (no generation token checked before the dimension comparison).

Related errors


AI-assisted analysis of louis-e/arnis@34048924d9 (2026-09-03). Data as JSON: /api/errors/8f9ff4682c7a9b27. Report an issue: GitHub.