Skip to main content
Glama

LocalTides MCP Server

sun-service.ts10.1 kB
import SunCalc from 'suncalc'; import { SunTimesParams, SunTimesRangeParams, SunPositionParams, NextSunEventParams } from '../interfaces/sun.js'; import { SunTimesInfo, SunPositionInfo, SunEventType } from '../types/sun.js'; /** * Service for sun calculations */ export class SunService { /** * Get sun times for a specific date and location * @param params Parameters for the request * @returns Sun times information */ getSunTimes(params: SunTimesParams): SunTimesInfo { const date = params.date ? new Date(params.date) : new Date(); const { latitude, longitude } = params; // Get sun times data const sunTimes = SunCalc.getTimes(date, latitude, longitude); // Format times or return null if not available const formatTime = (time: Date | null): string | null => { if (!time || isNaN(time.getTime())) return null; if (params.timezone) { try { return time.toLocaleTimeString('en-US', { timeZone: params.timezone }); } catch (error) { // If timezone is invalid, fall back to ISO string console.warn(`Invalid timezone: ${params.timezone}. Using UTC.`); } } return time.toISOString(); }; // Calculate day length in minutes const sunrise = sunTimes.sunrise; const sunset = sunTimes.sunset; let dayLength = 0; if (sunrise && sunset && !isNaN(sunrise.getTime()) && !isNaN(sunset.getTime())) { dayLength = (sunset.getTime() - sunrise.getTime()) / (60 * 1000); } return { date: date.toISOString().split('T')[0], sunrise: formatTime(sunTimes.sunrise), sunset: formatTime(sunTimes.sunset), solarNoon: formatTime(sunTimes.solarNoon), dawn: formatTime(sunTimes.dawn), dusk: formatTime(sunTimes.dusk), nightStart: formatTime(sunTimes.night), nightEnd: formatTime(sunTimes.nightEnd), goldenHourStart: formatTime(sunTimes.goldenHour), goldenHourEnd: formatTime(sunTimes.goldenHourEnd), nauticalDawn: formatTime(sunTimes.nauticalDawn), nauticalDusk: formatTime(sunTimes.nauticalDusk), astronomicalDawn: formatTime(sunTimes.astronomicalDawn), astronomicalDusk: formatTime(sunTimes.astronomicalDusk), dayLength }; } /** * Get sun times for a date range * @param params Parameters for the request * @returns Array of sun times information */ getSunTimesRange(params: SunTimesRangeParams): SunTimesInfo[] { const startDate = new Date(params.start_date); const endDate = new Date(params.end_date); if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) { throw new Error('Invalid date format. Please use YYYY-MM-DD format.'); } if (startDate > endDate) { throw new Error('Start date must be before end date.'); } const result: SunTimesInfo[] = []; const currentDate = new Date(startDate); while (currentDate <= endDate) { result.push(this.getSunTimes({ date: currentDate.toISOString().split('T')[0], latitude: params.latitude, longitude: params.longitude, timezone: params.timezone })); // Move to next day currentDate.setDate(currentDate.getDate() + 1); } return result; } /** * Get sun position for a specific date, time, and location * @param params Parameters for the request * @returns Sun position information */ getSunPosition(params: SunPositionParams): SunPositionInfo { const date = params.date ? new Date(params.date) : new Date(); const time = params.time; const { latitude, longitude } = params; // Set the time if provided if (time) { const [hours, minutes, seconds] = time.split(':').map(Number); if (!isNaN(hours) && !isNaN(minutes) && (!seconds || !isNaN(seconds))) { date.setHours(hours, minutes, seconds || 0, 0); } else { throw new Error('Invalid time format. Please use HH:MM:SS format.'); } } // Get sun position data const position = SunCalc.getPosition(date, latitude, longitude); // Calculate right ascension and declination (approximate values) // Note: These are approximate calculations and may not be precise const equatorialCoords = this.calculateEquatorialCoordinates(date, position.azimuth, position.altitude, latitude, longitude); return { date: date.toISOString().split('T')[0], time: date.toISOString().split('T')[1].split('.')[0], azimuth: position.azimuth * (180 / Math.PI), altitude: position.altitude * (180 / Math.PI), declination: equatorialCoords.declination, rightAscension: equatorialCoords.rightAscension }; } /** * Get the next occurrence(s) of a specific sun event * @param params Parameters for the request * @returns Array of dates for the next occurrences of the specified event */ getNextSunEvent(params: NextSunEventParams): { date: string, time: string, event: string }[] { const startDate = params.date ? new Date(params.date) : new Date(); const count = params.count !== undefined ? params.count : 1; const { latitude, longitude } = params; const timezone = params.timezone !== undefined ? params.timezone : 'UTC'; const results: { date: string, time: string, event: string }[] = []; let currentDate = new Date(startDate); // Find the next occurrences while (results.length < count) { const sunTimes = SunCalc.getTimes(currentDate, latitude, longitude); const eventTime = sunTimes[params.event as keyof typeof sunTimes]; if (eventTime && !isNaN(eventTime.getTime()) && eventTime > startDate) { let formattedTime: string; try { formattedTime = eventTime.toLocaleTimeString('en-US', { timeZone: timezone }); } catch (error) { // If timezone is invalid, fall back to ISO string console.warn(`Invalid timezone: ${timezone}. Using UTC.`); formattedTime = eventTime.toISOString().split('T')[1].split('.')[0]; } results.push({ date: eventTime.toISOString().split('T')[0], time: formattedTime, event: params.event as string }); // Move to next day to find the next occurrence currentDate.setDate(currentDate.getDate() + 1); } else { // Event not found for this day, try next day currentDate.setDate(currentDate.getDate() + 1); } // Safety check to prevent infinite loops if (results.length === 0 && currentDate.getTime() - startDate.getTime() > 366 * 24 * 60 * 60 * 1000) { throw new Error('Could not find the specified sun event within a year.'); } } return results; } /** * Calculate approximate equatorial coordinates (right ascension and declination) * from horizontal coordinates (azimuth and altitude) * Note: This is a simplified calculation and may not be precise * @param date Date of observation * @param azimuth Azimuth in radians * @param altitude Altitude in radians * @param latitude Observer's latitude * @param longitude Observer's longitude * @returns Approximate equatorial coordinates */ private calculateEquatorialCoordinates(date: Date, azimuth: number, altitude: number, latitude: number, longitude: number): { rightAscension: number, declination: number } { // Convert degrees to radians const lat = latitude * (Math.PI / 180); // Calculate hour angle and declination const sinDec = Math.sin(altitude) * Math.sin(lat) + Math.cos(altitude) * Math.cos(lat) * Math.cos(azimuth); const declination = Math.asin(sinDec) * (180 / Math.PI); const cosH = (Math.sin(altitude) - Math.sin(lat) * sinDec) / (Math.cos(lat) * Math.cos(declination * (Math.PI / 180))); const hourAngle = Math.acos(Math.max(-1, Math.min(1, cosH))); // Adjust hour angle based on azimuth const adjustedHourAngle = (azimuth > 0 && azimuth < Math.PI) ? (2 * Math.PI - hourAngle) : hourAngle; // Calculate right ascension const localSiderealTime = this.calculateLocalSiderealTime(date, longitude); let rightAscension = (localSiderealTime - adjustedHourAngle) * (12 / Math.PI); // Normalize right ascension to 0-24 hours rightAscension = rightAscension % 24; if (rightAscension < 0) rightAscension += 24; return { rightAscension, declination }; } /** * Calculate approximate local sidereal time * @param date Date of observation * @param longitude Observer's longitude * @returns Local sidereal time in radians */ private calculateLocalSiderealTime(date: Date, longitude: number): number { // Calculate days since J2000.0 const jd = this.calculateJulianDay(date); const d = jd - 2451545.0; // Calculate Greenwich Mean Sidereal Time const gmst = (18.697374558 + 24.06570982441908 * d) % 24; // Convert longitude to hours and calculate local sidereal time const longitudeHours = longitude / 15; let lst = gmst + longitudeHours; // Normalize to 0-24 hours lst = lst % 24; if (lst < 0) lst += 24; // Convert to radians return lst * (Math.PI / 12); } /** * Calculate Julian day from date * @param date Date to convert * @returns Julian day */ private calculateJulianDay(date: Date): number { const y = date.getFullYear(); const m = date.getMonth() + 1; const d = date.getDate(); // Calculate Julian day const jd = 367 * y - Math.floor(7 * (y + Math.floor((m + 9) / 12)) / 4) - Math.floor(3 * (Math.floor((y + (m - 9) / 7) / 100) + 1) / 4) + Math.floor(275 * m / 9) + d + 1721028.5; // Add time of day const hours = date.getUTCHours(); const minutes = date.getUTCMinutes(); const seconds = date.getUTCSeconds(); const milliseconds = date.getUTCMilliseconds(); return jd + (hours + minutes / 60 + seconds / 3600 + milliseconds / 3600000) / 24; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/RyanCardin15/NOAA-TidesAndCurrents-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server