Skip to content

0020: HURDAT2 as one whole file per basin with a format reader

Status: accepted. Date: 2026-09-12.

Context

The National Hurricane Center publishes the HURDAT2 best-track database as two plain-text files, Atlantic and northeast/north-central Pacific, in one Apache directory that also keeps past revisions — 41 HURDAT2 data files on 2026-09-12, the oldest revised 2017-04-13. How deep that archive runs is the NHC's choice and it is not a complete history: revisions older than the ones listed are simply gone. Filenames embed the data span and a revision date in MMDDYY or MMDDYYYY form, occasionally with a trailing letter, and the Atlantic basin token is sometimes absent. There is no records API, no per-storm file, and no server-side subsetting of any kind. The format is also unlike anything usdata already reads: storm headers declaring a track-point count, followed by that many fixed-position lines with hemisphere-suffixed coordinates and several missing-data sentinels.

Every previous dataset let a query narrow what was downloaded. Here nothing can, so the useful work is turning one whole file into an analyzable table.

Decision

Expose one asset per basin, selected by a basin param (atlantic by default, pacific). Resolve the directory listing to the newest data span and then the newest revision of that span, parsing revision dates rather than sorting names as text. Ignore other basins, unparseable dates, and non-local links. Keep the complete filename as the stable asset ID and preserve the original bytes; leave asset size unknown because the listing reports approximate sizes. Reuse the Storm Events pattern (ADR 0010) for listing, whole-file fetch, and lockfile pinning; capabilities stay false.

Reject dates instead of accepting them as informational bounds. Every revision holds the complete record for its basin, so a requested window selects nothing, and copying it into asset time bounds would misdescribe the cached file in the lockfile and provenance sidecar. Asset time bounds report the data span named in the filename. Reject geographic filters, variables, text queries, and unknown params for the same reason: nothing about the download changes.

Add a hurdat2 reader behind the existing pandas extra (ADR 0006), in its own private module beside the NetCDF and radar readers. Infer it from the dataset id or a hurdat2-*.txt asset ID, since the service serves text/plain and media type alone cannot identify the format. Return one row per track point with storm_id, name, UTC time, record codes, signed decimal coordinates, wind, pressure, the twelve wind radii, and the radius of maximum wind. Convert the documented sentinels (-999, and -99 for a maximum wind left unassigned on a non-developing depression) to NaN, normalize longitudes into [-180, 180], use float dtype so the gaps are representable, and encode units in column names rather than synthesizing a units map. Validate declared track-point counts, field counts, timestamps, coordinates, and measurements, raising Hurdat2FormatError with the offending line instead of returning a partly parsed table. Reject the CSV options, as the NetCDF and radar readers already do.

Consequences

A caller interested in one storm still downloads about 7 MB (Atlantic) or 4 MB (Pacific) and filters locally; the example and guide say so. Two basins cannot be combined in one asset, and a manifest wanting both lists two sources. Rejecting dates makes HURDAT2 the first adapter where a manifest's shared date range is an error, which is deliberate: a silent no-op would be indistinguishable from a working filter.

Revision selection depends on NHC filename conventions. A name that breaks the span-and-date pattern is ignored rather than guessed at, which is right for the names already in the listing: hurdat2-atl-02052024.txt carries no data span. The cost is that "no file in the listing" only surfaces once no name parses, so a convention change affecting only the newest file would leave the adapter serving the newest name it can still read. Pinning a revision in a lockfile, not trusting the listing, is what makes a run reproducible. When a new season's file appears under a new name, existing lockfiles keep restoring the old one until the NHC removes it; preserving the cache remains the durable reproducibility answer.

Accepting 20- as well as 21-field data lines, and longitudes written in the unwrapped 0-360 west convention, is what keeps archived revisions readable rather than merely downloadable: max_wind_radius_nm is NaN before 2021, and a point written 358.0W reads as 2.0, the value the NHC itself later published for it. The cost is a looser format check: with the longitude bound raised from 180 to 360, a transposition such as 290.2W for 209.2W now parses silently as 69.8E where it would once have raised. That is accepted because the unwrapped form is real and continuous in the archive — AL061966 drifts from 305.0W to 299.0W without a break — so no bound can separate it from a typo of the same magnitude. Of the 41 files the directory listed on 2026-09-12, 39 parse; the two that do not carry an upstream typo, a missing comma and a date of C0091018, each fixed by the next revision of the same span. Refusing those is the point of the format error. Column names, not attributes, carry units, so exports keep them. Scientific interpretation is unchanged by this decision: the reader does not correct the reanalysis's era-dependent undercounting, does not reconstruct a wind field from quadrant radii, and does not merge basins. IBTrACS remains a separate planned entry for global merged tracks.