import fetch from 'node-fetch';
import { WEATHER_CODES } from '../types/openmeteo.js';
export async function getWeatherForecast(args: any) {
const {
latitude,
longitude,
days = 7,
hourly = true,
daily = true,
units = 'celsius'
} = 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 > 16) {
throw new Error('Days must be between 1 and 16');
}
const temperatureUnit = units === 'fahrenheit' ? 'fahrenheit' : 'celsius';
const hourlyParams = hourly ? [
'temperature_2m',
'relative_humidity_2m',
'precipitation_probability',
'precipitation',
'rain',
'showers',
'snowfall',
'weather_code',
'cloud_cover',
'visibility',
'wind_speed_10m',
'wind_direction_10m',
'wind_gusts_10m',
'uv_index',
'is_day'
].join(',') : '';
const dailyParams = daily ? [
'weather_code',
'temperature_2m_max',
'temperature_2m_min',
'apparent_temperature_max',
'apparent_temperature_min',
'sunrise',
'sunset',
'daylight_duration',
'sunshine_duration',
'uv_index_max',
'precipitation_sum',
'rain_sum',
'showers_sum',
'snowfall_sum',
'precipitation_hours',
'precipitation_probability_max',
'wind_speed_10m_max',
'wind_gusts_10m_max',
'wind_direction_10m_dominant'
].join(',') : '';
let url = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&forecast_days=${days}&temperature_unit=${temperatureUnit}&wind_speed_unit=kmh&precipitation_unit=mm`;
if (hourlyParams) {
url += `&hourly=${hourlyParams}`;
}
if (dailyParams) {
url += `&daily=${dailyParams}`;
}
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Open Meteo API error: ${response.status} ${response.statusText}`);
}
const data = await response.json() as any;
const tempUnit = units === 'fahrenheit' ? '°F' : '°C';
const result: any = {
location: {
latitude: data.latitude,
longitude: data.longitude,
elevation: data.elevation,
timezone: data.timezone,
utc_offset_seconds: data.utc_offset_seconds,
},
};
let forecastText = `# Weather Forecast
**Location:** ${data.latitude.toFixed(4)}°N, ${data.longitude.toFixed(4)}°E
**Elevation:** ${data.elevation}m
**Timezone:** ${data.timezone}
**Forecast Days:** ${days}
`;
if (daily && data.daily) {
result.daily_forecast = data.daily.time.map((time: string, index: number) => ({
date: time,
weather_description: WEATHER_CODES[data.daily.weather_code[index]] || 'Unknown',
weather_code: data.daily.weather_code[index],
temperature_max: `${data.daily.temperature_2m_max[index]}${tempUnit}`,
temperature_min: `${data.daily.temperature_2m_min[index]}${tempUnit}`,
apparent_temperature_max: `${data.daily.apparent_temperature_max[index]}${tempUnit}`,
apparent_temperature_min: `${data.daily.apparent_temperature_min[index]}${tempUnit}`,
sunrise: data.daily.sunrise[index],
sunset: data.daily.sunset[index],
daylight_duration: `${(data.daily.daylight_duration[index] / 3600).toFixed(1)} hours`,
sunshine_duration: `${(data.daily.sunshine_duration[index] / 3600).toFixed(1)} hours`,
uv_index_max: data.daily.uv_index_max[index],
precipitation_sum: `${data.daily.precipitation_sum[index]} mm`,
rain_sum: `${data.daily.rain_sum[index]} mm`,
showers_sum: `${data.daily.showers_sum[index]} mm`,
snowfall_sum: `${data.daily.snowfall_sum[index]} mm`,
precipitation_hours: `${data.daily.precipitation_hours[index]} hours`,
precipitation_probability_max: `${data.daily.precipitation_probability_max[index]}%`,
wind_speed_max: `${data.daily.wind_speed_10m_max[index]} km/h`,
wind_gusts_max: `${data.daily.wind_gusts_10m_max[index]} km/h`,
wind_direction_dominant: `${data.daily.wind_direction_10m_dominant[index]}°`,
}));
forecastText += `## Daily Forecast\n\n`;
result.daily_forecast.forEach((day: any) => {
forecastText += `### ${day.date}
- **Weather:** ${day.weather_description}
- **Temperature:** ${day.temperature_min} to ${day.temperature_max}
- **Feels Like:** ${day.apparent_temperature_min} to ${day.apparent_temperature_max}
- **Precipitation:** ${day.precipitation_sum} (${day.precipitation_probability_max} chance)
- **Wind:** ${day.wind_speed_max}, gusts up to ${day.wind_gusts_max}
- **Sunrise/Sunset:** ${day.sunrise} / ${day.sunset}
- **UV Index:** ${day.uv_index_max}
`;
});
}
if (hourly && data.hourly) {
result.hourly_forecast = data.hourly.time.slice(0, Math.min(24 * days, data.hourly.time.length)).map((time: string, index: number) => ({
time: time,
temperature: `${data.hourly.temperature_2m[index]}${tempUnit}`,
humidity: `${data.hourly.relative_humidity_2m[index]}%`,
precipitation_probability: `${data.hourly.precipitation_probability[index]}%`,
precipitation: `${data.hourly.precipitation[index]} mm`,
rain: `${data.hourly.rain[index]} mm`,
weather_description: WEATHER_CODES[data.hourly.weather_code[index]] || 'Unknown',
weather_code: data.hourly.weather_code[index],
cloud_cover: `${data.hourly.cloud_cover[index]}%`,
wind_speed: `${data.hourly.wind_speed_10m[index]} km/h`,
wind_direction: `${data.hourly.wind_direction_10m[index]}°`,
wind_gusts: `${data.hourly.wind_gusts_10m[index]} km/h`,
uv_index: data.hourly.uv_index[index],
is_day: data.hourly.is_day[index] === 1,
}));
if (daily) {
forecastText += `\n## Sample Hourly Forecast (First 12 Hours)\n\n`;
} else {
forecastText += `## Hourly Forecast (First 24 Hours)\n\n`;
}
const hoursToShow = daily ? 12 : 24;
result.hourly_forecast.slice(0, hoursToShow).forEach((hour: any) => {
forecastText += `**${hour.time}** - ${hour.weather_description}, ${hour.temperature}, ${hour.precipitation_probability} rain chance\n`;
});
}
return {
content: [
{
type: 'text',
text: forecastText,
},
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
throw new Error(`Failed to fetch weather forecast: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}