Files
gods-eye-view/src/layers/firms/heatImagery.js
T
German Khokhlov f01200b467 Cross-fade Active Fires markers and heatmap; fix the spot size
The far-zoom heatmap now uses the ½× grid (4096 × 2048, ≈10 km per
pixel, one-pixel blur), so a lone fire's spot matches its marker at the
3,000 km hand-off. The spot size slider and its share option are gone.

Markers and heatmap are both kept built at every zoom and cross-fade by
camera height between 2,400 and 3,600 km on every frame, so a fast zoom
never shows a beat with neither. The heatmap fades by imagery alpha:
hiding a Cesium imagery layer unmaps it from the globe tiles, and showing
it again re-mapped them late, which was the gap at the band edge. Under
the markers the heatmap is built 150 ms after the data or filter changes.
2026-11-03 20:41:50 +04:00

89 lines
2.5 KiB
JavaScript

/**
* Drapes the Active Fires heat raster over the globe as one full-extent
* imagery tile on the active imagery host (globe or 3D-tiles), like the wind
* layer's scalar field. Owns the canvas and the imagery layer; nothing else
* in the fires layer touches imagery.
*
* `show` is only for the layer being on or off. The zoom hand-off fades
* `alpha` instead: Cesium unmaps a hidden imagery layer from the globe
* tiles and re-maps it when shown, so a show toggle at the band edge left
* a beat with neither markers nor heat. At alpha 0 the layer stays mapped
* and costs no draw.
*/
const defaultCanvas = (width, height) => {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
return canvas;
};
export function createHeatImagery({
cesium,
getHost,
createCanvas = defaultCanvas,
}) {
let raster = null;
let layer = null;
let collection = null;
let show = true;
let opacity = 1;
function detach() {
if (layer && collection) collection.remove(layer, true);
layer = null;
collection = null;
}
function attach() {
detach();
if (!raster) return;
const host = getHost?.() ?? { collection: null, kind: 'none' };
if (!host.collection) return;
const canvas = createCanvas(raster.width, raster.height);
const ctx = canvas.getContext('2d');
const image = ctx.createImageData(raster.width, raster.height);
image.data.set(raster.rgba);
ctx.putImageData(image, 0, 0);
const provider = new cesium.SingleTileImageryProvider({
url: canvas.toDataURL('image/png'),
tileWidth: raster.width,
tileHeight: raster.height,
rectangle: cesium.Rectangle.MAX_VALUE,
});
collection = host.collection;
layer = collection.addImageryProvider(provider);
layer.show = show;
layer.alpha = opacity;
collection.raiseToTop?.(layer);
}
return {
setRaster(next) {
raster = next;
attach();
},
/** True once a raster was handed over (painted or waiting for a host). */
hasRaster() {
return raster !== null;
},
setShow(value) {
show = Boolean(value);
if (layer) layer.show = show;
},
/** Hand-off fade, 0..1; the layer stays mapped at 0. */
setOpacity(value) {
opacity = Math.min(1, Math.max(0, Number(value) || 0));
if (layer) layer.alpha = opacity;
},
rehome() {
const host = getHost?.();
if ((host?.collection ?? null) !== collection) attach();
},
destroy() {
detach();
raster = null;
},
};
}