api/ndfdWeather.ts

Used to call the weather service and cache the weather data

Purpose

This module fetches, validates, and normalizes hourly weather forecast data from the NOAA NDFD (National Digital Forecast Database) XML API into a strongly typed internal format.

Its responsibilities include:

  • Constructing a constrained NDFD time-series request for a single geographic point
  • Fetching DWML/XML forecast data with timeout protection
  • Parsing XML into structured JSON
  • Validating the expected XML structure and parameter layouts
  • Aligning weather parameters by timestamp
  • Converting units into internal application units
  • Filtering out incomplete or invalid forecast rows
  • Returning a cacheable weather forecast payload with an expiration boundary

The resulting output is a normalized array of WeatherPoint objects suitable for downstream WBGT or forecast processing.


Invariants

The module enforces several important invariants to guarantee predictable downstream behavior.

Coordinate Validation

lat and lon must always satisfy:

  • lat ∈ [-90, 90]
  • lon ∈ [-180, 180]
  • both values must be finite numbers

Violations throw a RangeError.


Positive Integer Inputs

The following arguments must always be positive integers:

  • numHoursForecast
  • timeoutMs

Invalid values throw a RangeError.


Parameter Completeness

A forecast point is only emitted when all required parameters exist for the same timestamp:

  • temperature
  • humidity
  • wind speed
  • cloud amount

Incomplete timestamps are discarded.

This guarantees every emitted WeatherPoint is structurally complete.


Missing Sentinel Handling

NDFD commonly uses -9999 to represent missing values.

These values are always ignored and never emitted into forecast output.


Unit Normalization

Returned weather values are normalized into internal units:

SourceInternal
FahrenheitCelsius
Knotsmeters/second

Conversions are rounded to one decimal place.


Time Ordering

All emitted forecast points are guaranteed to be sorted in ascending chronological order.


Cache Validity Contract

validTo is always computed as:

(latest forecast timestamp) + 1 hour

This defines the upper bound of cache validity for the fetched forecast window.


Structural Parse Safety

The parser assumes:

  • dwml.data.parameters must exist
  • required parameter blocks must exist
  • referenced time-layout keys must resolve correctly

Malformed XML structures throw descriptive parse errors instead of silently failing.


Variants

XML Shape Variants

NDFD DWML responses are not fully consistent.

This module handles several schema variants:

layout-key

May appear as:

  • XML child element
  • XML attribute

The parser supports both forms.


Single vs Array Nodes

XML nodes may deserialize as:

  • single object/value
  • array of objects/values

The toArray() helper normalizes both representations into arrays.


Missing Forecast Rows

Different parameter blocks may contain different timestamp counts.

The module resolves this by intersecting timestamps and only keeping fully populated rows.


Timeout Behavior

Network requests are abortable via AbortController.

If the timeout is exceeded:

  • the request is aborted
  • fetch rejects
  • the caller receives an error

Windowing Variant

The requested forecast range is determined dynamically using:

ndfdLocalWindow(numHoursForecast, windowStart)

This allows:

  • arbitrary forecast durations
  • arbitrary starting windows
  • deterministic local-hour alignment

Exports

WeatherFetchResult

type WeatherFetchResult = {
	points: WeatherPoint[];
	validTo: number;
};

Represents:

  • normalized forecast points
  • cache expiration timestamp

fetchNdfdWeatherSeries(...)

async function fetchNdfdWeatherSeries(
	lat: number,
	lon: number,
	numHoursForecast = 24,
	timeoutMs = 12000,
	windowStart: Date | string = new Date(),
): Promise<WeatherFetchResult>

Fetches and parses hourly NDFD forecast data for a single coordinate.

Parameters

ParameterDescription
latLatitude
lonLongitude
numHoursForecastNumber of forecast hours requested
timeoutMsNetwork timeout in milliseconds
windowStartForecast window start time

Returns

A Promise<WeatherFetchResult> containing:

  • chronologically ordered forecast points
  • cache validity expiration timestamp

Throws

Throws when:

  • coordinates are invalid
  • arguments are invalid
  • fetch fails
  • timeout occurs
  • XML structure is malformed
  • no usable forecast points exist