/** * NASA FIRMS hotspot parsing, region selection, and clustering — pure * functions, no I/O. Mirrors the `metarStation.ts`/`composite.ts` precedent: * fetching (the Area API bbox call, the regional flat-file GET) is the * service's job; every judgement call about what the rows mean lives here, * unit-tested without HTTP. * * See `docs/plans/global-wildfire-plan.md` D4 (parsing) and D5 (output framing — * clustering is the load-bearing decision that keeps hundreds of raw * detections from a large fire unreadable). * * ## Why parse by header name * * FIRMS' two ingestion paths emit different CSV shapes (live-verified * 2026-08-14, see the plan's "Live re-verification notes"): * * - Area API (keyed): 14 columns, includes `instrument`, abbreviates * `confidence` to `l`/`n`/`h`, unpadded `acq_time` (`215`). * - Flat files (keyless): 13 columns, no `instrument`, spells confidence * out (`low`/`nominal`/`high`), zero-pads `acq_time` (`0048`). * * Column *positions* differ between the two paths, so indexing by position * would silently misalign fields on whichever shape wasn't tested last. * FIRMS CSV is unquoted (no embedded commas), so a per-line `split(',')` is * safe — the header row is what's authoritative, not position. */ import type { FIRMSDetection, FIRMSCluster, FIRMSRegionFile } from '../types/firms.js'; /** * Parse a FIRMS CSV payload (either the Area API or a flat-file shape) into * normalized detections, resolving every field by header name. * * Rows with a missing/non-numeric latitude or longitude, or an unparseable * acquisition timestamp, are dropped — those fields are load-bearing for * every downstream use (radius filtering, clustering, display) and a * detection without them isn't usable. `frp` is the one field parsed * defensively (see `parseFrpDefensive`): a bad FRP still leaves a real * detection worth showing. */ export declare function parseFIRMSCsv(csv: string): FIRMSDetection[]; /** * Choose the regional flat-file cut for a point, or `Global` when the point * doesn't fall comfortably inside any region's conservative inset. This is * a bandwidth optimization only (see `REGION_DEFINITIONS` doc comment) — * never a correctness gate. */ export declare function pickRegionFile(lat: number, lon: number): FIRMSRegionFile; /** Row cap applied to in-radius results — defense-in-depth against the ~10 MB Global-file worst case. */ export declare const MAX_RADIUS_DETECTIONS = 5000; export interface RadiusFilterResult { detections: FIRMSDetection[]; /** True when more than `MAX_RADIUS_DETECTIONS` fell within the radius and the result was capped. */ truncated: boolean; } /** * Filter detections to those within `radiusKm` of `(lat, lon)` (haversine, * via `calculateDistance`), then cap the result at `MAX_RADIUS_DETECTIONS`. * * When capping, the nearest detections are kept — sorted by distance * ascending before the slice — since proximity is what the caller actually * cares about, and this module's job is a deterministic, useful truncation. * The truncation *caveat* and any `securityEvent` warn log are the caller's * job (this module has no I/O and does no logging). */ export declare function filterByRadius(detections: FIRMSDetection[], lat: number, lon: number, radiusKm: number): RadiusFilterResult; /** Default clustering radius, km — see D5. */ export declare const DEFAULT_CLUSTER_RADIUS_KM = 2; /** * Cluster nearby detections into groups a reader can actually parse — a * single large fire produces dozens-to-hundreds of raw rows, which are * unreadable one-by-one (D5). * * **Algorithm (deterministic by construction):** * 1. Sort a *copy* of `detections` by FRP descending (ties keep original * array order — `Array.prototype.sort` is stable in the runtimes this * project targets). * 2. Walk the sorted list. For each detection, find the nearest *existing* * cluster centroid within `radiusKm`; if one qualifies, assign to it and * recompute the centroid as the running mean. Otherwise start a new * cluster with this detection as its founder. Ties in distance-to- * centroid resolve to whichever cluster was created first. * 3. Because detections are processed FRP-descending, a cluster's founder * always holds the cluster's `maxFrp` and supplies its `satellite`. * 4. Once all detections are assigned, compute each cluster's `distanceKm` * and `bearing` from `(lat, lon)` to its final centroid. * 5. Return clusters **sorted nearest-first** by `distanceKm` — that's the * order the renderer wants (D5: nearest cluster drives the safety * assessment), so sorting here keeps the caller simple. * * `(lat, lon)` is the point the caller queried around; it is only used to * compute `distanceKm`/`bearing` on the output, not for clustering itself. */ export declare function clusterDetections(detections: FIRMSDetection[], lat: number, lon: number, radiusKm?: number): FIRMSCluster[]; //# sourceMappingURL=firmsHotspots.d.ts.map