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

@floodnet/deck.gl-time-cog-layer

v0.2.13

Published

A deck.gl CompositeLayer for time-indexed Cloud-Optimized GeoTIFF playback.

Readme

deck.gl - Time-COGs: Smooth playback of time-indexed Cloud-Optimized GeoTIFF (COG) sequences

A deck.gl CompositeLayer for smooth playback of time-indexed Cloud-Optimized GeoTIFF (COG) sequences. Designed for weather radar, satellite imagery, and other regularly-sampled raster time series, building upon @developmentseed/deck.gl-raster.

npm install @floodnet/deck.gl-time-cog-layer

Demo


Quick start

import { Deck } from "@deck.gl/core";
import { TimeCOGLayer } from "@floodnet/deck.gl-time-cog-layer";

const deck = new Deck({
  layers: [
    new TimeCOGLayer({
      id: "precip",
      data: [
        { time: "2025-10-30T00:00:00Z", url: "/cogs/000.tif" },
        { time: "2025-10-30T00:02:00Z", url: "/cogs/002.tif" },
        { time: "2025-10-30T00:04:00Z", url: "/cogs/004.tif" },
      ],
      currentTime: Date.now(),
      playing: true,
      playbackRate: 60,  // 60× real-time (1 minute per second)
      getTileData: async (image, { device, x, y, signal, pool }) => {
        const tile = await image.fetchTile(x, y, { pool, signal });
        const texture = device.createTexture({ data: tile.array.data, /* ... */ });
        return { texture, width: tile.array.width, height: tile.array.height };
      },
      renderTile: (data) => ({
        renderPipeline: [
          { module: CreateTexture, props: { textureName: data.texture } },
          { module: ColorRamp },
        ],
      }),
    }),
  ],
});


const ColorRamp = {
  name: "precip-color-ramp",
  inject: {
    "fs:DECKGL_FILTER_COLOR": `
float rawValue = color.r * 65535.0;
if (rawValue <= 0.0) { discard; }

float t = clamp(rawValue, 0.0, 1.0);
t = pow(t, 0.72);

vec3 c0 = vec3(0.56, 0.77, 0.98);
vec3 c1 = vec3(0.10, 0.95, 0.86);
vec3 c2 = vec3(0.32, 0.98, 0.45);
vec3 c3 = vec3(0.96, 0.84, 0.20);
vec3 c4 = vec3(0.98, 0.38, 0.76);
vec3 c5 = vec3(0.98, 0.75, 0.93);

vec3 ramp;
if (t < 0.18) {        ramp = mix(c0, c1, smoothstep(0.0, 0.18, t));
} else if (t < 0.42) { ramp = mix(c1, c2, smoothstep(0.18, 0.42, t));
} else if (t < 0.68) { ramp = mix(c2, c3, smoothstep(0.42, 0.68, t));
} else if (t < 0.88) { ramp = mix(c3, c4, smoothstep(0.68, 0.88, t));
} else {               ramp = mix(c4, c5, smoothstep(0.88, 1.0, t));
}

float alpha = smoothstep(0.0, 0.06, t) * (0.20 + 0.70 * sqrt(t));
color = vec4(ramp, alpha);
`,
  },
} as const;

getTileData and renderTile follow the same signatures as @developmentseed/deck.gl-geotiff's COGLayer. Any existing COG render pipeline works unchanged.

Customizing frame data access

If your data is in a different shape (e.g. a GeoJSON feature collection or API response), use getTime and getUrl accessors to extract the timestamp and COG URL:

type Feature = { properties: { timestamp: string; cog_url: string } };

const features: Feature[] = await fetchCatalog();

new TimeCOGLayer({
  data: features,
  getTime: (f) => f.properties.timestamp,
  getUrl:  (f) => f.properties.cog_url,
  currentTime: Date.now(),
  // ...
});

All other fields on each item (e.g. id, meta, byteSizeHint) are still picked up automatically if they exist.

If getTime or getUrl depend on reactive state, signal that through updateTriggers so the catalog is re-normalized when they change:

updateTriggers: {
  getTime: [dependency],
  getUrl:  [dependency],
}

Props

| Prop | Type | Default | Description | |---|---|---|---| | data | TFrame[] | — | Ordered list of frame entries (any shape when getTime/getUrl are provided) | | getTime | (frame: TFrame) => number \| string \| Date | — | Extracts the timestamp from each frame item; falls back to frame.time when omitted | | getUrl | (frame: TFrame) => string \| URL | — | Extracts the COG URL from each frame item; falls back to frame.url when omitted | | currentTime | number \| string \| Date | — | Current playback time (epoch ms, ISO-8601, or Date) | | playing | boolean | false | Whether playback is active | | playbackRate | number | 0 | Speed multiplier (e.g. 60 = 1 minute of data per real second) | | maxFrameRate | number | 0 | Maximum display frame rate during playback (0 = unlimited) | | missingFramePolicy | "hold-last" \| "nearest" \| "skip" \| "transparent" | "hold-last" | How to resolve a time between catalog entries | | bufferPolicy | object | { backwardFrames: 2, forwardFrames: 6 } | How many frames to prefetch ahead / retain behind | | cachePolicy | object | {} | Tile cache limits — memoryBytes, maxFrames, maxTiles | | qualityPolicy | object | {} | Progressive loading behaviour — lowResFirst, previewOverviewBias, scrubOverviewBias, fullResUpgradeIdleMs | | schedulerPolicy | object | { maxNetworkRequests: 4 } | Prefetch concurrency (maxNetworkRequests, maxDecodeTasks, maxGpuUploadsPerFrame), optional cadence snapping (frameRateSnap), multiscale temporal bias (multiscaleLevelPenalty), and optional scoring weights | | descriptorMode | "reuse-first" \| "manifest" | "reuse-first" | How the shared tileset descriptor is determined | | descriptorManifest | object | — | Pre-declared GeoTIFF structure (required with descriptorMode: "manifest") | | getTileData | (image, options) => Promise<DataT> | — | As COGLayer's getTileData | | renderTile | (data: DataT) => RenderTileResult | — | As COGLayer's renderTile | | onFrameDisplayed | (frame) => void | — | Fired when a new frame becomes the display frame | | onFrameReady | (frame) => void | — | Fired when the display frame is fully cached at full resolution | | onMissingFrame | (timeMs) => void | — | Fired when the requested time has no exact catalog match | | onDescriptorMismatch | (frame, reason) => void | — | Fired when descriptorMode: "manifest" detects a structural mismatch | | onBufferStateChange | (state) => void | — | Fired on buffer state changes | | onStats | (stats) => void | — | Fired with combined cache + prefetcher statistics |

All remaining COGLayer props (opacity, maxRequests, loadOptions, signal, pool, epsgResolver, onGeoTIFFLoad, onViewportLoad, etc.) are forwarded to the underlying COGLayer.


How it works

Instead of creating a new COGLayer for every frame (which tears down GPU textures and produces a strobing effect), TimeCOGLayer renders a single persistent sublayer. Frame changes are communicated through updateTriggers, which keeps old tile content visible until new data is ready.

A shared SequenceTileCache (keyed by frameId, x, y, z) stores decoded GPU textures across frame transitions so that cache hits return instantly. A background FramePrefetcher proactively loads tiles for nearby frames within the buffer window, and a shared GeoTIFFRegistry eliminates redundant COG header fetches. The prefetcher scores tasks by temporal proximity, playback direction, buffer pressure, and estimated fetch cost — stale tasks are aborted on seek.


API reference

TimeCOGFrame

type TimeCOGFrame = {
  id?: string;                   // stable identifier (auto-derived if omitted)
  time: number | string | Date;  // timestamp (epoch ms, ISO-8601, or Date)
  url: string | URL;             // COG URL
  requestInit?: RequestInit;     // forwarded to fetch() when opening the COG
  meta?: Record<string, unknown>; // opaque metadata
  byteSizeHint?: number;         // estimated compressed COG size (bytes) — helps the prefetcher prioritise smaller frames
};

MissingFramePolicy

type MissingFramePolicy = "hold-last" | "nearest" | "skip" | "transparent";

| Policy | Behaviour | |---|---| | "hold-last" (default) | Show the most recent frame at or before the requested time. Least visually disruptive. | | "nearest" | Show the closest frame by absolute time difference. | | "skip" | Show nothing (displayFrame is null). | | "transparent" | Show nothing (displayFrame is null). |

TimeCOGBufferPolicy

type TimeCOGBufferPolicy = {
  backwardFrames?: number;  // default 2
  forwardFrames?: number;   // default 6
};

TimeCOGCachePolicy

type TimeCOGCachePolicy = {
  memoryBytes?: number;     // max total GPU bytes
  maxFrames?: number;       // max distinct frames in cache
  maxTiles?: number;  // max individual tile entries
};

QualityPolicy

type QualityPolicy = {
  lowResFirst?: boolean;           // fetch coarse overview tiles first, then refine
  previewOverviewBias?: number;    // levels coarser than ideal visible level for initial preview on seek (default 1)
  scrubOverviewBias?: number;      // additional coarse bias for scrub interactions (default 2)
  fullResUpgradeIdleMs?: number;   // ms of idle time before upgrading from preview to full-res (default 150)
};

SchedulerPolicy

type SchedulerPolicy = {
  maxNetworkRequests?: number;     // default 4 — prefetch concurrency
  maxDecodeTasks?: number;         // max concurrent decode tasks
  maxGpuUploadsPerFrame?: number;  // max GPU uploads per frame
  frameRateSnap?: "off" | "on" | "slower" | "faster"; // default "off"
  multiscaleLevelPenalty?: number; // default 0.5 — pushes coarse temporal buckets later in the schedule
  scoringWeights?: ScoringWeights;  // per-factor scoring weight overrides
};

frameRateSnap is useful when the playback bucket width implied by playbackRate and maxFrameRate is incommensurate with the source frame period (for example, 3-minute buckets over 2-minute source frames).

  • off: use the exact requested playback bucket width.
  • on / slower: widen the effective playback bucket to the next whole-number multiple of the representative source frame period. This preserves the maxFrameRate cap while producing a more regular cadence.
  • faster: narrow the effective playback bucket to the previous whole-number multiple of the representative source frame period. This can exceed the requested maxFrameRate cap in exchange for a more source-aligned cadence.

Callbacks

onFrameDisplayed?: (frame: NormalizedTimeCOGFrame) => void;
onFrameReady?: (frame: NormalizedTimeCOGFrame) => void;
onMissingFrame?: (timeMs: number) => void;
onDescriptorMismatch?: (frame: NormalizedTimeCOGFrame, reason: string) => void;
onBufferStateChange?: (state: TimeCOGBufferState) => void;
onStats?: (stats: TimeCOGStats) => void;

Development

npm install
npm run build          # compile TypeScript
npm test               # build + run tests
npm run dev            # dev server with demo
npm run build:demo     # production demo build
npm run preview        # preview production demo