@@ -2,58 +2,18 @@ import {
22 stations ,
33 near ,
44 nearest ,
5- type Station ,
65 type NearOptions ,
76 type NearestOptions ,
87} from "@neaps/tide-database" ;
9- import tidePredictor , {
10- type TimeSpan ,
11- type ExtremesInput ,
12- Extreme ,
13- TimelinePoint ,
8+ import {
9+ useStation ,
10+ type Station ,
11+ type StationPredictor ,
12+ type StationExtremesOptions ,
13+ type StationTimelineOptions ,
14+ type StationWaterLevelOptions ,
1415} from "@neaps/tide-predictor" ;
1516
16- type Units = "meters" | "feet" ;
17- type PredictionOptions = {
18- /** Datum to return predictions in. Defaults to 'MLLW' if available for the nearest station. */
19- datum ?: string ;
20-
21- /** Units for returned water levels. Defaults to 'meters'. */
22- units ?: Units ;
23- } ;
24-
25- export type ExtremesOptions = ExtremesInput & PredictionOptions ;
26- export type TimelineOptions = TimeSpan & PredictionOptions ;
27- export type WaterLevelOptions = { time : Date } & PredictionOptions ;
28-
29- export type StationPrediction = {
30- datum : string | undefined ;
31- units : Units ;
32- station : Station ;
33- distance ?: number ;
34- } ;
35-
36- export type StationExtremesPrediction = StationPrediction & {
37- extremes : Extreme [ ] ;
38- } ;
39-
40- export type StationTimelinePrediction = StationPrediction & {
41- timeline : TimelinePoint [ ] ;
42- } ;
43-
44- export type StationWaterLevelPrediction = StationPrediction & TimelinePoint ;
45-
46- export type StationPredictor = Station & {
47- distance ?: number ;
48- defaultDatum ?: string ;
49- getExtremesPrediction : ( options : ExtremesOptions ) => StationExtremesPrediction ;
50- getTimelinePrediction : ( options : TimelineOptions ) => StationTimelinePrediction ;
51- getWaterLevelAtTime : ( options : WaterLevelOptions ) => StationWaterLevelPrediction ;
52- } ;
53-
54- const feetPerMeter = 3.2808399 ;
55- const defaultUnits : Units = "meters" ;
56-
5717/**
5818 * Get extremes prediction using the nearest station to the given position.
5919 *
@@ -69,21 +29,21 @@ const defaultUnits: Units = "meters";
6929 * datum: 'MLLW', // optional, defaults to MLLW if available
7030 * })
7131 */
72- export function getExtremesPrediction ( options : NearestOptions & ExtremesOptions ) {
32+ export function getExtremesPrediction ( options : NearestOptions & StationExtremesOptions ) {
7333 return nearestStation ( options ) . getExtremesPrediction ( options ) ;
7434}
7535
7636/**
7737 * Get timeline prediction using the nearest station to the given position.
7838 */
79- export function getTimelinePrediction ( options : NearestOptions & TimelineOptions ) {
39+ export function getTimelinePrediction ( options : NearestOptions & StationTimelineOptions ) {
8040 return nearestStation ( options ) . getTimelinePrediction ( options ) ;
8141}
8242
8343/**
8444 * Get water level at a specific time using the nearest station to the given position.
8545 */
86- export function getWaterLevelAtTime ( options : NearestOptions & WaterLevelOptions ) {
46+ export function getWaterLevelAtTime ( options : NearestOptions & StationWaterLevelOptions ) {
8747 return nearestStation ( options ) . getWaterLevelAtTime ( options ) ;
8848}
8949
@@ -93,21 +53,21 @@ export function getWaterLevelAtTime(options: NearestOptions & WaterLevelOptions)
9353export function nearestStation ( options : NearestOptions ) {
9454 const data = nearest ( options ) ;
9555 if ( ! data ) throw new Error ( `No stations found with options: ${ JSON . stringify ( options ) } ` ) ;
96- return useStation ( ...data ) ;
56+ return useStation ( ...data , findStation ) ;
9757}
9858
9959/**
10060 * Find stations near the given position.
10161 * @param limit Maximum number of stations to return (default: 10)
10262 */
10363export function stationsNear ( options : NearOptions ) {
104- return near ( options ) . map ( ( [ station , distance ] ) => useStation ( station , distance ) ) ;
64+ return near ( options ) . map ( ( [ station , distance ] ) => useStation ( station , distance , findStation ) ) ;
10565}
10666
10767/**
10868 * Find a specific station by its ID or source ID.
10969 */
110- export function findStation ( query : string ) {
70+ export function findStation ( query : string ) : StationPredictor {
11171 const searches = [ ( s : Station ) => s . id === query , ( s : Station ) => s . source . id === query ] ;
11272
11373 let found : Station | undefined = undefined ;
@@ -119,96 +79,5 @@ export function findStation(query: string) {
11979
12080 if ( ! found ) throw new Error ( `Station not found: ${ query } ` ) ;
12181
122- return useStation ( found ) ;
123- }
124-
125- export function useStation ( station : Station , distance ?: number ) : StationPredictor {
126- // If subordinate station, use the reference station for datums and constituents
127- let reference = station ;
128- if ( station . type === "subordinate" && station . offsets ?. reference ) {
129- reference = findStation ( station . offsets ?. reference ) ;
130- }
131- const { datums, harmonic_constituents } = reference ;
132-
133- // Use MLLW as the default datum if available
134- const defaultDatum = "MLLW" in datums ? "MLLW" : undefined ;
135-
136- function getPredictor ( { datum = defaultDatum } : PredictionOptions = { } ) {
137- let offset = 0 ;
138-
139- if ( datum ) {
140- const datumOffset = datums ?. [ datum ] ;
141- const mslOffset = datums ?. [ "MSL" ] ;
142-
143- if ( typeof datumOffset !== "number" ) {
144- throw new Error (
145- `Station ${ station . id } missing ${ datum } datum. Available datums: ${ Object . keys ( datums ) . join ( ", " ) } ` ,
146- ) ;
147- }
148-
149- if ( typeof mslOffset !== "number" ) {
150- throw new Error (
151- `Station ${ station . id } missing MSL datum, so predictions can't be given in ${ datum } .` ,
152- ) ;
153- }
154-
155- offset = mslOffset - datumOffset ;
156- }
157-
158- return tidePredictor ( harmonic_constituents , { offset } ) ;
159- }
160-
161- return {
162- ...station ,
163- distance,
164- datums,
165- harmonic_constituents,
166- defaultDatum,
167- getExtremesPrediction ( {
168- datum = defaultDatum ,
169- units = defaultUnits ,
170- ...options
171- } : ExtremesOptions ) {
172- const extremes = getPredictor ( { datum } )
173- . getExtremesPrediction ( { ...options , offsets : station . offsets } )
174- . map ( ( e ) => toPreferredUnits ( e , units ) ) ;
175-
176- return { datum, units, station, distance, extremes } ;
177- } ,
178-
179- getTimelinePrediction ( {
180- datum = defaultDatum ,
181- units = defaultUnits ,
182- ...options
183- } : TimelineOptions ) {
184- if ( station . type === "subordinate" ) {
185- throw new Error ( `Timeline predictions are not supported for subordinate stations.` ) ;
186- }
187- const timeline = getPredictor ( { datum } )
188- . getTimelinePrediction ( options )
189- . map ( ( e ) => toPreferredUnits ( e , units ) ) ;
190-
191- return { datum, units, station, distance, timeline } ;
192- } ,
193-
194- getWaterLevelAtTime ( { time, datum = defaultDatum , units = defaultUnits } : WaterLevelOptions ) {
195- if ( station . type === "subordinate" ) {
196- throw new Error ( `Water level predictions are not supported for subordinate stations.` ) ;
197- }
198-
199- const prediction = toPreferredUnits (
200- getPredictor ( { datum } ) . getWaterLevelAtTime ( { time } ) ,
201- units ,
202- ) ;
203-
204- return { datum, units, station, distance, ...prediction } ;
205- } ,
206- } ;
207- }
208-
209- function toPreferredUnits < T extends { level : number } > ( prediction : T , units : Units ) : T {
210- let { level } = prediction ;
211- if ( units === "feet" ) level *= feetPerMeter ;
212- else if ( units !== "meters" ) throw new Error ( `Unsupported units: ${ units } ` ) ;
213- return { ...prediction , level } ;
82+ return useStation ( found , undefined , findStation ) ;
21483}
0 commit comments