Draw each far-zoom fire as a marker-sized heat spot

The heat raster was 2048 px wide with a 3 px blur, so every fire spread
over ~200 km and the far-zoom map read as clouds next to the markers.
Double the grid to ~10 km per pixel and blur by one pixel: a lone fire
is now a spot about as wide as its marker at the 3,000 km hand-off.
This commit is contained in:
German Khokhlov committed 2026-11-02 22:43:51 +04:00
1 parent 314292a006
commit ad171f2854
4 files changed
+37 -8

No files matched your search

+1 -1
View File
@@ -4,6 +4,6 @@
log-scale minimum-FRP slider whose track is the ramp, an All / Nominal+ / log-scale minimum-FRP slider whose track is the ramp, an All / Nominal+ /
High confidence switch and an "N of M shown" counter to the layer row. Far High confidence switch and an "N of M shown" counter to the layer row. Far
zoom shows a Snap Map–style heatmap in the same colors instead of squares, zoom shows a Snap Map–style heatmap in the same colors instead of squares,
empty where nothing burns. The filter applies once before rendering, so markers, cards, the heatmap, context and the top-fires query agree; share one marker-sized spot per fire, empty where nothing burns. The filter applies once before rendering, so markers, cards, the heatmap, context and the top-fires query agree; share
links keep it (`w` token: `f`, `c`), and `get_active_fires` takes matching links keep it (`w` token: `f`, `c`), and `get_active_fires` takes matching
`min_frp_mw` and `confidence` arguments. Default 0 MW / All (gekh, #1426). `min_frp_mw` and `confidence` arguments. Default 0 MW / All (gekh, #1426).
+3 -2
View File
@@ -2185,9 +2185,10 @@ accents read that ramp; confidence and brightness do not affect the color.
At far zoom (camera above ~3,000 km) the layer drapes a Snap Map–style At far zoom (camera above ~3,000 km) the layer drapes a Snap Map–style
heatmap instead of individual markers: the shown detections are binned on a heatmap instead of individual markers: the shown detections are binned on a
global 2048 × 1024 grid, each weighted by its FRP in megawatts (so heat is global 4096 × 2048 grid (≈10 km per pixel), each weighted by its FRP in megawatts (so heat is
radiated power, and raising the FRP slider does not dim areas where strong fires radiated power, and raising the FRP slider does not dim areas where strong fires
burn), Gaussian-blurred, burn), Gaussian-blurred by one pixel so each fire stays a spot about the size
of its marker,
normalized on a log scale against the 99.5th percentile and mapped through the normalized on a log scale against the 99.5th percentile and mapped through the
same hues, from a bright red edge through orange and gold to a pale same hues, from a bright red edge through orange and gold to a pale
white-hot core. The edge starts more than half opaque, so regions far below white-hot core. The edge starts more than half opaque, so regions far below
+25
View File
@@ -1,6 +1,8 @@
import test from 'node:test'; import test from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { createHeatImagery } from './heatImagery.js'; import { createHeatImagery } from './heatImagery.js';
import { buildHeatRaster } from '../../data/firmsHeatRaster.js';
import { HEAT_RASTER } from './policy.js';
function harness() { function harness() {
const layers = []; const layers = [];
@@ -85,3 +87,26 @@ test('destroy removes the layer', () => {
h.imagery.destroy(); h.imagery.destroy();
assert.equal(h.layers.length, 0); assert.equal(h.layers.length, 0);
}); });
test('the shipped raster paints a lone weak fire as a marker-sized spot', () => {
// A hot cluster elsewhere sets the scale, as Africa does on real data.
const fires = [{ lat: 0, lon: 0, frp: 5 }];
for (let i = 0; i < 40; i++) {
fires.push({ lat: -10 + (i % 5) * 0.1, lon: 25 + i * 0.05, frp: 300 });
}
const r = buildHeatRaster(fires, HEAT_RASTER);
const y = Math.floor((90 / 180) * r.height);
const painted = [];
for (let x = 0; x < r.width; x++) {
const lon = ((x + 0.5) / r.width) * 360 - 180;
if (Math.abs(lon) < 5 && r.rgba[(y * r.width + x) * 4 + 3] > 0) {
painted.push(x);
}
}
const kmPerPx = 40075 / r.width;
assert.ok(painted.length > 0, 'the weak fire still shows');
assert.ok(
painted.length * kmPerPx <= 80,
`spot ${Math.round(painted.length * kmPerPx)} km wide`,
);
});
+8 -5
View File
@@ -49,14 +49,17 @@ export const LOD_LEVELS = [
]; ];
/** /**
* Global heat raster for the `heat` bands: ≈0.18° per pixel, Gaussian σ in * Global heat raster for the `heat` bands: ≈0.09° (≈10 km) per pixel and a
* pixels. Built once per data refresh or filter change, never per camera move. * one-pixel Gaussian σ, so a lone fire is a spot about as wide as its marker
* at the 3,000 km hand-off and neighbours merge only when they really touch
* (2048 px with σ 3 smeared each fire over ~200 km). Built once per data
* refresh or filter change, never per camera move.
*/ */
export const HEAT_RASTER = Object.freeze({ export const HEAT_RASTER = Object.freeze({
width: 2048, width: 4096,
height: 1024, height: 2048,
sigmaPx: 3, sigmaPx: 1,
}); });
/** /**