Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Add fading contrail effect to flight paths
Browse filesEach completed flight path creates an independent trail layer
that fades out over 5 seconds. Multiple trails can coexist
and fade simultaneously.
- src/webgl/FlightPathLayer.js +78 -0
src/webgl/FlightPathLayer.js
CHANGED
|
@@ -10,6 +10,9 @@ export class FlightPathLayer {
|
|
| 10 |
this.fullPath = null;
|
| 11 |
this.layersAdded = false;
|
| 12 |
this.jetMarker = null;
|
|
|
|
|
|
|
|
|
|
| 13 |
}
|
| 14 |
|
| 15 |
createArcPath(start, end, numPoints = 50) {
|
|
@@ -190,6 +193,77 @@ export class FlightPathLayer {
|
|
| 190 |
}
|
| 191 |
}
|
| 192 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
// Show full static path
|
| 194 |
showPath(from, to) {
|
| 195 |
this.stopAnimation();
|
|
@@ -240,6 +314,9 @@ export class FlightPathLayer {
|
|
| 240 |
} else {
|
| 241 |
this.animationFrame = null;
|
| 242 |
this.hideJet();
|
|
|
|
|
|
|
|
|
|
| 243 |
if (onComplete) onComplete();
|
| 244 |
}
|
| 245 |
};
|
|
@@ -257,6 +334,7 @@ export class FlightPathLayer {
|
|
| 257 |
|
| 258 |
hide() {
|
| 259 |
this.stopAnimation();
|
|
|
|
| 260 |
this.updatePath([]);
|
| 261 |
this.fullPath = null;
|
| 262 |
}
|
|
|
|
| 10 |
this.fullPath = null;
|
| 11 |
this.layersAdded = false;
|
| 12 |
this.jetMarker = null;
|
| 13 |
+
// Trail system with multiple independent fading trails
|
| 14 |
+
this.trails = [];
|
| 15 |
+
this.trailCounter = 0;
|
| 16 |
}
|
| 17 |
|
| 18 |
createArcPath(start, end, numPoints = 50) {
|
|
|
|
| 193 |
}
|
| 194 |
}
|
| 195 |
|
| 196 |
+
createTrail(coords) {
|
| 197 |
+
const trailId = `trail-${this.trailCounter++}`;
|
| 198 |
+
const sourceId = `${this.id}-${trailId}-source`;
|
| 199 |
+
const layerId = `${this.id}-${trailId}`;
|
| 200 |
+
|
| 201 |
+
// Add source
|
| 202 |
+
this.map.addSource(sourceId, {
|
| 203 |
+
type: 'geojson',
|
| 204 |
+
data: {
|
| 205 |
+
type: 'Feature',
|
| 206 |
+
geometry: { type: 'LineString', coordinates: coords }
|
| 207 |
+
}
|
| 208 |
+
});
|
| 209 |
+
|
| 210 |
+
// Add layer
|
| 211 |
+
this.map.addLayer({
|
| 212 |
+
id: layerId,
|
| 213 |
+
type: 'line',
|
| 214 |
+
source: sourceId,
|
| 215 |
+
paint: {
|
| 216 |
+
'line-color': COLORS.white,
|
| 217 |
+
'line-width': 1.5,
|
| 218 |
+
'line-opacity': 0.5
|
| 219 |
+
}
|
| 220 |
+
}, `${this.id}-glow`); // Insert below the main path layers
|
| 221 |
+
|
| 222 |
+
// Start fade animation
|
| 223 |
+
const fadeDuration = 5000;
|
| 224 |
+
const startTime = performance.now();
|
| 225 |
+
|
| 226 |
+
const trail = { sourceId, layerId, fadeFrame: null };
|
| 227 |
+
|
| 228 |
+
const fade = (currentTime) => {
|
| 229 |
+
const elapsed = currentTime - startTime;
|
| 230 |
+
const progress = Math.min(elapsed / fadeDuration, 1);
|
| 231 |
+
const opacity = 0.5 * (1 - progress);
|
| 232 |
+
|
| 233 |
+
if (this.map.getLayer(layerId)) {
|
| 234 |
+
this.map.setPaintProperty(layerId, 'line-opacity', opacity);
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
if (progress < 1) {
|
| 238 |
+
trail.fadeFrame = requestAnimationFrame(fade);
|
| 239 |
+
} else {
|
| 240 |
+
// Clean up when fully faded
|
| 241 |
+
this.removeTrail(trail);
|
| 242 |
+
}
|
| 243 |
+
};
|
| 244 |
+
|
| 245 |
+
trail.fadeFrame = requestAnimationFrame(fade);
|
| 246 |
+
this.trails.push(trail);
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
removeTrail(trail) {
|
| 250 |
+
if (trail.fadeFrame) {
|
| 251 |
+
cancelAnimationFrame(trail.fadeFrame);
|
| 252 |
+
}
|
| 253 |
+
if (this.map.getLayer(trail.layerId)) {
|
| 254 |
+
this.map.removeLayer(trail.layerId);
|
| 255 |
+
}
|
| 256 |
+
if (this.map.getSource(trail.sourceId)) {
|
| 257 |
+
this.map.removeSource(trail.sourceId);
|
| 258 |
+
}
|
| 259 |
+
this.trails = this.trails.filter(t => t !== trail);
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
clearAllTrails() {
|
| 263 |
+
this.trails.forEach(trail => this.removeTrail(trail));
|
| 264 |
+
this.trails = [];
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
// Show full static path
|
| 268 |
showPath(from, to) {
|
| 269 |
this.stopAnimation();
|
|
|
|
| 314 |
} else {
|
| 315 |
this.animationFrame = null;
|
| 316 |
this.hideJet();
|
| 317 |
+
// Create fading trail from completed path
|
| 318 |
+
this.createTrail(this.fullPath);
|
| 319 |
+
this.updatePath([]);
|
| 320 |
if (onComplete) onComplete();
|
| 321 |
}
|
| 322 |
};
|
|
|
|
| 334 |
|
| 335 |
hide() {
|
| 336 |
this.stopAnimation();
|
| 337 |
+
this.clearAllTrails();
|
| 338 |
this.updatePath([]);
|
| 339 |
this.fullPath = null;
|
| 340 |
}
|