neaps 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,157 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) {
13
+ __defProp(to, key, {
14
+ get: ((k) => from[k]).bind(null, key),
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ });
17
+ }
18
+ }
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
26
+
27
+ //#endregion
28
+ let geolib = require("geolib");
29
+ let _neaps_tide_database = require("@neaps/tide-database");
30
+ _neaps_tide_database = __toESM(_neaps_tide_database);
31
+ let _neaps_tide_predictor = require("@neaps/tide-predictor");
32
+ _neaps_tide_predictor = __toESM(_neaps_tide_predictor);
33
+
34
+ //#region src/index.ts
35
+ /**
36
+ * Get extremes prediction using the nearest station to the given position.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * import { getExtremesPrediction } from 'neaps'
41
+ *
42
+ * const prediction = getExtremesPrediction({
43
+ * latitude: 26.7, // or `lat`
44
+ * longitude: -80.05, // or `lng` or `lon`
45
+ * start: new Date('2025-12-17'),
46
+ * end: new Date('2025-12-18'),
47
+ * datum: 'MLLW', // optional, defaults to MLLW if available
48
+ * })
49
+ */
50
+ function getExtremesPrediction(options) {
51
+ return nearestStation(options).getExtremesPrediction(options);
52
+ }
53
+ /**
54
+ * Get timeline prediction using the nearest station to the given position.
55
+ */
56
+ function getTimelinePrediction(options) {
57
+ return nearestStation(options).getTimelinePrediction(options);
58
+ }
59
+ /**
60
+ * Get water level at a specific time using the nearest station to the given position.
61
+ */
62
+ function getWaterLevelAtTime(options) {
63
+ return nearestStation(options).getWaterLevelAtTime(options);
64
+ }
65
+ /**
66
+ * Find the nearest station to the given position.
67
+ */
68
+ function nearestStation(position) {
69
+ return stationsNear(position, 1)[0];
70
+ }
71
+ /**
72
+ * Find stations near the given position.
73
+ * @param limit Maximum number of stations to return (default: 10)
74
+ */
75
+ function stationsNear(position, limit = 10) {
76
+ return _neaps_tide_database.default.map((station) => ({
77
+ station,
78
+ distance: (0, geolib.getDistance)(position, station)
79
+ })).sort((a, b) => a.distance - b.distance).slice(0, limit).map(({ station, distance }) => useStation(station, distance));
80
+ }
81
+ /**
82
+ * Find a specific station by its ID or source ID.
83
+ */
84
+ function findStation(query) {
85
+ const searches = [(s) => s.id === query, (s) => s.source.id === query];
86
+ let found = void 0;
87
+ for (const search of searches) {
88
+ found = _neaps_tide_database.default.find(search);
89
+ if (found) break;
90
+ }
91
+ if (!found) throw new Error(`Station not found: ${query}`);
92
+ return useStation(found);
93
+ }
94
+ function useStation(station, distance) {
95
+ let reference = station;
96
+ if (station.type === "subordinate") reference = findStation(station.offsets?.reference || "");
97
+ const { datums, harmonic_constituents } = reference;
98
+ const defaultDatum = "MLLW" in datums ? "MLLW" : void 0;
99
+ function getPredictor({ datum = defaultDatum } = {}) {
100
+ let offset = 0;
101
+ if (datum) {
102
+ const datumOffset = datums?.[datum];
103
+ const mslOffset = datums?.["MSL"];
104
+ if (typeof datumOffset !== "number") throw new Error(`Station ${station.id} missing ${datum} datum. Available datums: ${Object.keys(datums || {}).join(", ")}`);
105
+ if (typeof mslOffset !== "number") throw new Error(`Station ${station.id} missing MSL datum, so predictions can't be given in ${datum}.`);
106
+ offset = mslOffset - datumOffset;
107
+ }
108
+ return (0, _neaps_tide_predictor.default)(harmonic_constituents, {
109
+ phaseKey: "phase_UTC",
110
+ offset
111
+ });
112
+ }
113
+ return {
114
+ ...station,
115
+ distance,
116
+ datums,
117
+ harmonic_constituents,
118
+ defaultDatum,
119
+ getExtremesPrediction({ datum = defaultDatum, ...input }) {
120
+ return {
121
+ datum,
122
+ distance,
123
+ station,
124
+ extremes: getPredictor({ datum }).getExtremesPrediction({
125
+ ...input,
126
+ offsets: station.offsets
127
+ })
128
+ };
129
+ },
130
+ getTimelinePrediction({ datum = defaultDatum, ...params }) {
131
+ if (station.type === "subordinate") throw new Error(`Timeline predictions are not supported for subordinate stations.`);
132
+ return {
133
+ datum,
134
+ station,
135
+ timeline: getPredictor({ datum }).getTimelinePrediction(params)
136
+ };
137
+ },
138
+ getWaterLevelAtTime({ time, datum = defaultDatum }) {
139
+ if (station.type === "subordinate") throw new Error(`Water level predictions are not supported for subordinate stations.`);
140
+ return {
141
+ datum,
142
+ station,
143
+ ...getPredictor({ datum }).getWaterLevelAtTime({ time })
144
+ };
145
+ }
146
+ };
147
+ }
148
+
149
+ //#endregion
150
+ exports.findStation = findStation;
151
+ exports.getExtremesPrediction = getExtremesPrediction;
152
+ exports.getTimelinePrediction = getTimelinePrediction;
153
+ exports.getWaterLevelAtTime = getWaterLevelAtTime;
154
+ exports.nearestStation = nearestStation;
155
+ exports.stationsNear = stationsNear;
156
+ exports.useStation = useStation;
157
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["stations","found: Station | undefined"],"sources":["../src/index.ts"],"sourcesContent":["import { getDistance } from 'geolib'\nimport stations, { type Station } from '@neaps/tide-database'\nimport tidePredictor, {\n type TimeSpan,\n type ExtremesInput\n} from '@neaps/tide-predictor'\nimport type { GeolibInputCoordinates } from 'geolib/es/types'\n\ntype DatumOption = {\n /** Datum to return predictions in. Defaults to 'MLLW' if available for the nearest station. */\n datum?: string\n}\n\nexport type ExtremesOptions = ExtremesInput & DatumOption\nexport type TimelineOptions = TimeSpan & DatumOption\nexport type WaterLevelOptions = { time: Date } & DatumOption\n\n/**\n * Get extremes prediction using the nearest station to the given position.\n *\n * @example\n * ```ts\n * import { getExtremesPrediction } from 'neaps'\n *\n * const prediction = getExtremesPrediction({\n * latitude: 26.7, // or `lat`\n * longitude: -80.05, // or `lng` or `lon`\n * start: new Date('2025-12-17'),\n * end: new Date('2025-12-18'),\n * datum: 'MLLW', // optional, defaults to MLLW if available\n * })\n */\nexport function getExtremesPrediction(\n options: GeolibInputCoordinates & ExtremesOptions\n) {\n return nearestStation(options).getExtremesPrediction(options)\n}\n\n/**\n * Get timeline prediction using the nearest station to the given position.\n */\nexport function getTimelinePrediction(\n options: GeolibInputCoordinates & TimelineOptions\n) {\n return nearestStation(options).getTimelinePrediction(options)\n}\n\n/**\n * Get water level at a specific time using the nearest station to the given position.\n */\nexport function getWaterLevelAtTime(\n options: GeolibInputCoordinates & WaterLevelOptions\n) {\n return nearestStation(options).getWaterLevelAtTime(options)\n}\n\n/**\n * Find the nearest station to the given position.\n */\nexport function nearestStation(position: GeolibInputCoordinates) {\n return stationsNear(position, 1)[0]\n}\n\n/**\n * Find stations near the given position.\n * @param limit Maximum number of stations to return (default: 10)\n */\nexport function stationsNear(position: GeolibInputCoordinates, limit = 10) {\n return stations\n .map((station) => ({ station, distance: getDistance(position, station) }))\n .sort((a, b) => a.distance - b.distance)\n .slice(0, limit)\n .map(({ station, distance }) => useStation(station, distance))\n}\n\n/**\n * Find a specific station by its ID or source ID.\n */\nexport function findStation(query: string) {\n const searches = [\n (s: Station) => s.id === query,\n (s: Station) => s.source.id === query\n ]\n\n let found: Station | undefined = undefined\n\n for (const search of searches) {\n found = stations.find(search)\n if (found) break\n }\n\n if (!found) throw new Error(`Station not found: ${query}`)\n\n return useStation(found)\n}\n\nexport function useStation(station: Station, distance?: number) {\n // If subordinate station, use the reference station for datums and constituents\n let reference = station\n if (station.type === 'subordinate') {\n reference = findStation(station.offsets?.reference || '')\n }\n const { datums, harmonic_constituents } = reference\n\n // Use MLLW as the default datum if available\n const defaultDatum = 'MLLW' in datums ? 'MLLW' : undefined\n\n function getPredictor({ datum = defaultDatum }: DatumOption = {}) {\n let offset = 0\n\n if (datum) {\n const datumOffset = datums?.[datum]\n const mslOffset = datums?.['MSL']\n\n if (typeof datumOffset !== 'number') {\n throw new Error(\n `Station ${station.id} missing ${datum} datum. Available datums: ${Object.keys(datums || {}).join(', ')}`\n )\n }\n\n if (typeof mslOffset !== 'number') {\n throw new Error(\n `Station ${station.id} missing MSL datum, so predictions can't be given in ${datum}.`\n )\n }\n\n offset = mslOffset - datumOffset\n }\n\n return tidePredictor(harmonic_constituents, {\n phaseKey: 'phase_UTC',\n offset\n })\n }\n\n return {\n ...station,\n distance,\n datums,\n harmonic_constituents,\n defaultDatum,\n getExtremesPrediction({ datum = defaultDatum, ...input }: ExtremesOptions) {\n return {\n datum,\n distance,\n station,\n extremes: getPredictor({ datum }).getExtremesPrediction({\n ...input,\n offsets: station.offsets\n })\n }\n },\n\n getTimelinePrediction({\n datum = defaultDatum,\n ...params\n }: TimelineOptions) {\n if (station.type === 'subordinate') {\n throw new Error(\n `Timeline predictions are not supported for subordinate stations.`\n )\n }\n\n return {\n datum,\n station,\n timeline: getPredictor({ datum }).getTimelinePrediction(params)\n }\n },\n\n getWaterLevelAtTime({ time, datum = defaultDatum }: WaterLevelOptions) {\n if (station.type === 'subordinate') {\n throw new Error(\n `Water level predictions are not supported for subordinate stations.`\n )\n }\n\n return {\n datum,\n station,\n ...getPredictor({ datum }).getWaterLevelAtTime({ time })\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,sBACd,SACA;AACA,QAAO,eAAe,QAAQ,CAAC,sBAAsB,QAAQ;;;;;AAM/D,SAAgB,sBACd,SACA;AACA,QAAO,eAAe,QAAQ,CAAC,sBAAsB,QAAQ;;;;;AAM/D,SAAgB,oBACd,SACA;AACA,QAAO,eAAe,QAAQ,CAAC,oBAAoB,QAAQ;;;;;AAM7D,SAAgB,eAAe,UAAkC;AAC/D,QAAO,aAAa,UAAU,EAAE,CAAC;;;;;;AAOnC,SAAgB,aAAa,UAAkC,QAAQ,IAAI;AACzE,QAAOA,6BACJ,KAAK,aAAa;EAAE;EAAS,kCAAsB,UAAU,QAAQ;EAAE,EAAE,CACzE,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,SAAS,CACvC,MAAM,GAAG,MAAM,CACf,KAAK,EAAE,SAAS,eAAe,WAAW,SAAS,SAAS,CAAC;;;;;AAMlE,SAAgB,YAAY,OAAe;CACzC,MAAM,WAAW,EACd,MAAe,EAAE,OAAO,QACxB,MAAe,EAAE,OAAO,OAAO,MACjC;CAED,IAAIC,QAA6B;AAEjC,MAAK,MAAM,UAAU,UAAU;AAC7B,UAAQD,6BAAS,KAAK,OAAO;AAC7B,MAAI,MAAO;;AAGb,KAAI,CAAC,MAAO,OAAM,IAAI,MAAM,sBAAsB,QAAQ;AAE1D,QAAO,WAAW,MAAM;;AAG1B,SAAgB,WAAW,SAAkB,UAAmB;CAE9D,IAAI,YAAY;AAChB,KAAI,QAAQ,SAAS,cACnB,aAAY,YAAY,QAAQ,SAAS,aAAa,GAAG;CAE3D,MAAM,EAAE,QAAQ,0BAA0B;CAG1C,MAAM,eAAe,UAAU,SAAS,SAAS;CAEjD,SAAS,aAAa,EAAE,QAAQ,iBAA8B,EAAE,EAAE;EAChE,IAAI,SAAS;AAEb,MAAI,OAAO;GACT,MAAM,cAAc,SAAS;GAC7B,MAAM,YAAY,SAAS;AAE3B,OAAI,OAAO,gBAAgB,SACzB,OAAM,IAAI,MACR,WAAW,QAAQ,GAAG,WAAW,MAAM,4BAA4B,OAAO,KAAK,UAAU,EAAE,CAAC,CAAC,KAAK,KAAK,GACxG;AAGH,OAAI,OAAO,cAAc,SACvB,OAAM,IAAI,MACR,WAAW,QAAQ,GAAG,uDAAuD,MAAM,GACpF;AAGH,YAAS,YAAY;;AAGvB,4CAAqB,uBAAuB;GAC1C,UAAU;GACV;GACD,CAAC;;AAGJ,QAAO;EACL,GAAG;EACH;EACA;EACA;EACA;EACA,sBAAsB,EAAE,QAAQ,cAAc,GAAG,SAA0B;AACzE,UAAO;IACL;IACA;IACA;IACA,UAAU,aAAa,EAAE,OAAO,CAAC,CAAC,sBAAsB;KACtD,GAAG;KACH,SAAS,QAAQ;KAClB,CAAC;IACH;;EAGH,sBAAsB,EACpB,QAAQ,cACR,GAAG,UACe;AAClB,OAAI,QAAQ,SAAS,cACnB,OAAM,IAAI,MACR,mEACD;AAGH,UAAO;IACL;IACA;IACA,UAAU,aAAa,EAAE,OAAO,CAAC,CAAC,sBAAsB,OAAO;IAChE;;EAGH,oBAAoB,EAAE,MAAM,QAAQ,gBAAmC;AACrE,OAAI,QAAQ,SAAS,cACnB,OAAM,IAAI,MACR,sEACD;AAGH,UAAO;IACL;IACA;IACA,GAAG,aAAa,EAAE,OAAO,CAAC,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACzD;;EAEJ"}
@@ -0,0 +1,367 @@
1
+ import * as _neaps_tide_predictor0 from "@neaps/tide-predictor";
2
+ import { ExtremesInput, TimeSpan } from "@neaps/tide-predictor";
3
+ import { Station } from "@neaps/tide-database";
4
+ import { GeolibInputCoordinates } from "geolib/es/types";
5
+
6
+ //#region src/index.d.ts
7
+ type DatumOption = {
8
+ /** Datum to return predictions in. Defaults to 'MLLW' if available for the nearest station. */
9
+ datum?: string;
10
+ };
11
+ type ExtremesOptions = ExtremesInput & DatumOption;
12
+ type TimelineOptions = TimeSpan & DatumOption;
13
+ type WaterLevelOptions = {
14
+ time: Date;
15
+ } & DatumOption;
16
+ /**
17
+ * Get extremes prediction using the nearest station to the given position.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * import { getExtremesPrediction } from 'neaps'
22
+ *
23
+ * const prediction = getExtremesPrediction({
24
+ * latitude: 26.7, // or `lat`
25
+ * longitude: -80.05, // or `lng` or `lon`
26
+ * start: new Date('2025-12-17'),
27
+ * end: new Date('2025-12-18'),
28
+ * datum: 'MLLW', // optional, defaults to MLLW if available
29
+ * })
30
+ */
31
+ declare function getExtremesPrediction(options: GeolibInputCoordinates & ExtremesOptions): {
32
+ datum: string | undefined;
33
+ distance: number | undefined;
34
+ station: Station;
35
+ extremes: _neaps_tide_predictor0.Extreme[];
36
+ };
37
+ /**
38
+ * Get timeline prediction using the nearest station to the given position.
39
+ */
40
+ declare function getTimelinePrediction(options: GeolibInputCoordinates & TimelineOptions): {
41
+ datum: string | undefined;
42
+ station: Station;
43
+ timeline: _neaps_tide_predictor0.TimelinePoint[];
44
+ };
45
+ /**
46
+ * Get water level at a specific time using the nearest station to the given position.
47
+ */
48
+ declare function getWaterLevelAtTime(options: GeolibInputCoordinates & WaterLevelOptions): {
49
+ time: Date;
50
+ hour: number;
51
+ level: number;
52
+ datum: string | undefined;
53
+ station: Station;
54
+ };
55
+ /**
56
+ * Find the nearest station to the given position.
57
+ */
58
+ declare function nearestStation(position: GeolibInputCoordinates): {
59
+ distance: number | undefined;
60
+ datums: Record<string, number>;
61
+ harmonic_constituents: {
62
+ name: string;
63
+ description?: string;
64
+ amplitude: number;
65
+ phase_UTC: number;
66
+ phase_local: number;
67
+ speed?: number;
68
+ }[];
69
+ defaultDatum: string | undefined;
70
+ getExtremesPrediction({
71
+ datum,
72
+ ...input
73
+ }: ExtremesOptions): {
74
+ datum: string | undefined;
75
+ distance: number | undefined;
76
+ station: Station;
77
+ extremes: _neaps_tide_predictor0.Extreme[];
78
+ };
79
+ getTimelinePrediction({
80
+ datum,
81
+ ...params
82
+ }: TimelineOptions): {
83
+ datum: string | undefined;
84
+ station: Station;
85
+ timeline: _neaps_tide_predictor0.TimelinePoint[];
86
+ };
87
+ getWaterLevelAtTime({
88
+ time,
89
+ datum
90
+ }: WaterLevelOptions): {
91
+ time: Date;
92
+ hour: number;
93
+ level: number;
94
+ datum: string | undefined;
95
+ station: Station;
96
+ };
97
+ id: string;
98
+ name: string;
99
+ continent: string;
100
+ country: string;
101
+ region: string;
102
+ timezone: string;
103
+ disclaimers: string;
104
+ type: "reference" | "subordinate";
105
+ latitude: number;
106
+ longitude: number;
107
+ source: {
108
+ name: string;
109
+ id: string;
110
+ published_harmonics: boolean;
111
+ url: string;
112
+ source_url: string;
113
+ };
114
+ license: {
115
+ type: string;
116
+ commercial_use: boolean;
117
+ url: string;
118
+ notes?: string;
119
+ };
120
+ offsets?: {
121
+ reference: string;
122
+ height: {
123
+ high: number;
124
+ low: number;
125
+ type: "ratio" | "fixed";
126
+ };
127
+ time: {
128
+ high: number;
129
+ low: number;
130
+ };
131
+ };
132
+ };
133
+ /**
134
+ * Find stations near the given position.
135
+ * @param limit Maximum number of stations to return (default: 10)
136
+ */
137
+ declare function stationsNear(position: GeolibInputCoordinates, limit?: number): {
138
+ distance: number | undefined;
139
+ datums: Record<string, number>;
140
+ harmonic_constituents: {
141
+ name: string;
142
+ description?: string;
143
+ amplitude: number;
144
+ phase_UTC: number;
145
+ phase_local: number;
146
+ speed?: number;
147
+ }[];
148
+ defaultDatum: string | undefined;
149
+ getExtremesPrediction({
150
+ datum,
151
+ ...input
152
+ }: ExtremesOptions): {
153
+ datum: string | undefined;
154
+ distance: number | undefined;
155
+ station: Station;
156
+ extremes: _neaps_tide_predictor0.Extreme[];
157
+ };
158
+ getTimelinePrediction({
159
+ datum,
160
+ ...params
161
+ }: TimelineOptions): {
162
+ datum: string | undefined;
163
+ station: Station;
164
+ timeline: _neaps_tide_predictor0.TimelinePoint[];
165
+ };
166
+ getWaterLevelAtTime({
167
+ time,
168
+ datum
169
+ }: WaterLevelOptions): {
170
+ time: Date;
171
+ hour: number;
172
+ level: number;
173
+ datum: string | undefined;
174
+ station: Station;
175
+ };
176
+ id: string;
177
+ name: string;
178
+ continent: string;
179
+ country: string;
180
+ region: string;
181
+ timezone: string;
182
+ disclaimers: string;
183
+ type: "reference" | "subordinate";
184
+ latitude: number;
185
+ longitude: number;
186
+ source: {
187
+ name: string;
188
+ id: string;
189
+ published_harmonics: boolean;
190
+ url: string;
191
+ source_url: string;
192
+ };
193
+ license: {
194
+ type: string;
195
+ commercial_use: boolean;
196
+ url: string;
197
+ notes?: string;
198
+ };
199
+ offsets?: {
200
+ reference: string;
201
+ height: {
202
+ high: number;
203
+ low: number;
204
+ type: "ratio" | "fixed";
205
+ };
206
+ time: {
207
+ high: number;
208
+ low: number;
209
+ };
210
+ };
211
+ }[];
212
+ /**
213
+ * Find a specific station by its ID or source ID.
214
+ */
215
+ declare function findStation(query: string): {
216
+ distance: number | undefined;
217
+ datums: Record<string, number>;
218
+ harmonic_constituents: {
219
+ name: string;
220
+ description?: string;
221
+ amplitude: number;
222
+ phase_UTC: number;
223
+ phase_local: number;
224
+ speed?: number;
225
+ }[];
226
+ defaultDatum: string | undefined;
227
+ getExtremesPrediction({
228
+ datum,
229
+ ...input
230
+ }: ExtremesOptions): {
231
+ datum: string | undefined;
232
+ distance: number | undefined;
233
+ station: Station;
234
+ extremes: _neaps_tide_predictor0.Extreme[];
235
+ };
236
+ getTimelinePrediction({
237
+ datum,
238
+ ...params
239
+ }: TimelineOptions): {
240
+ datum: string | undefined;
241
+ station: Station;
242
+ timeline: _neaps_tide_predictor0.TimelinePoint[];
243
+ };
244
+ getWaterLevelAtTime({
245
+ time,
246
+ datum
247
+ }: WaterLevelOptions): {
248
+ time: Date;
249
+ hour: number;
250
+ level: number;
251
+ datum: string | undefined;
252
+ station: Station;
253
+ };
254
+ id: string;
255
+ name: string;
256
+ continent: string;
257
+ country: string;
258
+ region: string;
259
+ timezone: string;
260
+ disclaimers: string;
261
+ type: "reference" | "subordinate";
262
+ latitude: number;
263
+ longitude: number;
264
+ source: {
265
+ name: string;
266
+ id: string;
267
+ published_harmonics: boolean;
268
+ url: string;
269
+ source_url: string;
270
+ };
271
+ license: {
272
+ type: string;
273
+ commercial_use: boolean;
274
+ url: string;
275
+ notes?: string;
276
+ };
277
+ offsets?: {
278
+ reference: string;
279
+ height: {
280
+ high: number;
281
+ low: number;
282
+ type: "ratio" | "fixed";
283
+ };
284
+ time: {
285
+ high: number;
286
+ low: number;
287
+ };
288
+ };
289
+ };
290
+ declare function useStation(station: Station, distance?: number): {
291
+ distance: number | undefined;
292
+ datums: Record<string, number>;
293
+ harmonic_constituents: {
294
+ name: string;
295
+ description?: string;
296
+ amplitude: number;
297
+ phase_UTC: number;
298
+ phase_local: number;
299
+ speed?: number;
300
+ }[];
301
+ defaultDatum: string | undefined;
302
+ getExtremesPrediction({
303
+ datum,
304
+ ...input
305
+ }: ExtremesOptions): {
306
+ datum: string | undefined;
307
+ distance: number | undefined;
308
+ station: Station;
309
+ extremes: _neaps_tide_predictor0.Extreme[];
310
+ };
311
+ getTimelinePrediction({
312
+ datum,
313
+ ...params
314
+ }: TimelineOptions): {
315
+ datum: string | undefined;
316
+ station: Station;
317
+ timeline: _neaps_tide_predictor0.TimelinePoint[];
318
+ };
319
+ getWaterLevelAtTime({
320
+ time,
321
+ datum
322
+ }: WaterLevelOptions): {
323
+ time: Date;
324
+ hour: number;
325
+ level: number;
326
+ datum: string | undefined;
327
+ station: Station;
328
+ };
329
+ id: string;
330
+ name: string;
331
+ continent: string;
332
+ country: string;
333
+ region: string;
334
+ timezone: string;
335
+ disclaimers: string;
336
+ type: "reference" | "subordinate";
337
+ latitude: number;
338
+ longitude: number;
339
+ source: {
340
+ name: string;
341
+ id: string;
342
+ published_harmonics: boolean;
343
+ url: string;
344
+ source_url: string;
345
+ };
346
+ license: {
347
+ type: string;
348
+ commercial_use: boolean;
349
+ url: string;
350
+ notes?: string;
351
+ };
352
+ offsets?: {
353
+ reference: string;
354
+ height: {
355
+ high: number;
356
+ low: number;
357
+ type: "ratio" | "fixed";
358
+ };
359
+ time: {
360
+ high: number;
361
+ low: number;
362
+ };
363
+ };
364
+ };
365
+ //#endregion
366
+ export { ExtremesOptions, TimelineOptions, WaterLevelOptions, findStation, getExtremesPrediction, getTimelinePrediction, getWaterLevelAtTime, nearestStation, stationsNear, useStation };
367
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1,367 @@
1
+ import { Station } from "@neaps/tide-database";
2
+ import * as _neaps_tide_predictor0 from "@neaps/tide-predictor";
3
+ import { ExtremesInput, TimeSpan } from "@neaps/tide-predictor";
4
+ import { GeolibInputCoordinates } from "geolib/es/types";
5
+
6
+ //#region src/index.d.ts
7
+ type DatumOption = {
8
+ /** Datum to return predictions in. Defaults to 'MLLW' if available for the nearest station. */
9
+ datum?: string;
10
+ };
11
+ type ExtremesOptions = ExtremesInput & DatumOption;
12
+ type TimelineOptions = TimeSpan & DatumOption;
13
+ type WaterLevelOptions = {
14
+ time: Date;
15
+ } & DatumOption;
16
+ /**
17
+ * Get extremes prediction using the nearest station to the given position.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * import { getExtremesPrediction } from 'neaps'
22
+ *
23
+ * const prediction = getExtremesPrediction({
24
+ * latitude: 26.7, // or `lat`
25
+ * longitude: -80.05, // or `lng` or `lon`
26
+ * start: new Date('2025-12-17'),
27
+ * end: new Date('2025-12-18'),
28
+ * datum: 'MLLW', // optional, defaults to MLLW if available
29
+ * })
30
+ */
31
+ declare function getExtremesPrediction(options: GeolibInputCoordinates & ExtremesOptions): {
32
+ datum: string | undefined;
33
+ distance: number | undefined;
34
+ station: Station;
35
+ extremes: _neaps_tide_predictor0.Extreme[];
36
+ };
37
+ /**
38
+ * Get timeline prediction using the nearest station to the given position.
39
+ */
40
+ declare function getTimelinePrediction(options: GeolibInputCoordinates & TimelineOptions): {
41
+ datum: string | undefined;
42
+ station: Station;
43
+ timeline: _neaps_tide_predictor0.TimelinePoint[];
44
+ };
45
+ /**
46
+ * Get water level at a specific time using the nearest station to the given position.
47
+ */
48
+ declare function getWaterLevelAtTime(options: GeolibInputCoordinates & WaterLevelOptions): {
49
+ time: Date;
50
+ hour: number;
51
+ level: number;
52
+ datum: string | undefined;
53
+ station: Station;
54
+ };
55
+ /**
56
+ * Find the nearest station to the given position.
57
+ */
58
+ declare function nearestStation(position: GeolibInputCoordinates): {
59
+ distance: number | undefined;
60
+ datums: Record<string, number>;
61
+ harmonic_constituents: {
62
+ name: string;
63
+ description?: string;
64
+ amplitude: number;
65
+ phase_UTC: number;
66
+ phase_local: number;
67
+ speed?: number;
68
+ }[];
69
+ defaultDatum: string | undefined;
70
+ getExtremesPrediction({
71
+ datum,
72
+ ...input
73
+ }: ExtremesOptions): {
74
+ datum: string | undefined;
75
+ distance: number | undefined;
76
+ station: Station;
77
+ extremes: _neaps_tide_predictor0.Extreme[];
78
+ };
79
+ getTimelinePrediction({
80
+ datum,
81
+ ...params
82
+ }: TimelineOptions): {
83
+ datum: string | undefined;
84
+ station: Station;
85
+ timeline: _neaps_tide_predictor0.TimelinePoint[];
86
+ };
87
+ getWaterLevelAtTime({
88
+ time,
89
+ datum
90
+ }: WaterLevelOptions): {
91
+ time: Date;
92
+ hour: number;
93
+ level: number;
94
+ datum: string | undefined;
95
+ station: Station;
96
+ };
97
+ id: string;
98
+ name: string;
99
+ continent: string;
100
+ country: string;
101
+ region: string;
102
+ timezone: string;
103
+ disclaimers: string;
104
+ type: "reference" | "subordinate";
105
+ latitude: number;
106
+ longitude: number;
107
+ source: {
108
+ name: string;
109
+ id: string;
110
+ published_harmonics: boolean;
111
+ url: string;
112
+ source_url: string;
113
+ };
114
+ license: {
115
+ type: string;
116
+ commercial_use: boolean;
117
+ url: string;
118
+ notes?: string;
119
+ };
120
+ offsets?: {
121
+ reference: string;
122
+ height: {
123
+ high: number;
124
+ low: number;
125
+ type: "ratio" | "fixed";
126
+ };
127
+ time: {
128
+ high: number;
129
+ low: number;
130
+ };
131
+ };
132
+ };
133
+ /**
134
+ * Find stations near the given position.
135
+ * @param limit Maximum number of stations to return (default: 10)
136
+ */
137
+ declare function stationsNear(position: GeolibInputCoordinates, limit?: number): {
138
+ distance: number | undefined;
139
+ datums: Record<string, number>;
140
+ harmonic_constituents: {
141
+ name: string;
142
+ description?: string;
143
+ amplitude: number;
144
+ phase_UTC: number;
145
+ phase_local: number;
146
+ speed?: number;
147
+ }[];
148
+ defaultDatum: string | undefined;
149
+ getExtremesPrediction({
150
+ datum,
151
+ ...input
152
+ }: ExtremesOptions): {
153
+ datum: string | undefined;
154
+ distance: number | undefined;
155
+ station: Station;
156
+ extremes: _neaps_tide_predictor0.Extreme[];
157
+ };
158
+ getTimelinePrediction({
159
+ datum,
160
+ ...params
161
+ }: TimelineOptions): {
162
+ datum: string | undefined;
163
+ station: Station;
164
+ timeline: _neaps_tide_predictor0.TimelinePoint[];
165
+ };
166
+ getWaterLevelAtTime({
167
+ time,
168
+ datum
169
+ }: WaterLevelOptions): {
170
+ time: Date;
171
+ hour: number;
172
+ level: number;
173
+ datum: string | undefined;
174
+ station: Station;
175
+ };
176
+ id: string;
177
+ name: string;
178
+ continent: string;
179
+ country: string;
180
+ region: string;
181
+ timezone: string;
182
+ disclaimers: string;
183
+ type: "reference" | "subordinate";
184
+ latitude: number;
185
+ longitude: number;
186
+ source: {
187
+ name: string;
188
+ id: string;
189
+ published_harmonics: boolean;
190
+ url: string;
191
+ source_url: string;
192
+ };
193
+ license: {
194
+ type: string;
195
+ commercial_use: boolean;
196
+ url: string;
197
+ notes?: string;
198
+ };
199
+ offsets?: {
200
+ reference: string;
201
+ height: {
202
+ high: number;
203
+ low: number;
204
+ type: "ratio" | "fixed";
205
+ };
206
+ time: {
207
+ high: number;
208
+ low: number;
209
+ };
210
+ };
211
+ }[];
212
+ /**
213
+ * Find a specific station by its ID or source ID.
214
+ */
215
+ declare function findStation(query: string): {
216
+ distance: number | undefined;
217
+ datums: Record<string, number>;
218
+ harmonic_constituents: {
219
+ name: string;
220
+ description?: string;
221
+ amplitude: number;
222
+ phase_UTC: number;
223
+ phase_local: number;
224
+ speed?: number;
225
+ }[];
226
+ defaultDatum: string | undefined;
227
+ getExtremesPrediction({
228
+ datum,
229
+ ...input
230
+ }: ExtremesOptions): {
231
+ datum: string | undefined;
232
+ distance: number | undefined;
233
+ station: Station;
234
+ extremes: _neaps_tide_predictor0.Extreme[];
235
+ };
236
+ getTimelinePrediction({
237
+ datum,
238
+ ...params
239
+ }: TimelineOptions): {
240
+ datum: string | undefined;
241
+ station: Station;
242
+ timeline: _neaps_tide_predictor0.TimelinePoint[];
243
+ };
244
+ getWaterLevelAtTime({
245
+ time,
246
+ datum
247
+ }: WaterLevelOptions): {
248
+ time: Date;
249
+ hour: number;
250
+ level: number;
251
+ datum: string | undefined;
252
+ station: Station;
253
+ };
254
+ id: string;
255
+ name: string;
256
+ continent: string;
257
+ country: string;
258
+ region: string;
259
+ timezone: string;
260
+ disclaimers: string;
261
+ type: "reference" | "subordinate";
262
+ latitude: number;
263
+ longitude: number;
264
+ source: {
265
+ name: string;
266
+ id: string;
267
+ published_harmonics: boolean;
268
+ url: string;
269
+ source_url: string;
270
+ };
271
+ license: {
272
+ type: string;
273
+ commercial_use: boolean;
274
+ url: string;
275
+ notes?: string;
276
+ };
277
+ offsets?: {
278
+ reference: string;
279
+ height: {
280
+ high: number;
281
+ low: number;
282
+ type: "ratio" | "fixed";
283
+ };
284
+ time: {
285
+ high: number;
286
+ low: number;
287
+ };
288
+ };
289
+ };
290
+ declare function useStation(station: Station, distance?: number): {
291
+ distance: number | undefined;
292
+ datums: Record<string, number>;
293
+ harmonic_constituents: {
294
+ name: string;
295
+ description?: string;
296
+ amplitude: number;
297
+ phase_UTC: number;
298
+ phase_local: number;
299
+ speed?: number;
300
+ }[];
301
+ defaultDatum: string | undefined;
302
+ getExtremesPrediction({
303
+ datum,
304
+ ...input
305
+ }: ExtremesOptions): {
306
+ datum: string | undefined;
307
+ distance: number | undefined;
308
+ station: Station;
309
+ extremes: _neaps_tide_predictor0.Extreme[];
310
+ };
311
+ getTimelinePrediction({
312
+ datum,
313
+ ...params
314
+ }: TimelineOptions): {
315
+ datum: string | undefined;
316
+ station: Station;
317
+ timeline: _neaps_tide_predictor0.TimelinePoint[];
318
+ };
319
+ getWaterLevelAtTime({
320
+ time,
321
+ datum
322
+ }: WaterLevelOptions): {
323
+ time: Date;
324
+ hour: number;
325
+ level: number;
326
+ datum: string | undefined;
327
+ station: Station;
328
+ };
329
+ id: string;
330
+ name: string;
331
+ continent: string;
332
+ country: string;
333
+ region: string;
334
+ timezone: string;
335
+ disclaimers: string;
336
+ type: "reference" | "subordinate";
337
+ latitude: number;
338
+ longitude: number;
339
+ source: {
340
+ name: string;
341
+ id: string;
342
+ published_harmonics: boolean;
343
+ url: string;
344
+ source_url: string;
345
+ };
346
+ license: {
347
+ type: string;
348
+ commercial_use: boolean;
349
+ url: string;
350
+ notes?: string;
351
+ };
352
+ offsets?: {
353
+ reference: string;
354
+ height: {
355
+ high: number;
356
+ low: number;
357
+ type: "ratio" | "fixed";
358
+ };
359
+ time: {
360
+ high: number;
361
+ low: number;
362
+ };
363
+ };
364
+ };
365
+ //#endregion
366
+ export { ExtremesOptions, TimelineOptions, WaterLevelOptions, findStation, getExtremesPrediction, getTimelinePrediction, getWaterLevelAtTime, nearestStation, stationsNear, useStation };
367
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,122 @@
1
+ import { getDistance } from "geolib";
2
+ import stations from "@neaps/tide-database";
3
+ import tidePredictor from "@neaps/tide-predictor";
4
+
5
+ //#region src/index.ts
6
+ /**
7
+ * Get extremes prediction using the nearest station to the given position.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { getExtremesPrediction } from 'neaps'
12
+ *
13
+ * const prediction = getExtremesPrediction({
14
+ * latitude: 26.7, // or `lat`
15
+ * longitude: -80.05, // or `lng` or `lon`
16
+ * start: new Date('2025-12-17'),
17
+ * end: new Date('2025-12-18'),
18
+ * datum: 'MLLW', // optional, defaults to MLLW if available
19
+ * })
20
+ */
21
+ function getExtremesPrediction(options) {
22
+ return nearestStation(options).getExtremesPrediction(options);
23
+ }
24
+ /**
25
+ * Get timeline prediction using the nearest station to the given position.
26
+ */
27
+ function getTimelinePrediction(options) {
28
+ return nearestStation(options).getTimelinePrediction(options);
29
+ }
30
+ /**
31
+ * Get water level at a specific time using the nearest station to the given position.
32
+ */
33
+ function getWaterLevelAtTime(options) {
34
+ return nearestStation(options).getWaterLevelAtTime(options);
35
+ }
36
+ /**
37
+ * Find the nearest station to the given position.
38
+ */
39
+ function nearestStation(position) {
40
+ return stationsNear(position, 1)[0];
41
+ }
42
+ /**
43
+ * Find stations near the given position.
44
+ * @param limit Maximum number of stations to return (default: 10)
45
+ */
46
+ function stationsNear(position, limit = 10) {
47
+ return stations.map((station) => ({
48
+ station,
49
+ distance: getDistance(position, station)
50
+ })).sort((a, b) => a.distance - b.distance).slice(0, limit).map(({ station, distance }) => useStation(station, distance));
51
+ }
52
+ /**
53
+ * Find a specific station by its ID or source ID.
54
+ */
55
+ function findStation(query) {
56
+ const searches = [(s) => s.id === query, (s) => s.source.id === query];
57
+ let found = void 0;
58
+ for (const search of searches) {
59
+ found = stations.find(search);
60
+ if (found) break;
61
+ }
62
+ if (!found) throw new Error(`Station not found: ${query}`);
63
+ return useStation(found);
64
+ }
65
+ function useStation(station, distance) {
66
+ let reference = station;
67
+ if (station.type === "subordinate") reference = findStation(station.offsets?.reference || "");
68
+ const { datums, harmonic_constituents } = reference;
69
+ const defaultDatum = "MLLW" in datums ? "MLLW" : void 0;
70
+ function getPredictor({ datum = defaultDatum } = {}) {
71
+ let offset = 0;
72
+ if (datum) {
73
+ const datumOffset = datums?.[datum];
74
+ const mslOffset = datums?.["MSL"];
75
+ if (typeof datumOffset !== "number") throw new Error(`Station ${station.id} missing ${datum} datum. Available datums: ${Object.keys(datums || {}).join(", ")}`);
76
+ if (typeof mslOffset !== "number") throw new Error(`Station ${station.id} missing MSL datum, so predictions can't be given in ${datum}.`);
77
+ offset = mslOffset - datumOffset;
78
+ }
79
+ return tidePredictor(harmonic_constituents, {
80
+ phaseKey: "phase_UTC",
81
+ offset
82
+ });
83
+ }
84
+ return {
85
+ ...station,
86
+ distance,
87
+ datums,
88
+ harmonic_constituents,
89
+ defaultDatum,
90
+ getExtremesPrediction({ datum = defaultDatum, ...input }) {
91
+ return {
92
+ datum,
93
+ distance,
94
+ station,
95
+ extremes: getPredictor({ datum }).getExtremesPrediction({
96
+ ...input,
97
+ offsets: station.offsets
98
+ })
99
+ };
100
+ },
101
+ getTimelinePrediction({ datum = defaultDatum, ...params }) {
102
+ if (station.type === "subordinate") throw new Error(`Timeline predictions are not supported for subordinate stations.`);
103
+ return {
104
+ datum,
105
+ station,
106
+ timeline: getPredictor({ datum }).getTimelinePrediction(params)
107
+ };
108
+ },
109
+ getWaterLevelAtTime({ time, datum = defaultDatum }) {
110
+ if (station.type === "subordinate") throw new Error(`Water level predictions are not supported for subordinate stations.`);
111
+ return {
112
+ datum,
113
+ station,
114
+ ...getPredictor({ datum }).getWaterLevelAtTime({ time })
115
+ };
116
+ }
117
+ };
118
+ }
119
+
120
+ //#endregion
121
+ export { findStation, getExtremesPrediction, getTimelinePrediction, getWaterLevelAtTime, nearestStation, stationsNear, useStation };
122
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["found: Station | undefined"],"sources":["../src/index.ts"],"sourcesContent":["import { getDistance } from 'geolib'\nimport stations, { type Station } from '@neaps/tide-database'\nimport tidePredictor, {\n type TimeSpan,\n type ExtremesInput\n} from '@neaps/tide-predictor'\nimport type { GeolibInputCoordinates } from 'geolib/es/types'\n\ntype DatumOption = {\n /** Datum to return predictions in. Defaults to 'MLLW' if available for the nearest station. */\n datum?: string\n}\n\nexport type ExtremesOptions = ExtremesInput & DatumOption\nexport type TimelineOptions = TimeSpan & DatumOption\nexport type WaterLevelOptions = { time: Date } & DatumOption\n\n/**\n * Get extremes prediction using the nearest station to the given position.\n *\n * @example\n * ```ts\n * import { getExtremesPrediction } from 'neaps'\n *\n * const prediction = getExtremesPrediction({\n * latitude: 26.7, // or `lat`\n * longitude: -80.05, // or `lng` or `lon`\n * start: new Date('2025-12-17'),\n * end: new Date('2025-12-18'),\n * datum: 'MLLW', // optional, defaults to MLLW if available\n * })\n */\nexport function getExtremesPrediction(\n options: GeolibInputCoordinates & ExtremesOptions\n) {\n return nearestStation(options).getExtremesPrediction(options)\n}\n\n/**\n * Get timeline prediction using the nearest station to the given position.\n */\nexport function getTimelinePrediction(\n options: GeolibInputCoordinates & TimelineOptions\n) {\n return nearestStation(options).getTimelinePrediction(options)\n}\n\n/**\n * Get water level at a specific time using the nearest station to the given position.\n */\nexport function getWaterLevelAtTime(\n options: GeolibInputCoordinates & WaterLevelOptions\n) {\n return nearestStation(options).getWaterLevelAtTime(options)\n}\n\n/**\n * Find the nearest station to the given position.\n */\nexport function nearestStation(position: GeolibInputCoordinates) {\n return stationsNear(position, 1)[0]\n}\n\n/**\n * Find stations near the given position.\n * @param limit Maximum number of stations to return (default: 10)\n */\nexport function stationsNear(position: GeolibInputCoordinates, limit = 10) {\n return stations\n .map((station) => ({ station, distance: getDistance(position, station) }))\n .sort((a, b) => a.distance - b.distance)\n .slice(0, limit)\n .map(({ station, distance }) => useStation(station, distance))\n}\n\n/**\n * Find a specific station by its ID or source ID.\n */\nexport function findStation(query: string) {\n const searches = [\n (s: Station) => s.id === query,\n (s: Station) => s.source.id === query\n ]\n\n let found: Station | undefined = undefined\n\n for (const search of searches) {\n found = stations.find(search)\n if (found) break\n }\n\n if (!found) throw new Error(`Station not found: ${query}`)\n\n return useStation(found)\n}\n\nexport function useStation(station: Station, distance?: number) {\n // If subordinate station, use the reference station for datums and constituents\n let reference = station\n if (station.type === 'subordinate') {\n reference = findStation(station.offsets?.reference || '')\n }\n const { datums, harmonic_constituents } = reference\n\n // Use MLLW as the default datum if available\n const defaultDatum = 'MLLW' in datums ? 'MLLW' : undefined\n\n function getPredictor({ datum = defaultDatum }: DatumOption = {}) {\n let offset = 0\n\n if (datum) {\n const datumOffset = datums?.[datum]\n const mslOffset = datums?.['MSL']\n\n if (typeof datumOffset !== 'number') {\n throw new Error(\n `Station ${station.id} missing ${datum} datum. Available datums: ${Object.keys(datums || {}).join(', ')}`\n )\n }\n\n if (typeof mslOffset !== 'number') {\n throw new Error(\n `Station ${station.id} missing MSL datum, so predictions can't be given in ${datum}.`\n )\n }\n\n offset = mslOffset - datumOffset\n }\n\n return tidePredictor(harmonic_constituents, {\n phaseKey: 'phase_UTC',\n offset\n })\n }\n\n return {\n ...station,\n distance,\n datums,\n harmonic_constituents,\n defaultDatum,\n getExtremesPrediction({ datum = defaultDatum, ...input }: ExtremesOptions) {\n return {\n datum,\n distance,\n station,\n extremes: getPredictor({ datum }).getExtremesPrediction({\n ...input,\n offsets: station.offsets\n })\n }\n },\n\n getTimelinePrediction({\n datum = defaultDatum,\n ...params\n }: TimelineOptions) {\n if (station.type === 'subordinate') {\n throw new Error(\n `Timeline predictions are not supported for subordinate stations.`\n )\n }\n\n return {\n datum,\n station,\n timeline: getPredictor({ datum }).getTimelinePrediction(params)\n }\n },\n\n getWaterLevelAtTime({ time, datum = defaultDatum }: WaterLevelOptions) {\n if (station.type === 'subordinate') {\n throw new Error(\n `Water level predictions are not supported for subordinate stations.`\n )\n }\n\n return {\n datum,\n station,\n ...getPredictor({ datum }).getWaterLevelAtTime({ time })\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,sBACd,SACA;AACA,QAAO,eAAe,QAAQ,CAAC,sBAAsB,QAAQ;;;;;AAM/D,SAAgB,sBACd,SACA;AACA,QAAO,eAAe,QAAQ,CAAC,sBAAsB,QAAQ;;;;;AAM/D,SAAgB,oBACd,SACA;AACA,QAAO,eAAe,QAAQ,CAAC,oBAAoB,QAAQ;;;;;AAM7D,SAAgB,eAAe,UAAkC;AAC/D,QAAO,aAAa,UAAU,EAAE,CAAC;;;;;;AAOnC,SAAgB,aAAa,UAAkC,QAAQ,IAAI;AACzE,QAAO,SACJ,KAAK,aAAa;EAAE;EAAS,UAAU,YAAY,UAAU,QAAQ;EAAE,EAAE,CACzE,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,SAAS,CACvC,MAAM,GAAG,MAAM,CACf,KAAK,EAAE,SAAS,eAAe,WAAW,SAAS,SAAS,CAAC;;;;;AAMlE,SAAgB,YAAY,OAAe;CACzC,MAAM,WAAW,EACd,MAAe,EAAE,OAAO,QACxB,MAAe,EAAE,OAAO,OAAO,MACjC;CAED,IAAIA,QAA6B;AAEjC,MAAK,MAAM,UAAU,UAAU;AAC7B,UAAQ,SAAS,KAAK,OAAO;AAC7B,MAAI,MAAO;;AAGb,KAAI,CAAC,MAAO,OAAM,IAAI,MAAM,sBAAsB,QAAQ;AAE1D,QAAO,WAAW,MAAM;;AAG1B,SAAgB,WAAW,SAAkB,UAAmB;CAE9D,IAAI,YAAY;AAChB,KAAI,QAAQ,SAAS,cACnB,aAAY,YAAY,QAAQ,SAAS,aAAa,GAAG;CAE3D,MAAM,EAAE,QAAQ,0BAA0B;CAG1C,MAAM,eAAe,UAAU,SAAS,SAAS;CAEjD,SAAS,aAAa,EAAE,QAAQ,iBAA8B,EAAE,EAAE;EAChE,IAAI,SAAS;AAEb,MAAI,OAAO;GACT,MAAM,cAAc,SAAS;GAC7B,MAAM,YAAY,SAAS;AAE3B,OAAI,OAAO,gBAAgB,SACzB,OAAM,IAAI,MACR,WAAW,QAAQ,GAAG,WAAW,MAAM,4BAA4B,OAAO,KAAK,UAAU,EAAE,CAAC,CAAC,KAAK,KAAK,GACxG;AAGH,OAAI,OAAO,cAAc,SACvB,OAAM,IAAI,MACR,WAAW,QAAQ,GAAG,uDAAuD,MAAM,GACpF;AAGH,YAAS,YAAY;;AAGvB,SAAO,cAAc,uBAAuB;GAC1C,UAAU;GACV;GACD,CAAC;;AAGJ,QAAO;EACL,GAAG;EACH;EACA;EACA;EACA;EACA,sBAAsB,EAAE,QAAQ,cAAc,GAAG,SAA0B;AACzE,UAAO;IACL;IACA;IACA;IACA,UAAU,aAAa,EAAE,OAAO,CAAC,CAAC,sBAAsB;KACtD,GAAG;KACH,SAAS,QAAQ;KAClB,CAAC;IACH;;EAGH,sBAAsB,EACpB,QAAQ,cACR,GAAG,UACe;AAClB,OAAI,QAAQ,SAAS,cACnB,OAAM,IAAI,MACR,mEACD;AAGH,UAAO;IACL;IACA;IACA,UAAU,aAAa,EAAE,OAAO,CAAC,CAAC,sBAAsB,OAAO;IAChE;;EAGH,oBAAoB,EAAE,MAAM,QAAQ,gBAAmC;AACrE,OAAI,QAAQ,SAAS,cACnB,OAAM,IAAI,MACR,sEACD;AAGH,UAAO;IACL;IACA;IACA,GAAG,aAAa,EAAE,OAAO,CAAC,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACzD;;EAEJ"}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "neaps",
3
+ "version": "0.1.0",
4
+ "description": "Tide predictions",
5
+ "keywords": [
6
+ "tides",
7
+ "harmonics"
8
+ ],
9
+ "homepage": "https://github.com/neaps/neaps#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/neaps/neaps/issues"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/neaps/neaps.git",
16
+ "directory": "packages/neaps"
17
+ },
18
+ "license": "MIT",
19
+ "author": "Brandon Keepers <brandon@openwaters.io>",
20
+ "type": "module",
21
+ "main": "src/index.ts",
22
+ "scripts": {
23
+ "build": "tsdown",
24
+ "prepack": "npm run build"
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "dependencies": {
30
+ "@neaps/tide-predictor": "^0.2.0",
31
+ "@neaps/tide-database": "^0.0",
32
+ "geolib": "^3.3.4"
33
+ }
34
+ }
package/src/index.ts ADDED
@@ -0,0 +1,185 @@
1
+ import { getDistance } from 'geolib'
2
+ import stations, { type Station } from '@neaps/tide-database'
3
+ import tidePredictor, {
4
+ type TimeSpan,
5
+ type ExtremesInput
6
+ } from '@neaps/tide-predictor'
7
+ import type { GeolibInputCoordinates } from 'geolib/es/types'
8
+
9
+ type DatumOption = {
10
+ /** Datum to return predictions in. Defaults to 'MLLW' if available for the nearest station. */
11
+ datum?: string
12
+ }
13
+
14
+ export type ExtremesOptions = ExtremesInput & DatumOption
15
+ export type TimelineOptions = TimeSpan & DatumOption
16
+ export type WaterLevelOptions = { time: Date } & DatumOption
17
+
18
+ /**
19
+ * Get extremes prediction using the nearest station to the given position.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * import { getExtremesPrediction } from 'neaps'
24
+ *
25
+ * const prediction = getExtremesPrediction({
26
+ * latitude: 26.7, // or `lat`
27
+ * longitude: -80.05, // or `lng` or `lon`
28
+ * start: new Date('2025-12-17'),
29
+ * end: new Date('2025-12-18'),
30
+ * datum: 'MLLW', // optional, defaults to MLLW if available
31
+ * })
32
+ */
33
+ export function getExtremesPrediction(
34
+ options: GeolibInputCoordinates & ExtremesOptions
35
+ ) {
36
+ return nearestStation(options).getExtremesPrediction(options)
37
+ }
38
+
39
+ /**
40
+ * Get timeline prediction using the nearest station to the given position.
41
+ */
42
+ export function getTimelinePrediction(
43
+ options: GeolibInputCoordinates & TimelineOptions
44
+ ) {
45
+ return nearestStation(options).getTimelinePrediction(options)
46
+ }
47
+
48
+ /**
49
+ * Get water level at a specific time using the nearest station to the given position.
50
+ */
51
+ export function getWaterLevelAtTime(
52
+ options: GeolibInputCoordinates & WaterLevelOptions
53
+ ) {
54
+ return nearestStation(options).getWaterLevelAtTime(options)
55
+ }
56
+
57
+ /**
58
+ * Find the nearest station to the given position.
59
+ */
60
+ export function nearestStation(position: GeolibInputCoordinates) {
61
+ return stationsNear(position, 1)[0]
62
+ }
63
+
64
+ /**
65
+ * Find stations near the given position.
66
+ * @param limit Maximum number of stations to return (default: 10)
67
+ */
68
+ export function stationsNear(position: GeolibInputCoordinates, limit = 10) {
69
+ return stations
70
+ .map((station) => ({ station, distance: getDistance(position, station) }))
71
+ .sort((a, b) => a.distance - b.distance)
72
+ .slice(0, limit)
73
+ .map(({ station, distance }) => useStation(station, distance))
74
+ }
75
+
76
+ /**
77
+ * Find a specific station by its ID or source ID.
78
+ */
79
+ export function findStation(query: string) {
80
+ const searches = [
81
+ (s: Station) => s.id === query,
82
+ (s: Station) => s.source.id === query
83
+ ]
84
+
85
+ let found: Station | undefined = undefined
86
+
87
+ for (const search of searches) {
88
+ found = stations.find(search)
89
+ if (found) break
90
+ }
91
+
92
+ if (!found) throw new Error(`Station not found: ${query}`)
93
+
94
+ return useStation(found)
95
+ }
96
+
97
+ export function useStation(station: Station, distance?: number) {
98
+ // If subordinate station, use the reference station for datums and constituents
99
+ let reference = station
100
+ if (station.type === 'subordinate') {
101
+ reference = findStation(station.offsets?.reference || '')
102
+ }
103
+ const { datums, harmonic_constituents } = reference
104
+
105
+ // Use MLLW as the default datum if available
106
+ const defaultDatum = 'MLLW' in datums ? 'MLLW' : undefined
107
+
108
+ function getPredictor({ datum = defaultDatum }: DatumOption = {}) {
109
+ let offset = 0
110
+
111
+ if (datum) {
112
+ const datumOffset = datums?.[datum]
113
+ const mslOffset = datums?.['MSL']
114
+
115
+ if (typeof datumOffset !== 'number') {
116
+ throw new Error(
117
+ `Station ${station.id} missing ${datum} datum. Available datums: ${Object.keys(datums || {}).join(', ')}`
118
+ )
119
+ }
120
+
121
+ if (typeof mslOffset !== 'number') {
122
+ throw new Error(
123
+ `Station ${station.id} missing MSL datum, so predictions can't be given in ${datum}.`
124
+ )
125
+ }
126
+
127
+ offset = mslOffset - datumOffset
128
+ }
129
+
130
+ return tidePredictor(harmonic_constituents, {
131
+ phaseKey: 'phase_UTC',
132
+ offset
133
+ })
134
+ }
135
+
136
+ return {
137
+ ...station,
138
+ distance,
139
+ datums,
140
+ harmonic_constituents,
141
+ defaultDatum,
142
+ getExtremesPrediction({ datum = defaultDatum, ...input }: ExtremesOptions) {
143
+ return {
144
+ datum,
145
+ distance,
146
+ station,
147
+ extremes: getPredictor({ datum }).getExtremesPrediction({
148
+ ...input,
149
+ offsets: station.offsets
150
+ })
151
+ }
152
+ },
153
+
154
+ getTimelinePrediction({
155
+ datum = defaultDatum,
156
+ ...params
157
+ }: TimelineOptions) {
158
+ if (station.type === 'subordinate') {
159
+ throw new Error(
160
+ `Timeline predictions are not supported for subordinate stations.`
161
+ )
162
+ }
163
+
164
+ return {
165
+ datum,
166
+ station,
167
+ timeline: getPredictor({ datum }).getTimelinePrediction(params)
168
+ }
169
+ },
170
+
171
+ getWaterLevelAtTime({ time, datum = defaultDatum }: WaterLevelOptions) {
172
+ if (station.type === 'subordinate') {
173
+ throw new Error(
174
+ `Water level predictions are not supported for subordinate stations.`
175
+ )
176
+ }
177
+
178
+ return {
179
+ datum,
180
+ station,
181
+ ...getPredictor({ datum }).getWaterLevelAtTime({ time })
182
+ }
183
+ }
184
+ }
185
+ }