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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@carbonplan/zarr-layer

v0.0.2

Published

MapLibre/Mapbox custom layer for rendering Zarr datasets. Inspired by zarr-cesium, zarr-gl, and @carbonplan/maps.

Readme

@carbonplan/zarr-layer

NPM Version License: MIT

Custom layer for rendering Zarr datasets in MapLibre or Mapbox GL, inspired (and borrowing significant code and concepts from) zarr-gl, zarr-cesium, and @carbonplan/maps. Uses CustomLayerInterface to render data directly to the map and supports globe and mercator projections for both Maplibre and Mapbox.

This is an active experiment so expect to run into some bugs! Please report them.

demo

See the demo for a quick tour of capabilities. Code for the demo is in the /demo folder.

data requirements

Supports v2 and v3 zarr stores via zarrita.

For best performance, tiled data (EPSG:3857 or 4326) is preferred (see ndpyramid). The library also supports datasets that are untiled (4326 or 3857 coordinate systems) and tries to load chunks efficiently based on viewport intersections. Support for the emerging multiscales convention (non-slippy map conforming) is experimental!

install

npm install @carbonplan/zarr-layer

build locally

npm install
npm run build

usage

import maplibregl from 'maplibre-gl' // or mapbox
import { ZarrLayer } from '@carbonplan/zarr-layer'

const map = new maplibregl.Map({container: 'map'})
const layer = new ZarrLayer({
  id: 'zarr-layer',
  source: 'https://example.com/my.zarr',
  variable: 'temperature',
  clim: [270, 310],
  colormap: ['#000000', '#ffffff', ...],
  selector: { month: 1 },
})
map.on('load', () => {
  map.addLayer(layer)
  // optionally add before id to slot data into map layer stack.
  // map.addLayer(layer, 'beforeID')
})

options

Required: | Option | Type | Description | |--------|------|-------------| | id | string | Unique layer identifier | | source | string | Zarr store URL | | variable | string | Variable name to render | | colormap | array | Array of hex strings or [r,g,b] values | | clim | [min, max] | Color scale limits |

Optional: | Option | Type | Default | Description | |--------|------|---------|-------------| | selector | object | {} | Dimension selector (unspecified dims default to index 0) | | opacity | number | 1 | Layer opacity (0-1) | | zarrVersion | 2 | 3 | auto | Zarr format version (tries v3 first, falls back to v2) | | minzoom | number | 0 | Minimum zoom level for rendering | | maxzoom | number | Infinity | Maximum zoom level for rendering | | fillValue | number | auto | No-data value (from metadata if not set) | | spatialDimensions | object | auto | Custom { lat, lon } dim names | | bounds | array | auto | [west, south, east, north] in degrees, used for single image placement | | latIsAscending | boolean | auto | Latitude orientation | | renderingMode | '2d' | '3d' | '3d' | Custom layer rendering mode | | customFrag | string | - | Custom fragment shader | | uniforms | object | - | Shader uniform values (requires customFrag) | | onLoadingStateChange | function | - | Loading state callback | | throttleMs | number | 100 | Throttle interval (ms) for data fetching during rapid selector changes. Set to 0 to disable. |

methods

layer.setOpacity(0.8)
layer.setClim([0, 100])
layer.setColormap(['#000', '#fff'])
layer.setSelector({ time: 5 })
layer.setVariable('precipitation') // async - reloads metadata
layer.setUniforms({ u_weight: 1.5 }) // no-op unless layer has customFrag

selectors

Selectors specify which slice of your multidimensional data to render. Dimensions not specified default to index 0.

Basic syntax:

// Simple value - matches exact value in coordinate array
{ time: 5 }
{ time: '2024-01-15' }

// Explicit index - uses array index directly (no coordinate lookup)
{ time: { selected: 5, type: 'index' } }

// Explicit value - same as simple syntax, matches exact value
{ time: { selected: 5, type: 'value' } }

Multi-band selection (for custom shaders):

// String values use the string directly as the shader variable name
{ band: ['tavg', 'prec'] }
// exposes as: tavg, prec

// Numeric values are prefixed with the dimension key (required for valid GLSL identifiers)
{ month: [1, 2, 3] }
// exposes as: month_1, month_2, month_3

// Mix with other dimensions
{ band: ['red', 'green', 'blue'], time: 0 }

Query-specific selectors:

// Array of values for time series queries
const result = await layer.queryData(
  { type: 'Point', coordinates: [lng, lat] },
  { time: [0, 1, 2, 3, 4] } // returns data for all 5 time steps
)

Type options:

| Type | Behavior | | ------------------- | ------------------------------------------------------------- | | 'value' (default) | Matches exact value in coordinate array (throws if not found) | | 'index' | Uses value directly as array index |

custom shaders and uniforms

Custom fragment shaders let you do math on your data to change how it's displayed. This can be useful for things like log scales, combining bands, or aggregating data over a time window. Note that in order to access different bands/time slices etc., the data need to be in the same chunk. You can pass in uniforms to allow user interaction to influence the custom shader code.

new ZarrLayer({
  // ...
  customFrag: `
    uniform float u_weight;
    float val = band_a * u_weight;
    float norm = (val - clim.x) / (clim.y - clim.x);
    vec4 c = texture(colormap, vec2(clamp(norm, 0.0, 1.0), 0.5));
    fragColor = vec4(c.rgb, opacity);
  `,
  uniforms: { u_weight: 1.0 },
})

queries

Supports Point, Polygon, and MultiPolygon geometries in geojson format. You can optionally pass in a custom selector to override the visualization selector.

// Point query
const result = await layer.queryData(
  { type: 'Point', coordinates: [lng, lat] },
  // optional selector override (useful for e.g. time series creation)
  { time: [0, 1, 2] }
)

// Polygon query
const result = await layer.queryData({
  type: 'Polygon',
  coordinates: [[...]],
})

// Returns:
// {
//   [variable]: number[],
//   dimensions: ['lat', 'lon'],
//   coordinates: { lat: number[], lon: number[] }
// }

Note: Query results match rendered values (scale_factor/add_offset applied, fillValue/NaN filtered).

thanks

This experiment is only possible following in the footsteps of other work in this space. zarr-gl showed that custom layers are a viable rendering option and zarr-cesium showed how flexible web rendering can be. We borrow code and concepts from both. This library also leans on our prior work on @carbonplan/maps for many of its patterns. LLMs of several makes aided in the coding and debugging of this library.

license

All the code in this repository is MIT-licensed, but we request that you please provide attribution if reusing any of our digital content (graphics, logo, articles, etc.).

about us

CarbonPlan is a nonprofit organization that uses data and science for climate action. We aim to improve the transparency and scientific integrity of climate solutions with open data and tools. Find out more at carbonplan.org or get in touch by opening an issue or sending us an email.