Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Bring your own raster time series

This example converts georeferenced rasters on one grid into a chronozarr store and reads one pixel's values over time in Python. It then builds a static folder with the store, a viewer and an embed page. You can upload that folder to any static host. The folder needs no request to chronozarr.org or to its demo stores. The bundle script copies the JavaScript dependencies, including the GIF encoder, into it. You need a checkout of this repository, uv with Python 3.11 or newer, and Node.js with Chromium for the optional browser check in step 8.

1. Install

Run this command from the repository root.

uv sync --extra geo

This checkout uses the 0.4.0 source version from the repository. For a standalone install, use python -m pip install --upgrade "chronozarr[geo]==0.4.0" instead. Python 3.11 or newer is required.

The bundle script below needs this checkout because it copies the viewer from js/; it needs no npm install or build. To copy the viewer from the npm package instead, see self-host the packaged viewer.

2. Prepare dated GeoTIFFs

Put one GeoTIFF per timestep in a directory. Each filename must contain exactly one date, such as 20240131, 2024-01-31 or 2024-01:

rasters/
  ndvi_2024-01.tif
  ndvi_2024-02.tif
  ndvi_2024-03.tif

Use the same north-up grid in every file: CRS, width and height, pixel size and transform, band count and names, dtype, scale, offset, units and nodata metadata. Band names such as red, green, blue and nir let the viewer offer matching products. A single measured variable also works.

For another filename pattern, pass --date-pattern, for example --date-pattern "ndvi_%Y%m%d". A directory is not searched recursively; use a glob such as rasters/**/*.tif for subdirectories.

3. Convert, validate and preview

  1. Print the plan. A dry run writes nothing.

    uv run chronozarr convert "rasters/*.tif" /tmp/my-series --dry-run
  2. Convert the observations into a new output directory.

    uv run chronozarr convert "rasters/*.tif" /tmp/my-series
  3. Validate the store against the spec.

    uv run chronozarr validate /tmp/my-series
  4. Run doctor on the local store.

    uv run chronozarr doctor /tmp/my-series
  5. Open the store in the viewer before you upload it.

    uv run chronozarr preview /tmp/my-series

    The preview serves the store on localhost and opens the viewer. Press Ctrl-C to stop it before starting another command in the same terminal. To share a temporary view from your laptop, use uv run chronozarr share /tmp/my-series; it needs cloudflared, and the terminal and laptop must stay running while the recipient uses the link. The requested data is transferred from your laptop to the recipient's browser. In the viewer, choose the intended view and use Copy link to share it. The recipient needs only a modern browser. See the sharing walkthrough for setup and public-link limits.

4. Read a pixel's history

uv run python examples/bring_your_data/read_series.py /tmp/my-series --row 20 --col 30

The script opens the store with the lazy xarray backend and selects one pixel before it reads values. It prints physical values, which are the stored values after scale and offset, with NaN for invalid values. Add physical=False to xr.open_dataset to get the stored values. Over HTTP, a pixel history transfers one whole chunk per date.

5. Advanced inputs and source checks

To convert an existing Zarr or NetCDF time series, use its path as the source and add --variable NAME where needed. NetCDF input needs uv sync --extra geo --extra netcdf. For rendered PNG frames, follow the PNG georeferencing guide. A PNG holds display colors, and the converter stores them unchanged.

For a source audit, a manifest can name each COG and its date. Save this optional advanced manifest as observations.csv; the directory/glob flow above is the main onboarding route.

uri,datetime
January.tif,2024-01-01
February.tif,2024-02-01

The checks read one copy of the store with chronozarr and another with plain xarray and Zarr code, so convert the manifest a second time.

  1. Convert the second copy.

    uv run chronozarr convert observations.csv /tmp/my-plain
  2. Compare every value with the source COGs.

    uv run python examples/bring_your_data/verify.py observations.csv \
      /tmp/my-series /tmp/my-plain /tmp/my-verification.json
  3. Time full reads of all dates.

    uv run python examples/bring_your_data/compare_local.py observations.csv \
      /tmp/my-series /tmp/my-plain /tmp/my-comparison.json

verify.py compares every level-0 value and mask with the COGs and checks the physical scaling. It leaves the COG overviews out. The Sentinel-2 sample COGs are resampled to 10 m, so they differ from the original scenes. compare_local.py times level-0 reads with the COG, chronozarr and plain Zarr readers on local disk. It uses fresh file handles on warm caches and rotates the reader order. The --repeats option sets the number of repeats, which is 5 by default.

6. Build the folder and preview it

  1. Copy the store and the viewer into a new folder.

    uv run python examples/bring_your_data/bundle.py /tmp/my-series /tmp/my-published-series
  2. Start the preview server.

    uv run python examples/bring_your_data/serve.py /tmp/my-published-series --port 8000
  3. Open the Viewer and Embed URLs that the server prints.

The embed page has a slider, a product menu, play and pause buttons and a pixel readout. They control a self-hosted iframe through the chronozarr:* messages in embedding. bundle.py copies the validated store unchanged, so allow disk space for a second copy. It stops if the output folder exists or lies inside the input store. The folder holds store/, index.html, examples/embed.html, bundle.json (the reader version, the object count and the size) and the viewer code. The preview server binds to localhost, so use it only for previews.

7. Publish to your own host

  1. Upload the contents of /tmp/my-published-series to a static host and keep the directory structure. The folder works at a domain root or under a subdirectory.

  2. Serve JavaScript with the JavaScript MIME type and enable byte ranges, which a sharded store needs. Configure CORS if an application on another origin reads the store or imports the modules. See hosting.

  3. Check the hosted store.

    uv run chronozarr doctor https://your-host.example/my-published-series/store
  4. Read one pixel from the hosted store.

    uv run python examples/bring_your_data/read_series.py \
      https://your-host.example/my-published-series/store --row 20 --col 30

The recipe covers public data and data that the host of the page can read. Authentication and expiring tokens need extra work, so do not publish private rasters in this folder.

8. Run the browser check (optional)

  1. Install the browser tooling once.

    npm ci --prefix js
    (cd js && npx playwright install chromium)
  2. Leave the preview server running. Run the check with the base URL that the server printed, without index.html.

    node examples/bring_your_data/check.mjs http://127.0.0.1:8000/my-published-series/

The store needs at least two observations. The check renders the full viewer and moves the host slider until the embedded viewer shows the second observation. It also selects a product, clicks a pixel, and presses play and pause. It blocks requests to other origins and fails on browser errors.

Further checks and problem reports

The extended/, large/ and http_stress/ folders hold further check scripts for twelve dates, a larger source, appending a date and failed requests. When you report a problem, include your source type, the dimensions, bands and dates, your browser and host, and the conversion command and any error. Add the bundle.json and chronozarr doctor output. Remove credentials and signed URLs.