npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

chronozarr

v0.4.1

Published

Read chronozarr v0.3 stores in the browser and Node, draw them on MapLibre GL JS, and self-host the chronozarr viewer

Readme

chronozarr

Browser and Node reader for chronozarr stores. The package also has a MapLibre GL JS custom layer that draws a store.

A chronozarr store is a Zarr v3 time series of rasters with a multiscale pyramid. The writer makes one object per chunk by default and can shard the chunks. A client reads one timestep of one map cell with one HTTP request. For an unsharded store that request is a plain GET of one chunk. For a sharded store it is one range read of a shard, once the shard index is cached.

The reader turns (lod, row, col, t) into a typed array. It caches the shard indexes of sharded stores, decodes in a worker pool and prefetches a window of the time axis around the current timestep. It reads spec 0.3 stores (spec). The Python package chronozarr writes them.

The package is plain ES modules with no build step and no runtime dependency, because zarrita and numcodecs are vendored (see Licenses).

The viewer loads nothing from a third-party host at runtime and uses no web font. The MapLibre demo page, js/maplibre/index.html, loads a pinned version of maplibre-gl from a CDN.

Install

npm install chronozarr
import { openStore } from 'chronozarr';
import { ChronozarrLayer } from 'chronozarr/maplibre';

Without a bundler, map the names in an import map. Either point them into node_modules:

<script type="importmap">
{ "imports": {
  "chronozarr": "/node_modules/chronozarr/chronozarr/decoder.js",
  "chronozarr/maplibre": "/node_modules/chronozarr/maplibre/layer.js"
} }
</script>

or at a CDN:

<script type="importmap">
{ "imports": {
  "chronozarr": "https://cdn.jsdelivr.net/npm/[email protected]/chronozarr/decoder.js",
  "chronozarr/maplibre": "https://cdn.jsdelivr.net/npm/[email protected]/maplibre/layer.js"
} }
</script>

| Import | Content | |---|---| | chronozarr | openStore(url, options) and the ChronoStore that it resolves to | | chronozarr/maplibre | ChronozarrLayer, a MapLibre GL JS custom layer | | chronozarr/decode-worker | The module worker that openStore starts for decoding | | chronozarr/geolibre | createChronozarrPlugin, a GeoLibre plugin that adds a store as a MapLibre layer |

The chronozarr import also exports the helpers chunkKey, defaultTotalBytes, FetchError, samplePixelFrom, scrubCost and windowOrder. import.meta.resolve('chronozarr/decode-worker') returns the URL of the worker, for example for the spawnWorker option.

openStore finds the decode worker relative to decoder.js, with new URL('./decode-worker.js', import.meta.url). The worker therefore loads from wherever the package files are served: node_modules, a static host or a CDN.

A script from a CDN is cross-origin. The reader starts it through a same-origin blob: URL that imports it. The CDN must send CORS headers, and jsDelivr and unpkg do. A page with a Content Security Policy needs worker-src blob:.

In Node, and with { workers: 0 }, chunks decode on the calling thread.

Read a store

import { openStore } from 'chronozarr';

const store = await openStore('https://your-host/v03-store');
console.log(store.times.length, store.bands, store.dtype, store.crs);
// 117 [ 'B02', 'B03', 'B04', 'B08' ] uint16 EPSG:32718

const lod = store.levels.length - 1; // the coarsest pyramid level
const { data, chunkWidth, chunkHeight } = await store.getCell(lod, 0, 0, 5); // row 0, col 0, timestep 5
// data: typed array of the store's dtype, laid out [band][y][x] over the padded chunk. Do not write to it: the cache holds this array.
const stored = (band, y, x) => data[band * chunkHeight * chunkWidth + y * chunkWidth + x];
const { scale, offset } = store.attrs.bands[2]; // B04
console.log(stored(2, 100, 100) * scale + offset); // reflectance

store.close(); // aborts in-flight requests and releases the decode workers

getCell returns the exact stored values at every timestep. store.levels[lod] describes each pyramid level with gridRows, gridCols, width, height, resolution and transform. store.prefetch({ lod, cells, t }) fills the caches around a timestep. store.stats() reports requests, bytes and cache hits.

prefetch takes playing: true to extend the window to the whole loop. It takes masks: true to fetch the masks alongside the chunks.

openStore takes these options. chronozarr/decoder.js documents all of them.

| Option | Default | Meaning | |---|---|---| | fetch | globalThis.fetch | The fetch implementation. | | workers | cores minus 1, at most 8 | The number of decode workers. With 0 the calling thread decodes. | | totalBytes | 1.5 GiB on machines that report 8 GB or more, else 768 MiB | The joint cap for the decoded and compressed tiers. | | horizonSteps | 12 | The timesteps on either side of t that idle prefetch covers. | | idleBytes | 64 MiB | The bytes that idle prefetch may start per idle episode. | | idleMs | 3000 | The quiet time after the last scrub or playback before the viewer counts as idle. | | maxRequests | 12 | The cap on concurrent requests to the store. | | retryDelaysMs | [200, 600, 1500] | The delay before each retry of a failed request. |

The host must send CORS headers. A sharded store also needs byte-range requests, and an unsharded store needs only GET. chronozarr doctor <url> from the Python package checks the host. The hosting requirements has the details.

Draw a store on a MapLibre map

import * as maplibregl from 'maplibre-gl';
import { ChronozarrLayer } from 'chronozarr/maplibre';

const map = new maplibregl.Map({ container: 'map', style: 'https://demotiles.maplibre.org/style.json' });
const layer = new ChronozarrLayer({
  url: 'https://your-host/v03-store', // store root (holds zarr.json)
  product: 'true_color', // layer.products lists what the store's bands support
  t: 0, // timestep index
  prefetch: true, // fill the reader's caches around t in the background
});
layer.on('open', () => map.fitBounds(layer.bounds, { padding: 40, duration: 0 }));
layer.on('error', (event) => console.error(event.error));
map.on('load', () => map.addLayer(layer));

slider.oninput = () => layer.setTime(Number(slider.value)); // no refetch when the timestep is cached
button.onclick = () => layer.setProduct('ndvi');
map.on('click', async (event) => console.log(await layer.getValueAt(event.lngLat))); // stored values of the pixel

The layer needs MapLibre GL JS 5 or later. The host page loads MapLibre, which is not a dependency of this package. The tested version is in evidence.md.

The layer draws on the Web Mercator projection. It supports stores in UTM, EPSG:3857 and EPSG:4326. Options, events, limits and the level-of-detail rule are in js/maplibre/README.md.

Licenses

chronozarr is Apache-2.0 (LICENSE). The package vendors four MIT-licensed packages.

Trevor Manz wrote three of them: zarrita, @zarrita/storage and numcodecs. They are vendored unmodified apart from a header comment, and their licenses sit beside them. The header of each file records the package version and the SHA-256 of the published file.

| Path | Package | Version | License | |---|---|---|---| | vendor/zarrita/ | zarrita | 0.7.5 | MIT | | vendor/zarrita-storage/ | @zarrita/storage | 0.2.0 | MIT | | vendor/numcodecs/ | numcodecs | 0.3.2 | MIT | | vendor/gifenc.esm.js | gifenc | 1.0.3 | MIT |

The viewer export uses gifenc, written by Matt DesLauriers. It ships as one file, and its license text is in the header of that file.

vendor/numcodecs/blosc.js, lz4.js and zstd.js embed WebAssembly builds of Blosc (with its bundled zlib and snappy), LZ4 and Zstandard. Those C libraries carry their own permissive upstream licenses, BSD-style and zlib. numcodecs ships no separate notice for them.