import fetch from 'node-fetch';
function getWaveHeightDescription(height: number): string {
if (height < 0.5) return 'Calm (flat)';
if (height < 1.25) return 'Smooth (slight)';
if (height < 2.5) return 'Moderate';
if (height < 4) return 'Rough';
if (height < 6) return 'Very rough';
if (height < 9) return 'High';
if (height < 14) return 'Very high';
return 'Phenomenal';
}
function getBeaufortScale(windSpeed: number): { scale: number, description: string } {
if (windSpeed < 1) return { scale: 0, description: 'Calm' };
if (windSpeed < 6) return { scale: 1, description: 'Light air' };
if (windSpeed < 12) return { scale: 2, description: 'Light breeze' };
if (windSpeed < 20) return { scale: 3, description: 'Gentle breeze' };
if (windSpeed < 29) return { scale: 4, description: 'Moderate breeze' };
if (windSpeed < 39) return { scale: 5, description: 'Fresh breeze' };
if (windSpeed < 50) return { scale: 6, description: 'Strong breeze' };
if (windSpeed < 62) return { scale: 7, description: 'Near gale' };
if (windSpeed < 75) return { scale: 8, description: 'Gale' };
if (windSpeed < 89) return { scale: 9, description: 'Strong gale' };
if (windSpeed < 103) return { scale: 10, description: 'Storm' };
if (windSpeed < 118) return { scale: 11, description: 'Violent storm' };
return { scale: 12, description: 'Hurricane' };
}
function getDirectionName(degrees: number): string {
const directions = [
'N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE',
'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'
];
const index = Math.round(degrees / 22.5) % 16;
return directions[index];
}
export async function getMarineWeather(args: any) {
const { latitude, longitude, days = 7 } = args;
if (typeof latitude !== 'number' || typeof longitude !== 'number') {
throw new Error('Latitude and longitude must be numbers');
}
if (latitude < -90 || latitude > 90) {
throw new Error('Latitude must be between -90 and 90');
}
if (longitude < -180 || longitude > 180) {
throw new Error('Longitude must be between -180 and 180');
}
if (days < 1 || days > 7) {
throw new Error('Days must be between 1 and 7');
}
const hourlyParams = [
'wave_height',
'wave_direction',
'wave_period',
'wind_wave_height',
'wind_wave_direction',
'wind_wave_period',
'wind_wave_peak_period',
'swell_wave_height',
'swell_wave_direction',
'swell_wave_period',
'swell_wave_peak_period',
'ocean_current_velocity',
'ocean_current_direction'
].join(',');
const dailyParams = [
'wave_height_max',
'wave_direction_dominant',
'wave_period_max',
'wind_wave_height_max',
'wind_wave_direction_dominant',
'wind_wave_period_max',
'swell_wave_height_max',
'swell_wave_direction_dominant',
'swell_wave_period_max'
].join(',');
const url = `https://marine-api.open-meteo.com/v1/marine?latitude=${latitude}&longitude=${longitude}&forecast_days=${days}&hourly=${hourlyParams}&daily=${dailyParams}`;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Open Meteo Marine API error: ${response.status} ${response.statusText}`);
}
const data = await response.json() as any;
const result: any = {
location: {
latitude: data.latitude,
longitude: data.longitude,
timezone: data.timezone,
utc_offset_seconds: data.utc_offset_seconds,
},
};
let marineText = `# Marine Weather Forecast
**Location:** ${data.latitude.toFixed(4)}°N, ${data.longitude.toFixed(4)}°E
**Timezone:** ${data.timezone}
**Forecast Days:** ${days}
`;
if (data.daily) {
result.daily_marine_forecast = data.daily.time.map((time: string, index: number) => {
const waveHeight = data.daily.wave_height_max[index];
const waveDirection = data.daily.wave_direction_dominant[index];
const wavePeriod = data.daily.wave_period_max[index];
const windWaveHeight = data.daily.wind_wave_height_max[index];
const swellWaveHeight = data.daily.swell_wave_height_max[index];
return {
date: time,
wave_height_max: `${waveHeight} m`,
wave_height_description: getWaveHeightDescription(waveHeight),
wave_direction_dominant: `${waveDirection}° (${getDirectionName(waveDirection)})`,
wave_period_max: `${wavePeriod} s`,
wind_wave_height_max: `${windWaveHeight} m`,
wind_wave_direction_dominant: `${data.daily.wind_wave_direction_dominant[index]}° (${getDirectionName(data.daily.wind_wave_direction_dominant[index])})`,
wind_wave_period_max: `${data.daily.wind_wave_period_max[index]} s`,
swell_wave_height_max: `${swellWaveHeight} m`,
swell_wave_direction_dominant: `${data.daily.swell_wave_direction_dominant[index]}° (${getDirectionName(data.daily.swell_wave_direction_dominant[index])})`,
swell_wave_period_max: `${data.daily.swell_wave_period_max[index]} s`,
};
});
marineText += `## Daily Marine Forecast\n\n`;
result.daily_marine_forecast.forEach((day: any) => {
marineText += `### ${day.date}
**Wave Conditions:**
- **Significant Wave Height:** ${day.wave_height_max} - ${day.wave_height_description}
- **Wave Direction:** ${day.wave_direction_dominant}
- **Wave Period:** ${day.wave_period_max}
**Wind Waves:**
- **Height:** ${day.wind_wave_height_max}
- **Direction:** ${day.wind_wave_direction_dominant}
- **Period:** ${day.wind_wave_period_max}
**Swell Waves:**
- **Height:** ${day.swell_wave_height_max}
- **Direction:** ${day.swell_wave_direction_dominant}
- **Period:** ${day.swell_wave_period_max}
`;
});
}
if (data.hourly) {
// Process hourly data - show current conditions and next 24 hours
const hourlyData = data.hourly;
const hoursToShow = Math.min(24, hourlyData.time.length);
result.current_marine_conditions = {
time: hourlyData.time[0],
wave_height: `${hourlyData.wave_height[0]} m`,
wave_height_description: getWaveHeightDescription(hourlyData.wave_height[0]),
wave_direction: `${hourlyData.wave_direction[0]}° (${getDirectionName(hourlyData.wave_direction[0])})`,
wave_period: `${hourlyData.wave_period[0]} s`,
wind_wave_height: `${hourlyData.wind_wave_height[0]} m`,
wind_wave_direction: `${hourlyData.wind_wave_direction[0]}° (${getDirectionName(hourlyData.wind_wave_direction[0])})`,
wind_wave_period: `${hourlyData.wind_wave_period[0]} s`,
swell_wave_height: `${hourlyData.swell_wave_height[0]} m`,
swell_wave_direction: `${hourlyData.swell_wave_direction[0]}° (${getDirectionName(hourlyData.swell_wave_direction[0])})`,
swell_wave_period: `${hourlyData.swell_wave_period[0]} s`,
ocean_current_velocity: `${hourlyData.ocean_current_velocity[0]} m/s`,
ocean_current_direction: `${hourlyData.ocean_current_direction[0]}° (${getDirectionName(hourlyData.ocean_current_direction[0])})`,
};
marineText += `## Current Marine Conditions (${hourlyData.time[0]})
**Overall Wave Conditions:**
- **Significant Wave Height:** ${result.current_marine_conditions.wave_height} - ${result.current_marine_conditions.wave_height_description}
- **Wave Direction:** ${result.current_marine_conditions.wave_direction}
- **Wave Period:** ${result.current_marine_conditions.wave_period}
**Wind Waves (locally generated):**
- **Height:** ${result.current_marine_conditions.wind_wave_height}
- **Direction:** ${result.current_marine_conditions.wind_wave_direction}
- **Period:** ${result.current_marine_conditions.wind_wave_period}
**Swell Waves (distant storms):**
- **Height:** ${result.current_marine_conditions.swell_wave_height}
- **Direction:** ${result.current_marine_conditions.swell_wave_direction}
- **Period:** ${result.current_marine_conditions.swell_wave_period}
**Ocean Currents:**
- **Current Velocity:** ${result.current_marine_conditions.ocean_current_velocity}
- **Current Direction:** ${result.current_marine_conditions.ocean_current_direction}
`;
// Show next 12 hours as sample
marineText += `## Next 12 Hours Marine Conditions\n\n`;
for (let i = 1; i < Math.min(13, hourlyData.time.length); i++) {
const time = hourlyData.time[i];
const waveHeight = hourlyData.wave_height[i];
const waveDirection = hourlyData.wave_direction[i];
const windWaveHeight = hourlyData.wind_wave_height[i];
const swellWaveHeight = hourlyData.swell_wave_height[i];
marineText += `**${time.split('T')[1]}** - Wave: ${waveHeight}m ${getDirectionName(waveDirection)}, Wind waves: ${windWaveHeight}m, Swell: ${swellWaveHeight}m\n`;
}
// Include all hourly data in the result
result.hourly_marine_forecast = hourlyData.time.slice(0, hoursToShow).map((time: string, index: number) => ({
time: time,
wave_height: `${hourlyData.wave_height[index]} m`,
wave_direction: `${hourlyData.wave_direction[index]}° (${getDirectionName(hourlyData.wave_direction[index])})`,
wave_period: `${hourlyData.wave_period[index]} s`,
wind_wave_height: `${hourlyData.wind_wave_height[index]} m`,
wind_wave_direction: `${hourlyData.wind_wave_direction[index]}° (${getDirectionName(hourlyData.wind_wave_direction[index])})`,
wind_wave_period: `${hourlyData.wind_wave_period[index]} s`,
swell_wave_height: `${hourlyData.swell_wave_height[index]} m`,
swell_wave_direction: `${hourlyData.swell_wave_direction[index]}° (${getDirectionName(hourlyData.swell_wave_direction[index])})`,
swell_wave_period: `${hourlyData.swell_wave_period[index]} s`,
ocean_current_velocity: `${hourlyData.ocean_current_velocity[index]} m/s`,
ocean_current_direction: `${hourlyData.ocean_current_direction[index]}° (${getDirectionName(hourlyData.ocean_current_direction[index])})`,
}));
}
marineText += `\n## Marine Weather Interpretation
### Wave Height Scale:
- **0-0.5m:** Calm (flat) - Mirror-like surface
- **0.5-1.25m:** Smooth (slight) - Small ripples
- **1.25-2.5m:** Moderate - Larger ripples, some spray
- **2.5-4m:** Rough - Small waves with whitecaps
- **4-6m:** Very rough - Moderate waves, spray
- **6-9m:** High - Large waves, extensive spray
- **9-14m:** Very high - High waves with rolling crests
- **>14m:** Phenomenal - Exceptionally high waves
### Wave Components:
- **Significant Wave Height:** Combined height of wind waves and swell
- **Wind Waves:** Locally generated by current wind conditions
- **Swell Waves:** Long-period waves from distant weather systems
- **Wave Period:** Time between successive wave crests (longer = more powerful)
### Safety Considerations:
- **Small craft:** Use caution when wave heights exceed 1-2m
- **Large vessels:** Monitor conditions when wave heights exceed 4-6m
- **Ocean currents:** Consider velocity and direction for navigation
- **Wave period:** Longer periods (>10s) indicate more powerful waves
`;
return {
content: [
{
type: 'text',
text: marineText,
},
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
throw new Error(`Failed to fetch marine weather: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}