Author: Rodrigo Pires

# weatherOz for the Locationforecast 2.0 (Norwegian Meteorologisk Institutt)

#### Rodrigo Pires

## About MET Weather API Forecast Data

The Norwegian Meteorological Institute provides global weather forecast data through their Locationforecast API. The service uses high-resolution numerical weather prediction models and is available for locations worldwide, including Australia. Forecasts extend up to 9 days into the future with hourly resolution for the first few days and lower temporal resolution for later periods.

The MET Weather API is free to use but requires proper identification through a User-Agent header containing a valid email address. This is a requirement of MET Weather API’s Terms of Service and ensures responsible use of the service.

All forecast times returned by {weatherOz} are converted to Australian Western Standard Time (AWST, Australia/Perth timezone) for consistency with other data sources in the package.

## A Note on API Keys

Unlike the DPIRD API and similar to SILO API, MET Weather API does not require a formal API key. However, you must provide a valid email address which is used in the User-Agent header when making requests. This email address should be yours and allows Norwegian Meteorological Institute to contact you if there are issues with your usage patterns.

For convenience, you can store your email in your .Renviron file using the format:

    METNO_API_KEY="your.email@example.com"

See Chapter 8 in “What They Forgot to Teach You About R” by Bryan et al. for more on storing details in your .Renviron if you are unfamiliar.

## Working With MET Weather API Forecast Data

Two primary functions are provided for accessing MET Weather API forecast data:

- `get_metno_forecast()`, which returns hourly weather forecast data with complete metadata from the API response; and
- `get_metno_daily_forecast()`, which returns daily aggregated forecast data for a specified number of days.

Three helper functions are also exported for advanced users:

- `metno_timeseries_to_data_table()`, which converts the raw JSON timeseries to a tidy data.table;
- `metno_resample_data_table()`, which aggregates hourly data to daily or other frequencies; and
- `metno_get_dominant_symbol()`, which determines the most representative weather symbol for a period.

## Getting Hourly Forecast Data

The `get_metno_forecast()` function retrieves the complete hourly forecast for any location in Australia. The function returns a list containing three elements: `data` (a data.table with hourly forecasts), `raw` (the complete parsed API response), and `metadata` (request information and cache-relevant headers).

### Example 1: Get Hourly Forecast for Perth

Retrieve the hourly forecast for Perth, Western Australia.

    library(weatherOz)

    perth_forecast <- get_metno_forecast(
      latitude = -31.95,
      longitude = 115.86,
      api_key = Sys.getenv("METNO_API_KEY")
    )

    # Inspect the structure
    names(perth_forecast)
    #> [1] "data"     "raw"      "metadata"

    # View the first few rows of hourly data
    head(perth_forecast$data)
    #>                   time air_temperature relative_humidity wind_speed
    #>                 <POSc>           <num>             <num>      <num>
    #> 1: 2026-02-07 22:00:00            35.7              22.7        5.3
    #> 2: 2026-02-07 23:00:00            35.9              22.4        6.6
    #> 3: 2026-02-08 00:00:00            35.5              22.7        6.8
    #> 4: 2026-02-08 01:00:00            34.1              24.4        6.6
    #> 5: 2026-02-08 02:00:00            32.4              27.2        6.1
    #> 6: 2026-02-08 03:00:00            30.3              31.9        5.6
    #>    wind_from_direction cloud_area_fraction air_pressure_at_sea_level
    #>                  <num>               <num>                     <num>
    #> 1:               187.8                   0                    1009.8
    #> 2:               201.5                   0                    1009.1
    #> 3:               198.4                   0                    1008.5
    #> 4:               200.2                   0                    1008.3
    #> 5:               201.1                   0                    1008.3
    #> 6:               200.9                   0                    1009.1
    #>    precipitation_amount    symbol_code
    #>                   <num>         <char>
    #> 1:                    0   clearsky_day
    #> 2:                    0   clearsky_day
    #> 3:                    0   clearsky_day
    #> 4:                    0   clearsky_day
    #> 5:                    0   clearsky_day
    #> 6:                    0 clearsky_night

    # Check metadata
    perth_forecast$metadata$status_code
    #> [1] 200
    perth_forecast$metadata$expires
    #> [1] "2026-02-07 15:16:48 AWST"

### Example 2: Understanding the Metadata

The metadata returned includes useful information for caching and understanding the API response.

    library(weatherOz)

    forecast <- get_metno_forecast(
      latitude = -27.47,
      longitude = 153.03,
      api_key = Sys.getenv("METNO_API_KEY")
    )

    # When was this forecast retrieved?
    forecast$metadata$retrieved_at
    #> [1] "2026-02-07 14:46:33 AWST"

    # When does this forecast expire (for caching purposes)?
    forecast$metadata$expires
    #> [1] "2026-02-07 15:16:49 AWST"

    # When was the forecast last modified at the source?
    forecast$metadata$last_modified
    #> [1] "2026-02-07 14:46:32 AWST"

### Example 3: Working With Hourly Data

The hourly forecast data includes temperature, precipitation, wind, humidity, pressure, and cloud cover.

    library(weatherOz)

    forecast <- get_metno_forecast(
      latitude = -35.28,
      longitude = 149.13,
      api_key = Sys.getenv("METNO_API_KEY")
    )

    # Get the forecast data
    hourly_data <- forecast$data

    # Check the timezone
    attr(hourly_data$time, "tzone")
    #> [1] "Australia/Perth"

    # Summary statistics for the next 24 hours
    library(data.table)
    next_24h <- hourly_data[1:24, ]

    # Temperature range
    range(next_24h$air_temperature, na.rm = TRUE)
    #> [1] 18.8 31.7

    # Total precipitation expected
    sum(next_24h$precipitation_amount, na.rm = TRUE)
    #> [1] 14.7

    # Maximum wind speed
    max(next_24h$wind_speed, na.rm = TRUE)
    #> [1] 4.9

We can visualise the hourly forecast for the next 48 hours.

    library(ggplot2)

    # Get 48 hours of data
    plot_data <- hourly_data[1:48, ]

    # Create dual-axis plot for temperature and precipitation
    ggplot(plot_data, aes(x = time)) +
      geom_line(aes(y = air_temperature, colour = "Temperature"),
                linewidth = 1) +
      geom_bar(aes(y = precipitation_amount * 5, fill = "Precipitation"),
               stat = "identity", alpha = 0.3) +
      scale_y_continuous(
        name = "Temperature (°C)",
        sec.axis = sec_axis(~ . / 5, name = "Precipitation (mm)")
      ) +
      scale_colour_manual(values = c("Temperature" = "red")) +
      scale_fill_manual(values = c("Precipitation" = "blue")) +
      labs(
        title = "48-Hour Weather Forecast",
        subtitle = "Canberra, ACT",
        x = "Time (AWST)",
        colour = NULL,
        fill = NULL
      ) +
      theme_minimal(base_size = 16) +
      theme(
        legend.position = "bottom",
        axis.text.x = element_text(angle = 45, hjust = 1)
      )

plot of chunk hourly_forecast_plot

## Getting Daily Forecast Data

The `get_metno_daily_forecast()` function provides a convenient way to obtain daily aggregated forecasts. This function internally calls `get_metno_forecast()` and aggregates the hourly data into daily summaries.

Daily aggregations include:

- Minimum and maximum temperatures
- Total precipitation
- Average wind speed and maximum wind gust
- Average relative humidity
- Average air pressure
- Average cloud cover
- Dominant weather symbol for the day

### Example 4: Get 7-Day Daily Forecast

Retrieve a 7-day daily forecast for Hobart, Tasmania.

    library(weatherOz)

    hobart_daily <- get_metno_daily_forecast(
      latitude = -42.88,
      longitude = 147.33,
      days = 7,
      api_key = Sys.getenv("METNO_API_KEY")
    )

    hobart_daily
    #>          date min_temperature max_temperature total_precipitation
    #>        <Date>           <num>           <num>               <num>
    #> 1: 2026-02-07            16.6            17.1                 0.7
    #> 2: 2026-02-08            14.5            20.9                 7.8
    #> 3: 2026-02-09            12.5            19.8                 3.0
    #> 4: 2026-02-10            14.6            20.2                 0.8
    #> 5: 2026-02-11            15.2            25.1                 0.6
    #> 6: 2026-02-12            11.0            14.5                 1.5
    #> 7: 2026-02-13            10.5            17.7                 0.0
    #>    avg_wind_speed max_wind_speed avg_relative_humidity avg_pressure
    #>             <num>          <num>                 <num>        <num>
    #> 1:       5.550000            5.6              85.85000     1017.950
    #> 2:       3.487500            5.0              87.25000     1016.642
    #> 3:       3.654167            7.2              86.75417     1012.800
    #> 4:       3.469231            6.4              89.16923     1014.985
    #> 5:       2.025000            3.7              72.77500     1003.050
    #> 6:       4.750000            5.5              61.70000      998.150
    #> 7:       2.775000            4.3              62.87500     1012.325
    #>    avg_cloud_fraction dominant_weather_symbol
    #>                 <num>                  <char>
    #> 1:          100.00000                    rain
    #> 2:           94.47083               heavyrain
    #> 3:           70.93333               heavyrain
    #> 4:           93.33077                  cloudy
    #> 5:           99.80000                  cloudy
    #> 6:           44.12500         rainshowers_day
    #> 7:           54.70000        partlycloudy_day

Visualise the 7-day forecast with temperature ranges and precipitation.

    library(ggplot2)

    # Add day labels
    hobart_daily[, day_label := format(date, "%a\n%d %b")]

    # Create plot
    ggplot(hobart_daily, aes(x = date)) +
      geom_ribbon(aes(ymin = min_temperature, ymax = max_temperature),
                  fill = "lightblue", alpha = 0.5) +
      geom_line(aes(y = max_temperature, colour = "Maximum"),
                linewidth = 1.2) +
      geom_line(aes(y = min_temperature, colour = "Minimum"),
                linewidth = 1.2) +
      geom_point(aes(y = max_temperature), colour = "red", size = 3) +
      geom_point(aes(y = min_temperature), colour = "blue", size = 3) +
      geom_bar(aes(y = total_precipitation * 5, fill = "Precipitation"),
               stat = "identity", alpha = 0.4, width = 0.6) +
      scale_y_continuous(
        name = "Temperature (°C)",
        sec.axis = sec_axis(~ . / 5, name = "Precipitation (mm)")
      ) +
      scale_colour_manual(values = c("Maximum" = "red", "Minimum" = "blue")) +
      scale_fill_manual(values = c("Precipitation" = "steelblue")) +
      scale_x_date(date_breaks = "1 day", date_labels = "%a\n%d %b") +
      labs(
        title = "7-Day Weather Forecast",
        subtitle = "Hobart, Tasmania - Daily temperature range and precipitation",
        x = "Date",
        colour = "Temperature",
        fill = NULL
      ) +
      theme_minimal(base_size = 16) +
      theme(
        legend.position = "bottom",
        panel.grid.minor = element_blank()
      )

plot of chunk daily_forecast_plot

### Example 5: Compare Daily Aggregations

Demonstrate the difference between hourly and daily aggregated data.

    library(weatherOz)

    # Get both hourly and daily forecasts
    hourly <- get_metno_forecast(
      latitude = -34.93,
      longitude = 138.60,
      api_key = Sys.getenv("METNO_API_KEY")
    )

    daily <- get_metno_daily_forecast(
      latitude = -34.93,
      longitude = 138.60,
      days = 3,
      api_key = Sys.getenv("METNO_API_KEY")
    )

    # Compare temperature ranges for first day
    first_day <- as.Date(hourly$data$time[1])
    day1_hourly <- hourly$data[as.Date(time) == first_day, ]

    # Hourly data statistics
    cat("Day 1 from hourly data:\n")
    #> Day 1 from hourly data:
    cat("  Min temp:", min(day1_hourly$air_temperature, na.rm = TRUE), "°C\n")
    #>   Min temp: 23.2 °C
    cat("  Max temp:", max(day1_hourly$air_temperature, na.rm = TRUE), "°C\n")
    #>   Max temp: 32.3 °C
    cat("  Total precip:", sum(day1_hourly$precipitation_amount, na.rm = TRUE), "mm\n")
    #>   Total precip: 0 mm

    # Daily aggregated data
    cat("\nDay 1 from daily aggregation:\n")
    #>
    #> Day 1 from daily aggregation:
    cat("  Min temp:", daily$min_temperature[1], "°C\n")
    #>   Min temp: 32 °C
    cat("  Max temp:", daily$max_temperature[1], "°C\n")
    #>   Max temp: 32.3 °C
    cat("  Total precip:", daily$total_precipitation[1], "mm\n")
    #>   Total precip: 0 mm

## Combining Forecast with Historical Data

One of the most powerful features of {weatherOz} is the ability to combine forecast data from MET Weather API with historical observations from SILO or DPIRD. This creates a continuous time series with historical observations and the nine-days forecast from the MET Weather API.

### Example 6: Combining MET Weather API Forecast with SILO Historical Data

Create a continuous 40-day dataset using 30 days of historical data and 9 days of forecast.

    library(weatherOz)
    library(data.table)

    # Define location (Toowoomba, Qld)
    lat <- -27.56
    lon <- 151.95

    # Get 30 days of historical data from SILO
    end_date <- Sys.Date() - 1
    start_date <- end_date - 30

    historical <- get_data_drill(
      latitude = lat,
      longitude = lon,
      start_date = format(start_date, "%Y%m%d"),
      end_date = format(end_date, "%Y%m%d"),
      values = c("max_temp", "min_temp", "rain"),
      api_key = Sys.getenv("SILO_API_KEY")
    )

    # Get 9 days of forecast data from METNO
    forecast <- get_metno_daily_forecast(
      latitude = lat,
      longitude = lon,
      days = 9,
      api_key = Sys.getenv("METNO_API_KEY")
    )

    # Prepare historical data for joining
    historical_clean <- historical[, .(
      date = date,
      min_temp = air_tmin,
      max_temp = air_tmax,
      precipitation = rainfall,
      data_source = "SILO (historical)"
    )]

    # Prepare forecast data for joining
    forecast_clean <- forecast[, .(
      date = date,
      min_temp = min_temperature,
      max_temp = max_temperature,
      precipitation = total_precipitation,
      data_source = "met.no (forecast)"
    )]

    # Combine datasets
    combined <- rbind(historical_clean, forecast_clean)

    # View the transition point
    combined[(.N - 15):.N, ]
    #>           date min_temp max_temp precipitation       data_source
    #>         <Date>    <num>    <num>         <num>            <char>
    #>  1: 2026-01-31     18.3     28.4           0.0 SILO (historical)
    #>  2: 2026-02-01     17.3     34.3           0.0 SILO (historical)
    #>  3: 2026-02-02     20.8     30.0           0.0 SILO (historical)
    #>  4: 2026-02-03     15.7     25.8           0.0 SILO (historical)
    #>  5: 2026-02-04     14.8     28.2           0.0 SILO (historical)
    #>  6: 2026-02-05     15.1     32.0           0.0 SILO (historical)
    #>  7: 2026-02-06     17.1     27.5           0.0 SILO (historical)
    #>  8: 2026-02-07     25.8     26.8           0.0 met.no (forecast)
    #>  9: 2026-02-08     15.2     27.4           0.0 met.no (forecast)
    #> 10: 2026-02-09     16.1     29.4           0.0 met.no (forecast)
    #> 11: 2026-02-10     19.2     33.1           3.6 met.no (forecast)
    #> 12: 2026-02-11     18.9     31.9           1.0 met.no (forecast)
    #> 13: 2026-02-12     19.5     30.9          13.3 met.no (forecast)
    #> 14: 2026-02-13     21.8     22.9          29.1 met.no (forecast)
    #> 15: 2026-02-14     16.8     22.9           0.2 met.no (forecast)
    #> 16: 2026-02-15     17.2     24.4           0.8 met.no (forecast)

Check the transition from historical data to forecast.

    library(ggplot2)

    # Plot the combined dataset
    ggplot(combined, aes(x = date)) +
      geom_ribbon(aes(ymin = min_temp, ymax = max_temp, fill = data_source),
                  alpha = 0.3) +
      geom_line(aes(y = max_temp, colour = "Maximum"), linewidth = 0.8) +
      geom_line(aes(y = min_temp, colour = "Minimum"), linewidth = 0.8) +
      geom_vline(xintercept = as.numeric(Sys.Date()),
                 linetype = "dashed", colour = "black", linewidth = 0.8) +
      annotate("text", x = Sys.Date(), y = max(combined$max_temp, na.rm = TRUE),
               label = "Today", vjust = -0.5, hjust = -0.1) +
      scale_fill_manual(values = c("SILO (historical)" = "steelblue",
                                    "met.no (forecast)" = "coral")) +
      scale_colour_manual(values = c("Maximum" = "red", "Minimum" = "blue")) +
      labs(
        title = "Historical Observations + Weather Forecast",
        subtitle = "Toowoomba, Queensland - 30 days history + 9 days forecast",
        x = "Date",
        y = "Temperature (°C)",
        fill = "Data Source",
        colour = "Temperature"
      ) +
      theme_minimal(base_size = 16) +
      theme(
        legend.position = "bottom",
        axis.text.x = element_text(angle = 45, hjust = 1)
      )
    #> Warning in scale_x_date(): A <numeric> value was passed to a Date scale.
    #> ℹ The value was converted to a <Date> object.

plot of chunk combined_historical_forecast_plot

### Example 7: Combining MET Weather API Forecast with DPIRD Historical Data

For Western Australian locations, combine DPIRD observations with METNO forecasts.

    library(weatherOz)
    library(data.table)

    # Define location (Northam, WA)
    lat <- -31.65
    lon <- 116.67

    # Get recent DPIRD observations
    dpird_data <- get_dpird_summaries(
      station_code = "NO",
      start_date = format(Sys.Date() - 14, "%Y%m%d"),
      end_date = format(Sys.Date() - 1, "%Y%m%d"),
      interval = "daily",
      values = c("airTemperature", "rainfall")
    )

    # Get METNO forecast
    metno_data <- get_metno_daily_forecast(
      latitude = lat,
      longitude = lon,
      days = 7,
      api_key = Sys.getenv("METNO_API_KEY")
    )

    # Prepare DPIRD data
    dpird_clean <- dpird_data[, .(
      date = as.Date(date),
      min_temp = air_tmin,
      max_temp = air_tmax,
      precipitation = rainfall,
      data_source = "DPIRD (observed)"
    )]

    # Prepare METNO data
    metno_clean <- metno_data[, .(
      date = date,
      min_temp = min_temperature,
      max_temp = max_temperature,
      precipitation = total_precipitation,
      data_source = "met.no (forecast)"
    )]

    # Combine
    combined <- rbind(dpird_clean, metno_clean)

    combined
    #>           date min_temp max_temp precipitation       data_source
    #>         <Date>    <num>    <num>         <num>            <char>
    #>  1: 2026-01-24     15.2     34.9           0.0  DPIRD (observed)
    #>  2: 2026-01-25     14.1     30.3           0.0  DPIRD (observed)
    #>  3: 2026-01-26     16.7     27.5           0.0  DPIRD (observed)
    #>  4: 2026-01-27     11.8     29.5           0.0  DPIRD (observed)
    #>  5: 2026-01-28     13.9     33.5           0.0  DPIRD (observed)
    #>  6: 2026-01-29     15.7     38.5           0.0  DPIRD (observed)
    #>  7: 2026-01-30     19.9     41.9           0.0  DPIRD (observed)
    #>  8: 2026-01-31     18.8     37.2           0.0  DPIRD (observed)
    #>  9: 2026-02-01     18.0     39.0           0.0  DPIRD (observed)
    #> 10: 2026-02-02     21.6     42.2           0.0  DPIRD (observed)
    #> 11: 2026-02-03     21.7     37.6           0.0  DPIRD (observed)
    #> 12: 2026-02-04     16.1     32.1           0.0  DPIRD (observed)
    #> 13: 2026-02-05     13.0     35.0           0.0  DPIRD (observed)
    #> 14: 2026-02-06     15.7     37.1           0.0  DPIRD (observed)
    #> 15: 2026-02-07     34.0     34.7           0.0 met.no (forecast)
    #> 16: 2026-02-08     18.7     38.6           0.0 met.no (forecast)
    #> 17: 2026-02-09     21.6     38.9           0.4 met.no (forecast)
    #> 18: 2026-02-10     20.7     35.5           7.6 met.no (forecast)
    #> 19: 2026-02-11     17.6     28.0           0.0 met.no (forecast)
    #> 20: 2026-02-12     15.9     27.6           0.0 met.no (forecast)
    #> 21: 2026-02-13     18.3     29.8           0.0 met.no (forecast)
    #>           date min_temp max_temp precipitation       data_source
    #>         <Date>    <num>    <num>         <num>            <char>

## Practical Use Case: Agricultural Planning

This example demonstrates how to use combined historical and forecast data for agricultural decision-making, specifically for irrigation scheduling ahead of predicted hot weather.

### Example 8: Heat Stress Event Planning

An irrigated crop farmer wants to identify upcoming heat stress events (days with maximum temperature > 38°C) and ensure adequate irrigation is in place.

    library(weatherOz)
    library(data.table)

    # Location: Emerald, Queensland (agricultural region)
    lat <- -23.53
    lon <- 148.16

    # Get recent historical context (14 days)
    historical <- get_data_drill(
      latitude = lat,
      longitude = lon,
      start_date = format(Sys.Date() - 14, "%Y%m%d"),
      end_date = format(Sys.Date() - 1, "%Y%m%d"),
      values = c("max_temp", "min_temp", "rain", "evap_pan"),
      api_key = Sys.getenv("SILO_API_KEY")
    )

    # Get 9-day forecast
    forecast <- get_metno_daily_forecast(
      latitude = lat,
      longitude = lon,
      days = 9,
      api_key = Sys.getenv("METNO_API_KEY")
    )

    # Prepare datasets
    hist_prep <- historical[, .(
      date = date,
      max_temp = air_tmax,
      precipitation = rainfall,
      period = "Historical"
    )]

    fcst_prep <- forecast[, .(
      date = date,
      max_temp = max_temperature,
      precipitation = total_precipitation,
      period = "Forecast"
    )]

    # Combine
    weather_data <- rbind(hist_prep, fcst_prep)

    # Identify heat stress days (max temp > 38°C) in forecast period
    heat_stress_days <- forecast[max_temperature > 38, .(
      date,
      max_temperature,
      days_ahead = as.numeric(date - Sys.Date())
    )]

    if (nrow(heat_stress_days) > 0) {
      cat("HEAT STRESS ALERT\n")
      cat("=================\n\n")
      cat("Heat stress conditions predicted on:\n")
      print(heat_stress_days)
      cat("\nRecommendation: Schedule irrigation 2-3 days before events to ensure\n")
      cat("adequate soil moisture for evaporative cooling.\n")
    } else {
      cat("No heat stress events (>38°C) predicted in the next 9 days.\n")
    }
    #> No heat stress events (>38°C) predicted in the next 9 days.

    # Calculate cumulative water balance
    weather_data[, water_balance := precipitation]
    weather_data[, cumulative_balance := cumsum(water_balance)]

    # Show recent and upcoming water balance
    weather_data[, .(date, max_temp, precipitation, cumulative_balance)]
    #>           date max_temp precipitation cumulative_balance
    #>         <Date>    <num>         <num>              <num>
    #>  1: 2026-01-24     33.8           0.0                0.0
    #>  2: 2026-01-25     35.3           0.0                0.0
    #>  3: 2026-01-26     34.8           0.0                0.0
    #>  4: 2026-01-27     36.2           0.0                0.0
    #>  5: 2026-01-28     35.8           0.0                0.0
    #>  6: 2026-01-29     34.9           0.0                0.0
    #>  7: 2026-01-30     33.8           0.0                0.0
    #>  8: 2026-01-31     35.5           0.0                0.0
    #>  9: 2026-02-01     37.6           0.0                0.0
    #> 10: 2026-02-02     39.0           0.0                0.0
    #> 11: 2026-02-03     30.3           1.3                1.3
    #> 12: 2026-02-04     31.9           0.0                1.3
    #> 13: 2026-02-05     33.6           0.0                1.3
    #> 14: 2026-02-06     33.1           0.0                1.3
    #> 15: 2026-02-07     33.0           0.0                1.3
    #> 16: 2026-02-08     33.1           0.1                1.4
    #> 17: 2026-02-09     36.1           0.0                1.4
    #> 18: 2026-02-10     37.2           0.0                1.4
    #> 19: 2026-02-11     33.8           1.5                2.9
    #> 20: 2026-02-12     34.9           2.4                5.3
    #> 21: 2026-02-13     32.5          16.0               21.3
    #> 22: 2026-02-14     27.8          21.2               42.5
    #> 23: 2026-02-15     23.7          49.3               91.8
    #>           date max_temp precipitation cumulative_balance
    #>         <Date>    <num>         <num>              <num>

Visualise temperature trends and identify heat stress risk days.

    library(ggplot2)

    # Add heat stress indicator
    weather_data[, heat_stress := max_temp > 38]

    # Create plot
    ggplot(weather_data, aes(x = date)) +
      geom_rect(data = weather_data[heat_stress == TRUE],
                aes(xmin = date - 0.5, xmax = date + 0.5,
                    ymin = -Inf, ymax = Inf),
                fill = "red", alpha = 0.1) +
      geom_line(aes(y = max_temp, colour = period), linewidth = 1) +
      geom_point(aes(y = max_temp, colour = period, shape = period), size = 3) +
      geom_hline(yintercept = 38, linetype = "dashed", colour = "red",
                 linewidth = 0.8) +
      geom_bar(aes(y = precipitation * 2, fill = "Precipitation"),
               stat = "identity", alpha = 0.3) +
      geom_vline(xintercept = as.numeric(Sys.Date()),
                 linetype = "dotted", colour = "black", linewidth = 0.8) +
      annotate("text", x = Sys.Date(), y = max(weather_data$max_temp, na.rm = TRUE),
               label = "Today", vjust = -0.5, hjust = -0.1, size = 3) +
      annotate("text", x = min(weather_data$date), y = 38,
               label = "Heat Stress Threshold (38°C)", vjust = -0.5,
               hjust = 0, colour = "red", size = 3) +
      scale_y_continuous(
        name = "Temperature (°C)",
        sec.axis = sec_axis(~ . / 2, name = "Precipitation (mm)")
      ) +
      scale_colour_manual(values = c("Historical" = "steelblue",
                                      "Forecast" = "coral")) +
      scale_shape_manual(values = c("Historical" = 16, "Forecast" = 17)) +
      scale_fill_manual(values = c("Precipitation" = "blue")) +
      labs(
        title = "Heat Stress Event Planning",
        subtitle = "Emerald, Queensland - Temperature forecast with heat stress threshold",
        x = "Date",
        colour = "Period",
        shape = "Period",
        fill = NULL
      ) +
      theme_minimal(base_size = 16) +
      theme(
        legend.position = "bottom",
        axis.text.x = element_text(angle = 45, hjust = 1)
      )
    #> Warning in scale_x_date(): A <numeric> value was passed to a Date scale.
    #> ℹ The value was converted to a <Date> object.

plot of chunk heat_stress_plot

## Advanced Usage: Helper Functions

{weatherOz} exports helper functions that provide finer control over forecast data processing.

### Example 9: Custom Aggregation with metno_resample_data_table()

Use the helper functions to create custom temporal aggregations.

    library(weatherOz)

    # Get hourly forecast
    forecast <- get_metno_forecast(
      latitude = -33.87,
      longitude = 151.21,
      api_key = Sys.getenv("METNO_API_KEY")
    )

    # Access the hourly data
    hourly_data <- forecast$data

    # Create daily aggregation manually
    daily_aggregated <- metno_resample_data_table(hourly_data, freq = "daily")

    # View the structure
    str(daily_aggregated)
    #> Classes 'data.table' and 'data.frame':   11 obs. of  10 variables:
    #>  $ date                   : Date, format: "2026-02-07" "2026-02-08" ...
    #>  $ min_temperature        : num  27.9 22.7 20.8 21 21.3 22.3 19.6 19.6 18.9 18.7 ...
    #>  $ max_temperature        : num  28.5 27.5 26.5 25 26.6 24.6 21.6 21.9 23.3 23.5 ...
    #>  $ total_precipitation    : num  0 7.6 21.4 0.2 0 11.9 0.7 0.3 0.4 1.7 ...
    #>  $ avg_wind_speed         : num  8 5.36 5.61 5.98 3.05 ...
    #>  $ max_wind_speed         : num  8.1 8.1 9.6 7.8 5.9 7.3 8.5 5.2 4.1 4.6 ...
    #>  $ avg_relative_humidity  : num  55.2 80.3 84.8 83.2 82.6 ...
    #>  $ avg_pressure           : num  1014 1016 1011 1012 1009 ...
    #>  $ avg_cloud_fraction     : num  100 100 90.6 17.8 33.4 ...
    #>  $ dominant_weather_symbol: chr  "cloudy" "heavyrain" "heavyrain" "partlycloudy_day" ...
    #>  - attr(*, ".internal.selfref")=<externalptr>

    # Compare with built-in function
    daily_builtin <- get_metno_daily_forecast(
      latitude = -33.87,
      longitude = 151.21,
      days = 5,
      api_key = Sys.getenv("METNO_API_KEY")
    )

    # Results should be identical
    all.equal(
      daily_aggregated[1:5, ],
      daily_builtin[1:5, ],
      check.attributes = FALSE
    )
    #> [1] TRUE

### Example 10: Understanding Weather Symbols

Weather symbols provide a categorical description of expected conditions.

    library(weatherOz)

    forecast <- get_metno_forecast(
      latitude = -37.81,
      longitude = 144.96,
      api_key = Sys.getenv("METNO_API_KEY")
    )

    # Extract unique weather symbols for the next 48 hours
    symbols_48h <- unique(forecast$data[1:48, symbol_code])

    cat("Weather symbols predicted in next 48 hours:\n")
    #> Weather symbols predicted in next 48 hours:
    print(symbols_48h)
    #>  [1] "clearsky_day"         "fair_night"           "partlycloudy_night"
    #>  [4] "clearsky_night"       "cloudy"               "partlycloudy_day"
    #>  [7] "lightrainshowers_day" "heavyrainshowers_day" "rain"
    #> [10] "fair_day"

    # Get daily forecast to see dominant symbols
    daily <- get_metno_daily_forecast(
      latitude = -37.81,
      longitude = 144.96,
      days = 5,
      api_key = Sys.getenv("METNO_API_KEY")
    )

    # Show dominant symbol for each day
    daily[, .(date, dominant_weather_symbol)]
    #>          date dominant_weather_symbol
    #>        <Date>                  <char>
    #> 1: 2026-02-07            clearsky_day
    #> 2: 2026-02-08    heavyrainshowers_day
    #> 3: 2026-02-09      partlycloudy_night
    #> 4: 2026-02-10        partlycloudy_day
    #> 5: 2026-02-11                  cloudy

## Appendix: Field Descriptions

### Hourly Forecast Fields

The `get_metno_forecast()` function returns a data.table with the following fields:

time
  POSIXct timestamp in AWST timezone (Australia/Perth)
air_temperature (°C)
  Air temperature at 2 metres above ground
precipitation_amount (mm)
  Precipitation amount for the 1-hour period
precipitation_min (mm)
  Minimum precipitation amount (lower bound of uncertainty)
precipitation_max (mm)
  Maximum precipitation amount (upper bound of uncertainty)
relative_humidity (%)
  Relative humidity at 2 metres above ground
wind_speed (m/s)
  Wind speed at 10 metres above ground
wind_from_direction (degrees)
  Wind direction (0-360°, meteorological convention)
air_pressure_at_sea_level (hPa)
  Air pressure reduced to mean sea level
cloud_area_fraction (%)
  Total cloud cover as percentage (0-100)
fog_area_fraction (%)
  Fog coverage as percentage (0-100)
dew_point_temperature (°C)
  Dew point temperature at 2 metres above ground
symbol_code
  METNO weather symbol code (categorical description)

### Daily Forecast Fields

The `get_metno_daily_forecast()` function returns a data.table with the following aggregated fields:

date
  Date of the forecast (Date class)
min_temperature (°C)
  Minimum temperature for the day
max_temperature (°C)
  Maximum temperature for the day
total_precipitation (mm)
  Total precipitation for the 24-hour period
avg_wind_speed (m/s)
  Average wind speed
max_wind_speed (m/s)
  Maximum wind speed (gust)
avg_relative_humidity (%)
  Average relative humidity
avg_pressure (hPa)
  Average air pressure at sea level
avg_cloud_fraction (%)
  Average cloud cover
dominant_weather_symbol
  Most frequently occurring weather symbol for the day

### Weather Symbol Codes

The MET Weather API uses symbolic weather codes that describe expected conditions. Common symbols include:

clearsky_day / clearsky_night
  Clear sky conditions
fair_day / fair_night
  Fair weather with few clouds
partlycloudy_day / partlycloudy_night
  Partly cloudy conditions
cloudy
  Cloudy conditions
lightrainshowers_day / lightrainshowers_night
  Light rain showers
rainshowers_day / rainshowers_night
  Rain showers
heavyrainshowers_day / heavyrainshowers_night
  Heavy rain showers
lightrain
  Light rain
rain
  Rain
heavyrain
  Heavy rain
lightrainandthunder
  Light rain and thunder
rainandthunder
  Rain and thunder
fog
  Fog

The full list of symbol codes is available in the METNO Locationforecast documentation.

### Metadata Structure

The metadata element returned by `get_metno_forecast()` contains:

latitude
  Request latitude (truncated to 4 decimal places)
longitude
  Request longitude (truncated to 4 decimal places)
format
  API format used (“compact” or “complete”)
status_code
  HTTP status code (200 for success)
retrieved_at
  POSIXct timestamp when data was retrieved
expires_raw
  RFC 1123 formatted expiry time from Expires header
expires
  POSIXct expiry time (for caching logic)
last_modified_raw
  RFC 1123 formatted last modification time
last_modified
  POSIXct last modification time

These metadata fields are particularly useful for implementing caching strategies and understanding when forecasts are updated at the source.

## References