FlowCorridorLayer
The FlowCorridorLayer renders a static-geometry overview whose per-vertex color is a time series. It is the pre-aggregated OD / flow renderer behind the nyc-taxi-flows demo: a corridor network (e.g. street segments carrying taxi volume) is stored once, and every corridor "pulses" as the playhead moves — width-constant lines whose color tracks the active flow value.
It extends AnimatedTripsLayer, keeping all of its tile caching and sublayer plumbing, and overrides just the gradient seams so the per-vertex scalar comes from a time bucket instead of a single static channel.
How it works#
A flow-corridor tile stores its geometry once and carries a per-vertex × per-time-bucket value matrix (BinaryFeatures.vertexValueMatrix, vertexValueBuckets columns). The geometry never re-uploads as time advances — only the per-vertex color changes:
- For the current playhead the layer finds the continuous bucket position, selects the two adjacent bucket columns, and linearly blends them into a per-vertex scalar (or, with
persistenceMs, takes a trailing-window max). The base class maps that scalar through the gradient ramp into per-vertex RGBA. - The cross-fade is quantized to a sub-step grid (
STEP = 0.5, i.e. 2 sub-steps per bucket). That quantized sub-step is the prepared tile'sdynamicKey, kept separate from the structuralstyleKey: between sub-steps the cache hits and nothing happens at all, and on a crossing the layer re-expands only the per-vertex colour (and chevron-direction) buffers and swaps those attribute wrappers in place. The tile'sdataobject stays reference-identical, so deck sees no data change and skips both path re-tessellation and the fp64 hi/lo re-upload of the position buffer — only the swapped attributes are invalidated, viaupdateTriggers.AnimatedTripsLayeradditionally memoizes the purely geometry-derived arrays (synthesizeVertexTimes, the static-width expansion) on the tile'sbinaryidentity, so they are computed once per tile load rather than once per sub-step. The fine (1-minute) bucketschevronPerTripLighttargets are why the step is this coarse: a smaller one would re-expand the two per-vertex signals ~24× per bucket. - Corridors are timeless: each feature's
[start, end]spans the whole range, so the window-mode time filter never hides the network.
trailLength is pinned to 0#
FlowCorridorLayer overrides AnimatedTripsLayer's trailLength: 180000
default with 0. A corridor is static geometry animated by colour: it runs
the time filter in window mode and repurposes the instanceVertexTime slot
to carry the chevron direction sign. Any trailLength > 0 switches the shader
into trail mode (precedence: cumulative > wake > trail > window), where that
slot is read as a relative vertex time — blanking the entire corridor
network, silently, once the relative playhead passes it (and, under
signedFlow, misreading the direction signs as timestamps). Overriding it
warns once; leave it at 0.
Installation#
import { FlowCorridorLayer } from '@poopdeck.gl/layers';
Usage#
const layer = new FlowCorridorLayer({id: 'taxi-flows',data: '/data/nyc-taxi-flows/manifest.json',currentTime,gradientDomain: [0, 400], // flow-count range mapped onto the rampgradientColorRamp: [[40, 40, 80, 180],[80, 180, 255, 220],[255, 220, 120, 255],],tripWidth: 2,});
The active-bucket scalar is colored through the inherited per-vertex gradient props — gradientDomain and gradientColorRamp (the gradientProperty channel is selected automatically from the value matrix). See AnimatedTripsLayer for the full gradient and width prop tables.
Properties#
Inherits all properties from AnimatedTripsLayer (and through it SpatioTemporalLayer). The relevant inherited props are:
| Property | Type | Default | Description |
|---|---|---|---|
gradientDomain | [number, number] | [0, 1] | Flow-value range mapped onto the color ramp. |
gradientColorRamp | Color[] | [] | Low→high color stops (piecewise-lerped) for the pulsing color. |
tripWidth | number | string | 3 | Corridor line width (constant; widths do not animate here). |
widthUnits / widthScale / widthMinPixels / widthMaxPixels | — | — | PathLayer width controls (see AnimatedTripsLayer). |
trailLength | number | 0 | Overridden to 0 here, unlike AnimatedTripsLayer's 180000. See above — any non-zero value blanks the network. |
FlowCorridorLayer also adds its own props, all optional, that control how the active-bucket scalar is derived and how a paired ChevronFlowExtension reads direction/intensity off it:
| Property | Type | Default | Description |
|---|---|---|---|
signedFlow | boolean | false | Read the value matrix as signed, so the sign carries travel direction. |
chevronPerTripLight | boolean | false | Pack a rolling aggregate and an instantaneous flash into one buffer. |
chevronAggregateWindowMs | number | 240000 | Half-span (ms of data time) of the chevronPerTripLight RGB aggregate. |
chevronInstantDomain | number | 1.5 | Trailing-sum value that reads as a full-brightness flash. |
chevronInstantDecayMs | number | 120000 | Decay time constant (ms of data time) of the instant flash. |
chevronDirectionWindowMs | number | 0 (inherits chevronAggregateWindowMs) | Half-span of the signedFlow directional-coherence window. |
persistenceMs | number | 0 (off) | TRAILING persistence window (ms of data time) for the default matrix path: each vertex holds the max of the bucket signal over [t − persistenceMs, t] instead of the instantaneous two-bucket blend. See below. |
signedFlow — abs(value) drives the color (volume) while the sign carries
per-bucket travel direction. Set it for tiles built with
bixi --streets --per-bucket-direction. When on, chevronDirectionsFor emits a
continuous [-1, 1] directional-coherence signal — a rolling
Σsigned / Σ abs(value) ratio — instead of the default plain-magnitude blend, so
a paired ChevronFlowExtension can morph arrow shape, hue, and march smoothly
between forward and reverse.
chevronPerTripLight — switches the gradient source to the two-signal mode
ChevronFlowExtension({ perTripLight: true }) expects. The RGB channel becomes a
rolling-window aggregate mean of abs(value) (the colour over a granular
period), and the colour buffer's alpha byte carries a separate instantaneous
per-trip flash — a short trailing decay of the nearest fine bucket. No extra GPU
attribute is needed.
persistenceMs — generators bin a trip's whole route into its START bucket,
so without persistence a corridor's highlight fades one bucket after departure
while the ride (a moving-heads overlay) is still traversing it. Set it to the
typical trip DURATION and the highlight rises over one bucket, holds for the
window, then fades over one bucket. Time-CHUNKED archives clamp the window at
each chunk's first column (a brief reset at chunk boundaries); whole-range
corridor archives are unaffected. Only applies on the default matrix path, not
under chevronPerTripLight.
chevronAggregateWindowMs defaults to ±4 min; wider windows smooth the ramp
colour further. A non-positive chevronInstantDomain falls back to the default.
chevronDirectionWindowMs defaults to 0, which inherits the aggregate window
so colour and direction resolve at the same temporal granularity — set it
non-zero to decouple them.
These props drive the directional chevrons in the bixi-streets-flow demo, where
signedFlow and chevronPerTripLight pair with a ChevronFlowExtension added
through the inherited extensions prop.
Related layers#
FlowStrokeLayer extends FlowCorridorLayer for merged, directed corridor networks — it keeps the same per-vertex time-bucket coloring and adds breathing width plus twin directional offset ribbons. ChevronFlowExtension is the standalone LayerExtension form of the marching-arrow effect this layer's signedFlow / chevronPerTripLight props are designed to feed — it can also be applied to any other PathLayer-based layer.
Difference from FlowmapLayer / FlowLinesLayer#
FlowmapLayer and FlowLinesLayer animate weighted arrows/arcs between an origin and destination — the width tracks volume at the playhead. FlowCorridorLayer instead renders the static corridor geometry and animates color: the network shape never changes, and each corridor's value pulses through the gradient ramp over time. Use it when you have a fixed network (streets, rails) with a per-segment volume time series, rather than discrete OD pairs.