feat(fires): MODIS in Active Fires and a Fire Perimeters layer (#737)

Adds MODIS NRT to Active Fires and a keyless Fire Perimeters layer (NIFC WFIGS) with
InciWeb incident links, served through a capped, cached /api/fire-perimeters proxy.

Co-authored-by: Gustavo Beneduzi <[email protected]>
Co-authored-by: James Cooke <[email protected]>
This commit is contained in:
authored and GitHub committed 2026-09-23 21:21:52 -05:00
1 parent 4b56d0e917
commit f913066cf2
40 files changed
+3201 -26

No files matched your search

+117
View File
@@ -0,0 +1,117 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { createWfigsPerimeterSource } from './source.js';
import { normalizeFirePerimeterSnapshot } from './records.js';
import { firePerimetersProxy } from '../../../server/providers/firePerimeters.js';
const ring = [
[-108.1, 35.2],
[-108.0, 35.2],
[-108.0, 35.3],
[-108.1, 35.2],
];
const validPayload = {
features: [
{
id: 1,
geometry: { type: 'Polygon', coordinates: [ring] },
properties: { attr_UniqueFireIdentifier: '2026-NMGNF-000123' },
},
],
};
test('a successful response yields normalized perimeter rows', async () => {
let requested;
const source = createWfigsPerimeterSource({
fetchImpl: async (url) => {
requested = String(url);
return Response.json({
rows: normalizeFirePerimeterSnapshot(validPayload),
});
},
});
const rows = await source.getSnapshot();
assert.equal(rows.length, 1);
assert.equal(rows[0].stableId, '2026-NMGNF-000123');
assert.equal(requested, '/api/fire-perimeters');
});
test('a truncated response pages until the feed is complete', async () => {
const pageRing = (id) => ({
id,
geometry: { type: 'Polygon', coordinates: [ring] },
properties: { attr_UniqueFireIdentifier: `fire-${id}` },
});
const requests = [];
let handler;
const plugin = firePerimetersProxy({
fetchImpl: async (url) => {
requests.push(new URL(String(url)).searchParams.get('resultOffset'));
const page = requests.length;
return Response.json({
features: [pageRing(page)],
properties: { exceededTransferLimit: page < 3 },
});
},
});
plugin.configureServer({
middlewares: {
use: (_path, callback) => {
handler = callback;
},
},
});
let payload;
await handler(
{ url: '/', method: 'GET' },
{
writeHead(status) {
assert.equal(status, 200);
},
end(body) {
payload = JSON.parse(body);
},
},
);
const rows = payload.rows;
assert.deepEqual(
rows.map((row) => row.stableId),
['fire-1', 'fire-2', 'fire-3'],
);
assert.deepEqual(requests, [null, '1', '2']);
});
test('an upstream failure surfaces its HTTP status', async () => {
const source = createWfigsPerimeterSource({
fetchImpl: async () => ({ ok: false, status: 503 }),
});
await assert.rejects(source.getSnapshot(), /WFIGS HTTP 503/);
});
test('a malformed successful response is never accepted as an empty snapshot', async () => {
for (const payload of [{}, { rows: null }, { rows: {} }]) {
const source = createWfigsPerimeterSource({
fetchImpl: async () => Response.json(payload),
});
await assert.rejects(source.getSnapshot(), /Malformed perimeter snapshot/);
}
});
test('response-body completion honors cancellation without replacing records', async () => {
const abort = new AbortController();
const source = createWfigsPerimeterSource({
fetchImpl: async () => ({
ok: true,
headers: new Headers(),
text: async () => {
abort.abort();
return JSON.stringify({
rows: normalizeFirePerimeterSnapshot(validPayload),
});
},
}),
});
await assert.rejects(source.getSnapshot({ signal: abort.signal }), {
name: 'AbortError',
});
});