Skip to content

Commit 751b970

Browse files
committed
Move useStation into @neaps/tide-pridictor
1 parent fc63077 commit 751b970

8 files changed

Lines changed: 600 additions & 566 deletions

File tree

.changeset/free-pumas-occur.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@neaps/tide-predictor": minor
3+
"neaps": minor
4+
---
5+
6+
Moved `useStation` into @neaps/tide-predictor so it can be used without the heavy dependency of @neaps/tide-database.

packages/neaps/src/index.ts

Lines changed: 13 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -2,62 +2,18 @@ import {
22
stations,
33
near,
44
nearest,
5-
type Station,
65
type NearOptions,
76
type NearestOptions,
87
} from "@neaps/tide-database";
98
import {
10-
createTidePredictor,
11-
type ExtremesInput,
12-
type TimelineInput,
13-
type Extreme,
14-
type TimelinePoint,
9+
useStation,
10+
type Station,
11+
type StationPredictor,
12+
type StationExtremesOptions,
13+
type StationTimelineOptions,
14+
type StationWaterLevelOptions,
1515
} from "@neaps/tide-predictor";
1616

17-
type Units = "meters" | "feet";
18-
type PredictionOptions = {
19-
/** Datum to return predictions in. Defaults to the nearest station's datum. */
20-
datum?: string;
21-
22-
/** Units for returned water levels. Defaults to 'meters'. */
23-
units?: Units;
24-
25-
/** Nodal correction fundamentals. Defaults to 'iho'. */
26-
nodeCorrections?: "iho" | "schureman";
27-
};
28-
29-
export type ExtremesOptions = ExtremesInput & PredictionOptions;
30-
export type TimelineOptions = TimelineInput & PredictionOptions;
31-
export type WaterLevelOptions = { time: Date } & PredictionOptions;
32-
33-
export type StationPrediction = {
34-
datum: string | undefined;
35-
units: Units;
36-
station: Station;
37-
distance?: number;
38-
};
39-
40-
export type StationExtremesPrediction = StationPrediction & {
41-
extremes: Extreme[];
42-
};
43-
44-
export type StationTimelinePrediction = StationPrediction & {
45-
timeline: TimelinePoint[];
46-
};
47-
48-
export type StationWaterLevelPrediction = StationPrediction & TimelinePoint;
49-
50-
export type StationPredictor = Station & {
51-
distance?: number;
52-
defaultDatum?: string;
53-
getExtremesPrediction: (options: ExtremesOptions) => StationExtremesPrediction;
54-
getTimelinePrediction: (options: TimelineOptions) => StationTimelinePrediction;
55-
getWaterLevelAtTime: (options: WaterLevelOptions) => StationWaterLevelPrediction;
56-
};
57-
58-
const feetPerMeter = 3.2808399;
59-
const defaultUnits: Units = "meters";
60-
6117
/**
6218
* Get extremes prediction using the nearest station to the given position.
6319
*
@@ -73,21 +29,21 @@ const defaultUnits: Units = "meters";
7329
* datum: 'MLLW', // optional, defaults to station's datum
7430
* })
7531
*/
76-
export function getExtremesPrediction(options: NearestOptions & ExtremesOptions) {
32+
export function getExtremesPrediction(options: NearestOptions & StationExtremesOptions) {
7733
return nearestStation(options).getExtremesPrediction(options);
7834
}
7935

8036
/**
8137
* Get timeline prediction using the nearest station to the given position.
8238
*/
83-
export function getTimelinePrediction(options: NearestOptions & TimelineOptions) {
39+
export function getTimelinePrediction(options: NearestOptions & StationTimelineOptions) {
8440
return nearestStation(options).getTimelinePrediction(options);
8541
}
8642

8743
/**
8844
* Get water level at a specific time using the nearest station to the given position.
8945
*/
90-
export function getWaterLevelAtTime(options: NearestOptions & WaterLevelOptions) {
46+
export function getWaterLevelAtTime(options: NearestOptions & StationWaterLevelOptions) {
9147
return nearestStation(options).getWaterLevelAtTime(options);
9248
}
9349

@@ -97,21 +53,21 @@ export function getWaterLevelAtTime(options: NearestOptions & WaterLevelOptions)
9753
export function nearestStation(options: NearestOptions) {
9854
const data = nearest(options);
9955
if (!data) throw new Error(`No stations found with options: ${JSON.stringify(options)}`);
100-
return useStation(...data);
56+
return useStation(...data, findStation);
10157
}
10258

10359
/**
10460
* Find stations near the given position.
10561
* @param limit Maximum number of stations to return (default: 10)
10662
*/
10763
export function stationsNear(options: NearOptions) {
108-
return near(options).map(([station, distance]) => useStation(station, distance));
64+
return near(options).map(([station, distance]) => useStation(station, distance, findStation));
10965
}
11066

11167
/**
11268
* Find a specific station by its ID or source ID.
11369
*/
114-
export function findStation(query: string) {
70+
export function findStation(query: string): StationPredictor {
11571
const searches = [(s: Station) => s.id === query, (s: Station) => s.source.id === query];
11672

11773
let found: Station | undefined = undefined;
@@ -122,100 +78,5 @@ export function findStation(query: string) {
12278
}
12379

12480
if (!found) throw new Error(`Station not found: ${query}`);
125-
126-
return useStation(found);
127-
}
128-
129-
export function useStation(station: Station, distance?: number): StationPredictor {
130-
// If subordinate station, use the reference station for datums and constituents
131-
let reference = station;
132-
if (station.type === "subordinate" && station.offsets?.reference) {
133-
reference = findStation(station.offsets?.reference);
134-
}
135-
const { datums, harmonic_constituents } = reference;
136-
137-
// Use station chart datum as the default datum if available
138-
const defaultDatum = station.chart_datum in datums ? station.chart_datum : undefined;
139-
140-
function getPredictor({ datum = defaultDatum, nodeCorrections }: PredictionOptions = {}) {
141-
let offset = 0;
142-
143-
if (datum) {
144-
const datumOffset = datums?.[datum];
145-
const mslOffset = datums?.["MSL"];
146-
147-
if (typeof datumOffset !== "number") {
148-
throw new Error(
149-
`Station ${station.id} missing ${datum} datum. Available datums: ${Object.keys(datums).join(", ")}`,
150-
);
151-
}
152-
153-
if (typeof mslOffset !== "number") {
154-
throw new Error(
155-
`Station ${station.id} missing MSL datum, so predictions can't be given in ${datum}.`,
156-
);
157-
}
158-
159-
offset = mslOffset - datumOffset;
160-
}
161-
162-
return createTidePredictor(harmonic_constituents, { offset, nodeCorrections });
163-
}
164-
165-
return {
166-
...station,
167-
distance,
168-
datums,
169-
harmonic_constituents,
170-
defaultDatum,
171-
getExtremesPrediction({
172-
datum = defaultDatum,
173-
units = defaultUnits,
174-
nodeCorrections,
175-
...options
176-
}: ExtremesOptions) {
177-
const extremes = getPredictor({ datum, nodeCorrections })
178-
.getExtremesPrediction({ ...options, offsets: station.offsets })
179-
.map((e) => toPreferredUnits(e, units));
180-
181-
return { datum, units, station, distance, extremes };
182-
},
183-
184-
getTimelinePrediction({
185-
datum = defaultDatum,
186-
units = defaultUnits,
187-
nodeCorrections,
188-
...options
189-
}: TimelineOptions) {
190-
const timeline = getPredictor({ datum, nodeCorrections })
191-
.getTimelinePrediction({ ...options, offsets: station.offsets })
192-
.map((e) => toPreferredUnits(e, units));
193-
194-
return { datum, units, station, distance, timeline };
195-
},
196-
197-
getWaterLevelAtTime({
198-
time,
199-
datum = defaultDatum,
200-
units = defaultUnits,
201-
nodeCorrections,
202-
}: WaterLevelOptions) {
203-
const prediction = toPreferredUnits(
204-
getPredictor({ datum, nodeCorrections }).getWaterLevelAtTime({
205-
time,
206-
offsets: station.offsets,
207-
}),
208-
units,
209-
);
210-
211-
return { datum, units, station, distance, ...prediction };
212-
},
213-
};
214-
}
215-
216-
function toPreferredUnits<T extends { level: number }>(prediction: T, units: Units): T {
217-
let { level } = prediction;
218-
if (units === "feet") level *= feetPerMeter;
219-
else if (units !== "meters") throw new Error(`Unsupported units: ${units}`);
220-
return { ...prediction, level };
81+
return useStation(found, undefined, findStation);
22182
}

0 commit comments

Comments
 (0)