import fetch from 'node-fetch';
const CLIMATE_MODELS = {
'EC_Earth3P_HR': 'EC-Earth3P-HR (High Resolution European Centre)',
'FGOALS_f3_H': 'FGOALS-f3-H (Chinese Academy of Sciences)',
'HiRAM_SIT_HR': 'HiRAM-SIT-HR (NOAA High Resolution)',
'MRI_AGCM3_2_S': 'MRI-AGCM3-2-S (Japan Meteorological Research Institute)',
'EC_Earth3P': 'EC-Earth3P (European Centre Standard)',
'FGOALS_f3': 'FGOALS-f3 (Chinese Academy of Sciences Standard)',
'MPI_ESM1_2_HR': 'MPI-ESM1-2-HR (Max Planck Institute High Resolution)',
'MRI_AGCM3_2': 'MRI-AGCM3-2 (Japan Meteorological Research Institute Standard)',
};
export async function getClimateData(args: any) {
const {
latitude,
longitude,
start_date,
end_date,
models = Object.keys(CLIMATE_MODELS)
} = 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');
}
// Validate date format
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
if (!dateRegex.test(start_date) || !dateRegex.test(end_date)) {
throw new Error('Dates must be in YYYY-MM-DD format');
}
const startDate = new Date(start_date);
const endDate = new Date(end_date);
if (startDate > endDate) {
throw new Error('Start date must be before end date');
}
// Climate data is typically available from 1950 onwards and projections to 2100
if (startDate.getFullYear() < 1950) {
throw new Error('Climate data is typically available from 1950 onwards');
}
if (endDate.getFullYear() > 2100) {
throw new Error('Climate projections are typically available up to 2100');
}
// Validate models
const validModels = Object.keys(CLIMATE_MODELS);
const invalidModels = models.filter((model: string) => !validModels.includes(model));
if (invalidModels.length > 0) {
throw new Error(`Invalid climate models: ${invalidModels.join(', ')}. Valid models: ${validModels.join(', ')}`);
}
const dailyParams = [
'temperature_2m_mean',
'temperature_2m_max',
'temperature_2m_min',
'precipitation_sum',
'wind_speed_10m_mean',
'wind_speed_10m_max',
'shortwave_radiation_sum',
'relative_humidity_2m_mean',
'dewpoint_2m_mean',
'pressure_msl_mean',
'cloud_cover_mean',
'et0_fao_evapotranspiration'
].join(',');
// For climate data, we'll use the first model as primary and note others are available
const primaryModel = models[0];
const url = `https://climate-api.open-meteo.com/v1/climate?latitude=${latitude}&longitude=${longitude}&start_date=${start_date}&end_date=${end_date}&daily=${dailyParams}&models=${primaryModel}`;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Open Meteo Climate API error: ${response.status} ${response.statusText}`);
}
const data = await response.json() as any;
const result: any = {
location: {
latitude: data.latitude,
longitude: data.longitude,
elevation: data.elevation,
timezone: data.timezone,
utc_offset_seconds: data.utc_offset_seconds,
},
period: {
start_date,
end_date,
},
climate_model: {
name: primaryModel,
description: CLIMATE_MODELS[primaryModel as keyof typeof CLIMATE_MODELS],
},
other_available_models: models.slice(1).map((model: string) => ({
name: model,
description: CLIMATE_MODELS[model as keyof typeof CLIMATE_MODELS],
})),
};
let climateText = `# Climate Data Report
**Location:** ${data.latitude.toFixed(4)}°N, ${data.longitude.toFixed(4)}°E
**Elevation:** ${data.elevation}m
**Timezone:** ${data.timezone}
**Period:** ${start_date} to ${end_date}
**Primary Climate Model:** ${CLIMATE_MODELS[primaryModel as keyof typeof CLIMATE_MODELS]}
`;
if (data.daily) {
result.daily_climate_data = data.daily.time.map((time: string, index: number) => ({
date: time,
temperature_mean: `${data.daily.temperature_2m_mean[index].toFixed(1)}°C`,
temperature_max: `${data.daily.temperature_2m_max[index].toFixed(1)}°C`,
temperature_min: `${data.daily.temperature_2m_min[index].toFixed(1)}°C`,
precipitation: `${data.daily.precipitation_sum[index].toFixed(1)} mm`,
wind_speed_mean: `${data.daily.wind_speed_10m_mean[index].toFixed(1)} km/h`,
wind_speed_max: `${data.daily.wind_speed_10m_max[index].toFixed(1)} km/h`,
solar_radiation: `${data.daily.shortwave_radiation_sum[index].toFixed(1)} MJ/m²`,
humidity_mean: `${data.daily.relative_humidity_2m_mean[index].toFixed(1)}%`,
dewpoint_mean: `${data.daily.dewpoint_2m_mean[index].toFixed(1)}°C`,
pressure_mean: `${data.daily.pressure_msl_mean[index].toFixed(1)} hPa`,
cloud_cover_mean: `${data.daily.cloud_cover_mean[index].toFixed(1)}%`,
evapotranspiration: `${data.daily.et0_fao_evapotranspiration[index].toFixed(2)} mm`,
}));
// Calculate climate statistics
const temperatures = data.daily.temperature_2m_mean;
const precipitations = data.daily.precipitation_sum;
const totalDays = temperatures.length;
const years = totalDays / 365.25;
const avgTemp = (temperatures.reduce((a: number, b: number) => a + b, 0) / temperatures.length).toFixed(1);
const maxTemp = Math.max(...temperatures).toFixed(1);
const minTemp = Math.min(...temperatures).toFixed(1);
const totalPrecip = precipitations.reduce((a: number, b: number) => a + b, 0).toFixed(1);
const avgAnnualPrecip = (parseFloat(totalPrecip) / years).toFixed(1);
// Calculate seasonal averages if we have at least a year of data
let seasonalData = null;
if (totalDays >= 365) {
const seasons = {
spring: { temps: [] as number[], precips: [] as number[] },
summer: { temps: [] as number[], precips: [] as number[] },
autumn: { temps: [] as number[], precips: [] as number[] },
winter: { temps: [] as number[], precips: [] as number[] },
};
data.daily.time.forEach((dateStr: string, index: number) => {
const date = new Date(dateStr);
const month = date.getMonth(); // 0-11
let season: keyof typeof seasons;
if (month >= 2 && month <= 4) season = 'spring';
else if (month >= 5 && month <= 7) season = 'summer';
else if (month >= 8 && month <= 10) season = 'autumn';
else season = 'winter';
seasons[season].temps.push(temperatures[index]);
seasons[season].precips.push(precipitations[index]);
});
seasonalData = {
spring: {
avg_temp: (seasons.spring.temps.reduce((a, b) => a + b, 0) / seasons.spring.temps.length).toFixed(1),
total_precip: seasons.spring.precips.reduce((a, b) => a + b, 0).toFixed(1),
},
summer: {
avg_temp: (seasons.summer.temps.reduce((a, b) => a + b, 0) / seasons.summer.temps.length).toFixed(1),
total_precip: seasons.summer.precips.reduce((a, b) => a + b, 0).toFixed(1),
},
autumn: {
avg_temp: (seasons.autumn.temps.reduce((a, b) => a + b, 0) / seasons.autumn.temps.length).toFixed(1),
total_precip: seasons.autumn.precips.reduce((a, b) => a + b, 0).toFixed(1),
},
winter: {
avg_temp: (seasons.winter.temps.reduce((a, b) => a + b, 0) / seasons.winter.temps.length).toFixed(1),
total_precip: seasons.winter.precips.reduce((a, b) => a + b, 0).toFixed(1),
},
};
result.seasonal_climate_summary = seasonalData;
}
result.climate_summary = {
total_days: totalDays,
total_years: parseFloat(years.toFixed(1)),
average_temperature: `${avgTemp}°C`,
highest_temperature: `${maxTemp}°C`,
lowest_temperature: `${minTemp}°C`,
total_precipitation: `${totalPrecip} mm`,
average_annual_precipitation: `${avgAnnualPrecip} mm/year`,
wet_days: precipitations.filter((p: number) => p > 0.1).length,
dry_days: precipitations.filter((p: number) => p <= 0.1).length,
};
climateText += `## Climate Summary
**Period:** ${years.toFixed(1)} years (${totalDays} days)
**Average Temperature:** ${avgTemp}°C
**Temperature Range:** ${minTemp}°C to ${maxTemp}°C
**Total Precipitation:** ${totalPrecip} mm
**Average Annual Precipitation:** ${avgAnnualPrecip} mm/year
**Wet Days (>0.1mm):** ${result.climate_summary.wet_days}
**Dry Days (≤0.1mm):** ${result.climate_summary.dry_days}
`;
if (seasonalData) {
climateText += `## Seasonal Climate Patterns
**Spring (Mar-May):**
- Average Temperature: ${seasonalData.spring.avg_temp}°C
- Total Precipitation: ${seasonalData.spring.total_precip} mm
**Summer (Jun-Aug):**
- Average Temperature: ${seasonalData.summer.avg_temp}°C
- Total Precipitation: ${seasonalData.summer.total_precip} mm
**Autumn (Sep-Nov):**
- Average Temperature: ${seasonalData.autumn.avg_temp}°C
- Total Precipitation: ${seasonalData.autumn.total_precip} mm
**Winter (Dec-Feb):**
- Average Temperature: ${seasonalData.winter.avg_temp}°C
- Total Precipitation: ${seasonalData.winter.total_precip} mm
`;
}
// Show sample data
const sampleSize = Math.min(10, result.daily_climate_data.length);
climateText += `## Sample Climate Data (First ${sampleSize} Days)
`;
result.daily_climate_data.slice(0, sampleSize).forEach((day: any) => {
climateText += `### ${day.date}
- **Temperature:** ${day.temperature_min} to ${day.temperature_max} (avg: ${day.temperature_mean})
- **Precipitation:** ${day.precipitation}
- **Humidity:** ${day.humidity_mean}
- **Wind Speed:** ${day.wind_speed_mean} (max: ${day.wind_speed_max})
- **Solar Radiation:** ${day.solar_radiation}
`;
});
if (result.daily_climate_data.length > sampleSize) {
climateText += `... and ${result.daily_climate_data.length - sampleSize} more days (see JSON data for complete information)\n\n`;
}
}
climateText += `## About Climate Models
**Primary Model Used:** ${CLIMATE_MODELS[primaryModel as keyof typeof CLIMATE_MODELS]}
Climate models are mathematical representations of the Earth's climate system that help scientists understand and predict climate patterns. The data provided includes:
### Temperature Variables:
- **Mean Temperature:** Daily average temperature
- **Maximum Temperature:** Daily highest temperature
- **Minimum Temperature:** Daily lowest temperature
### Precipitation & Humidity:
- **Precipitation Sum:** Total daily rainfall/snowfall
- **Relative Humidity:** Average daily humidity percentage
- **Dewpoint:** Temperature at which air becomes saturated
### Wind & Pressure:
- **Wind Speed:** Average and maximum daily wind speeds
- **Sea Level Pressure:** Atmospheric pressure at sea level
### Radiation & Evaporation:
- **Solar Radiation:** Total daily solar energy received
- **Evapotranspiration:** Water loss from evaporation and plant transpiration
### Available Models:
${Object.entries(CLIMATE_MODELS).map(([key, desc]) => `- **${key}:** ${desc}`).join('\n')}
### Data Applications:
- Climate change impact assessment
- Agricultural planning and crop modeling
- Water resource management
- Urban planning and infrastructure design
- Renewable energy potential assessment
- Ecosystem and biodiversity studies
`;
if (models.length > 1) {
climateText += `\n**Note:** This report shows data from the ${primaryModel} model. Data from ${models.length - 1} other model(s) is also available for comparison and ensemble analysis.`;
}
return {
content: [
{
type: 'text',
text: climateText,
},
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
throw new Error(`Failed to fetch climate data: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}