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

> Query Zarr raster layers in GeoLibre using the queryZarrLayer API. Access raster data with layer IDs and GeoJSON geometries for points or polygons.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: how-to-guide
- Published: 2026-08-18

---

**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`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/maplibre-components.ts) (lines 33–45):

```ts
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:

```ts
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:

```ts
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:

```ts
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:

```ts
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`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/maplibre-components.ts) | `queryZarrLayer` implementation and Zarr utilities |
| [`packages/plugins/src/types.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/types.ts) | TypeScript definitions for `QueryResult`, `QueryGeometry`, `Selector` |
| [`apps/geolibre-desktop/src/hooks/usePlugins.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/hooks/usePlugins.ts) | React hook integrating plugin API into UI components |
| [`tests/zarr-layer-api.test.ts`](https://github.com/opengeos/GeoLibre/blob/main/tests/zarr-layer-api.test.ts) | Test coverage for query behavior and edge cases |
| [`docs/architecture.md`](https://github.com/opengeos/GeoLibre/blob/main/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.