Skip to content

Latest commit

 

History

32 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

omgo - Open-Meteo Go Client

A Go client for the Open-Meteo weather API. Supports forecasts, historical data, and ensemble forecasts through the library's typed metric catalog.

Important

v0.2.x is a significant refactor of v0.1.x and not backwards compatible. Once stable, this is likely to be promoted to v1.0.0. Please see the migration guide at the bottom of this readme.

Installation

go get github.com/hectormalot/omgo

Requires Go 1.21 or later.

Quick Start

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/hectormalot/omgo"
)

func main() {
    client := omgo.NewClient()

    // Create a forecast request for Amsterdam
    req, err := omgo.NewForecastRequest(52.3738, 4.8910)
    if err != nil {
        log.Fatal(err)
    }

    // Request temperature and precipitation
    req.WithHourly(omgo.HourlyTemperature2m, omgo.HourlyPrecipitation).
        WithDaily(omgo.DailyTemperature2mMax, omgo.DailyTemperature2mMin).
        WithTimezone("Europe/Berlin")

    weather, err := client.Forecast(context.Background(), req)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Current temperature: %.1f%s\n",
        weather.Hourly.Temperature2m[0],
        weather.HourlyUnits.Temperature2m)
}

Features

  • Type-safe metrics: All weather metrics are typed constants with autocomplete support
  • Builder pattern: Fluent API for building requests
  • 15-minutely data: High-resolution data for supported regions
  • Historical data: Access to historical weather archives
  • Ensemble forecasts: Dynamic members from one Ensemble API model per request
  • Units: Full control over temperature, wind speed, and precipitation units

Usage Examples

Current Weather

req, _ := omgo.NewForecastRequest(40.7128, -74.0060) // New York
req.WithCurrent(
    omgo.CurrentTemperature2m,
    omgo.CurrentWeatherCode,
    omgo.CurrentWindSpeed10m,
)

weather, _ := client.Forecast(context.Background(), req)

fmt.Printf("Temperature: %.1f°C\n", *weather.Current.Temperature2m)
fmt.Printf("Conditions: %s\n", weather.Current.WeatherCode.String())
fmt.Printf("Is daytime: %v\n", weather.Current.IsDaytime())

Hourly Forecast

req, _ := omgo.NewForecastRequest(52.52, 13.41)
req.WithHourly(
    omgo.HourlyTemperature2m,
    omgo.HourlyRelativeHumidity2m,
    omgo.HourlyPrecipitation,
    omgo.HourlyWeatherCode,
).WithForecastDays(7).
  WithTimezone("Europe/Berlin")

weather, _ := client.Forecast(context.Background(), req)

for i, t := range weather.Hourly.Times {
    fmt.Printf("%s: %.1f°C, %s\n",
        t.Format("Mon 15:04"),
        weather.Hourly.Temperature2m[i],
        weather.Hourly.WeatherCode[i].String())
}

Daily Forecast with Sunrise/Sunset

req, _ := omgo.NewForecastRequest(52.52, 13.41)
req.WithDaily(
    omgo.DailyTemperature2mMax,
    omgo.DailyTemperature2mMin,
    omgo.DailySunrise,
    omgo.DailySunset,
    omgo.DailyPrecipitationSum,
).WithTimezone("Europe/Berlin")

weather, _ := client.Forecast(context.Background(), req)

for i, t := range weather.Daily.Times {
    fmt.Printf("%s: %.0f°C to %.0f°C, Sunrise: %s\n",
        t.Format("Mon Jan 2"),
        weather.Daily.Temperature2mMin[i],
        weather.Daily.Temperature2mMax[i],
        weather.Daily.Sunrise[i].Format("15:04"))
}

15-Minutely Data

req, _ := omgo.NewForecastRequest(52.52, 13.41)
req.WithMinutely15(
    omgo.Minutely15Temperature2m,
    omgo.Minutely15Precipitation,
)

weather, _ := client.Forecast(context.Background(), req)

// High-resolution data for the next hours
for i, t := range weather.Minutely15.Times {
    fmt.Printf("%s: %.1f°C\n", t.Format("15:04"), weather.Minutely15.Temperature2m[i])
}

Multiple Models

req, err := omgo.NewForecastRequest(52.52, 13.41)
if err != nil {
    // handle error
}
req.WithModels("ecmwf_ifs", "gfs_global").
    WithHourly(omgo.HourlyTemperature2m).
    WithDaily(omgo.DailyTemperature2mMax)

weather, err := client.Forecast(context.Background(), req)
if err != nil {
    // handle error
}
ecmwf, hasECMWF := weather.HourlyByModel["ecmwf_ifs"]
gfs, hasGFS := weather.HourlyByModel["gfs_global"]
if hasECMWF && hasGFS {
    fmt.Println(ecmwf.Temperature2m[0], gfs.Temperature2m[0])
}

// Existing fields select the first requested model when it can be identified.
fmt.Println(weather.PrimaryModel) // ecmwf_ifs

Use the *ByModel maps when comparing deterministic model forecasts. The existing Hourly, Minutely15, and Daily fields remain compatibility views of the first explicitly requested model whenever that model has data for the cadence. If a model is outside its coverage area, Open-Meteo may collapse the response to unsuffixed fields. The *ByModel maps are then nil, PrimaryModel is empty, and the compatibility fields contain the surviving model's unattributed data.

Open-Meteo uses nulls for unavailable variables and shorter model horizons. The library's existing non-nullable typed slices decode those values as zero (and null timestamps as zero time.Time values), rather than truncating the series. Unit fields describe PrimaryModel only and may be "undefined" for unsupported model-variable combinations.

Ensemble Forecasts

Deterministic multi-model forecasts above use WithModels and *ByModel. Ensembles are distinct: construct one EnsembleRequest with exactly one ensemble model and read its dynamically discovered *ByMember maps.

req, err := omgo.NewEnsembleRequest(52.52, 13.41, "icon_seamless")
if err != nil {
    // handle error
}
req.WithHourly(omgo.HourlyTemperature2m).
    WithDaily(omgo.DailyTemperature2mMax).
    WithTimezone("Europe/Berlin")

weather, err := client.Ensemble(context.Background(), req)
if err != nil {
    // handle error
}
member0 := weather.HourlyByMember["member00"]
member1 := weather.HourlyByMember["member01"]
fmt.Println(member0.Temperature2m[0], member1.Temperature2m[0])

member00 represents unsuffixed member-valued series; member counts vary by model, and hourly and daily members are discovered independently. Calculated fields such as is_day, sunrise, sunset, and daylight duration are shared with each normalized member. This first Ensemble API surface accepts one model per request. As with other omgo responses, unavailable values returned as null are decoded as zero values by the existing non-nullable typed slices; a field whose entire JSON value is null remains a nil slice.

Historical Data

req, _ := omgo.NewHistoricalRequest(52.52, 13.41, "2023-06-01", "2023-06-30")
req.WithHourly(omgo.HourlyTemperature2m, omgo.HourlyPrecipitation).
    WithDaily(omgo.DailyTemperature2mMax).
    WithTimezone("Europe/Berlin")

weather, _ := client.Historical(context.Background(), req)

Custom Units

req, _ := omgo.NewForecastRequest(40.7128, -74.0060) // New York
req.WithHourly(omgo.HourlyTemperature2m, omgo.HourlyWindSpeed10m, omgo.HourlyPrecipitation).
    WithTemperatureUnit(omgo.Fahrenheit).
    WithWindSpeedUnit(omgo.MilesPerHour).
    WithPrecipitationUnit(omgo.Inches)

weather, _ := client.Forecast(context.Background(), req)

fmt.Printf("Temperature: %.1f%s\n",
    weather.Hourly.Temperature2m[0],
    weather.HourlyUnits.Temperature2m) // "°F"

Commercial API Access

client := omgo.NewClient(
    omgo.WithForecastURL("https://customer-api.open-meteo.com/v1/forecast"),
    omgo.WithAPIKey("your-api-key"),
)

For commercial Ensemble API access, configure its endpoint explicitly too:

client := omgo.NewClient(
    omgo.WithEnsembleURL("https://customer-ensemble-api.open-meteo.com/v1/ensemble"),
    omgo.WithAPIKey("your-api-key"),
)

Available Metrics

Current Weather

  • Temperature, apparent temperature, humidity
  • Precipitation, rain, showers, snowfall
  • Weather code, cloud cover
  • Pressure (MSL and surface)
  • Wind speed, direction, gusts
  • Is day/night

Hourly Metrics

  • Basic: temperature, humidity, dew point, apparent temperature
  • Precipitation: rain, snow, showers, probability
  • Cloud cover: total, low, mid, high
  • Wind: speed and direction at multiple heights (10m, 80m, 120m, 180m)
  • Solar radiation: shortwave, direct, diffuse, global tilted
  • Soil: temperature and moisture at multiple depths
  • Pressure levels: temperature, humidity, wind, cloud cover at 19 pressure levels

Daily Metrics

  • Temperature: max, min, mean
  • Apparent temperature: max, min, mean
  • Precipitation: sum, hours, probability
  • Sun: sunrise, sunset, sunshine duration, daylight duration
  • Wind: max speed, max gusts, dominant direction
  • UV index

15-Minutely Metrics

  • Temperature, humidity, apparent temperature
  • Precipitation, rain, snow
  • Solar radiation
  • Wind speed and direction
  • Lightning potential (regional)

Weather Codes

The WeatherCode type includes all WMO weather interpretation codes:

code := omgo.RainModerate // 63
fmt.Println(code.String())      // "Moderate rain"
fmt.Println(code.Description()) // "Moderate rain"

Error Handling

weather, err := client.Forecast(context.Background(), req)
if err != nil {
    var apiErr *omgo.APIError
    if errors.As(err, &apiErr) {
        fmt.Printf("API error %d: %s\n", apiErr.StatusCode, apiErr.Reason)
    }
}

Migration from v0.1.x

Version 0.2.0 is a complete rewrite with breaking changes:

Before (v0.1.x)

c, _ := omgo.NewClient()
loc, _ := omgo.NewLocation(52.52, 13.41)
opts := omgo.Options{
    HourlyMetrics: []string{"temperature_2m", "precipitation"},
}
res, _ := c.Forecast(ctx, loc, &opts)
temps := res.HourlyMetrics["temperature_2m"]

After (v0.2.0)

c := omgo.NewClient()
req, _ := omgo.NewForecastRequest(52.52, 13.41)
req.WithHourly(omgo.HourlyTemperature2m, omgo.HourlyPrecipitation)
weather, _ := c.Forecast(ctx, req)
temps := weather.Hourly.Temperature2m
unit := weather.HourlyUnits.Temperature2m // "°C"

Key Changes

  • NewClient() no longer returns an error
  • Options struct replaced by builder pattern (NewForecastRequest, NewHistoricalRequest)
  • Forecast response type renamed to Weather
  • Metrics are now typed constants instead of strings
  • Response data accessed via typed struct fields instead of maps
  • Units available via parallel *Units structs

License

MIT License.

Acknowledgments

This library uses the free Open-Meteo API. Consider supporting their work if you use it commercially.

About

Simple Go client for open-meteo.com

Resources

Stars

45 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages