Add a heatmap spot size slider to the Active Fires row

A ½×–4× slider (log2 axis, 5 % steps, default 1×) under the intensity
filter sets how large far-zoom heat spots are. It scales the grid, not
the blur: 2× is a 1024 × 512 grid with the same one-pixel blur, so each
spot grows exactly and weak lone fires don't fade as they widen. A change
rebuilds only the heatmap. Share links keep it as w.s (percent, 50–400).
The layer row now takes a list of sliders.
This commit is contained in:
German Khokhlov committed 2026-11-03 14:56:40 +04:00
1 parent ef5c034dc9
commit 3139369673
26 files changed
+449 -91

No files matched your search

+30 -2
View File
@@ -30,6 +30,11 @@ import { stampInitialShareGesture } from '../navigationPolicy.js';
import { MAX_SINCE_DAYS } from '../layers/streetLevel/policy.js';
import { SINCE_STOPS } from '../ui/streetLevelPresentation.js';
import { FIRMS_CONFIDENCE_MODES, MIN_FRP_MW_MAX } from './firmsIntensity.js';
import {
HEAT_SPOT_SIZE_DEFAULT,
HEAT_SPOT_SIZE_MAX,
HEAT_SPOT_SIZE_MIN,
} from './firmsHeatRaster.js';
function deferred() {
let resolve;
@@ -3019,9 +3024,14 @@ test('Active Fires: the intensity filter rides share token w and round-trips', (
assert.deepEqual(state.options['local-firms'], {
minFrp: 0,
confidence: 'all',
spotSize: 100,
});
state.enabledLayerIds = ['local-firms'];
state.options['local-firms'] = { minFrp: 20, confidence: 'high' };
state.options['local-firms'] = {
minFrp: 20,
confidence: 'high',
spotSize: 250,
};
const encoded = encode(state);
assert.equal(new URLSearchParams(encoded).get('l'), 'w');
// Other owners write their own explicit defaults into `lo` too (flights 3D,
@@ -3029,10 +3039,12 @@ test('Active Fires: the intensity filter rides share token w and round-trips', (
const assignments = new URLSearchParams(encoded).get('lo').split('_');
assert.ok(assignments.includes('w.f.20'), assignments.join(' '));
assert.ok(assignments.includes('w.c.h'), assignments.join(' '));
assert.ok(assignments.includes('w.s.250'), assignments.join(' '));
const decoded = decodeLayerStateParams(new URLSearchParams(encoded));
assert.deepEqual(decoded.options['local-firms'], {
minFrp: 20,
confidence: 'high',
spotSize: 250,
});
assert.deepEqual(
parseStoredLayerState(serializeStoredLayerState(state)).options[
@@ -3050,7 +3062,11 @@ test('Active Fires: the intensity filter rides share token w and round-trips', (
.filter((assignment) => assignment.startsWith('w.')).length,
0,
);
assert.deepEqual(firmsOptions(''), { minFrp: 0, confidence: 'all' });
assert.deepEqual(firmsOptions(''), {
minFrp: 0,
confidence: 'all',
spotSize: 100,
});
});
test('Active Fires: the codec accepts whole megawatts in bounds and the three confidence codes', () => {
@@ -3078,5 +3094,17 @@ test('Active Fires: the codec accepts whole megawatts in bounds and the three co
assert.deepEqual(firmsOptions('w.c.n_w.f.5'), {
minFrp: 5,
confidence: 'nominal',
spotSize: 100,
});
});
test('Active Fires: the codec accepts whole-percent spot sizes from half to four times', () => {
assert.equal(HEAT_SPOT_SIZE_MIN, 50);
assert.equal(HEAT_SPOT_SIZE_MAX, 400);
assert.equal(HEAT_SPOT_SIZE_DEFAULT, 100);
assert.equal(firmsOptions('w.s.50').spotSize, 50);
assert.equal(firmsOptions('w.s.400').spotSize, 400);
assert.equal(firmsOptions('w.s.45').spotSize, 100, 'out of bounds → default');
assert.equal(firmsOptions('w.s.401').spotSize, 100);
assert.equal(firmsOptions('w.s.x').spotSize, 100);
});