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

@fxi/zartigl

v0.1.4

Published

MapLibre GL JS plugin for animated particle flow from Zarr ocean current data

Readme

zartigl

A MapLibre GL JS plugin for rendering animated particle flow from Zarr-based ocean current data directly in the browser.

Demo

Features

  • GPU-accelerated particle simulation (WebGL ping-pong framebuffers)
  • Reads Zarr v2 stores directly in the browser (blosc + zlib decompression)
  • Streams data from Copernicus Marine ARCO S3 — no local data or server needed
  • Viewport-aware chunk loading with LRU cache
  • Speed-based particle coloring via configurable color ramp
  • Trail rendering with adjustable fade opacity

Quick start

npm install zartigl maplibre-gl
import maplibregl from "maplibre-gl";
import { ParticleLayer } from "zartigl";

const map = new maplibregl.Map({ container: "map", style: "..." });

map.on("load", () => {
  map.addLayer(
    new ParticleLayer({
      id: "currents",
      source: "https://your-zarr-store/",
      variableU: "uo",
      variableV: "vo",
    }),
  );
});

Demo

npm install
npm run dev

The demo reads a catalog (public/data/catalog.json) that points to the Copernicus Marine ARCO Zarr store on S3. No local data download is needed — chunks are fetched directly from Copernicus (public, CORS enabled).

To update the catalog metadata:

cd scripts
uv run build_catalog.py

This requires Python >= 3.12, uv, and a free Copernicus Marine account.

Dataset

GLOBAL_ANALYSISFORECAST_PHY_001_024 from Copernicus Marine Service — uo/vo surface current velocity at ~0.083deg resolution, streamed as ARCO Zarr directly from Copernicus S3.

Other use cases

Any 2D vector field stored as U/V components in a Zarr store works as a source. Examples:

  • Atmospheric wind — reanalysis products (ERA5, MERRA-2) or NWP model output; visualise jet streams, storm tracks, or surface winds at any pressure level
  • Ocean surface waves — Stokes drift or wave propagation direction fields
  • Ice drift — sea-ice motion vectors from passive microwave retrievals
  • River discharge / flood routing — depth-averaged U/V from hydrodynamic models (Delft3D, SCHISM)
  • Air quality transport — horizontal advection of pollutant plumes from dispersion models
  • Climate projections — CMIP6 ocean or atmosphere circulation at any time slice; compare historical vs. SSP scenarios
  • Groundwater flow — Darcy velocity fields from subsurface models
  • Wildfire smoke — wind-driven particle transport overlaid on active fire data

The only requirement is a Zarr v2 store with two named float variables representing the east and north components of the field.

How it works

A plain-language walkthrough for GIS analysts unfamiliar with WebGL or Zarr.

1. Zarr is a tiled raster cube

Think of a Cloud Optimized GeoTIFF extended into four dimensions: longitude × latitude × depth × time. The data is split into chunks — here: 1 time step × 1 depth level × 512 rows × 2048 columns. Zartigl requests only the chunks that intersect the current viewport at the selected time and depth, exactly like a COG tile request.

2. Velocity becomes a two-channel raster

Each chunk holds two float arrays — U (eastward m/s) and V (northward m/s). These are normalized and packed into the R and G channels of an image uploaded to the GPU:

R = eastward velocity  (0 → max west, 128 → still, 255 → max east)
G = northward velocity
A = 255 (ocean) | 0 (land / nodata)   ← validity mask

The result is a global velocity raster in GPU memory. The alpha channel is a nodata mask.

3. Particles are virtual drifters

Thousands of virtual oceanographic drifters are seeded randomly across the viewport. Every frame (~60 fps), the GPU runs this logic for all of them simultaneously:

Where am I? → sample the velocity raster at my position → move a tiny step in that direction → if I drift off-screen or hit land (A = 0), teleport to a random ocean pixel inside the viewport.

Same logic as a drift model — just running on 65 000+ drifters at 60 frames per second.

4. Trails are faded time-lapse

Before each new frame, the previous frame is multiplied by ~0.9999. Particles leave a fading streak — like a long-exposure photograph of drifters. Streak length encodes time; color encodes speed magnitude via a configurable color ramp.

5. Particle state lives in GPU memory

Drifter positions are never stored in a CPU array. They are encoded as pixel colors in a GPU texture (X → red, Y → blue, sub-pixel precision in the low byte). Two textures alternate each frame — read from A, write to B, swap — so the GPU never waits on itself. This is the ping-pong pattern.

Pipeline at a glance

| Step | What happens | GIS analogy | |---|---|---| | Fetch | Request Zarr chunks for viewport + time + depth | COG tile request | | Pack | Encode U/V floats into R/G/A texture | Raster → GPU image | | Seed | Place N drifters randomly in viewport | Random point sampling | | Advect | Each frame: sample velocity, move each drifter | Drift model at 60 fps | | Mask | Discard drifters over land (A = 0) | Nodata mask | | Fade | Dim previous frame slightly | Long-exposure photography | | Color | Map speed to color ramp | Graduated color by value | | Composite | Blend result over basemap | Raster layer blending |

License

MIT