How to Query Files Within the GeoLibre Project: Zarr Raster API Guide

Use the queryZarrLayer function from @geolibre/plugins to read values from Zarr raster layers by passing a layer ID and GeoJSON geometry (Point for clicks, Polygon for regions).

GeoLibre is an open-source geospatial monorepo combining a React frontend, Tauri desktop shell, and Python FastAPI sidecar. When you need to query file contents—specifically values from cloud-optimized Zarr raster layers—the project exposes a unified API through its Zustand-based state management system. This guide walks through the complete query workflow as implemented in the opengeos/GeoLibre source code.

The queryZarrLayer API

All Zarr querying flows through the high-level queryZarrLayer function, exported from the plugins package.

Function Signature

As defined in packages/plugins/src/plugins/maplibre-components.ts (lines 33–45):

export async function queryZarrLayer(
  layerId: string,
  geometry: QueryGeometry,
  selector?: Selector,
  options?: QueryOptions,
): Promise<QueryResult | null>

Parameter Reference

Parameter Type Description
layerId string Identifier returned by addZarrRasterLayer when the layer was registered
geometry GeoJSON.Point or Polygon/MultiPolygon Query region in WGS-84 coordinates [lng, lat]
selector Selector (optional) Dimension overrides, e.g., { time: 12 } to read a non-current time slice
options QueryOptions (optional) Configuration including AbortSignal and includeSpatialCoordinates flag

Implementation Details

The function performs a lookup in the internal zarrControl map and delegates to the layer's queryData method. This design means:

  • Plugins do not need to import the low-level @carbonplan/zarr-layer reader directly
  • CRS reprojection is handled once by the renderer, avoiding duplicated coordinate transformations
  • Missing layers or layers without queryData resolve to null rather than throwing

Query Types and Code Examples

Single-Point Value Queries (Click-to-Value)

Fetch the raster value at a specific map coordinate:

import { queryZarrLayer } from "@geolibre/plugins";

async function getValueAtClick(layerId: string, lng: number, lat: number) {
  const point = { type: "Point", coordinates: [lng, lat] as [number, number] };
  const result = await queryZarrLayer(layerId, point);
  console.log("Raster values at click:", result?.values);
}

The result.values array contains the raw cell value(s). For multi-band rasters, this contains one value per band.

Polygon Region Statistics

Aggregate values across an area of interest:

async function getStatsForPolygon(layerId: string, polygonGeoJSON: GeoJSON.Polygon) {
  const result = await queryZarrLayer(layerId, polygonGeoJSON);
  const values = result?.values ?? [];
  
  // Compute your own statistics—the API returns raw pixel arrays
  const mean = values.reduce((a, b) => a + b, 0) / values.length;
  console.log(`Mean value across ${values.length} pixels:`, mean);
}

The renderer performs spatial masking and returns only valid data cells. Empty arrays indicate the polygon falls outside raster bounds or intersects only no-data cells.

Time-Dimension Override Queries

Read data from a specific time step without changing the visible layer state:

async function readTimeSlice(
  layerId: string, 
  point: GeoJSON.Point, 
  timeStep: number
) {
  const selector = { time: timeStep };
  const result = await queryZarrLayer(layerId, point, selector);
  console.log(`Values at time index ${timeStep}:`, result?.values);
}

This bypasses the Time Slider component's current position while maintaining the same spatial query geometry.

Cancellable Queries with AbortSignal

Prevent stale results during rapid user interactions:

async function queryWithAbort(layerId: string, geometry: QueryGeometry) {
  const controller = new AbortController();
  
  const resultPromise = queryZarrLayer(layerId, geometry, undefined, {
    signal: controller.signal,
  });
  
  // Abort after timeout or user action
  setTimeout(() => controller.abort(), 2000);
  
  try {
    const result = await resultPromise;
    return result;
  } catch (err) {
    if (err.name === 'AbortError') {
      console.log("Query cancelled by user");
    }
    throw err;
  }
}

Cancelled queries reject with an AbortError. Always handle this case in UI components to prevent error state displays.

Architecture: How Queries Flow Through GeoLibre

Understanding the data flow helps debug failed queries and extend the system.

Step 1: Layer Registration

When you add a Zarr raster via addZarrRasterLayer, the function:

  1. Creates a ZarrLayer instance with the configured Zarr store URL
  2. Registers it in the Zustand store (@geolibre/core)
  3. Adds it to the internal zarrControl Map accessible to plugins

Step 2: Renderer Initialization

The ZarrLayer renderer:

  • Loads initial data chunks asynchronously
  • Builds internal coordinate transformation pipelines
  • Exposes the queryData method used by queryZarrLayer

Important: Queries during initial chunk loading return empty arrays rather than errors. Check result.values.length to distinguish "no data" from "not yet loaded."

Step 3: Temporal Adapter Synchronization

For time-enabled cubes, the registerZarrTemporalAdapter function (same source file) connects the Time Slider to the Zarr time axis. It reads the private dimensionValues property via readZarrDimensionValues, ensuring the UI slider matches available time steps without exposing Zarr internals to React components.

Error Handling and Edge Cases

GeoLibre's query implementation has specific behaviors for edge conditions:

  • Out-of-bounds geometry: Returns QueryResult with empty values array
  • Layer not found: Resolves to null
  • Layer loading: Empty values until first chunks arrive
  • Missing data cells: Omitted from values array (sparse representation)
  • Aborted request: Promise rejects with AbortError

Always validate result is non-null before accessing properties, and handle empty values arrays appropriately in your UI.

Source File Reference

File Purpose
packages/plugins/src/plugins/maplibre-components.ts queryZarrLayer implementation and Zarr utilities
packages/plugins/src/types.ts TypeScript definitions for QueryResult, QueryGeometry, Selector
apps/geolibre-desktop/src/hooks/usePlugins.ts React hook integrating plugin API into UI components
tests/zarr-layer-api.test.ts Test coverage for query behavior and edge cases
docs/architecture.md Monorepo structure and state management overview

Summary

  • Import queryZarrLayer from @geolibre/plugins for all Zarr raster queries
  • Pass WGS-84 GeoJSON geometries—Points for single values, Polygons for regions
  • Use the selector parameter to read non-current time slices without UI changes
  • Include an AbortSignal in options for cancellable queries in responsive UIs
  • Check for null results (missing layer) and empty values arrays (out of bounds or loading)
  • The renderer handles all CRS transformations—don't duplicate this work by accessing @carbonplan/zarr-layer directly

Frequently Asked Questions

What coordinate system does queryZarrLayer expect?

All geometries must be supplied in WGS-84 (EPSG:4326) with longitude-latitude coordinate order: [lng, lat]. The renderer internally reprojects to the raster's native CRS. Passing projected coordinates produces incorrect query locations.

Why does my query return null instead of values?

queryZarrLayer returns null when the layerId does not exist in the internal zarrControl map. Verify the layer was successfully added via addZarrRasterLayer and that you're using the exact ID string returned by that function.

How do I query a specific time step without changing the visible layer?

Pass a selector object as the third argument: await queryZarrLayer(id, geometry, { time: 5 }). The time index is zero-based and corresponds to the Zarr array's time dimension. The visible layer state remains unchanged.

Can I query multiple rasters simultaneously?

The API handles one layer per call. For concurrent queries, use Promise.all with separate queryZarrLayer invocations. Each maintains its own AbortSignal for independent cancellation control.

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 →