Maximum Zoom Level for PMTiles Conversion in GeoLibre: How It Syncs with the WASM Binary

The maximum zoom level for PMTiles conversion in GeoLibre is zoom level 18, enforced by a single source-of-truth constant that synchronizes the UI, validation logic, and test suite with the underlying WebAssembly binary.

In opengeos/GeoLibre, client-side PMTiles conversion relies on a Rust-based WASM tool that hardcodes its own zoom ceiling. Rather than scattering magic numbers across the codebase, the project exports one constant—MAX_VECTOR_PMTILES_ZOOM—to keep every layer of the stack aligned. This article breaks down where that limit lives, how it propagates through the system, and what happens when you try to exceed it.


Where the Limit Is Defined

The authoritative definition sits in packages/processing/src/wasm-convert.ts, the TypeScript wrapper around the vector_to_pmtiles WASM tool:

// packages/processing/src/wasm-convert.ts
/** The deepest zoom `vector_to_pmtiles` accepts; past it the tool exits with
 * `validation error: max_zoom must be <= 18`. */
export const MAX_VECTOR_PMTILES_ZOOM = 18;

This constant serves two critical purposes:

  • It documents the binary's behavior—the underlying Rust tool aborts with a validation error if max_zoom exceeds 18.
  • It acts as the single export that every consumer (UI, validation, tests) imports to stay synchronized.

The WASM binary itself does not expose this limit programmatically; it simply fails at runtime. By hoisting the constant into TypeScript, GeoLibre creates a compile-time contract that prevents silent mismatches.


UI Synchronization: Parsing Zoom Inputs

The desktop application's conversion dialog enforces the limit at the point of user input. In apps/geolibre-desktop/src/components/processing/ConversionDialog.tsx, the parseZoomRange utility receives MAX_VECTOR_PMTILES_ZOOM as an explicit ceiling:

// apps/geolibre-desktop/src/components/processing/ConversionDialog.tsx
// Parse user-entered zoom range, capping it at the WASM engine limit.
const zooms = parseZoomRange(minZoom, maxZoom, MAX_VECTOR_PMTILES_ZOOM);

This call on line 1115 ensures that:

  • Values above 18 are rejected or clamped before reaching the WASM layer.
  • The error message shown to users matches the actual binary limitation.

By threading the constant through the parsing function, the UI never promises a zoom level the engine cannot deliver.


Test-Level Safeguards

The test suite in tests/wasm-convert.test.ts provides the final synchronization checkpoint. It verifies both that zoom 18 succeeds and that zoom 19 triggers the expected runtime error:

// tests/wasm-convert.test.ts
it("accepts the documented maximum zoom and rejects one deeper", async () => {
  const atCap = await tileVectorToPmtiles(
    { name: "points.geojson", data: pointsGeoJson },
    "cap.pmtiles",
    { minZoom: MAX_VECTOR_PMTILES_ZOOM, maxZoom: MAX_VECTOR_PMTILES_ZOOM },
  );
  assert.ok(hasMagic(atCap.data, PMTILES_MAGIC));

  await assert.rejects(
    tileVectorToPmtiles(
      { name: "points.geojson", data: pointsGeoJson },
      "over.pmtiles",
      { maxZoom: MAX_VECTOR_PMTILES_ZOOM + 1 },
    ),
    /max_zoom must be <= 18/i,
  );
});

This test on line 33 creates a hard link between the constant and the binary's actual behavior. If the WASM tool's limit changes (e.g., in a future Rust upgrade), the test fails—forcing developers to update MAX_VECTOR_PMTILES_ZOOM rather than letting the UI and binary drift out of sync.


How the Synchronization Chain Works

Component Mechanism File Path
WASM binary Hardcoded runtime validation Bundled Rust tool (source in vector_to_pmtiles)
TypeScript constant MAX_VECTOR_PMTILES_ZOOM = 18 packages/processing/src/wasm-convert.ts
UI validation parseZoomRange consumes the constant apps/geolibre-desktop/src/components/processing/ConversionDialog.tsx
Test verification Asserts success at 18, rejection at 19 tests/wasm-convert.test.ts

When the WASM binary is updated, developers modify one location: the constant in wasm-convert.ts. The change propagates automatically:

  1. UI validation adjusts its ceiling.
  2. Tests validate the new boundary (or fail until aligned).
  3. Documentation comments in the source reflect the current limit.

This pattern eliminates the "works in UI, fails in engine" class of bugs common in WASM-frontend integrations.


What Happens If You Bypass the Constant

Attempting to pass maxZoom: 19 directly to tileVectorToPmtiles triggers a Rust-side assertion before any tiles are generated:


validation error: max_zoom must be <= 18

The WASM tool exits non-gracefully, returning no output and logging the error through the standard error channel. GeoLibre's wrapper catches this and rejects the promise, but no partial PMTiles archive is created.

This rigid behavior explains why the constant exists: the binary offers no negotiation, only enforcement. The TypeScript layer adds user-friendly handling and early validation to avoid wasting computation on doomed conversion attempts.


Summary

  • Maximum zoom level for PMTiles conversion is 18, hardcoded in the vector_to_pmtiles WASM tool.
  • Synchronization relies on MAX_VECTOR_PMTILES_ZOOM, a single exported constant from packages/processing/src/wasm-convert.ts.
  • UI enforcement occurs in ConversionDialog.tsx via parseZoomRange, which accepts the constant as its ceiling parameter.
  • Test coverage in tests/wasm-convert.test.ts ensures the constant matches runtime behavior, catching WASM updates that change the limit.
  • Maintenance requires editing only the constant; all consumers update automatically.

Frequently Asked Questions

Why is the PMTiles zoom limit 18 and not higher?

The underlying Rust tool vector_to_pmtiles enforces this ceiling to control memory usage and processing time during client-side conversion. According to the GeoLibre source code, higher zoom levels would exponentially increase tile count, exceeding the WASM memory constraints of browser environments. The limit balances detail coverage against practical runtime performance on typical hardware.

How do I change the maximum zoom level if my use case requires deeper tiles?

You must rebuild the WASM binary itself after modifying the Rust source, then update MAX_VECTOR_PMTILES_ZOOM in packages/processing/src/wasm-convert.ts to match. The test suite will confirm whether your rebuilt binary actually supports the new limit. Note that GeoLibre does not support runtime configuration of this parameter—the constant and binary must stay manually aligned.

What error message appears when requesting zoom 19?

The WASM binary returns: validation error: max_zoom must be <= 18. This string is regex-matched in tests/wasm-convert.test.ts to verify correct rejection. In the desktop UI, users see a friendlier validation error before the conversion even attempts to start, since parseZoomRange intercepts out-of-range values.

Does GeoLibre support different zoom limits for raster versus vector PMTiles?

The source code references MAX_VECTOR_PMTILES_ZOOM specifically, indicating the constant governs vector tile conversion. Raster PMTiles conversion—if implemented—would likely use a separate constant or inherit from the WASM tool's raster-specific limits. As of the current codebase, vector conversion is the primary WASM-powered path with this documented ceiling.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →