@ryancardin/noaa-tides-currents-mcp-server 1.0.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/.claude/settings.local.json +29 -0
- package/CLAUDE.md +71 -0
- package/Dockerfile +14 -0
- package/LICENSE +21 -0
- package/README.md +234 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -0
- package/dist/interfaces/config.d.ts +6 -0
- package/dist/interfaces/config.js +1 -0
- package/dist/interfaces/moon.d.ts +65 -0
- package/dist/interfaces/moon.js +35 -0
- package/dist/interfaces/noaa.d.ts +334 -0
- package/dist/interfaces/noaa.js +98 -0
- package/dist/interfaces/parameters.d.ts +18 -0
- package/dist/interfaces/parameters.js +5 -0
- package/dist/interfaces/sun.d.ts +103 -0
- package/dist/interfaces/sun.js +45 -0
- package/dist/mcp-server.d.ts +12 -0
- package/dist/mcp-server.js +103 -0
- package/dist/moon-phase-service.d.ts +122 -0
- package/dist/moon-phase-service.js +187 -0
- package/dist/noaa-service.d.ts +60 -0
- package/dist/noaa-service.js +159 -0
- package/dist/schemas/common.d.ts +14 -0
- package/dist/schemas/common.js +18 -0
- package/dist/schemas/dpapi.d.ts +198 -0
- package/dist/schemas/dpapi.js +89 -0
- package/dist/server/config.d.ts +9 -0
- package/dist/server/config.js +40 -0
- package/dist/server/mcp-server.d.ts +12 -0
- package/dist/server/mcp-server.js +103 -0
- package/dist/services/dpapi-service.d.ts +72 -0
- package/dist/services/dpapi-service.js +164 -0
- package/dist/services/moon-phase-service.d.ts +34 -0
- package/dist/services/moon-phase-service.js +147 -0
- package/dist/services/noaa-parameters-service.d.ts +76 -0
- package/dist/services/noaa-parameters-service.js +128 -0
- package/dist/services/noaa-service.d.ts +52 -0
- package/dist/services/noaa-service.js +151 -0
- package/dist/services/sun-service.d.ts +60 -0
- package/dist/services/sun-service.js +234 -0
- package/dist/sun-service.d.ts +184 -0
- package/dist/sun-service.js +218 -0
- package/dist/tools/derived-product-tools.d.ts +6 -0
- package/dist/tools/derived-product-tools.js +168 -0
- package/dist/tools/index.d.ts +16 -0
- package/dist/tools/index.js +36 -0
- package/dist/tools/moon-tools.d.ts +6 -0
- package/dist/tools/moon-tools.js +69 -0
- package/dist/tools/parameter-tools.d.ts +6 -0
- package/dist/tools/parameter-tools.js +77 -0
- package/dist/tools/station-tools.d.ts +6 -0
- package/dist/tools/station-tools.js +51 -0
- package/dist/tools/sun-tools.d.ts +6 -0
- package/dist/tools/sun-tools.js +109 -0
- package/dist/tools/water-tools.d.ts +6 -0
- package/dist/tools/water-tools.js +150 -0
- package/dist/types/moon.d.ts +26 -0
- package/dist/types/moon.js +14 -0
- package/dist/types/sun.d.ts +49 -0
- package/dist/types/sun.js +19 -0
- package/dist/types.d.ts +337 -0
- package/dist/types.js +98 -0
- package/package.json +56 -0
- package/smithery.yaml +16 -0
- package/src/index.ts +13 -0
- package/src/interfaces/moon.ts +44 -0
- package/src/interfaces/noaa.ts +130 -0
- package/src/interfaces/parameters.ts +20 -0
- package/src/interfaces/sun.ts +57 -0
- package/src/schemas/common.ts +23 -0
- package/src/schemas/dpapi.ts +99 -0
- package/src/server/config.ts +43 -0
- package/src/server/mcp-server.ts +135 -0
- package/src/services/dpapi-service.ts +187 -0
- package/src/services/moon-phase-service.ts +167 -0
- package/src/services/noaa-parameters-service.ts +139 -0
- package/src/services/noaa-service.ts +171 -0
- package/src/services/sun-service.ts +275 -0
- package/src/tools/derived-product-tools.ts +180 -0
- package/src/tools/index.ts +40 -0
- package/src/tools/moon-tools.ts +79 -0
- package/src/tools/parameter-tools.ts +82 -0
- package/src/tools/station-tools.ts +57 -0
- package/src/tools/sun-tools.ts +120 -0
- package/src/tools/water-tools.ts +166 -0
- package/src/types/moon.ts +27 -0
- package/src/types/sun.ts +51 -0
- package/src/types/suncalc.d.ts +110 -0
- package/test-dpapi.js +0 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { GetWaterLevelsSchema, GetTidePredictionsSchema, GetCurrentsSchema, GetCurrentPredictionsSchema, GetMeteorologicalDataSchema, GetStationsSchema, GetStationDetailsSchema } from './types.js';
|
|
2
|
+
// Class for the MCP server
|
|
3
|
+
export class McpServer {
|
|
4
|
+
tools;
|
|
5
|
+
noaaService;
|
|
6
|
+
constructor(noaaService) {
|
|
7
|
+
this.noaaService = noaaService;
|
|
8
|
+
this.tools = this.initializeTools();
|
|
9
|
+
}
|
|
10
|
+
// Initialize the tools
|
|
11
|
+
initializeTools() {
|
|
12
|
+
// Water Levels tool
|
|
13
|
+
const getWaterLevels = {
|
|
14
|
+
name: "get_water_levels",
|
|
15
|
+
description: "Get water level data for a station",
|
|
16
|
+
inputSchema: GetWaterLevelsSchema,
|
|
17
|
+
handler: async (params) => {
|
|
18
|
+
return this.noaaService.getWaterLevels(params);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
// Tide Predictions tool
|
|
22
|
+
const getTidePredictions = {
|
|
23
|
+
name: "get_tide_predictions",
|
|
24
|
+
description: "Get tide prediction data",
|
|
25
|
+
inputSchema: GetTidePredictionsSchema,
|
|
26
|
+
handler: async (params) => {
|
|
27
|
+
return this.noaaService.getTidePredictions(params);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
// Currents tool
|
|
31
|
+
const getCurrents = {
|
|
32
|
+
name: "get_currents",
|
|
33
|
+
description: "Get currents data for a station",
|
|
34
|
+
inputSchema: GetCurrentsSchema,
|
|
35
|
+
handler: async (params) => {
|
|
36
|
+
return this.noaaService.getCurrents(params);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
// Current Predictions tool
|
|
40
|
+
const getCurrentPredictions = {
|
|
41
|
+
name: "get_current_predictions",
|
|
42
|
+
description: "Get current predictions",
|
|
43
|
+
inputSchema: GetCurrentPredictionsSchema,
|
|
44
|
+
handler: async (params) => {
|
|
45
|
+
return this.noaaService.getCurrentPredictions(params);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
// Meteorological Data tool
|
|
49
|
+
const getMeteorologicalData = {
|
|
50
|
+
name: "get_meteorological_data",
|
|
51
|
+
description: "Get meteorological data",
|
|
52
|
+
inputSchema: GetMeteorologicalDataSchema,
|
|
53
|
+
handler: async (params) => {
|
|
54
|
+
return this.noaaService.getMeteorologicalData(params);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
// Stations tool
|
|
58
|
+
const getStations = {
|
|
59
|
+
name: "get_stations",
|
|
60
|
+
description: "Get list of stations",
|
|
61
|
+
inputSchema: GetStationsSchema,
|
|
62
|
+
handler: async (params) => {
|
|
63
|
+
return this.noaaService.getStations(params);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
// Station Details tool
|
|
67
|
+
const getStationDetails = {
|
|
68
|
+
name: "get_station_details",
|
|
69
|
+
description: "Get detailed information about a station",
|
|
70
|
+
inputSchema: GetStationDetailsSchema,
|
|
71
|
+
handler: async (params) => {
|
|
72
|
+
return this.noaaService.getStationDetails(params);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
return [
|
|
76
|
+
getWaterLevels,
|
|
77
|
+
getTidePredictions,
|
|
78
|
+
getCurrents,
|
|
79
|
+
getCurrentPredictions,
|
|
80
|
+
getMeteorologicalData,
|
|
81
|
+
getStations,
|
|
82
|
+
getStationDetails
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
// Method to get all tools
|
|
86
|
+
getTools() {
|
|
87
|
+
return this.tools.map(tool => ({
|
|
88
|
+
name: tool.name,
|
|
89
|
+
description: tool.description
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
// Method to handle tool execution
|
|
93
|
+
async executeTool(toolName, params) {
|
|
94
|
+
const tool = this.tools.find(t => t.name === toolName);
|
|
95
|
+
if (!tool) {
|
|
96
|
+
throw new Error(`Tool '${toolName}' not found`);
|
|
97
|
+
}
|
|
98
|
+
// Validate the parameters against the schema
|
|
99
|
+
const validatedParams = tool.inputSchema.parse(params);
|
|
100
|
+
// Execute the tool handler
|
|
101
|
+
return tool.handler(validatedParams);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Moon phase names and their approximate ranges
|
|
4
|
+
*/
|
|
5
|
+
export declare enum MoonPhaseName {
|
|
6
|
+
NEW_MOON = "New Moon",
|
|
7
|
+
WAXING_CRESCENT = "Waxing Crescent",
|
|
8
|
+
FIRST_QUARTER = "First Quarter",
|
|
9
|
+
WAXING_GIBBOUS = "Waxing Gibbous",
|
|
10
|
+
FULL_MOON = "Full Moon",
|
|
11
|
+
WANING_GIBBOUS = "Waning Gibbous",
|
|
12
|
+
LAST_QUARTER = "Last Quarter",
|
|
13
|
+
WANING_CRESCENT = "Waning Crescent"
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Moon phase information
|
|
17
|
+
*/
|
|
18
|
+
export interface MoonPhaseInfo {
|
|
19
|
+
date: string;
|
|
20
|
+
phase: number;
|
|
21
|
+
phaseName: MoonPhaseName;
|
|
22
|
+
illumination: number;
|
|
23
|
+
age: number;
|
|
24
|
+
distance: number;
|
|
25
|
+
diameter: number;
|
|
26
|
+
isWaxing: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Parameters for getting moon phase
|
|
30
|
+
*/
|
|
31
|
+
export declare const MoonPhaseParamsSchema: z.ZodObject<{
|
|
32
|
+
date: z.ZodOptional<z.ZodString>;
|
|
33
|
+
latitude: z.ZodOptional<z.ZodNumber>;
|
|
34
|
+
longitude: z.ZodOptional<z.ZodNumber>;
|
|
35
|
+
format: z.ZodOptional<z.ZodEnum<["json", "text"]>>;
|
|
36
|
+
}, "strip", z.ZodTypeAny, {
|
|
37
|
+
date?: string | undefined;
|
|
38
|
+
format?: "text" | "json" | undefined;
|
|
39
|
+
latitude?: number | undefined;
|
|
40
|
+
longitude?: number | undefined;
|
|
41
|
+
}, {
|
|
42
|
+
date?: string | undefined;
|
|
43
|
+
format?: "text" | "json" | undefined;
|
|
44
|
+
latitude?: number | undefined;
|
|
45
|
+
longitude?: number | undefined;
|
|
46
|
+
}>;
|
|
47
|
+
export type MoonPhaseParams = z.infer<typeof MoonPhaseParamsSchema>;
|
|
48
|
+
/**
|
|
49
|
+
* Parameters for getting moon phases for a date range
|
|
50
|
+
*/
|
|
51
|
+
export declare const MoonPhasesRangeParamsSchema: z.ZodObject<{
|
|
52
|
+
start_date: z.ZodString;
|
|
53
|
+
end_date: z.ZodString;
|
|
54
|
+
latitude: z.ZodOptional<z.ZodNumber>;
|
|
55
|
+
longitude: z.ZodOptional<z.ZodNumber>;
|
|
56
|
+
format: z.ZodOptional<z.ZodEnum<["json", "text"]>>;
|
|
57
|
+
}, "strip", z.ZodTypeAny, {
|
|
58
|
+
start_date: string;
|
|
59
|
+
end_date: string;
|
|
60
|
+
format?: "text" | "json" | undefined;
|
|
61
|
+
latitude?: number | undefined;
|
|
62
|
+
longitude?: number | undefined;
|
|
63
|
+
}, {
|
|
64
|
+
start_date: string;
|
|
65
|
+
end_date: string;
|
|
66
|
+
format?: "text" | "json" | undefined;
|
|
67
|
+
latitude?: number | undefined;
|
|
68
|
+
longitude?: number | undefined;
|
|
69
|
+
}>;
|
|
70
|
+
export type MoonPhasesRangeParams = z.infer<typeof MoonPhasesRangeParamsSchema>;
|
|
71
|
+
/**
|
|
72
|
+
* Parameters for getting next moon phase
|
|
73
|
+
*/
|
|
74
|
+
export declare const NextMoonPhaseParamsSchema: z.ZodObject<{
|
|
75
|
+
phase: z.ZodEnum<[MoonPhaseName.NEW_MOON, MoonPhaseName.FIRST_QUARTER, MoonPhaseName.FULL_MOON, MoonPhaseName.LAST_QUARTER]>;
|
|
76
|
+
date: z.ZodOptional<z.ZodString>;
|
|
77
|
+
count: z.ZodOptional<z.ZodNumber>;
|
|
78
|
+
format: z.ZodOptional<z.ZodEnum<["json", "text"]>>;
|
|
79
|
+
}, "strip", z.ZodTypeAny, {
|
|
80
|
+
phase: MoonPhaseName.NEW_MOON | MoonPhaseName.FIRST_QUARTER | MoonPhaseName.FULL_MOON | MoonPhaseName.LAST_QUARTER;
|
|
81
|
+
date?: string | undefined;
|
|
82
|
+
format?: "text" | "json" | undefined;
|
|
83
|
+
count?: number | undefined;
|
|
84
|
+
}, {
|
|
85
|
+
phase: MoonPhaseName.NEW_MOON | MoonPhaseName.FIRST_QUARTER | MoonPhaseName.FULL_MOON | MoonPhaseName.LAST_QUARTER;
|
|
86
|
+
date?: string | undefined;
|
|
87
|
+
format?: "text" | "json" | undefined;
|
|
88
|
+
count?: number | undefined;
|
|
89
|
+
}>;
|
|
90
|
+
export type NextMoonPhaseParams = z.infer<typeof NextMoonPhaseParamsSchema>;
|
|
91
|
+
/**
|
|
92
|
+
* Service for moon phase calculations
|
|
93
|
+
*/
|
|
94
|
+
export declare class MoonPhaseService {
|
|
95
|
+
/**
|
|
96
|
+
* Get the moon phase for a specific date
|
|
97
|
+
* @param params Parameters for the request
|
|
98
|
+
* @returns Moon phase information
|
|
99
|
+
*/
|
|
100
|
+
getMoonPhase(params: MoonPhaseParams): MoonPhaseInfo;
|
|
101
|
+
/**
|
|
102
|
+
* Get moon phases for a date range
|
|
103
|
+
* @param params Parameters for the request
|
|
104
|
+
* @returns Array of moon phase information
|
|
105
|
+
*/
|
|
106
|
+
getMoonPhasesRange(params: MoonPhasesRangeParams): MoonPhaseInfo[];
|
|
107
|
+
/**
|
|
108
|
+
* Get the next occurrence(s) of a specific moon phase
|
|
109
|
+
* @param params Parameters for the request
|
|
110
|
+
* @returns Array of dates for the next occurrences of the specified phase
|
|
111
|
+
*/
|
|
112
|
+
getNextMoonPhase(params: NextMoonPhaseParams): {
|
|
113
|
+
date: string;
|
|
114
|
+
phase: string;
|
|
115
|
+
}[];
|
|
116
|
+
/**
|
|
117
|
+
* Get the name of the moon phase based on the phase value
|
|
118
|
+
* @param phase Phase value (0-1)
|
|
119
|
+
* @returns Moon phase name
|
|
120
|
+
*/
|
|
121
|
+
private getMoonPhaseName;
|
|
122
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import SunCalc from 'suncalc';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* Moon phase names and their approximate ranges
|
|
5
|
+
*/
|
|
6
|
+
export var MoonPhaseName;
|
|
7
|
+
(function (MoonPhaseName) {
|
|
8
|
+
MoonPhaseName["NEW_MOON"] = "New Moon";
|
|
9
|
+
MoonPhaseName["WAXING_CRESCENT"] = "Waxing Crescent";
|
|
10
|
+
MoonPhaseName["FIRST_QUARTER"] = "First Quarter";
|
|
11
|
+
MoonPhaseName["WAXING_GIBBOUS"] = "Waxing Gibbous";
|
|
12
|
+
MoonPhaseName["FULL_MOON"] = "Full Moon";
|
|
13
|
+
MoonPhaseName["WANING_GIBBOUS"] = "Waning Gibbous";
|
|
14
|
+
MoonPhaseName["LAST_QUARTER"] = "Last Quarter";
|
|
15
|
+
MoonPhaseName["WANING_CRESCENT"] = "Waning Crescent";
|
|
16
|
+
})(MoonPhaseName || (MoonPhaseName = {}));
|
|
17
|
+
/**
|
|
18
|
+
* Parameters for getting moon phase
|
|
19
|
+
*/
|
|
20
|
+
export const MoonPhaseParamsSchema = z.object({
|
|
21
|
+
date: z.string().optional().describe('Date to get moon phase for (YYYY-MM-DD format). Defaults to current date.'),
|
|
22
|
+
latitude: z.number().min(-90).max(90).optional().describe('Latitude for location-specific calculations'),
|
|
23
|
+
longitude: z.number().min(-180).max(180).optional().describe('Longitude for location-specific calculations'),
|
|
24
|
+
format: z.enum(['json', 'text']).optional().describe('Output format (json or text)')
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* Parameters for getting moon phases for a date range
|
|
28
|
+
*/
|
|
29
|
+
export const MoonPhasesRangeParamsSchema = z.object({
|
|
30
|
+
start_date: z.string().describe('Start date (YYYY-MM-DD format)'),
|
|
31
|
+
end_date: z.string().describe('End date (YYYY-MM-DD format)'),
|
|
32
|
+
latitude: z.number().min(-90).max(90).optional().describe('Latitude for location-specific calculations'),
|
|
33
|
+
longitude: z.number().min(-180).max(180).optional().describe('Longitude for location-specific calculations'),
|
|
34
|
+
format: z.enum(['json', 'text']).optional().describe('Output format (json or text)')
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* Parameters for getting next moon phase
|
|
38
|
+
*/
|
|
39
|
+
export const NextMoonPhaseParamsSchema = z.object({
|
|
40
|
+
phase: z.enum([
|
|
41
|
+
MoonPhaseName.NEW_MOON,
|
|
42
|
+
MoonPhaseName.FIRST_QUARTER,
|
|
43
|
+
MoonPhaseName.FULL_MOON,
|
|
44
|
+
MoonPhaseName.LAST_QUARTER
|
|
45
|
+
]).describe('Moon phase to find'),
|
|
46
|
+
date: z.string().optional().describe('Starting date (YYYY-MM-DD format). Defaults to current date.'),
|
|
47
|
+
count: z.number().positive().optional().describe('Number of occurrences to return. Defaults to 1.'),
|
|
48
|
+
format: z.enum(['json', 'text']).optional().describe('Output format (json or text)')
|
|
49
|
+
});
|
|
50
|
+
/**
|
|
51
|
+
* Service for moon phase calculations
|
|
52
|
+
*/
|
|
53
|
+
export class MoonPhaseService {
|
|
54
|
+
/**
|
|
55
|
+
* Get the moon phase for a specific date
|
|
56
|
+
* @param params Parameters for the request
|
|
57
|
+
* @returns Moon phase information
|
|
58
|
+
*/
|
|
59
|
+
getMoonPhase(params) {
|
|
60
|
+
const date = params.date ? new Date(params.date) : new Date();
|
|
61
|
+
// Get moon illumination data
|
|
62
|
+
const illuminationData = SunCalc.getMoonIllumination(date);
|
|
63
|
+
// Get moon position data (requires location)
|
|
64
|
+
const latitude = params.latitude ?? 0;
|
|
65
|
+
const longitude = params.longitude ?? 0;
|
|
66
|
+
const positionData = SunCalc.getMoonPosition(date, latitude, longitude);
|
|
67
|
+
// Calculate moon phase name
|
|
68
|
+
const phaseName = this.getMoonPhaseName(illuminationData.phase);
|
|
69
|
+
// Calculate if the moon is waxing (increasing illumination)
|
|
70
|
+
const isWaxing = illuminationData.phase < 0.5;
|
|
71
|
+
// Calculate approximate moon age (0-29.53 days)
|
|
72
|
+
const lunarMonth = 29.53; // days
|
|
73
|
+
const age = illuminationData.phase * lunarMonth;
|
|
74
|
+
// Calculate apparent diameter (in degrees)
|
|
75
|
+
const diameter = 0.5181 * (384400 / positionData.distance);
|
|
76
|
+
return {
|
|
77
|
+
date: date.toISOString().split('T')[0],
|
|
78
|
+
phase: illuminationData.phase,
|
|
79
|
+
phaseName,
|
|
80
|
+
illumination: illuminationData.fraction,
|
|
81
|
+
age,
|
|
82
|
+
distance: positionData.distance,
|
|
83
|
+
diameter,
|
|
84
|
+
isWaxing
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get moon phases for a date range
|
|
89
|
+
* @param params Parameters for the request
|
|
90
|
+
* @returns Array of moon phase information
|
|
91
|
+
*/
|
|
92
|
+
getMoonPhasesRange(params) {
|
|
93
|
+
const startDate = new Date(params.start_date);
|
|
94
|
+
const endDate = new Date(params.end_date);
|
|
95
|
+
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
|
|
96
|
+
throw new Error('Invalid date format. Please use YYYY-MM-DD format.');
|
|
97
|
+
}
|
|
98
|
+
if (startDate > endDate) {
|
|
99
|
+
throw new Error('Start date must be before end date.');
|
|
100
|
+
}
|
|
101
|
+
const result = [];
|
|
102
|
+
const currentDate = new Date(startDate);
|
|
103
|
+
while (currentDate <= endDate) {
|
|
104
|
+
result.push(this.getMoonPhase({
|
|
105
|
+
date: currentDate.toISOString().split('T')[0],
|
|
106
|
+
latitude: params.latitude,
|
|
107
|
+
longitude: params.longitude
|
|
108
|
+
}));
|
|
109
|
+
// Move to next day
|
|
110
|
+
currentDate.setDate(currentDate.getDate() + 1);
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Get the next occurrence(s) of a specific moon phase
|
|
116
|
+
* @param params Parameters for the request
|
|
117
|
+
* @returns Array of dates for the next occurrences of the specified phase
|
|
118
|
+
*/
|
|
119
|
+
getNextMoonPhase(params) {
|
|
120
|
+
const startDate = params.date ? new Date(params.date) : new Date();
|
|
121
|
+
const count = params.count || 1;
|
|
122
|
+
const targetPhase = params.phase;
|
|
123
|
+
// Map phase names to their approximate values
|
|
124
|
+
const phaseValues = {
|
|
125
|
+
[MoonPhaseName.NEW_MOON]: 0,
|
|
126
|
+
[MoonPhaseName.FIRST_QUARTER]: 0.25,
|
|
127
|
+
[MoonPhaseName.FULL_MOON]: 0.5,
|
|
128
|
+
[MoonPhaseName.LAST_QUARTER]: 0.75
|
|
129
|
+
};
|
|
130
|
+
const targetPhaseValue = phaseValues[targetPhase];
|
|
131
|
+
const results = [];
|
|
132
|
+
let currentDate = new Date(startDate);
|
|
133
|
+
// Find the next occurrences
|
|
134
|
+
while (results.length < count) {
|
|
135
|
+
// Check every day (could be optimized with better algorithms)
|
|
136
|
+
const illuminationData = SunCalc.getMoonIllumination(currentDate);
|
|
137
|
+
const prevDate = new Date(currentDate);
|
|
138
|
+
prevDate.setDate(prevDate.getDate() - 1);
|
|
139
|
+
const prevIlluminationData = SunCalc.getMoonIllumination(prevDate);
|
|
140
|
+
// Check if we've passed the target phase between yesterday and today
|
|
141
|
+
const prevDiff = Math.abs(prevIlluminationData.phase - targetPhaseValue);
|
|
142
|
+
const currentDiff = Math.abs(illuminationData.phase - targetPhaseValue);
|
|
143
|
+
// If we're getting closer to the target phase and then further away, we've passed it
|
|
144
|
+
// Or if we're very close to the target phase (within 0.01)
|
|
145
|
+
if ((prevDiff > currentDiff && currentDiff < 0.01) || currentDiff < 0.005) {
|
|
146
|
+
results.push({
|
|
147
|
+
date: currentDate.toISOString().split('T')[0],
|
|
148
|
+
phase: targetPhase
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
// Move to next day
|
|
152
|
+
currentDate.setDate(currentDate.getDate() + 1);
|
|
153
|
+
// Safety check to prevent infinite loops
|
|
154
|
+
if (results.length === 0 && currentDate.getTime() - startDate.getTime() > 366 * 24 * 60 * 60 * 1000) {
|
|
155
|
+
throw new Error('Could not find the specified moon phase within a year.');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return results;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Get the name of the moon phase based on the phase value
|
|
162
|
+
* @param phase Phase value (0-1)
|
|
163
|
+
* @returns Moon phase name
|
|
164
|
+
*/
|
|
165
|
+
getMoonPhaseName(phase) {
|
|
166
|
+
// Normalize phase to 0-1 range
|
|
167
|
+
phase = phase % 1;
|
|
168
|
+
if (phase < 0)
|
|
169
|
+
phase += 1;
|
|
170
|
+
// Determine phase name based on value ranges
|
|
171
|
+
if (phase < 0.0625 || phase >= 0.9375)
|
|
172
|
+
return MoonPhaseName.NEW_MOON;
|
|
173
|
+
if (phase < 0.1875)
|
|
174
|
+
return MoonPhaseName.WAXING_CRESCENT;
|
|
175
|
+
if (phase < 0.3125)
|
|
176
|
+
return MoonPhaseName.FIRST_QUARTER;
|
|
177
|
+
if (phase < 0.4375)
|
|
178
|
+
return MoonPhaseName.WAXING_GIBBOUS;
|
|
179
|
+
if (phase < 0.5625)
|
|
180
|
+
return MoonPhaseName.FULL_MOON;
|
|
181
|
+
if (phase < 0.6875)
|
|
182
|
+
return MoonPhaseName.WANING_GIBBOUS;
|
|
183
|
+
if (phase < 0.8125)
|
|
184
|
+
return MoonPhaseName.LAST_QUARTER;
|
|
185
|
+
return MoonPhaseName.WANING_CRESCENT;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration options for the NOAA service
|
|
3
|
+
*/
|
|
4
|
+
export interface NoaaConfig {
|
|
5
|
+
applicationName?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Service for interacting with NOAA Tides and Currents APIs
|
|
9
|
+
*/
|
|
10
|
+
export declare class NoaaService {
|
|
11
|
+
private config;
|
|
12
|
+
constructor(config?: NoaaConfig);
|
|
13
|
+
/**
|
|
14
|
+
* Build parameters for the API request
|
|
15
|
+
* @param params Parameters for the request
|
|
16
|
+
* @returns URL-encoded parameters string
|
|
17
|
+
*/
|
|
18
|
+
private buildParams;
|
|
19
|
+
/**
|
|
20
|
+
* Make a request to the Data API
|
|
21
|
+
* @param params Parameters for the request
|
|
22
|
+
* @returns Response data
|
|
23
|
+
*/
|
|
24
|
+
fetchDataApi(params: Record<string, any>): Promise<any>;
|
|
25
|
+
/**
|
|
26
|
+
* Make a request to the Metadata API
|
|
27
|
+
* @param endpoint Endpoint path
|
|
28
|
+
* @param params Parameters for the request
|
|
29
|
+
* @returns Response data
|
|
30
|
+
*/
|
|
31
|
+
fetchMetadataApi(endpoint: string, params?: Record<string, any>): Promise<any>;
|
|
32
|
+
/**
|
|
33
|
+
* Get water level data
|
|
34
|
+
*/
|
|
35
|
+
getWaterLevels(params: Record<string, any>): Promise<any>;
|
|
36
|
+
/**
|
|
37
|
+
* Get tide predictions
|
|
38
|
+
*/
|
|
39
|
+
getTidePredictions(params: Record<string, any>): Promise<any>;
|
|
40
|
+
/**
|
|
41
|
+
* Get currents data
|
|
42
|
+
*/
|
|
43
|
+
getCurrents(params: Record<string, any>): Promise<any>;
|
|
44
|
+
/**
|
|
45
|
+
* Get current predictions
|
|
46
|
+
*/
|
|
47
|
+
getCurrentPredictions(params: Record<string, any>): Promise<any>;
|
|
48
|
+
/**
|
|
49
|
+
* Get meteorological data (air_temperature, wind, etc.)
|
|
50
|
+
*/
|
|
51
|
+
getMeteorologicalData(params: Record<string, any>): Promise<any>;
|
|
52
|
+
/**
|
|
53
|
+
* Get list of stations
|
|
54
|
+
*/
|
|
55
|
+
getStations(params: Record<string, any>): Promise<any>;
|
|
56
|
+
/**
|
|
57
|
+
* Get station details
|
|
58
|
+
*/
|
|
59
|
+
getStationDetails(params: Record<string, any>): Promise<any>;
|
|
60
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
// Base URLs for the different NOAA APIs
|
|
3
|
+
const DATA_API_BASE_URL = 'https://api.tidesandcurrents.noaa.gov/api/prod/datagetter';
|
|
4
|
+
const METADATA_API_BASE_URL = 'https://api.tidesandcurrents.noaa.gov/mdapi/prod/webapi';
|
|
5
|
+
/**
|
|
6
|
+
* Service for interacting with NOAA Tides and Currents APIs
|
|
7
|
+
*/
|
|
8
|
+
export class NoaaService {
|
|
9
|
+
config;
|
|
10
|
+
constructor(config = {}) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Build parameters for the API request
|
|
15
|
+
* @param params Parameters for the request
|
|
16
|
+
* @returns URL-encoded parameters string
|
|
17
|
+
*/
|
|
18
|
+
buildParams(params) {
|
|
19
|
+
// Add application name if provided in config
|
|
20
|
+
if (this.config.applicationName) {
|
|
21
|
+
params.application = this.config.applicationName;
|
|
22
|
+
}
|
|
23
|
+
// Remove undefined and null values
|
|
24
|
+
const filteredParams = Object.entries(params)
|
|
25
|
+
.filter(([_, value]) => value !== undefined && value !== null)
|
|
26
|
+
.reduce((acc, [key, value]) => {
|
|
27
|
+
acc[key] = value;
|
|
28
|
+
return acc;
|
|
29
|
+
}, {});
|
|
30
|
+
// Convert to URL parameters
|
|
31
|
+
return new URLSearchParams(filteredParams).toString();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Make a request to the Data API
|
|
35
|
+
* @param params Parameters for the request
|
|
36
|
+
* @returns Response data
|
|
37
|
+
*/
|
|
38
|
+
async fetchDataApi(params) {
|
|
39
|
+
try {
|
|
40
|
+
const queryParams = this.buildParams(params);
|
|
41
|
+
const url = `${DATA_API_BASE_URL}?${queryParams}`;
|
|
42
|
+
const response = await axios.get(url);
|
|
43
|
+
return response.data;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (axios.isAxiosError(error) && error.response) {
|
|
47
|
+
throw new Error(`NOAA API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`);
|
|
48
|
+
}
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Make a request to the Metadata API
|
|
54
|
+
* @param endpoint Endpoint path
|
|
55
|
+
* @param params Parameters for the request
|
|
56
|
+
* @returns Response data
|
|
57
|
+
*/
|
|
58
|
+
async fetchMetadataApi(endpoint, params = {}) {
|
|
59
|
+
try {
|
|
60
|
+
const queryParams = this.buildParams(params);
|
|
61
|
+
const url = `${METADATA_API_BASE_URL}${endpoint}${queryParams ? '?' + queryParams : ''}`;
|
|
62
|
+
const response = await axios.get(url);
|
|
63
|
+
return response.data;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (axios.isAxiosError(error) && error.response) {
|
|
67
|
+
throw new Error(`NOAA API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`);
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get water level data
|
|
74
|
+
*/
|
|
75
|
+
async getWaterLevels(params) {
|
|
76
|
+
return this.fetchDataApi({
|
|
77
|
+
...params,
|
|
78
|
+
product: 'water_level'
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get tide predictions
|
|
83
|
+
*/
|
|
84
|
+
async getTidePredictions(params) {
|
|
85
|
+
return this.fetchDataApi({
|
|
86
|
+
...params,
|
|
87
|
+
product: 'predictions'
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get currents data
|
|
92
|
+
*/
|
|
93
|
+
async getCurrents(params) {
|
|
94
|
+
return this.fetchDataApi({
|
|
95
|
+
...params,
|
|
96
|
+
product: 'currents'
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get current predictions
|
|
101
|
+
*/
|
|
102
|
+
async getCurrentPredictions(params) {
|
|
103
|
+
return this.fetchDataApi({
|
|
104
|
+
...params,
|
|
105
|
+
product: 'currents_predictions'
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Get meteorological data (air_temperature, wind, etc.)
|
|
110
|
+
*/
|
|
111
|
+
async getMeteorologicalData(params) {
|
|
112
|
+
const { product, ...rest } = params;
|
|
113
|
+
return this.fetchDataApi({
|
|
114
|
+
...rest,
|
|
115
|
+
product
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get list of stations
|
|
120
|
+
*/
|
|
121
|
+
async getStations(params) {
|
|
122
|
+
const { type, name, lat_min, lat_max, lon_min, lon_max, state, limit, offset, sort_by, sort_order, ...rest } = params;
|
|
123
|
+
const endpoint = '/stations.' + (rest.format || 'json');
|
|
124
|
+
// Build query parameters with all the filters
|
|
125
|
+
const queryParams = { ...rest };
|
|
126
|
+
// Add filters only if they are defined
|
|
127
|
+
if (type)
|
|
128
|
+
queryParams.type = type;
|
|
129
|
+
if (name)
|
|
130
|
+
queryParams.name = name;
|
|
131
|
+
if (lat_min !== undefined)
|
|
132
|
+
queryParams.lat_min = lat_min;
|
|
133
|
+
if (lat_max !== undefined)
|
|
134
|
+
queryParams.lat_max = lat_max;
|
|
135
|
+
if (lon_min !== undefined)
|
|
136
|
+
queryParams.lon_min = lon_min;
|
|
137
|
+
if (lon_max !== undefined)
|
|
138
|
+
queryParams.lon_max = lon_max;
|
|
139
|
+
if (state)
|
|
140
|
+
queryParams.state = state;
|
|
141
|
+
if (limit !== undefined)
|
|
142
|
+
queryParams.limit = limit;
|
|
143
|
+
if (offset !== undefined)
|
|
144
|
+
queryParams.offset = offset;
|
|
145
|
+
if (sort_by)
|
|
146
|
+
queryParams.sort_by = sort_by;
|
|
147
|
+
if (sort_order)
|
|
148
|
+
queryParams.sort_order = sort_order;
|
|
149
|
+
return this.fetchMetadataApi(endpoint, queryParams);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Get station details
|
|
153
|
+
*/
|
|
154
|
+
async getStationDetails(params) {
|
|
155
|
+
const { station, ...rest } = params;
|
|
156
|
+
const endpoint = `/stations/${station}/details.` + (rest.format || 'json');
|
|
157
|
+
return this.fetchMetadataApi(endpoint, rest);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const StationSchema: z.ZodString;
|
|
3
|
+
export declare const DateSchema: z.ZodOptional<z.ZodString>;
|
|
4
|
+
export declare const BeginDateSchema: z.ZodOptional<z.ZodString>;
|
|
5
|
+
export declare const EndDateSchema: z.ZodOptional<z.ZodString>;
|
|
6
|
+
export declare const RangeSchema: z.ZodOptional<z.ZodNumber>;
|
|
7
|
+
export declare const DatumSchema: z.ZodOptional<z.ZodString>;
|
|
8
|
+
export declare const UnitsSchema: z.ZodOptional<z.ZodEnum<["english", "metric"]>>;
|
|
9
|
+
export declare const TimeZoneSchema: z.ZodOptional<z.ZodEnum<["gmt", "lst", "lst_ldt"]>>;
|
|
10
|
+
export declare const FormatSchema: z.ZodOptional<z.ZodEnum<["json", "xml", "csv"]>>;
|
|
11
|
+
export declare const BinSchema: z.ZodOptional<z.ZodNumber>;
|
|
12
|
+
export declare const IntervalSchema: z.ZodOptional<z.ZodString>;
|
|
13
|
+
export declare const refineDateParams: (data: any) => any;
|
|
14
|
+
export declare const dateRefinementMessage = "You must provide either 'date', 'begin_date' and 'end_date', 'begin_date' and 'range', 'end_date' and 'range', or just 'range'";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
// Common parameter schemas
|
|
3
|
+
export const StationSchema = z.string().min(1).describe('Station ID');
|
|
4
|
+
export const DateSchema = z.string().optional().describe('Date to retrieve data for ("today", "latest", "recent", or specific date)');
|
|
5
|
+
export const BeginDateSchema = z.string().optional().describe('Start date (YYYYMMDD or MM/DD/YYYY)');
|
|
6
|
+
export const EndDateSchema = z.string().optional().describe('End date (YYYYMMDD or MM/DD/YYYY)');
|
|
7
|
+
export const RangeSchema = z.number().optional().describe('Number of hours to retrieve data for');
|
|
8
|
+
export const DatumSchema = z.string().optional().describe('Datum to use (MLLW, MSL, etc.)');
|
|
9
|
+
export const UnitsSchema = z.enum(['english', 'metric']).optional().describe('Units to use ("english" or "metric")');
|
|
10
|
+
export const TimeZoneSchema = z.enum(['gmt', 'lst', 'lst_ldt']).optional().describe('Time zone (gmt, lst, lst_ldt)');
|
|
11
|
+
export const FormatSchema = z.enum(['json', 'xml', 'csv']).optional().describe('Output format (json, xml, csv)');
|
|
12
|
+
export const BinSchema = z.number().optional().describe('Bin number');
|
|
13
|
+
export const IntervalSchema = z.string().optional().describe('Interval (hilo, hl, h, or a number for minutes)');
|
|
14
|
+
// Schema refinement function for date parameters
|
|
15
|
+
export const refineDateParams = (data) => (data.date || (data.begin_date && data.end_date) ||
|
|
16
|
+
(data.begin_date && data.range) || (data.end_date && data.range) ||
|
|
17
|
+
data.range);
|
|
18
|
+
export const dateRefinementMessage = "You must provide either 'date', 'begin_date' and 'end_date', 'begin_date' and 'range', 'end_date' and 'range', or just 'range'";
|