Get marine conditions from NOAA via their various free APIs in one convoluted and far too complicated package.
This is a slightly cleaned up version of the backend of my rideai project to provide AI generated surf reports circa 2022-23. I rolled my own version of a sea state data API using NOAA resources -- so it'll only work in the US.
Caveat: These are most of the juicy bits. I didn't put a whole lot of effort into ensuring that I extracted every tiny bit of "cool" (I use that word loosely -- the entire ordeal of developing rideai was a farcical exercise in what not to do when you don't know JavaScript).
So, along with my getting banned from r/surfing for promoting it, maybe by publishing this stuff in a public repo, someone out there will find some use for something in here and two good things will come of the project.
# Clone and install
git clone https://github.com/daveshilobod/buoyant.git
cd buoyant
npm install
# Option 1: Run directly with node
node cli.js zip 96813 # Honolulu
node cli.js coords -- 21.3 -157.8 # By coordinates
node cli.js buoy 51211 # Specific buoy
# Option 2: Install as global command (like pip install -e .)
chmod +x cli.js # One time only
npm link
# Now use from anywhere
buoyant report 96813
buoyant coords -- 21.3 -157.8
buoyant buoy 51211
# Option 3: Use the simple wrapper (no npm link needed)
chmod +x buoyant # One time only
./buoyant report 96813That's it, just clone and run.
- Wave conditions from NDBC buoys (height, period, direction)
- Wind data (speed, direction, gusts)
- Tide levels from CO-OPS stations
- Water temperature from buoys
If you want to use it in your own code:
const Buoyant = require('./buoyant/index'); // Adjust path as needed
const buoyant = new Buoyant();
// Get conditions by coordinates
const conditions = await buoyant.getSeaState(21.289, -157.917);
// Or use a coastal zip code
const data = await buoyant.getSeaStateByZip('96712');
// Get specific buoy data
const buoy = await buoyant.getBuoy('51201');- US mainland coastal waters (Atlantic, Pacific, Gulf of Mexico)
- Great Lakes
- Hawaii, Alaska, Puerto Rico, USVI, Guam
- Requires location to be within ~10 miles of coast
Get marine conditions for a US zip code. Only works for coastal areas.
Get all available marine data for coordinates.
And yes, lon. Not lng. Because lng is objectively wrong.
Get current observations from a specific NDBC buoy.
Find available data sources near a location.
Get tide predictions and current water level.
Get NWS weather forecast for coordinates.
- NDBC Buoys: Wave height/period/direction, wind, water temp (30-60 min updates)
- CO-OPS Stations: Tide levels and predictions (6 min updates)
- NWS Gridpoints: Wave forecasts when buoy data unavailable
- Data can be 1-2 hours delayed
// Get marine data by zip code
const data = await buoyant.getSeaStateByZip('90266'); // Manhattan Beach, CA
// Returns: {
// location: { lat: 33.88, lon: -118.41 },
// waves: { height: 1.5, period: 8, direction: 270 },
// wind: { speed: 12, direction: 90, gusts: 15 },
// tides: { current: { height: 1.2, units: 'ft' }},
// sources: ['NDBC', 'CO-OPS']
// }
// Access the data
if (data.waves?.height) {
console.log(`Wave height: ${data.waves.height}m`);
}
// Not all fields are always available
const conditions = await buoyant.getSeaState(47.6, -122.3);
if (!conditions.waves) {
console.log('No wave data available for this location');
}
// Inland locations throw an error
try {
await buoyant.getSeaStateByZip('80202'); // Denver
} catch (err) {
console.log(err.message); // "Zip code 80202 appears to be an inland location"
}This library includes several approaches developed for rideai to handle NOAA's data quirks:
Yes, this repo includes ~40MB of JSON files. No, they're not bloat. Here's why:
nwsStations.json (37MB): Contains every NOAA weather station with coordinates. NOAA doesn't provide a proper API endpoint for "find stations near location", so I needed this for spatial lookups. Loading this once on startup is still faster than making multiple API calls.
Coastal boundary files (1-2MB): These geojson files enable instant coastal validation without API calls. See below on Coastal Boundary Validation
NDBC/Tide station lists: Pre-computed station locations with metadata. Again, NOAA doesn't provide a "find nearest" API, so I built my own spatial index.
The coastal boundary polygon (thickUSOutline.geojson) was created by:
- Taking a high-resolution US polygon
- Creating an outward buffer of ~15km
- Creating an inward buffer of ~15km
- Subtracting the inner from the outer to create a "thick outline" along the coast
This allows point-in-polygon tests to validate coastal locations without needing to check water bodies. You can read about the details of this kludge in buoyant/tools/COASTAL_BOUNDARY.md
Tide station locations are stored using geohashes as keys. The geohash itself encodes the latitude/longitude, eliminating the need for separate coordinate lookups while providing built-in spatial proximity.
NOAA's Weather Service provides wave data on a 2.5km grid, but not all coastal gridpoints have marine data. The library implements an expanding ring search:
- Check the target gridpoint
- If no data, check 8 immediate neighbors
- If still no data, expand to 12 outer neighbors
- Return data from the first gridpoint that has it
This handles the sparse coverage without requiring pre-mapping of valid gridpoints.
The comprehensive NDBC station list was built by scraping station metadata from NOAA's website, as they don't provide a complete API endpoint for all stations with coordinates.
The NWS forecast.weather.gov site accepts zip codes and redirects to a URL containing coordinates. This library leverages that redirect to convert zip codes to lat/lon without requiring a geocoding API.
NDBC spectral data includes a STEEPNESS field with values: AVERAGE, STEEP, VERY_STEEP, and inexplicably, SWELL. NOAA provides no documentation on what these mean. Best guesses:
AVERAGE- Normal wave steepness ratioSTEEP- Waves breaking more abruptly than normalVERY_STEEP- Approaching critical steepness, expect powerful breaking wavesSWELL- ??? Your guess is as good as mine. Maybe clean groundswell? Maybe a data error? I interpreted it as "promising conditions" but honestly, who knows
If anyone figures out what NDBC actually means by these classifications, please let me know.
- NDBC buoys can go offline without warning
- Some coastal zip codes may not have nearby data
- Government servers occasionally timeout
- Data can be 1-2 hours old
- Inland locations like Denver give you a helpful warning that they are, in fact, inland. Towns on the border between Mexico and Canada do not give you the same warning -- this is due to the coastal boundary doughnut including national borders
HAHAHAHHAHAHAHAHAAAA
No. I'm done with this. Do what you will.
terminal > buoyant report 96815 --radius 15 # Default is 25km
Loaded 1502 NDBC stations from ndbc-stations.json
Loaded 298 tide stations
Loaded coastal boundary validation
π COMPREHENSIVE MARINE REPORT
π Honolulu
π°οΈ 9/18/2025, 8:50:11 PM
ββββββββββββββββββββββββββββββββββββββββββββββββββ
π WAVES
ββββββββββββββββββββββββββββββ
Height: 1.2m (3.9ft)
Dominant Period: 6s
Average Period: 5.7s
Direction: 147Β°
Source: Buoy 51211 (10.8km away)
π¨ WIND
ββββββββββββββββββββββββββββββ
Speed: 3.6 m/s
Direction: 70Β°
Gusts: 6.7 m/s
Source: Buoy OOUH1 (1.0km away)
π TIDES
ββββββββββββββββββββββββββββββ
Current Level: 0.211 m
As of: 8:42:00 PM
Next 4 Tide Changes:
πΊ high: 02:01 AM (0.329 m)
π» low: 07:17 AM (0.079 m)
πΊ high: 02:12 PM (0.665 m)
π» low: 09:03 PM (0.069 m)
Station: 1612340 (1.0km away)
π‘οΈ WATER TEMPERATURE
ββββββββββββββββββββββββββββββ
27Β°C
βοΈ WEATHER
ββββββββββββββββββββββββββββββ
Temperature: 81Β°F (27.2Β°C)
Conditions: Mostly Clear
Humidity: 67%
Visibility: 10.0 miles
Pressure: 1017.3 mb
Forecast:
Tonight: 78Β°F - Scattered Rain Showers
Friday: 85Β°F - Scattered Rain Showers then Mostly Sunny
Friday Night: 79Β°F - Scattered Rain Showers
π‘ AVAILABLE DATA SOURCES
ββββββββββββββββββββββββββββββ
Nearby Buoys:
OOUH1: 1612340 - Honolulu, HI (1.0km)
KNOH1: Kilo Nalu Observatory, Hawaii (2.1km)
51211: Pearl Harbor Entrance, HI (233) (10.8km)
Tide Stations:
1612340: Unknown (1.0km)
ββββββββββββββββββββββββββββββββββββββββββββββββββ
Try specific buoy: buoyant buoy <id> --spectral
MIT
Data provided by NOAA/NDBC/NWS.