Skip to content

Commit 75fbdab

Browse files
committed
Document new module
1 parent 373e44b commit 75fbdab

4 files changed

Lines changed: 161 additions & 42 deletions

File tree

README.md

Lines changed: 125 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,10 @@
44

55
A tide prediction engine written in TypeScript.
66

7-
## 🚨Not for navigational use🚨
8-
9-
**Do not use calculations from this project for navigation, or depend on them in any situation where inaccuracies could result in harm to a person or property.**
10-
11-
Tide predictions are only as good as the harmonics data available, and these can be inconsistent and vary widely based on the accuracy of the source data and local conditions.
12-
13-
The tide predictions do not factor events such as storm surge, wind waves, uplift, tsunamis, or sadly, climate change. 😢
7+
> [!CAUTION]
8+
> **Not for navigational use**
9+
>
10+
> Do not use calculations from this project for navigation, or depend on them in any situation where inaccuracies could result in harm to a person or property. Tide predictions are only as good as the harmonics data available, and these can be inconsistent and vary widely based on the accuracy of the source data and local conditions. The tide predictions do not factor events such as storm surge, wind waves, uplift, tsunamis, or sadly, climate change. 😢
1411
1512
# Installation
1613

@@ -20,6 +17,8 @@ npm install neaps
2017

2118
# Usage
2219

20+
## Tide Extremes Prediction
21+
2322
```typescript
2423
import { getExtremesPrediction } from 'neaps'
2524

@@ -46,3 +45,122 @@ console.log(extremes)
4645
// ]
4746
// }
4847
```
48+
49+
## Get Timeline Prediction
50+
51+
```typescript
52+
import { getTimelinePrediction } from 'neaps'
53+
54+
const timeline = getTimelinePrediction({
55+
lat: 26.7,
56+
lon: -80.05,
57+
start: new Date('2025-12-19T00:00:00-05:00'),
58+
end: new Date('2025-12-19T01:00:00-05:00')
59+
})
60+
61+
console.log(timeline)
62+
// {
63+
// datum: 'MLLW',
64+
// station: {
65+
// id: 'us-fl-port-of-west-palm-beach',
66+
// name: 'Port of West Palm Beach',
67+
// // ...
68+
// },
69+
// timeline: [
70+
// { time: 2025-12-19T05:00:00.000Z, hour: 0, level: 0.07269280868130157 },
71+
// { time: 2025-12-19T05:10:00.000Z, hour: 0.16666666666666666, level: 0.054517202710722634 },
72+
// { time: 2025-12-19T05:20:00.000Z, hour: 0.3333333333333333, level: 0.0387568479105333 },
73+
// { time: 2025-12-19T05:30:00.000Z, hour: 0.5, level: 0.025594147655661648 },
74+
// { time: 2025-12-19T05:40:00.000Z, hour: 0.6666666666666666, level: 0.015185512178782279 },
75+
// { time: 2025-12-19T05:50:00.000Z, hour: 0.8333333333333334, level: 0.00765764296563648 },
76+
// { time: 2025-12-19T06:00:00.000Z, hour: 1, level: 0.0031046829237997287 }
77+
// ]
78+
// }
79+
```
80+
81+
## Get Water Level at Specific Time
82+
83+
```typescript
84+
import { getWaterLevelAtTime } from 'neaps'
85+
86+
const prediction = getWaterLevelAtTime({
87+
lat: 26.7,
88+
lon: -80.05,
89+
time: new Date('2025-12-19T00:30:00-05:00'),
90+
datum: 'MSL'
91+
})
92+
93+
console.log(prediction)
94+
// {
95+
// datum: 'MSL',
96+
// station: {
97+
// id: 'us-fl-port-of-west-palm-beach',
98+
// name: 'Port of West Palm Beach',
99+
// // ...
100+
// },
101+
// time: 2025-12-19T05:30:00.000Z,
102+
// hour: 0,
103+
// level: -0.43840585181640557
104+
// }
105+
```
106+
107+
## Finding stations
108+
109+
Neaps uses [@neaps/tide-database](https://github.com/neaps/tide-database) to find station data. You can find stations by location or ID.
110+
111+
### Nearest Station
112+
113+
```typescript
114+
import { nearestStation } from 'neaps'
115+
116+
const station = nearestStation({ lat: 26.7, lon: -80.05 })
117+
console.log(`${station.name} (${station.source.id})`) // Fort Lauderdale, FL (8722588)
118+
```
119+
120+
Once you've found a station, you can get predictions, timeline, or water level at a specific time for that station:
121+
122+
```typescript
123+
// Get extremes prediction for the nearest station
124+
station.getExtremesPrediction({
125+
start: new Date('2025-12-17'),
126+
end: new Date('2025-12-18')
127+
})
128+
129+
// Get timeline prediction for the nearest station
130+
station.getTimelinePrediction({
131+
start: new Date('2025-12-19'),
132+
end: new Date('2025-12-20')
133+
})
134+
135+
// Get timeline prediction for the nearest station
136+
station.getWaterLevelAt({ time: new Date('2025-12-19T00:30:00-00:00') })
137+
```
138+
139+
### List Nearby Stations
140+
141+
```typescript
142+
import { stationsNear } from 'neaps'
143+
144+
stationsNear({ latitude: 45.6, longitude: -122.7 }, 5).forEach((s) => {
145+
console.log(
146+
`${s.name} (${s.source.id}) - ${(s.distance / 1000).toFixed(2)} km away`
147+
)
148+
})
149+
// Vancouver (9440083) - 3.49 km away
150+
// Portland Morrison Street Bridge (9439221) - 10.24 km away
151+
// KNAPP(THORNES)LNDG, WILLOW BAR (9440171) - 16.34 km away
152+
// Rocky Point (9439189) - 16.93 km away
153+
// WASHOUGAL, COLUMBIA RIVER (9440047) - 24.89 km away
154+
```
155+
156+
### Find station by ID
157+
158+
```typescript
159+
import { findStation } from 'neaps'
160+
161+
// Find station by Neaps ID
162+
findStation('us-wa-seattle') // Seattle
163+
164+
// Find station by source ID (e.g. NOAA)
165+
findStation('9440083') // Vancouver
166+
```

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"private": true,
3+
"type": "module",
34
"scripts": {
45
"test": "vitest",
56
"coverage": "vitest run --coverage",

packages/neaps/src/index.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -94,48 +94,48 @@ export function findStation(query: string) {
9494
return useStation(found)
9595
}
9696

97-
export function useStation(metadata: Station, distance?: number) {
97+
export function useStation(station: Station, distance?: number) {
9898
// Use MLLW as the default datum if available
99-
const defaultDatum = 'MLLW' in metadata.datums ? 'MLLW' : undefined
99+
const defaultDatum = 'MLLW' in station.datums ? 'MLLW' : undefined
100100

101101
function getPredictor({ datum = defaultDatum }: DatumOption = {}) {
102102
let offset = 0
103103

104104
if (datum) {
105-
const datumOffset = metadata.datums?.[datum]
106-
const mslOffset = metadata.datums?.['MSL']
105+
const datumOffset = station.datums?.[datum]
106+
const mslOffset = station.datums?.['MSL']
107107

108108
if (!datumOffset) {
109109
throw new Error(
110-
`Station ${metadata.id} missing ${datum} datum. Available datums: ${Object.keys(metadata.datums || {}).join(', ')}`
110+
`Station ${station.id} missing ${datum} datum. Available datums: ${Object.keys(station.datums || {}).join(', ')}`
111111
)
112112
}
113113

114114
if (!mslOffset) {
115115
throw new Error(
116-
`Station ${metadata.id} missing MSL datum, so predictions can't be given in ${datum}.`
116+
`Station ${station.id} missing MSL datum, so predictions can't be given in ${datum}.`
117117
)
118118
}
119119

120120
offset = mslOffset - datumOffset
121121
}
122122

123-
return tidePredictor(metadata.harmonic_constituents, {
123+
return tidePredictor(station.harmonic_constituents, {
124124
phaseKey: 'phase_UTC',
125125
offset
126126
})
127127
}
128128

129129
return {
130-
metadata,
130+
...station,
131131
distance,
132132
defaultDatum,
133133
getExtremesPrediction({ datum = defaultDatum, ...input }: ExtremesOptions) {
134134
return {
135135
datum,
136136
distance,
137-
station: metadata,
138-
predictions: getPredictor({ datum }).getExtremesPrediction(input)
137+
station,
138+
extremes: getPredictor({ datum }).getExtremesPrediction(input)
139139
}
140140
},
141141

@@ -145,15 +145,15 @@ export function useStation(metadata: Station, distance?: number) {
145145
}: TimelineOptions) {
146146
return {
147147
datum,
148-
station: metadata,
148+
station,
149149
timeline: getPredictor({ datum }).getTimelinePrediction(params)
150150
}
151151
},
152152

153153
getWaterLevelAtTime({ time, datum = defaultDatum }: WaterLevelOptions) {
154154
return {
155155
datum,
156-
station: metadata,
156+
station,
157157
...getPredictor({ datum }).getWaterLevelAtTime({ time })
158158
}
159159
}

packages/neaps/test/index.test.ts

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ process.env.TZ = 'UTC'
1515

1616
describe('getExtremesPrediction', () => {
1717
test('gets extremes from nearest station', () => {
18-
const extremes = getExtremesPrediction({
18+
const prediction = getExtremesPrediction({
1919
lat: 26.7,
2020
lon: -80.05,
2121
start: new Date('2025-12-18T00:00:00-05:00'),
@@ -24,16 +24,16 @@ describe('getExtremesPrediction', () => {
2424
datum: 'MLLW'
2525
})
2626

27-
expect(extremes.station.id).toEqual('us-fl-port-of-west-palm-beach')
28-
expect(extremes.datum).toBe('MLLW')
27+
expect(prediction.station.id).toEqual('us-fl-port-of-west-palm-beach')
28+
expect(prediction.datum).toBe('MLLW')
2929

30-
const { predictions } = extremes
31-
expect(predictions.length).toBe(4)
32-
expect(predictions[0].time).toEqual(new Date('2025-12-18T05:29:48.000Z'))
33-
expect(predictions[0].level).toBeCloseTo(0.02, 2)
34-
expect(predictions[0].high).toBe(false)
35-
expect(predictions[0].low).toBe(true)
36-
expect(predictions[0].label).toBe('Low')
30+
const { extremes } = prediction
31+
expect(extremes.length).toBe(4)
32+
expect(extremes[0].time).toEqual(new Date('2025-12-18T05:29:48.000Z'))
33+
expect(extremes[0].level).toBeCloseTo(0.02, 2)
34+
expect(extremes[0].high).toBe(false)
35+
expect(extremes[0].low).toBe(true)
36+
expect(extremes[0].label).toBe('Low')
3737
})
3838
})
3939

@@ -54,17 +54,17 @@ describe('getTimelinePrediction', () => {
5454

5555
describe('getWaterLevelAtTime', () => {
5656
test('gets water level at specific time from nearest station', () => {
57-
const waterLevel = getWaterLevelAtTime({
57+
const prediction = getWaterLevelAtTime({
5858
lat: 26.7,
5959
lon: -80.05,
6060
time: new Date('2025-12-19T00:30:00-05:00'),
6161
datum: 'MSL'
6262
})
6363

64-
expect(waterLevel.station.id).toEqual('us-fl-port-of-west-palm-beach')
65-
expect(waterLevel.datum).toBe('MSL')
66-
expect(waterLevel.time).toEqual(new Date('2025-12-19T05:30:00.000Z'))
67-
expect(typeof waterLevel.level).toBe('number')
64+
expect(prediction.station.id).toEqual('us-fl-port-of-west-palm-beach')
65+
expect(prediction.datum).toBe('MSL')
66+
expect(prediction.time).toEqual(new Date('2025-12-19T05:30:00.000Z'))
67+
expect(typeof prediction.level).toBe('number')
6868
})
6969
})
7070

@@ -78,7 +78,7 @@ describe('for a specific station', () => {
7878
const start = new Date('2025-12-17T00:00:00-05:00')
7979
const end = new Date('2025-12-18T05:00:00-05:00')
8080

81-
const { predictions } = station.getExtremesPrediction({
81+
const { extremes: predictions } = station.getExtremesPrediction({
8282
start,
8383
end,
8484
timeFidelity: 6,
@@ -121,9 +121,9 @@ describe('for a specific station', () => {
121121
describe('nearestStation', () => {
122122
test('finds the nearest station', () => {
123123
const station = nearestStation({ lat: 26.7, lon: -80.05 })
124-
expect(station.metadata.source.id).toBe('8722588')
125-
expect(station.metadata.latitude).toBeCloseTo(26.77)
126-
expect(station.metadata.longitude).toBeCloseTo(-80.0517)
124+
expect(station.source.id).toBe('8722588')
125+
expect(station.latitude).toBeCloseTo(26.77)
126+
expect(station.longitude).toBeCloseTo(-80.0517)
127127
})
128128
;[
129129
{ lat: 26.7, lon: -80.05 },
@@ -132,7 +132,7 @@ describe('nearestStation', () => {
132132
].forEach((position) => {
133133
test(`finds station with ${Object.keys(position).join('/')}`, () => {
134134
const station = nearestStation(position)
135-
expect(station.metadata.id).toEqual('us-fl-port-of-west-palm-beach')
135+
expect(station.id).toEqual('us-fl-port-of-west-palm-beach')
136136
})
137137
})
138138
})
@@ -145,14 +145,14 @@ describe('findStation', () => {
145145
test('finds station by id', () => {
146146
const station = findStation('us-fl-ankona-indian-river')
147147
expect(station).toBeDefined()
148-
expect(station.metadata.id).toBe('us-fl-ankona-indian-river')
148+
expect(station.id).toBe('us-fl-ankona-indian-river')
149149
expect(station.getExtremesPrediction).toBeDefined()
150150
})
151151

152152
test('finds station by source id', () => {
153153
const station = findStation('8443970')
154154
expect(station).toBeDefined()
155-
expect(station.metadata.id).toBe('us-ma-boston')
155+
expect(station.id).toBe('us-ma-boston')
156156
expect(station.getExtremesPrediction).toBeDefined()
157157
})
158158
})
@@ -213,6 +213,6 @@ describe('datum', () => {
213213
})
214214

215215
expect(extremes.datum).toBeUndefined()
216-
expect(extremes.predictions.length).toBeGreaterThan(0)
216+
expect(extremes.extremes.length).toBeGreaterThan(0)
217217
})
218218
})

0 commit comments

Comments
 (0)