@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,151 @@
|
|
|
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
|
+
/**
|
|
10
|
+
* Build parameters for the API request
|
|
11
|
+
* @param params Parameters for the request
|
|
12
|
+
* @returns URL-encoded parameters string
|
|
13
|
+
*/
|
|
14
|
+
buildParams(params) {
|
|
15
|
+
// Remove undefined and null values
|
|
16
|
+
const filteredParams = Object.entries(params)
|
|
17
|
+
.filter(([_, value]) => value !== undefined && value !== null)
|
|
18
|
+
.reduce((acc, [key, value]) => {
|
|
19
|
+
acc[key] = value;
|
|
20
|
+
return acc;
|
|
21
|
+
}, {});
|
|
22
|
+
// Convert to URL parameters
|
|
23
|
+
return new URLSearchParams(filteredParams).toString();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Make a request to the Data API
|
|
27
|
+
* @param params Parameters for the request
|
|
28
|
+
* @returns Response data
|
|
29
|
+
*/
|
|
30
|
+
async fetchDataApi(params) {
|
|
31
|
+
try {
|
|
32
|
+
const queryParams = this.buildParams(params);
|
|
33
|
+
const url = `${DATA_API_BASE_URL}?${queryParams}`;
|
|
34
|
+
const response = await axios.get(url);
|
|
35
|
+
return response.data;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (axios.isAxiosError(error) && error.response) {
|
|
39
|
+
throw new Error(`NOAA API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`);
|
|
40
|
+
}
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Make a request to the Metadata API
|
|
46
|
+
* @param endpoint Endpoint path
|
|
47
|
+
* @param params Parameters for the request
|
|
48
|
+
* @returns Response data
|
|
49
|
+
*/
|
|
50
|
+
async fetchMetadataApi(endpoint, params = {}) {
|
|
51
|
+
try {
|
|
52
|
+
const queryParams = this.buildParams(params);
|
|
53
|
+
const url = `${METADATA_API_BASE_URL}${endpoint}${queryParams ? '?' + queryParams : ''}`;
|
|
54
|
+
const response = await axios.get(url);
|
|
55
|
+
return response.data;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (axios.isAxiosError(error) && error.response) {
|
|
59
|
+
throw new Error(`NOAA API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`);
|
|
60
|
+
}
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get water level data
|
|
66
|
+
*/
|
|
67
|
+
async getWaterLevels(params) {
|
|
68
|
+
return this.fetchDataApi({
|
|
69
|
+
...params,
|
|
70
|
+
product: 'water_level'
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get tide predictions
|
|
75
|
+
*/
|
|
76
|
+
async getTidePredictions(params) {
|
|
77
|
+
return this.fetchDataApi({
|
|
78
|
+
...params,
|
|
79
|
+
product: 'predictions'
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get currents data
|
|
84
|
+
*/
|
|
85
|
+
async getCurrents(params) {
|
|
86
|
+
return this.fetchDataApi({
|
|
87
|
+
...params,
|
|
88
|
+
product: 'currents'
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Get current predictions
|
|
93
|
+
*/
|
|
94
|
+
async getCurrentPredictions(params) {
|
|
95
|
+
return this.fetchDataApi({
|
|
96
|
+
...params,
|
|
97
|
+
product: 'currents_predictions'
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Get meteorological data (air_temperature, wind, etc.)
|
|
102
|
+
*/
|
|
103
|
+
async getMeteorologicalData(params) {
|
|
104
|
+
const { product, ...rest } = params;
|
|
105
|
+
return this.fetchDataApi({
|
|
106
|
+
...rest,
|
|
107
|
+
product
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get list of stations
|
|
112
|
+
*/
|
|
113
|
+
async getStations(params) {
|
|
114
|
+
const { type, name, lat_min, lat_max, lon_min, lon_max, state, limit, offset, sort_by, sort_order, ...rest } = params;
|
|
115
|
+
const endpoint = '/stations.' + (rest.format || 'json');
|
|
116
|
+
// Build query parameters with all the filters
|
|
117
|
+
const queryParams = { ...rest };
|
|
118
|
+
// Add filters only if they are defined
|
|
119
|
+
if (type)
|
|
120
|
+
queryParams.type = type;
|
|
121
|
+
if (name)
|
|
122
|
+
queryParams.name = name;
|
|
123
|
+
if (lat_min !== undefined)
|
|
124
|
+
queryParams.lat_min = lat_min;
|
|
125
|
+
if (lat_max !== undefined)
|
|
126
|
+
queryParams.lat_max = lat_max;
|
|
127
|
+
if (lon_min !== undefined)
|
|
128
|
+
queryParams.lon_min = lon_min;
|
|
129
|
+
if (lon_max !== undefined)
|
|
130
|
+
queryParams.lon_max = lon_max;
|
|
131
|
+
if (state)
|
|
132
|
+
queryParams.state = state;
|
|
133
|
+
if (limit !== undefined)
|
|
134
|
+
queryParams.limit = limit;
|
|
135
|
+
if (offset !== undefined)
|
|
136
|
+
queryParams.offset = offset;
|
|
137
|
+
if (sort_by)
|
|
138
|
+
queryParams.sort_by = sort_by;
|
|
139
|
+
if (sort_order)
|
|
140
|
+
queryParams.sort_order = sort_order;
|
|
141
|
+
return this.fetchMetadataApi(endpoint, queryParams);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Get station details
|
|
145
|
+
*/
|
|
146
|
+
async getStationDetails(params) {
|
|
147
|
+
const { station, ...rest } = params;
|
|
148
|
+
const endpoint = `/stations/${station}/details.` + (rest.format || 'json');
|
|
149
|
+
return this.fetchMetadataApi(endpoint, rest);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { SunTimesParams, SunTimesRangeParams, SunPositionParams, NextSunEventParams } from '../interfaces/sun.js';
|
|
2
|
+
import { SunTimesInfo, SunPositionInfo } from '../types/sun.js';
|
|
3
|
+
/**
|
|
4
|
+
* Service for sun calculations
|
|
5
|
+
*/
|
|
6
|
+
export declare class SunService {
|
|
7
|
+
/**
|
|
8
|
+
* Get sun times for a specific date and location
|
|
9
|
+
* @param params Parameters for the request
|
|
10
|
+
* @returns Sun times information
|
|
11
|
+
*/
|
|
12
|
+
getSunTimes(params: SunTimesParams): SunTimesInfo;
|
|
13
|
+
/**
|
|
14
|
+
* Get sun times for a date range
|
|
15
|
+
* @param params Parameters for the request
|
|
16
|
+
* @returns Array of sun times information
|
|
17
|
+
*/
|
|
18
|
+
getSunTimesRange(params: SunTimesRangeParams): SunTimesInfo[];
|
|
19
|
+
/**
|
|
20
|
+
* Get sun position for a specific date, time, and location
|
|
21
|
+
* @param params Parameters for the request
|
|
22
|
+
* @returns Sun position information
|
|
23
|
+
*/
|
|
24
|
+
getSunPosition(params: SunPositionParams): SunPositionInfo;
|
|
25
|
+
/**
|
|
26
|
+
* Get the next occurrence(s) of a specific sun event
|
|
27
|
+
* @param params Parameters for the request
|
|
28
|
+
* @returns Array of dates for the next occurrences of the specified event
|
|
29
|
+
*/
|
|
30
|
+
getNextSunEvent(params: NextSunEventParams): {
|
|
31
|
+
date: string;
|
|
32
|
+
time: string;
|
|
33
|
+
event: string;
|
|
34
|
+
}[];
|
|
35
|
+
/**
|
|
36
|
+
* Calculate approximate equatorial coordinates (right ascension and declination)
|
|
37
|
+
* from horizontal coordinates (azimuth and altitude)
|
|
38
|
+
* Note: This is a simplified calculation and may not be precise
|
|
39
|
+
* @param date Date of observation
|
|
40
|
+
* @param azimuth Azimuth in radians
|
|
41
|
+
* @param altitude Altitude in radians
|
|
42
|
+
* @param latitude Observer's latitude
|
|
43
|
+
* @param longitude Observer's longitude
|
|
44
|
+
* @returns Approximate equatorial coordinates
|
|
45
|
+
*/
|
|
46
|
+
private calculateEquatorialCoordinates;
|
|
47
|
+
/**
|
|
48
|
+
* Calculate approximate local sidereal time
|
|
49
|
+
* @param date Date of observation
|
|
50
|
+
* @param longitude Observer's longitude
|
|
51
|
+
* @returns Local sidereal time in radians
|
|
52
|
+
*/
|
|
53
|
+
private calculateLocalSiderealTime;
|
|
54
|
+
/**
|
|
55
|
+
* Calculate Julian day from date
|
|
56
|
+
* @param date Date to convert
|
|
57
|
+
* @returns Julian day
|
|
58
|
+
*/
|
|
59
|
+
private calculateJulianDay;
|
|
60
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import SunCalc from 'suncalc';
|
|
2
|
+
/**
|
|
3
|
+
* Service for sun calculations
|
|
4
|
+
*/
|
|
5
|
+
export class SunService {
|
|
6
|
+
/**
|
|
7
|
+
* Get sun times for a specific date and location
|
|
8
|
+
* @param params Parameters for the request
|
|
9
|
+
* @returns Sun times information
|
|
10
|
+
*/
|
|
11
|
+
getSunTimes(params) {
|
|
12
|
+
const date = params.date ? new Date(params.date) : new Date();
|
|
13
|
+
const { latitude, longitude } = params;
|
|
14
|
+
// Get sun times data
|
|
15
|
+
const sunTimes = SunCalc.getTimes(date, latitude, longitude);
|
|
16
|
+
// Format times or return null if not available
|
|
17
|
+
const formatTime = (time) => {
|
|
18
|
+
if (!time || isNaN(time.getTime()))
|
|
19
|
+
return null;
|
|
20
|
+
if (params.timezone) {
|
|
21
|
+
try {
|
|
22
|
+
return time.toLocaleTimeString('en-US', { timeZone: params.timezone });
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
// If timezone is invalid, fall back to ISO string
|
|
26
|
+
console.warn(`Invalid timezone: ${params.timezone}. Using UTC.`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return time.toISOString();
|
|
30
|
+
};
|
|
31
|
+
// Calculate day length in minutes
|
|
32
|
+
const sunrise = sunTimes.sunrise;
|
|
33
|
+
const sunset = sunTimes.sunset;
|
|
34
|
+
let dayLength = 0;
|
|
35
|
+
if (sunrise && sunset && !isNaN(sunrise.getTime()) && !isNaN(sunset.getTime())) {
|
|
36
|
+
dayLength = (sunset.getTime() - sunrise.getTime()) / (60 * 1000);
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
date: date.toISOString().split('T')[0],
|
|
40
|
+
sunrise: formatTime(sunTimes.sunrise),
|
|
41
|
+
sunset: formatTime(sunTimes.sunset),
|
|
42
|
+
solarNoon: formatTime(sunTimes.solarNoon),
|
|
43
|
+
dawn: formatTime(sunTimes.dawn),
|
|
44
|
+
dusk: formatTime(sunTimes.dusk),
|
|
45
|
+
nightStart: formatTime(sunTimes.night),
|
|
46
|
+
nightEnd: formatTime(sunTimes.nightEnd),
|
|
47
|
+
goldenHourStart: formatTime(sunTimes.goldenHour),
|
|
48
|
+
goldenHourEnd: formatTime(sunTimes.goldenHourEnd),
|
|
49
|
+
nauticalDawn: formatTime(sunTimes.nauticalDawn),
|
|
50
|
+
nauticalDusk: formatTime(sunTimes.nauticalDusk),
|
|
51
|
+
astronomicalDawn: formatTime(sunTimes.astronomicalDawn),
|
|
52
|
+
astronomicalDusk: formatTime(sunTimes.astronomicalDusk),
|
|
53
|
+
dayLength
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get sun times for a date range
|
|
58
|
+
* @param params Parameters for the request
|
|
59
|
+
* @returns Array of sun times information
|
|
60
|
+
*/
|
|
61
|
+
getSunTimesRange(params) {
|
|
62
|
+
const startDate = new Date(params.start_date);
|
|
63
|
+
const endDate = new Date(params.end_date);
|
|
64
|
+
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
|
|
65
|
+
throw new Error('Invalid date format. Please use YYYY-MM-DD format.');
|
|
66
|
+
}
|
|
67
|
+
if (startDate > endDate) {
|
|
68
|
+
throw new Error('Start date must be before end date.');
|
|
69
|
+
}
|
|
70
|
+
const result = [];
|
|
71
|
+
const currentDate = new Date(startDate);
|
|
72
|
+
while (currentDate <= endDate) {
|
|
73
|
+
result.push(this.getSunTimes({
|
|
74
|
+
date: currentDate.toISOString().split('T')[0],
|
|
75
|
+
latitude: params.latitude,
|
|
76
|
+
longitude: params.longitude,
|
|
77
|
+
timezone: params.timezone
|
|
78
|
+
}));
|
|
79
|
+
// Move to next day
|
|
80
|
+
currentDate.setDate(currentDate.getDate() + 1);
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Get sun position for a specific date, time, and location
|
|
86
|
+
* @param params Parameters for the request
|
|
87
|
+
* @returns Sun position information
|
|
88
|
+
*/
|
|
89
|
+
getSunPosition(params) {
|
|
90
|
+
const date = params.date ? new Date(params.date) : new Date();
|
|
91
|
+
const time = params.time;
|
|
92
|
+
const { latitude, longitude } = params;
|
|
93
|
+
// Set the time if provided
|
|
94
|
+
if (time) {
|
|
95
|
+
const [hours, minutes, seconds] = time.split(':').map(Number);
|
|
96
|
+
if (!isNaN(hours) && !isNaN(minutes) && (!seconds || !isNaN(seconds))) {
|
|
97
|
+
date.setHours(hours, minutes, seconds || 0, 0);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
throw new Error('Invalid time format. Please use HH:MM:SS format.');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Get sun position data
|
|
104
|
+
const position = SunCalc.getPosition(date, latitude, longitude);
|
|
105
|
+
// Calculate right ascension and declination (approximate values)
|
|
106
|
+
// Note: These are approximate calculations and may not be precise
|
|
107
|
+
const equatorialCoords = this.calculateEquatorialCoordinates(date, position.azimuth, position.altitude, latitude, longitude);
|
|
108
|
+
return {
|
|
109
|
+
date: date.toISOString().split('T')[0],
|
|
110
|
+
time: date.toISOString().split('T')[1].split('.')[0],
|
|
111
|
+
azimuth: position.azimuth * (180 / Math.PI),
|
|
112
|
+
altitude: position.altitude * (180 / Math.PI),
|
|
113
|
+
declination: equatorialCoords.declination,
|
|
114
|
+
rightAscension: equatorialCoords.rightAscension
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Get the next occurrence(s) of a specific sun event
|
|
119
|
+
* @param params Parameters for the request
|
|
120
|
+
* @returns Array of dates for the next occurrences of the specified event
|
|
121
|
+
*/
|
|
122
|
+
getNextSunEvent(params) {
|
|
123
|
+
const startDate = params.date ? new Date(params.date) : new Date();
|
|
124
|
+
const count = params.count !== undefined ? params.count : 1;
|
|
125
|
+
const { latitude, longitude } = params;
|
|
126
|
+
const timezone = params.timezone !== undefined ? params.timezone : 'UTC';
|
|
127
|
+
const results = [];
|
|
128
|
+
let currentDate = new Date(startDate);
|
|
129
|
+
// Find the next occurrences
|
|
130
|
+
while (results.length < count) {
|
|
131
|
+
const sunTimes = SunCalc.getTimes(currentDate, latitude, longitude);
|
|
132
|
+
const eventTime = sunTimes[params.event];
|
|
133
|
+
if (eventTime && !isNaN(eventTime.getTime()) && eventTime > startDate) {
|
|
134
|
+
let formattedTime;
|
|
135
|
+
try {
|
|
136
|
+
formattedTime = eventTime.toLocaleTimeString('en-US', { timeZone: timezone });
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
// If timezone is invalid, fall back to ISO string
|
|
140
|
+
console.warn(`Invalid timezone: ${timezone}. Using UTC.`);
|
|
141
|
+
formattedTime = eventTime.toISOString().split('T')[1].split('.')[0];
|
|
142
|
+
}
|
|
143
|
+
results.push({
|
|
144
|
+
date: eventTime.toISOString().split('T')[0],
|
|
145
|
+
time: formattedTime,
|
|
146
|
+
event: params.event
|
|
147
|
+
});
|
|
148
|
+
// Move to next day to find the next occurrence
|
|
149
|
+
currentDate.setDate(currentDate.getDate() + 1);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// Event not found for this day, try next day
|
|
153
|
+
currentDate.setDate(currentDate.getDate() + 1);
|
|
154
|
+
}
|
|
155
|
+
// Safety check to prevent infinite loops
|
|
156
|
+
if (results.length === 0 && currentDate.getTime() - startDate.getTime() > 366 * 24 * 60 * 60 * 1000) {
|
|
157
|
+
throw new Error('Could not find the specified sun event within a year.');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return results;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Calculate approximate equatorial coordinates (right ascension and declination)
|
|
164
|
+
* from horizontal coordinates (azimuth and altitude)
|
|
165
|
+
* Note: This is a simplified calculation and may not be precise
|
|
166
|
+
* @param date Date of observation
|
|
167
|
+
* @param azimuth Azimuth in radians
|
|
168
|
+
* @param altitude Altitude in radians
|
|
169
|
+
* @param latitude Observer's latitude
|
|
170
|
+
* @param longitude Observer's longitude
|
|
171
|
+
* @returns Approximate equatorial coordinates
|
|
172
|
+
*/
|
|
173
|
+
calculateEquatorialCoordinates(date, azimuth, altitude, latitude, longitude) {
|
|
174
|
+
// Convert degrees to radians
|
|
175
|
+
const lat = latitude * (Math.PI / 180);
|
|
176
|
+
// Calculate hour angle and declination
|
|
177
|
+
const sinDec = Math.sin(altitude) * Math.sin(lat) + Math.cos(altitude) * Math.cos(lat) * Math.cos(azimuth);
|
|
178
|
+
const declination = Math.asin(sinDec) * (180 / Math.PI);
|
|
179
|
+
const cosH = (Math.sin(altitude) - Math.sin(lat) * sinDec) / (Math.cos(lat) * Math.cos(declination * (Math.PI / 180)));
|
|
180
|
+
const hourAngle = Math.acos(Math.max(-1, Math.min(1, cosH)));
|
|
181
|
+
// Adjust hour angle based on azimuth
|
|
182
|
+
const adjustedHourAngle = (azimuth > 0 && azimuth < Math.PI) ? (2 * Math.PI - hourAngle) : hourAngle;
|
|
183
|
+
// Calculate right ascension
|
|
184
|
+
const localSiderealTime = this.calculateLocalSiderealTime(date, longitude);
|
|
185
|
+
let rightAscension = (localSiderealTime - adjustedHourAngle) * (12 / Math.PI);
|
|
186
|
+
// Normalize right ascension to 0-24 hours
|
|
187
|
+
rightAscension = rightAscension % 24;
|
|
188
|
+
if (rightAscension < 0)
|
|
189
|
+
rightAscension += 24;
|
|
190
|
+
return { rightAscension, declination };
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Calculate approximate local sidereal time
|
|
194
|
+
* @param date Date of observation
|
|
195
|
+
* @param longitude Observer's longitude
|
|
196
|
+
* @returns Local sidereal time in radians
|
|
197
|
+
*/
|
|
198
|
+
calculateLocalSiderealTime(date, longitude) {
|
|
199
|
+
// Calculate days since J2000.0
|
|
200
|
+
const jd = this.calculateJulianDay(date);
|
|
201
|
+
const d = jd - 2451545.0;
|
|
202
|
+
// Calculate Greenwich Mean Sidereal Time
|
|
203
|
+
const gmst = (18.697374558 + 24.06570982441908 * d) % 24;
|
|
204
|
+
// Convert longitude to hours and calculate local sidereal time
|
|
205
|
+
const longitudeHours = longitude / 15;
|
|
206
|
+
let lst = gmst + longitudeHours;
|
|
207
|
+
// Normalize to 0-24 hours
|
|
208
|
+
lst = lst % 24;
|
|
209
|
+
if (lst < 0)
|
|
210
|
+
lst += 24;
|
|
211
|
+
// Convert to radians
|
|
212
|
+
return lst * (Math.PI / 12);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Calculate Julian day from date
|
|
216
|
+
* @param date Date to convert
|
|
217
|
+
* @returns Julian day
|
|
218
|
+
*/
|
|
219
|
+
calculateJulianDay(date) {
|
|
220
|
+
const y = date.getFullYear();
|
|
221
|
+
const m = date.getMonth() + 1;
|
|
222
|
+
const d = date.getDate();
|
|
223
|
+
// Calculate Julian day
|
|
224
|
+
const jd = 367 * y - Math.floor(7 * (y + Math.floor((m + 9) / 12)) / 4) -
|
|
225
|
+
Math.floor(3 * (Math.floor((y + (m - 9) / 7) / 100) + 1) / 4) +
|
|
226
|
+
Math.floor(275 * m / 9) + d + 1721028.5;
|
|
227
|
+
// Add time of day
|
|
228
|
+
const hours = date.getUTCHours();
|
|
229
|
+
const minutes = date.getUTCMinutes();
|
|
230
|
+
const seconds = date.getUTCSeconds();
|
|
231
|
+
const milliseconds = date.getUTCMilliseconds();
|
|
232
|
+
return jd + (hours + minutes / 60 + seconds / 3600 + milliseconds / 3600000) / 24;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Sun event types
|
|
4
|
+
*/
|
|
5
|
+
export declare enum SunEventType {
|
|
6
|
+
SUNRISE = "sunrise",
|
|
7
|
+
SUNSET = "sunset",
|
|
8
|
+
DAWN = "dawn",
|
|
9
|
+
DUSK = "dusk",
|
|
10
|
+
SOLAR_NOON = "solarNoon",
|
|
11
|
+
NIGHT_START = "night",
|
|
12
|
+
NIGHT_END = "nightEnd",
|
|
13
|
+
GOLDEN_HOUR_START = "goldenHourStart",
|
|
14
|
+
GOLDEN_HOUR_END = "goldenHourEnd",
|
|
15
|
+
NAUTICAL_DAWN = "nauticalDawn",
|
|
16
|
+
NAUTICAL_DUSK = "nauticalDusk",
|
|
17
|
+
ASTRONOMICAL_DAWN = "astronomicalDawn",
|
|
18
|
+
ASTRONOMICAL_DUSK = "astronomicalDusk"
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Sun times information
|
|
22
|
+
*/
|
|
23
|
+
export interface SunTimesInfo {
|
|
24
|
+
date: string;
|
|
25
|
+
sunrise: string | null;
|
|
26
|
+
sunset: string | null;
|
|
27
|
+
solarNoon: string | null;
|
|
28
|
+
dawn: string | null;
|
|
29
|
+
dusk: string | null;
|
|
30
|
+
nightStart: string | null;
|
|
31
|
+
nightEnd: string | null;
|
|
32
|
+
goldenHourStart: string | null;
|
|
33
|
+
goldenHourEnd: string | null;
|
|
34
|
+
nauticalDawn: string | null;
|
|
35
|
+
nauticalDusk: string | null;
|
|
36
|
+
astronomicalDawn: string | null;
|
|
37
|
+
astronomicalDusk: string | null;
|
|
38
|
+
dayLength: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Sun position information
|
|
42
|
+
*/
|
|
43
|
+
export interface SunPositionInfo {
|
|
44
|
+
date: string;
|
|
45
|
+
time: string;
|
|
46
|
+
azimuth: number;
|
|
47
|
+
altitude: number;
|
|
48
|
+
declination: number;
|
|
49
|
+
rightAscension: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Parameters for getting sun times
|
|
53
|
+
*/
|
|
54
|
+
export declare const SunTimesParamsSchema: z.ZodObject<{
|
|
55
|
+
date: z.ZodOptional<z.ZodString>;
|
|
56
|
+
latitude: z.ZodNumber;
|
|
57
|
+
longitude: z.ZodNumber;
|
|
58
|
+
format: z.ZodOptional<z.ZodEnum<["json", "text"]>>;
|
|
59
|
+
timezone: z.ZodOptional<z.ZodString>;
|
|
60
|
+
}, "strip", z.ZodTypeAny, {
|
|
61
|
+
latitude: number;
|
|
62
|
+
longitude: number;
|
|
63
|
+
date?: string | undefined;
|
|
64
|
+
format?: "text" | "json" | undefined;
|
|
65
|
+
timezone?: string | undefined;
|
|
66
|
+
}, {
|
|
67
|
+
latitude: number;
|
|
68
|
+
longitude: number;
|
|
69
|
+
date?: string | undefined;
|
|
70
|
+
format?: "text" | "json" | undefined;
|
|
71
|
+
timezone?: string | undefined;
|
|
72
|
+
}>;
|
|
73
|
+
export type SunTimesParams = z.infer<typeof SunTimesParamsSchema>;
|
|
74
|
+
/**
|
|
75
|
+
* Parameters for getting sun times for a date range
|
|
76
|
+
*/
|
|
77
|
+
export declare const SunTimesRangeParamsSchema: z.ZodObject<{
|
|
78
|
+
start_date: z.ZodString;
|
|
79
|
+
end_date: z.ZodString;
|
|
80
|
+
latitude: z.ZodNumber;
|
|
81
|
+
longitude: z.ZodNumber;
|
|
82
|
+
format: z.ZodOptional<z.ZodEnum<["json", "text"]>>;
|
|
83
|
+
timezone: z.ZodOptional<z.ZodString>;
|
|
84
|
+
}, "strip", z.ZodTypeAny, {
|
|
85
|
+
latitude: number;
|
|
86
|
+
longitude: number;
|
|
87
|
+
start_date: string;
|
|
88
|
+
end_date: string;
|
|
89
|
+
format?: "text" | "json" | undefined;
|
|
90
|
+
timezone?: string | undefined;
|
|
91
|
+
}, {
|
|
92
|
+
latitude: number;
|
|
93
|
+
longitude: number;
|
|
94
|
+
start_date: string;
|
|
95
|
+
end_date: string;
|
|
96
|
+
format?: "text" | "json" | undefined;
|
|
97
|
+
timezone?: string | undefined;
|
|
98
|
+
}>;
|
|
99
|
+
export type SunTimesRangeParams = z.infer<typeof SunTimesRangeParamsSchema>;
|
|
100
|
+
/**
|
|
101
|
+
* Parameters for getting sun position
|
|
102
|
+
*/
|
|
103
|
+
export declare const SunPositionParamsSchema: z.ZodObject<{
|
|
104
|
+
date: z.ZodOptional<z.ZodString>;
|
|
105
|
+
time: z.ZodOptional<z.ZodString>;
|
|
106
|
+
latitude: z.ZodNumber;
|
|
107
|
+
longitude: z.ZodNumber;
|
|
108
|
+
format: z.ZodOptional<z.ZodEnum<["json", "text"]>>;
|
|
109
|
+
}, "strip", z.ZodTypeAny, {
|
|
110
|
+
latitude: number;
|
|
111
|
+
longitude: number;
|
|
112
|
+
time?: string | undefined;
|
|
113
|
+
date?: string | undefined;
|
|
114
|
+
format?: "text" | "json" | undefined;
|
|
115
|
+
}, {
|
|
116
|
+
latitude: number;
|
|
117
|
+
longitude: number;
|
|
118
|
+
time?: string | undefined;
|
|
119
|
+
date?: string | undefined;
|
|
120
|
+
format?: "text" | "json" | undefined;
|
|
121
|
+
}>;
|
|
122
|
+
export type SunPositionParams = z.infer<typeof SunPositionParamsSchema>;
|
|
123
|
+
/**
|
|
124
|
+
* Parameters for finding the next sun event
|
|
125
|
+
*/
|
|
126
|
+
export declare const NextSunEventParamsSchema: z.ZodObject<{
|
|
127
|
+
event: z.ZodNativeEnum<typeof SunEventType>;
|
|
128
|
+
date: z.ZodOptional<z.ZodString>;
|
|
129
|
+
latitude: z.ZodNumber;
|
|
130
|
+
longitude: z.ZodNumber;
|
|
131
|
+
count: z.ZodOptional<z.ZodNumber>;
|
|
132
|
+
format: z.ZodOptional<z.ZodEnum<["json", "text"]>>;
|
|
133
|
+
timezone: z.ZodOptional<z.ZodString>;
|
|
134
|
+
}, "strip", z.ZodTypeAny, {
|
|
135
|
+
event: SunEventType;
|
|
136
|
+
latitude: number;
|
|
137
|
+
longitude: number;
|
|
138
|
+
date?: string | undefined;
|
|
139
|
+
format?: "text" | "json" | undefined;
|
|
140
|
+
count?: number | undefined;
|
|
141
|
+
timezone?: string | undefined;
|
|
142
|
+
}, {
|
|
143
|
+
event: SunEventType;
|
|
144
|
+
latitude: number;
|
|
145
|
+
longitude: number;
|
|
146
|
+
date?: string | undefined;
|
|
147
|
+
format?: "text" | "json" | undefined;
|
|
148
|
+
count?: number | undefined;
|
|
149
|
+
timezone?: string | undefined;
|
|
150
|
+
}>;
|
|
151
|
+
export type NextSunEventParams = z.infer<typeof NextSunEventParamsSchema>;
|
|
152
|
+
/**
|
|
153
|
+
* Service for sun calculations
|
|
154
|
+
*/
|
|
155
|
+
export declare class SunService {
|
|
156
|
+
/**
|
|
157
|
+
* Get sun times for a specific date and location
|
|
158
|
+
* @param params Parameters for the request
|
|
159
|
+
* @returns Sun times information
|
|
160
|
+
*/
|
|
161
|
+
getSunTimes(params: SunTimesParams): SunTimesInfo;
|
|
162
|
+
/**
|
|
163
|
+
* Get sun times for a date range
|
|
164
|
+
* @param params Parameters for the request
|
|
165
|
+
* @returns Array of sun times information
|
|
166
|
+
*/
|
|
167
|
+
getSunTimesRange(params: SunTimesRangeParams): SunTimesInfo[];
|
|
168
|
+
/**
|
|
169
|
+
* Get sun position for a specific date, time, and location
|
|
170
|
+
* @param params Parameters for the request
|
|
171
|
+
* @returns Sun position information
|
|
172
|
+
*/
|
|
173
|
+
getSunPosition(params: SunPositionParams): SunPositionInfo;
|
|
174
|
+
/**
|
|
175
|
+
* Get the next occurrence(s) of a specific sun event
|
|
176
|
+
* @param params Parameters for the request
|
|
177
|
+
* @returns Array of dates for the next occurrences of the specified event
|
|
178
|
+
*/
|
|
179
|
+
getNextSunEvent(params: NextSunEventParams): {
|
|
180
|
+
date: string;
|
|
181
|
+
time: string;
|
|
182
|
+
event: string;
|
|
183
|
+
}[];
|
|
184
|
+
}
|