mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
Merge pull request #1342 from rudycelekli/fix/firms-acquisition-calendar
fix(firms): keep impossible acquisition dates off the fire clock
This commit is contained in:
3 files changed
+72
-14
No files matched your search
@@ -0,0 +1,3 @@
|
|||||||
|
- Keep an impossible wildfire acquisition date off the detection clock, so a
|
||||||
|
fire is not labeled with a later day it did not happen
|
||||||
|
(Rudy Celekli, @rudycelekli, #1342).
|
||||||
+33
-10
@@ -81,21 +81,44 @@ export function parseAcquisitionMs(date, time, cache = new Map()) {
|
|||||||
const cached = cache.get(key);
|
const cached = cache.get(key);
|
||||||
if (cached !== undefined) return cached;
|
if (cached !== undefined) return cached;
|
||||||
|
|
||||||
|
// Same calendar as the CSV gate. Date.UTC would otherwise roll 30 February
|
||||||
|
// into March, and the fire's age would describe a detection that never happened.
|
||||||
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
||||||
|
cache.set(key, 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const timeText = String(time ?? '').trim();
|
||||||
|
if (!/^\d{1,4}$/.test(timeText)) {
|
||||||
|
cache.set(key, 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const hhmm = timeText.padStart(4, '0');
|
||||||
const year = Number(date.slice(0, 4));
|
const year = Number(date.slice(0, 4));
|
||||||
const month = Number(date.slice(5, 7));
|
const month = Number(date.slice(5, 7));
|
||||||
const day = Number(date.slice(8, 10));
|
const day = Number(date.slice(8, 10));
|
||||||
const hhmm = String(time ?? '0000').padStart(4, '0');
|
|
||||||
const hours = Number(hhmm.slice(0, 2));
|
const hours = Number(hhmm.slice(0, 2));
|
||||||
const minutes = Number(hhmm.slice(2, 4));
|
const minutes = Number(hhmm.slice(2, 4));
|
||||||
const valid =
|
if (
|
||||||
Number.isFinite(year) &&
|
month < 1 ||
|
||||||
Number.isFinite(month) &&
|
month > 12 ||
|
||||||
Number.isFinite(day) &&
|
day < 1 ||
|
||||||
Number.isFinite(hours) &&
|
day > 31 ||
|
||||||
Number.isFinite(minutes);
|
hours > 23 ||
|
||||||
const ms = valid ? Date.UTC(year, month - 1, day, hours, minutes) : 0;
|
minutes > 59
|
||||||
cache.set(key, ms);
|
) {
|
||||||
return ms;
|
cache.set(key, 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const ms = Date.UTC(year, month - 1, day, hours, minutes);
|
||||||
|
const parsed = new Date(ms);
|
||||||
|
const result =
|
||||||
|
parsed.getUTCFullYear() === year &&
|
||||||
|
parsed.getUTCMonth() === month - 1 &&
|
||||||
|
parsed.getUTCDate() === day
|
||||||
|
? ms
|
||||||
|
: 0;
|
||||||
|
cache.set(key, result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -63,12 +63,39 @@ test('unpadded acq_time parses in the adapter path ("45" = 00:45Z)', () => {
|
|||||||
|
|
||||||
test('parseAcquisitionMs: memo cache is honored, invalid input → 0', () => {
|
test('parseAcquisitionMs: memo cache is honored, invalid input → 0', () => {
|
||||||
const cache = new Map();
|
const cache = new Map();
|
||||||
assert.equal(parseAcquisitionMs('2026-07-16', '1006', cache), Date.UTC(2026, 6, 16, 10, 6));
|
assert.equal(
|
||||||
|
parseAcquisitionMs('2026-07-16', '1006', cache),
|
||||||
|
Date.UTC(2026, 6, 16, 10, 6),
|
||||||
|
);
|
||||||
assert.equal(cache.size, 1);
|
assert.equal(cache.size, 1);
|
||||||
assert.equal(parseAcquisitionMs('2026-07-16', '1006', cache), Date.UTC(2026, 6, 16, 10, 6));
|
assert.equal(
|
||||||
|
parseAcquisitionMs('2026-07-16', '1006', cache),
|
||||||
|
Date.UTC(2026, 6, 16, 10, 6),
|
||||||
|
);
|
||||||
assert.equal(parseAcquisitionMs(undefined, '1006', cache), 0);
|
assert.equal(parseAcquisitionMs(undefined, '1006', cache), 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('impossible acquisition dates stay untimed instead of rolling forward', () => {
|
||||||
|
for (const [date, time] of [
|
||||||
|
['2026-02-30', '1200'],
|
||||||
|
['2026-04-31', '1200'],
|
||||||
|
['2026-13-01', '1200'],
|
||||||
|
['2026-07-16', '2400'],
|
||||||
|
['2026-07-16', '1260'],
|
||||||
|
]) {
|
||||||
|
assert.equal(parseAcquisitionMs(date, time), 0, `${date}:${time}`);
|
||||||
|
const [fire] = adaptFirmsRecords([
|
||||||
|
proxyRecord({ acqDate: date, acqTime: time }),
|
||||||
|
]);
|
||||||
|
assert.equal(fire.acqMs, 0, `${date}:${time}`);
|
||||||
|
assert.equal(fire.lat, 38.99488);
|
||||||
|
}
|
||||||
|
const [leap] = adaptFirmsRecords([
|
||||||
|
proxyRecord({ acqDate: '2024-02-29', acqTime: '45' }),
|
||||||
|
]);
|
||||||
|
assert.equal(leap.acqMs, Date.UTC(2024, 1, 29, 0, 45));
|
||||||
|
});
|
||||||
|
|
||||||
test('daynight "D" → night false', () => {
|
test('daynight "D" → night false', () => {
|
||||||
const [fire] = adaptFirmsRecords([proxyRecord({ daynight: 'D' })]);
|
const [fire] = adaptFirmsRecords([proxyRecord({ daynight: 'D' })]);
|
||||||
assert.equal(fire.night, false);
|
assert.equal(fire.night, false);
|
||||||
@@ -89,11 +116,16 @@ test('records with non-finite lat/lon are skipped; index stays sequential', () =
|
|||||||
proxyRecord({ lat: 40.1 }),
|
proxyRecord({ lat: 40.1 }),
|
||||||
]);
|
]);
|
||||||
assert.equal(fires.length, 2);
|
assert.equal(fires.length, 2);
|
||||||
assert.deepEqual(fires.map((f) => f.index), [0, 1]);
|
assert.deepEqual(
|
||||||
|
fires.map((f) => f.index),
|
||||||
|
[0, 1],
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('non-finite frp/brightness → 0; empty input → []', () => {
|
test('non-finite frp/brightness → 0; empty input → []', () => {
|
||||||
const [fire] = adaptFirmsRecords([proxyRecord({ frp: 'n/a', brightness: undefined })]);
|
const [fire] = adaptFirmsRecords([
|
||||||
|
proxyRecord({ frp: 'n/a', brightness: undefined }),
|
||||||
|
]);
|
||||||
assert.equal(fire.frp, 0);
|
assert.equal(fire.frp, 0);
|
||||||
assert.equal(fire.brightness, 0);
|
assert.equal(fire.brightness, 0);
|
||||||
assert.deepEqual(adaptFirmsRecords([]), []);
|
assert.deepEqual(adaptFirmsRecords([]), []);
|
||||||
|
|||||||
Reference in new issue
Block a user