Skip to contents

Weather forecasts

The open-meteo project combines the best models for each location across the globe to provide the best possible forecast. open-meteo refers to this as model = "generic". The maximum forecast horizon is 16 days.

https://open-meteo.com/en/docs

df <- get_forecast(latitude = 37.30,
                   longitude = -79.83,
                   forecast_days = 7,
                   past_days = 2,
                   model = "generic",
                   variables = c("temperature_2m"))
head(df)
#> # A tibble: 6 × 7
#>   datetime            reference_datetime  site_id   model_id variable prediction
#>   <dttm>              <dttm>              <chr>     <chr>    <chr>         <dbl>
#> 1 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79… generic  tempera…       14  
#> 2 2026-05-14 01:00:00 2026-05-16 00:00:00 37.3_-79… generic  tempera…       13.3
#> 3 2026-05-14 02:00:00 2026-05-16 00:00:00 37.3_-79… generic  tempera…       13  
#> 4 2026-05-14 03:00:00 2026-05-16 00:00:00 37.3_-79… generic  tempera…       12.3
#> 5 2026-05-14 04:00:00 2026-05-16 00:00:00 37.3_-79… generic  tempera…       11.9
#> 6 2026-05-14 05:00:00 2026-05-16 00:00:00 37.3_-79… generic  tempera…       11.6
#> # ℹ 1 more variable: unit <chr>
df |>
  mutate(variable = paste(variable, unit)) |>
  ggplot(aes(x = datetime, y = prediction)) +
  geom_line(color = "#F8766D") +
  geom_vline(aes(xintercept = reference_datetime)) +
  facet_wrap(~variable, scale = "free")

Additional forecast models

Besides "generic", model-specific endpoints are available. These can be useful when you need a particular agency’s output or regional high-resolution data.

Model ID Agency Coverage
"gfs" NOAA (US) Global
"ecmwf" ECMWF (EU) Global, 15 days
"meteofrance" Météo-France Global/Europe
"dwd" DWD (Germany, ICON) Global
"gem" Environment Canada Global, 10 days
"jma" JMA (Japan) Global, 11 days
"metno" MET Norway Scandinavia only
"kma" KMA (Korea) Global, 12 days
"bom" BOM ACCESS-G (Australia) Global, 7 days
"ukmo" UK Met Office Global, 7 days
df_ecmwf <- get_forecast(latitude = 37.30,
                         longitude = -79.83,
                         forecast_days = 7,
                         past_days = 2,
                         model = "ecmwf",
                         variables = c("temperature_2m"))

Ensemble Weather Forecasts

Ensemble forecasts provide probabilistic output from multiple model runs and are available for up to 35 days ahead.

https://open-meteo.com/en/docs/ensemble-api

df <- get_ensemble_forecast(
  latitude = 37.30,
  longitude = -79.83,
  forecast_days = 7,
  past_days = 2,
  model = "gfs_seamless",
  variables = c("temperature_2m"))
head(df)
df |>
  mutate(variable = paste(variable, unit)) |>
  ggplot(aes(x = datetime, y = prediction, color = ensemble)) +
  geom_line() +
  geom_vline(aes(xintercept = reference_datetime)) +
  facet_wrap(~variable, scale = "free", ncol = 2)

Available ensemble models include:

icon_seamless_eps, icon_global_eps, icon_eu_eps, icon_d2_eps,
gfs_seamless_eps, gfs025_eps, gfs05_eps,
ecmwf_ifs025, ecmwf_aifs025,
gem_global, bom_access_global,
ukmo_global_20km, ukmo_uk_2km

Note: ecmwf_ifs025 does not include shortwave radiation.

Use with the General Lake Model

We have included functions that allow the output to be used with the General Lake Model (https://doi.org/10.5194/gmd-12-473-2019). Since the open-meteo models do not include longwave radiation, the package provides a function to calculate it from cloud cover and air temperature.

glm_variables() returns the variable set required by GLM for each product and time step:

df <- get_ensemble_forecast(
  latitude = 37.30,
  longitude = -79.83,
  forecast_days = 7,
  past_days = 2,
  model = "gfs_seamless",
  variables = glm_variables(product = "ensemble_forecast",
                            time_step = "hourly"))
head(df)
#> # A tibble: 6 × 8
#>   datetime            reference_datetime  site_id     model_id ensemble variable
#>   <dttm>              <dttm>              <chr>       <chr>    <chr>    <chr>   
#> 1 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… 00       relativ…
#> 2 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… 01       relativ…
#> 3 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… 02       relativ…
#> 4 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… 03       relativ…
#> 5 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… 04       relativ…
#> 6 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… 05       relativ…
#> # ℹ 2 more variables: prediction <dbl>, unit <chr>
df |>
  mutate(variable = paste(variable, unit)) |>
  ggplot(aes(x = datetime, y = prediction, color = ensemble)) +
  geom_line() +
  geom_vline(aes(xintercept = reference_datetime)) +
  facet_wrap(~variable, scale = "free", ncol = 2)

The following converts to GLM format:

path <- tempdir()
df |>
  add_longwave() |>
  write_glm_format(path = path)
head(read_csv(list.files(path = path, full.names = TRUE, pattern = ".csv")[1],
              show_col_types = FALSE))
#> # A tibble: 6 × 7
#>   time                AirTemp ShortWave LongWave RelHum WindSpeed   Rain
#>   <dttm>                <dbl>     <dbl>    <dbl>  <dbl>     <dbl>  <dbl>
#> 1 2026-05-14 00:00:00    11.8         1     323.     94      2.31 0.0144
#> 2 2026-05-14 01:00:00    10.8         0     292.     97      1.84 0.0144
#> 3 2026-05-14 02:00:00    10.4         0     268.     97      1.96 0.0144
#> 4 2026-05-14 03:00:00     9.9         0     264.     96      2.63 0     
#> 5 2026-05-14 04:00:00     9.1         0     260.     95      2.69 0     
#> 6 2026-05-14 05:00:00     8.3         0     256.     94      2.58 0

Converting to Ecological Forecasting Initiative convention

The standard used in the NEON Ecological Forecasting Challenge differs slightly from this package’s standard. It uses parameter instead of ensemble because the Challenge standard accommodates both ensemble (sample) and parametric distribution forecasts in the same format. The family column defines the distribution type (here family = "ensemble").

The EFI standard also follows CF conventions, so variable names are converted to be CF-compliant.

The output from convert_to_efi_standard() matches the format of neon4cast::stage2(). Learn more at https://projects.ecoforecast.org/neon4cast-docs/Shared-Forecast-Drivers.html.

df |>
  add_longwave() |>
  convert_to_efi_standard()
#> # A tibble: 53,568 × 8
#>    datetime            reference_datetime  site_id     model_id family parameter
#>    <dttm>              <dttm>              <chr>       <chr>    <chr>  <chr>    
#>  1 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… ensem… 00       
#>  2 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… ensem… 00       
#>  3 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… ensem… 00       
#>  4 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… ensem… 00       
#>  5 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… ensem… 00       
#>  6 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… ensem… 00       
#>  7 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… ensem… 00       
#>  8 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… ensem… 00       
#>  9 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… ensem… 01       
#> 10 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… ensem… 01       
#> # ℹ 53,558 more rows
#> # ℹ 2 more variables: variable <chr>, prediction <dbl>

Note that neon4cast::stage3() is equivalent to filtering to the observed period:

df |>
  add_longwave() |>
  convert_to_efi_standard() |>
  filter(datetime < reference_datetime)
#> # A tibble: 11,904 × 8
#>    datetime            reference_datetime  site_id     model_id family parameter
#>    <dttm>              <dttm>              <chr>       <chr>    <chr>  <chr>    
#>  1 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… ensem… 00       
#>  2 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… ensem… 00       
#>  3 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… ensem… 00       
#>  4 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… ensem… 00       
#>  5 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… ensem… 00       
#>  6 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… ensem… 00       
#>  7 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… ensem… 00       
#>  8 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… ensem… 00       
#>  9 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… ensem… 01       
#> 10 2026-05-14 00:00:00 2026-05-16 00:00:00 37.3_-79.83 gfs_sea… ensem… 01       
#> # ℹ 11,894 more rows
#> # ℹ 2 more variables: variable <chr>, prediction <dbl>

The number of days in the observed period equals past_days in the call to get_ensemble_forecast(). The maximum past_days from open-meteo is 92 days.

Historical Weather

If you need more historical days for model calibration and testing, historical data are available through open-meteo’s ERA5 reanalysis API. ERA5 is available from 1940-01-01 to approximately 5 days before the current date.

https://open-meteo.com/en/docs/historical-weather-api

df <- get_historical_weather(
  latitude = 37.30,
  longitude = -79.83,
  start_date = "2023-01-01",
  end_date = Sys.Date() - lubridate::days(5),
  variables = c("temperature_2m"))
tail(df |> na.omit())
#> # A tibble: 6 × 6
#>   datetime            site_id     model_id variable       prediction unit 
#>   <dttm>              <chr>       <chr>    <chr>               <dbl> <chr>
#> 1 2026-05-11 18:00:00 37.3_-79.83 ERA5     temperature_2m        8.5 °C   
#> 2 2026-05-11 19:00:00 37.3_-79.83 ERA5     temperature_2m       10.9 °C   
#> 3 2026-05-11 20:00:00 37.3_-79.83 ERA5     temperature_2m       12.8 °C   
#> 4 2026-05-11 21:00:00 37.3_-79.83 ERA5     temperature_2m       13.6 °C   
#> 5 2026-05-11 22:00:00 37.3_-79.83 ERA5     temperature_2m       12.8 °C   
#> 6 2026-05-11 23:00:00 37.3_-79.83 ERA5     temperature_2m       11.3 °C
df |>
  mutate(variable = paste(variable, unit)) |>
  ggplot(aes(x = datetime, y = prediction)) +
  geom_line(color = "#F8766D") +
  geom_vline(aes(xintercept = lubridate::with_tz(Sys.time(), tzone = "UTC"))) +
  facet_wrap(~variable, scale = "free")

Seasonal Forecasts

Seasonal forecasts covering up to 7 months ahead are provided by ECMWF. The default "ecmwf_seasonal_seamless" model blends EC46 (updated daily, 46-day horizon) with SEAS5 (updated monthly, 7-month horizon) for the best available coverage at any lead time.

https://open-meteo.com/en/docs/seasonal-forecast-api

df <- get_seasonal_forecast(
  latitude = 37.30,
  longitude = -79.83,
  forecast_days = 30,
  past_days = 5,
  variables = c("temperature_2m"))
head(df)
#> # A tibble: 6 × 8
#>   datetime            reference_datetime  site_id     model_id ensemble variable
#>   <dttm>              <dttm>              <chr>       <chr>    <chr>    <chr>   
#> 1 2026-05-11 00:00:00 2026-05-16 00:00:00 37.3_-79.83 ecmwf_s… 00       tempera…
#> 2 2026-05-11 00:00:00 2026-05-16 00:00:00 37.3_-79.83 ecmwf_s… 01       tempera…
#> 3 2026-05-11 00:00:00 2026-05-16 00:00:00 37.3_-79.83 ecmwf_s… 02       tempera…
#> 4 2026-05-11 00:00:00 2026-05-16 00:00:00 37.3_-79.83 ecmwf_s… 03       tempera…
#> 5 2026-05-11 00:00:00 2026-05-16 00:00:00 37.3_-79.83 ecmwf_s… 04       tempera…
#> 6 2026-05-11 00:00:00 2026-05-16 00:00:00 37.3_-79.83 ecmwf_s… 05       tempera…
#> # ℹ 2 more variables: prediction <dbl>, unit <chr>
df |>
  mutate(variable = paste(variable, unit)) |>
  ggplot(aes(x = datetime, y = prediction, color = ensemble)) +
  geom_line() +
  geom_vline(aes(xintercept = reference_datetime)) +
  facet_wrap(~variable, scale = "free")

Downscaling from 6-hourly to hourly time step

The seasonal API delivers data at 6-hourly intervals. The package provides six_hourly_to_hourly() to downscale to hourly using linear interpolation for most variables and solar geometry for shortwave radiation.

df <- get_seasonal_forecast(
  latitude = 37.30,
  longitude = -79.83,
  forecast_days = 30,
  past_days = 5,
  variables = glm_variables(product = "seasonal_forecast",
                            time_step = "6hourly"))
df |>
  six_hourly_to_hourly(latitude = 37.30, longitude = -79.83,
                       use_solar_geom = TRUE) |>
  mutate(variable = paste(variable, unit)) |>
  ggplot(aes(x = datetime, y = prediction, color = ensemble)) +
  geom_line() +
  geom_vline(aes(xintercept = reference_datetime)) +
  facet_wrap(~variable, scale = "free", ncol = 2)

Climate Projections

Daily climate projections from seven high-resolution CMIP6 models are available from 1950 through 2050.

Note that shortwave radiation units differ from the forecast APIs: the climate API returns shortwave_radiation_sum in MJ/m² (daily total) rather than instantaneous W/m².

https://open-meteo.com/en/docs/climate-api

df <- get_climate_projections(
  latitude = 37.30,
  longitude = -79.83,
  start_date = Sys.Date(),
  end_date = Sys.Date() + lubridate::years(1),
  model = "EC_Earth3P_HR",
  variables = c("temperature_2m_mean"))
head(df)
#> # A tibble: 6 × 6
#>   datetime   site_id     model_id      variable            prediction unit 
#>   <date>     <chr>       <chr>         <chr>                    <dbl> <chr>
#> 1 2026-05-16 37.3_-79.83 EC_Earth3P_HR temperature_2m_mean       17.5 °C   
#> 2 2026-05-17 37.3_-79.83 EC_Earth3P_HR temperature_2m_mean       19.6 °C   
#> 3 2026-05-18 37.3_-79.83 EC_Earth3P_HR temperature_2m_mean       20.4 °C   
#> 4 2026-05-19 37.3_-79.83 EC_Earth3P_HR temperature_2m_mean       18.9 °C   
#> 5 2026-05-20 37.3_-79.83 EC_Earth3P_HR temperature_2m_mean       20.9 °C   
#> 6 2026-05-21 37.3_-79.83 EC_Earth3P_HR temperature_2m_mean       20.1 °C
df |>
  mutate(variable = paste(variable, unit)) |>
  ggplot(aes(x = datetime, y = prediction)) +
  geom_line(color = "#F8766D") +
  facet_wrap(~variable, scale = "free")

Downloading multiple sites or models

Multiple models

models <- c("CMCC_CM2_VHR4", "FGOALS_f3_H", "HiRAM_SIT_HR",
            "MRI_AGCM3_2_S", "EC_Earth3P_HR", "MPI_ESM1_2_XR", "NICAM16_8S")

df <- map_df(models, function(model) {
  get_climate_projections(
    latitude = 37.30,
    longitude = -79.83,
    start_date = Sys.Date(),
    end_date = Sys.Date() + lubridate::years(1),
    model = model,
    variables = c("temperature_2m_mean"))
})
df |>
  mutate(variable = paste(variable, unit)) |>
  ggplot(aes(x = datetime, y = prediction, color = model_id)) +
  geom_line() +
  facet_wrap(~variable, scale = "free")

Multiple sites

The optional site_id argument labels rows for each location, making it easy to combine results from multiple sites.

sites <- tibble(site_id = c("fcre", "sunp"),
                latitude = c(37.30, 43.39),
                longitude = c(-79.83, -72.05))

df <- map_df(1:nrow(sites), function(i, sites) {
  get_climate_projections(
    latitude = sites$latitude[i],
    longitude = sites$longitude[i],
    site_id = sites$site_id[i],
    start_date = Sys.Date(),
    end_date = Sys.Date() + lubridate::years(1),
    model = "MPI_ESM1_2_XR",
    variables = c("temperature_2m_mean"))
}, sites)
head(df)
#> # A tibble: 6 × 6
#>   datetime   site_id model_id      variable            prediction unit 
#>   <date>     <chr>   <chr>         <chr>                    <dbl> <chr>
#> 1 2026-05-16 fcre    MPI_ESM1_2_XR temperature_2m_mean       22.4 °C   
#> 2 2026-05-17 fcre    MPI_ESM1_2_XR temperature_2m_mean       21.5 °C   
#> 3 2026-05-18 fcre    MPI_ESM1_2_XR temperature_2m_mean       19.6 °C   
#> 4 2026-05-19 fcre    MPI_ESM1_2_XR temperature_2m_mean       18.8 °C   
#> 5 2026-05-20 fcre    MPI_ESM1_2_XR temperature_2m_mean       19.9 °C   
#> 6 2026-05-21 fcre    MPI_ESM1_2_XR temperature_2m_mean       19.7 °C
df |>
  mutate(variable = paste(variable, unit)) |>
  ggplot(aes(x = datetime, y = prediction, color = site_id)) +
  geom_line() +
  facet_wrap(~variable, scale = "free")

Converting from daily to hourly time step

Photosynthesis responds non-linearly to shortwave radiation, so applying a daily mean to every hour underestimates peak midday production. daily_to_hourly() uses solar geometry to redistribute the daily radiation total across daylight hours, and divides precipitation sums evenly across 24 hours. All other variables have their daily mean applied to each hour.

df <- get_climate_projections(
  latitude = 37.30,
  longitude = -79.83,
  start_date = Sys.Date(),
  end_date = Sys.Date() + lubridate::years(1),
  model = "EC_Earth3P_HR",
  variables = glm_variables(product = "climate_projection", time_step = "daily"))
#> Waiting 4s for throttling delay ■■■■■■■■■■                      
#> Waiting 4s for throttling delay ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 
#> Waiting 7s for throttling delay ■■■■■■                          
#> Waiting 7s for throttling delay ■■■■■■■■■■■■■■■                 
#> Waiting 7s for throttling delay ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■  
#> Waiting 7s for throttling delay ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 
#> Waiting 6s for throttling delay ■■■■■■■■■■■■■■■■■               
#> Waiting 6s for throttling delay ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 
#> Waiting 7s for throttling delay ■■■■■■                          
#> Waiting 7s for throttling delay ■■■■■■■■■■■■■■■■■■■             
#> Waiting 7s for throttling delay ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 
#> Waiting 7s for throttling delay ■■■■■■                          
#> Waiting 7s for throttling delay ■■■■■■■■■■■■■■■■■■              
#> Waiting 7s for throttling delay ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 
#> Waiting 7s for throttling delay ■■■■■■                          
#> Waiting 7s for throttling delay ■■■■■■■■■■■■■■■■■■              
#> Waiting 7s for throttling delay ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 
df |>
  daily_to_hourly(latitude = 37.30, longitude = -79.83) |>
  mutate(variable = paste(variable, unit)) |>
  ggplot(aes(x = datetime, y = prediction)) +
  geom_line(color = "#F8766D") +
  facet_wrap(~variable, scale = "free", ncol = 2)