Layer for animating trips and paths with temporal data support, including smooth path interpolation, time-based animation controls, and trail effects.
Layer for animating trips/paths with temporal data, providing smooth movement visualization and configurable trail effects.
/**
* Layer for animating trips and paths with temporal data
* Renders animated paths with time-based progression and trail effects
*/
class TripsLayer<DataT = any> extends Layer<TripsLayerProps<DataT>> {
constructor(props: TripsLayerProps<DataT>);
}
interface TripsLayerProps<DataT = unknown> extends LayerProps<DataT> {
/** Accessor for trip path coordinates */
getPath?: AccessorFunction<DataT, Position[]>;
/** Accessor for timestamp array corresponding to path points */
getTimestamps?: AccessorFunction<DataT, number[]>;
/** Accessor for trip color */
getColor?: AccessorFunction<DataT, Color>;
/** Accessor for path width in pixels */
getWidth?: AccessorFunction<DataT, number>;
/** Current animation time */
currentTime?: number;
/** Length of trailing path in time units */
trailLength?: number;
/** Cap style for path ends ('round', 'square', 'butt') */
capRounded?: boolean;
/** Join style for path segments ('round', 'miter', 'bevel') */
jointRounded?: boolean;
/** Width units ('pixels', 'common', 'meters') */
widthUnits?: string;
/** Minimum width in pixels */
widthMinPixels?: number;
/** Maximum width in pixels */
widthMaxPixels?: number;
/** Width scaling factor */
widthScale?: number;
/** Fade trail over time */
fadeTrail?: boolean;
}
type Position = [longitude: number, latitude: number] | [longitude: number, latitude: number, elevation: number];
type Color = [r: number, g: number, b: number] | [r: number, g: number, b: number, a: number];Usage Examples:
import { TripsLayer } from "@deck.gl/geo-layers";
// Basic trip animation
const tripsLayer = new TripsLayer({
id: 'trips',
data: [
{
id: 'trip-1',
path: [
[-122.4, 37.8, 0],
[-122.41, 37.81, 10],
[-122.42, 37.82, 5],
[-122.43, 37.83, 0]
],
timestamps: [0, 30, 60, 120], // Seconds
color: [255, 0, 0],
width: 8
},
{
id: 'trip-2',
path: [
[-122.45, 37.85, 0],
[-122.44, 37.84, 15],
[-122.43, 37.83, 20],
[-122.42, 37.82, 5]
],
timestamps: [10, 40, 80, 130],
color: [0, 255, 0],
width: 6
}
],
getPath: d => d.path,
getTimestamps: d => d.timestamps,
getColor: d => d.color,
getWidth: d => d.width,
currentTime: 0, // Start animation
trailLength: 30, // 30 second trail
capRounded: true,
jointRounded: true,
fadeTrail: true,
pickable: true
});
// Animate the trips over time
function animateTrips() {
let time = 0;
const animate = () => {
tripsLayer.setProps({
currentTime: time
});
time += 1; // Advance 1 second
if (time < 150) {
requestAnimationFrame(animate);
}
};
animate();
}
// Real-time GPS tracking visualization
const gpsTripsLayer = new TripsLayer({
id: 'gps-trips',
data: vehicleData, // Array of vehicle objects with GPS tracks
getPath: d => d.gpsTrack.map(point => [point.lon, point.lat, point.altitude]),
getTimestamps: d => d.gpsTrack.map(point => point.timestamp),
getColor: d => {
switch (d.vehicleType) {
case 'bus': return [255, 165, 0]; // Orange
case 'taxi': return [255, 255, 0]; // Yellow
case 'delivery': return [139, 69, 19]; // Brown
default: return [128, 128, 128]; // Gray
}
},
getWidth: d => d.vehicleType === 'bus' ? 12 : 8,
currentTime: Date.now() / 1000, // Current Unix timestamp
trailLength: 300, // 5 minute trail
widthUnits: 'pixels',
widthMinPixels: 2,
widthMaxPixels: 20,
fadeTrail: true,
capRounded: true,
pickable: true,
onHover: info => {
if (info.object) {
console.log(`Vehicle ${info.object.id}: ${info.object.vehicleType}`);
}
}
});
// Flight path visualization with altitude
const flightPathsLayer = new TripsLayer({
id: 'flight-paths',
data: flightData,
getPath: d => d.waypoints.map(wp => [wp.longitude, wp.latitude, wp.altitude]),
getTimestamps: d => d.waypoints.map(wp => wp.time),
getColor: d => {
const airline = d.airline;
const colors = {
'AA': [255, 0, 0], // American - Red
'UA': [0, 0, 255], // United - Blue
'DL': [255, 0, 255], // Delta - Magenta
'SW': [255, 165, 0] // Southwest - Orange
};
return colors[airline] || [128, 128, 128];
},
getWidth: d => Math.max(6, d.aircraft_size * 2),
currentTime: animationTime,
trailLength: 1800, // 30 minute trail
// 3D path rendering
extruded: true,
widthUnits: 'meters',
widthScale: 1000, // Scale for visibility at altitude
fadeTrail: true,
pickable: true
});Control animation progression using time values and trail effects.
interface AnimationProps {
/** Current animation time in same units as timestamps */
currentTime?: number;
/** Length of trailing path in time units */
trailLength?: number;
/** Fade trail opacity over time */
fadeTrail?: boolean;
}
interface AnimationState {
/** Calculate current position along path for given time */
getCurrentPosition(path: Position[], timestamps: number[], currentTime: number): Position | null;
/** Get trail segment for current time and trail length */
getTrailSegment(path: Position[], timestamps: number[], currentTime: number, trailLength: number): {
positions: Position[];
colors: Color[];
};
}Animation Control Examples:
// Smooth animation with controls
class TripAnimationController {
constructor(layer) {
this.layer = layer;
this.currentTime = 0;
this.isPlaying = false;
this.playbackSpeed = 1.0;
}
play() {
if (this.isPlaying) return;
this.isPlaying = true;
this.animate();
}
pause() {
this.isPlaying = false;
}
setSpeed(speed) {
this.playbackSpeed = speed;
}
setTime(time) {
this.currentTime = time;
this.updateLayer();
}
animate() {
if (!this.isPlaying) return;
this.currentTime += this.playbackSpeed;
this.updateLayer();
requestAnimationFrame(() => this.animate());
}
updateLayer() {
this.layer.setProps({
currentTime: this.currentTime
});
}
}
// Usage
const controller = new TripAnimationController(tripsLayer);
controller.setSpeed(2.0); // 2x speed
controller.play();
// Time range controls
const timeControls = {
startTime: 0,
endTime: 3600, // 1 hour
currentTime: 0,
scrubToTime(time) {
this.currentTime = Math.max(this.startTime, Math.min(this.endTime, time));
tripsLayer.setProps({
currentTime: this.currentTime
});
},
getProgress() {
return (this.currentTime - this.startTime) / (this.endTime - this.startTime);
}
};Control how positions are interpolated between timestamp points.
interface InterpolationOptions {
/** Interpolation method for positions between timestamps */
interpolation?: 'linear' | 'cubic' | 'great-circle';
/** Smooth path curves at waypoints */
smoothing?: boolean;
/** Handle missing timestamps */
fillGaps?: boolean;
}Interpolation Examples:
// Linear interpolation (default)
const linearTrips = new TripsLayer({
id: 'linear-trips',
data: tripData,
getPath: d => d.path,
getTimestamps: d => d.timestamps,
// Linear interpolation between waypoints
});
// Great circle interpolation for geographic accuracy
const greatCircleTrips = new TripsLayer({
id: 'great-circle-trips',
data: longDistanceFlights,
getPath: d => d.path,
getTimestamps: d => d.timestamps,
// Uses great circle arcs between waypoints for accuracy
});
// Handle irregular timestamps
const irregularTrips = new TripsLayer({
id: 'irregular-trips',
data: sensorData.map(d => ({
...d,
// Fill missing timestamps with interpolation
timestamps: fillTimestampGaps(d.timestamps, d.path)
})),
getPath: d => d.path,
getTimestamps: d => d.timestamps
});
function fillTimestampGaps(timestamps, path) {
// Interpolate missing timestamps based on path length
const filled = [...timestamps];
for (let i = 1; i < path.length; i++) {
if (!timestamps[i]) {
// Estimate timestamp based on distance and speed
const dist = calculateDistance(path[i-1], path[i]);
const timeDelta = dist / estimatedSpeed;
filled[i] = filled[i-1] + timeDelta;
}
}
return filled;
}Control the visual appearance of animated paths.
interface PathStyling {
/** Path width in specified units */
getWidth?: AccessorFunction<DataT, number>;
/** Path color with optional alpha */
getColor?: AccessorFunction<DataT, Color>;
/** Cap style for path endpoints */
capRounded?: boolean;
/** Join style for path segments */
jointRounded?: boolean;
/** Width units and scaling */
widthUnits?: 'pixels' | 'common' | 'meters';
widthScale?: number;
widthMinPixels?: number;
widthMaxPixels?: number;
}Styling Examples:
// Speed-based coloring
const speedColoredTrips = new TripsLayer({
id: 'speed-colored',
data: tripData,
getPath: d => d.path,
getTimestamps: d => d.timestamps,
getColor: d => {
const avgSpeed = calculateAverageSpeed(d.path, d.timestamps);
if (avgSpeed > 60) return [255, 0, 0]; // Red for fast
if (avgSpeed > 30) return [255, 255, 0]; // Yellow for medium
return [0, 255, 0]; // Green for slow
},
getWidth: d => Math.max(4, calculateAverageSpeed(d.path, d.timestamps) / 10),
currentTime: animationTime,
trailLength: 60,
fadeTrail: true
});
// Route type styling
const routeStyledTrips = new TripsLayer({
id: 'route-styled',
data: deliveryRoutes,
getPath: d => d.route,
getTimestamps: d => d.schedule,
getColor: d => {
switch (d.priority) {
case 'express': return [255, 0, 0, 200]; // Bright red
case 'standard': return [0, 0, 255, 150]; // Blue
case 'economy': return [128, 128, 128, 100]; // Gray
}
},
getWidth: d => d.priority === 'express' ? 12 : 8,
capRounded: true,
jointRounded: true,
widthUnits: 'pixels',
widthMinPixels: 3,
widthMaxPixels: 15,
currentTime: currentDeliveryTime,
trailLength: 300
});Control trail visualization and fading effects.
interface TrailEffects {
/** Trail length in time units */
trailLength?: number;
/** Fade trail opacity over time */
fadeTrail?: boolean;
/** Custom trail opacity calculation */
getTrailOpacity?: (segmentAge: number, maxAge: number) => number;
}Trail Effect Examples:
// Custom trail fading
const customFadeTrips = new TripsLayer({
id: 'custom-fade',
data: tripData,
getPath: d => d.path,
getTimestamps: d => d.timestamps,
getColor: d => [...d.baseColor, 255], // Full opacity base color
currentTime: animationTime,
trailLength: 120,
fadeTrail: true,
// Custom fade calculation
updateTriggers: {
getColor: [animationTime] // Recalculate colors when time changes
}
});
// Pulsing trail effect
const pulsingTrails = new TripsLayer({
id: 'pulsing-trails',
data: tripData,
getPath: d => d.path,
getTimestamps: d => d.timestamps,
getColor: d => {
// Pulse effect based on current time
const pulse = Math.sin(animationTime * 0.01) * 0.5 + 0.5;
return [d.color[0], d.color[1], d.color[2], pulse * 255];
},
getWidth: d => {
// Width pulse effect
const pulse = Math.sin(animationTime * 0.02) * 0.3 + 0.7;
return d.baseWidth * pulse;
},
currentTime: animationTime,
trailLength: 90,
updateTriggers: {
getColor: [animationTime],
getWidth: [animationTime]
}
});