New js/layers/fires.js — fetches EONET open wildfire events directly (ACAO:*), refreshes every 15 min, rebuilt wholesale (no time dynamics — EONET events are slow-moving). Uses the latest dated geometry per event; Point coords direct, Polygon reduced to first-ring centroid. Flame-teardrop billboard (new lib.flameGlyph, drawn in #ff7a1a with dark outline, shared canvas), aggressive label translucency so dense California/Australia clusters stay legible, cap 500. HUD row "Wildfires (NASA EONET)". PITFALL handled: EONET mislabels Content-Type as application/rss+xml but the body is JSON — we call res.json() and never gate on the header. Verified in browser: 500 fires, all finite positions, glyphs render over North America, zero console errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
90 lines
2.5 KiB
JavaScript
90 lines
2.5 KiB
JavaScript
// Shared helpers used across layers. Cesium and satellite are CDN globals.
|
|
|
|
const Cesium = window.Cesium;
|
|
|
|
// Build an offscreen canvas glyph for a BillboardCollection.
|
|
export function makeGlyphCanvas(size, drawFn) {
|
|
const c = document.createElement('canvas');
|
|
c.width = size;
|
|
c.height = size;
|
|
drawFn(c.getContext('2d'), size);
|
|
return c;
|
|
}
|
|
|
|
// White upward-pointing triangle (aircraft). Tinted per-billboard via .color.
|
|
export function aircraftGlyph() {
|
|
return makeGlyphCanvas(32, (g) => {
|
|
g.fillStyle = '#ffffff';
|
|
g.beginPath();
|
|
g.moveTo(16, 2);
|
|
g.lineTo(28, 30);
|
|
g.lineTo(16, 23);
|
|
g.lineTo(4, 30);
|
|
g.closePath();
|
|
g.fill();
|
|
});
|
|
}
|
|
|
|
// Upward teardrop/flame (wildfires). Drawn directly in `hex` with a dark
|
|
// outline — one shared canvas reused across every fire billboard.
|
|
export function flameGlyph(hex) {
|
|
return makeGlyphCanvas(28, (g, s) => {
|
|
const cx = s / 2;
|
|
g.beginPath();
|
|
g.moveTo(cx, 3); // pointed top
|
|
g.bezierCurveTo(s * 0.86, s * 0.34, s * 0.80, s * 0.72, cx, s - 4);
|
|
g.bezierCurveTo(s * 0.20, s * 0.72, s * 0.14, s * 0.34, cx, 3);
|
|
g.closePath();
|
|
g.fillStyle = hex;
|
|
g.fill();
|
|
g.lineWidth = 2;
|
|
g.strokeStyle = '#0a0f14';
|
|
g.stroke();
|
|
});
|
|
}
|
|
|
|
// White diamond (ships).
|
|
export function shipGlyph() {
|
|
return makeGlyphCanvas(28, (g) => {
|
|
g.fillStyle = '#ffffff';
|
|
g.beginPath();
|
|
g.moveTo(14, 2);
|
|
g.lineTo(26, 14);
|
|
g.lineTo(14, 26);
|
|
g.lineTo(2, 14);
|
|
g.closePath();
|
|
g.fill();
|
|
});
|
|
}
|
|
|
|
// Great-circle initial bearing, degrees (0 = north, clockwise).
|
|
export function bearingDeg(lat1, lon1, lat2, lon2) {
|
|
const toR = Math.PI / 180;
|
|
const p1 = lat1 * toR;
|
|
const p2 = lat2 * toR;
|
|
const dl = (lon2 - lon1) * toR;
|
|
const y = Math.sin(dl) * Math.cos(p2);
|
|
const x = Math.cos(p1) * Math.sin(p2) - Math.sin(p1) * Math.cos(p2) * Math.cos(dl);
|
|
return (Math.atan2(y, x) * 180 / Math.PI + 360) % 360;
|
|
}
|
|
|
|
// Aircraft altitude → band color.
|
|
export function altitudeColor(CONFIG, altMeters) {
|
|
const c = CONFIG.colors;
|
|
if (altMeters == null) return c.aircraftHigh;
|
|
if (altMeters < 3000) return c.aircraftLow;
|
|
if (altMeters < 9000) return c.aircraftMid;
|
|
return c.aircraftHigh;
|
|
}
|
|
|
|
// CSS hex → Cesium.Color, optional alpha.
|
|
export function cz(hex, alpha) {
|
|
const col = Cesium.Color.fromCssColorString(hex);
|
|
return alpha == null ? col : col.withAlpha(alpha);
|
|
}
|
|
|
|
// Billboard screen-space rotation (CCW) for a compass heading (CW from north).
|
|
export function headingToRotation(headingDeg) {
|
|
return -Cesium.Math.toRadians(headingDeg || 0);
|
|
}
|