nws

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 29, 2026 License: MIT Imports: 13 Imported by: 0

README

National Weather Service Go client

An unofficial Go client for the National Weather Service API. Requires Go 1.24 or later.

Installation

go get github.com/Three-Ships/national-weather-service-client

Usage

Fetch a forecast for a coordinate inside an application function that returns an error:

import (
    "context"
    "errors"
    "fmt"
    "time"

    nws "github.com/Three-Ships/national-weather-service-client"
)

client, err := nws.NewClient("example-company-weather/1.0 (weather-ops@example.com)")
if err != nil {
    return err
}

ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
defer cancel()

point, response, err := client.Points.Get(ctx, 38.8977, -77.0365)
if err != nil {
    return err
}
fmt.Println(response.StatusCode, response.Header.Get("Cache-Control"))
if point.Properties.Forecast == "" {
    return errors.New("point has no forecast URL")
}

// Follow the NWS link because grid assignments can change.
forecast, _, err := client.Gridpoints.GetForecast(ctx, point.Properties.Forecast, nil)
if err != nil {
    return err
}
for _, period := range forecast.Properties.Periods {
    fmt.Println(period.Name, period.StartTime, period.ShortForecast)
}

NewClient takes your application's HTTP User-Agent. NWS requires application identification

The client retries transport failures, incomplete successful-response reads, 429, and 5xx responses, with a default limit of four attempts. Each attempt is one http.Client.Do call and follows at most three validated redirects. Exponential backoff uses jitter and honors Retry-After on retryable responses; the caller's context deadline also bounds retry waits.

Resource URLs and redirects must remain within the configured API origin and path prefix. Query parameters use url.Values; malformed encoding in a resource URL is rejected.

Resource services and types

NewClient initializes Points, Gridpoints, Alerts, Stations, and Zones. All services share the same connection pool, retries, and pacing. Reuse the client and pass a context to each operation; no separate service setup is needed.

Response types live in the nws package, for example nws.Point, nws.AlertCollection, nws.GridpointForecast, and nws.ZoneForecast. nws.Feature[T] wraps a resource's properties with its GeoJSON metadata. Client.Get and Client.Fetch remain available for unmodeled endpoints, caller-defined response types, and discovered links.

This replaces the previous endpoints/* package API. Change calls such as points.Get(ctx, client, lat, lon) to client.Points.Get(ctx, lat, lon) and import response types from nws instead of an endpoint package.

Configuration

Pass options to NewClient to override the defaults:

  • WithHTTPClient: custom HTTP client; the default timeout is 30 seconds per attempt.
  • WithRetryPolicy: attempt budget and backoff; defaults to four total attempts with jitter.
  • WithRequestInterval: request spacing per client; defaults to 200 milliseconds.
  • WithBaseURL: API origin or test server; defaults to https://api.weather.gov.

Set a context deadline to bound the whole operation, including pacing and retry waits. Pacing does not coordinate separate clients or processes.

Query parameters and pagination

Pass query parameters as unescaped strings in url.Values; the client encodes them. NWS multi-value filters generally use comma-separated values, for example url.Values{"area": {"DC,MD"}}, rather than repeated keys. The client passes parameter names and values through to NWS, including parameters added after this library was released. Malformed encoding in a resource URL, such as area=%ZZ, returns an error rather than silently dropping a filter.

List operations return one page. When Pagination.Next is present, pass that URL to Client.Get with a nil query and a fresh collection as the destination. Do not reapply initial filters: the continuation URL already contains its query. Stop when pagination is absent or its next URL is empty. Use an overall context deadline or an application page limit to bound the traversal.

See the executable alert pagination example for the complete loop. Station and observation collections follow the same pattern.

Working with source values

The client preserves NWS timestamps, ISO 8601 intervals, unit codes, and nullable measurements. A nil QuantitativeValue.Value is missing data, not zero. Parse timestamps and convert units in your application when needed.

Forecast temperature and wind fields use json.RawMessage because NWS can return scalar values or quantitative-value objects depending on feature flags. The forecast decoding example handles both formats and distinguishes missing data from zero without converting units. If your application requests a fixed format, use Client.Get with your own response type. Feature-flag headers can be added by a caller-owned transport.

Batch workloads

Reuse a client for connection pooling and pacing. The default 200ms interval admits about five HTTP requests per second per client; retries and redirects consume those admissions too. Pacing does not cap in-flight requests or coordinate separate clients or processes. Bound worker concurrency in your application.

Successful responses are buffered in full before decoding, with no size limit; decoded data also consumes memory. Process collection pages incrementally rather than collecting every page in memory. The client does not cache responses or deduplicate simultaneous requests; applications with repeated lookups can add caching through a transport or their own data layer, honoring NWS cache headers.

Errors

Request failures return a *nws.RequestError. Use errors.As to inspect the HTTP status and NWS problem type:

var failure *nws.RequestError
if errors.As(err, &failure) {
    fmt.Println(failure.StatusCode, failure.Problem.Type)
}

The client handles retries. Retryable describes the failure, not whether attempts remain. Configuration and input validation errors return before HTTP. Use errors.Is to detect context cancellation or deadlines. Avoid adding an unbounded outer retry loop: each call already owns its attempt budget.

Testing

Use net/http/httptest with WithBaseURL to exercise HTTP behavior without contacting NWS. Inject an http.RoundTripper through WithHTTPClient when a test needs transport or response-stream failures.

Code organization

The public client, endpoint services, and response types share one nws package. Files separate their responsibilities without requiring separate package imports:

Responsibility Files
Client setup, service wiring, HTTP, retries, pacing, and decoding client.go
Endpoint methods and resource-specific types points.go, gridpoints.go, alerts.go, stations.go, zones.go
Related resource types and subresources Resource-prefixed files such as points_astronomy.go, gridpoints_forecast.go, stations_observations.go, and zones_forecast.go
Shared GeoJSON envelopes, measurements, and request errors geojson.go, quantitative_value.go, errors.go
Shared resource-identifier validation resource.go
Tests, executable examples, and response fixtures Matching *_test.go files and testdata/<resource>/

Endpoint services validate inputs, build routes, and decode into resource types through the shared client. HTTP policy stays in client.go. Adding an operation to an existing service only requires its resource files and tests; adding a new service also requires wiring it into NewClient.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/Three-Ships/national-weather-service-client.

Use the Go version in .tool-versions and run make check before submitting changes. make fmt formats Go files, make cover writes a coverage profile, and lefthook install enables the pre-commit checks. Keep tests beside their source in matching _test.go files, with named tables for related cases.

License

This package is available under the MIT License.

Documentation

Overview

Package nws provides an unofficial client for the National Weather Service API.

Create a Client with NewClient and call its resource services. Reuse the client across requests; it is safe for concurrent use.

Resource methods return a typed result, a Response, and an error. Results are nil on error. Client.Get documents which errors return a response.

See https://www.weather.gov/documentation/services-web-api for API guidance.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ActiveAlertCounts

type ActiveAlertCounts struct {
	Total   int            `json:"total"`
	Land    int            `json:"land"`
	Marine  int            `json:"marine"`
	Regions map[string]int `json:"regions"`
	Areas   map[string]int `json:"areas"`
	Zones   map[string]int `json:"zones"`
}

ActiveAlertCounts models the inline success-response schema for GET /alerts/active/count, grouping active alerts by land/marine coverage and NWS codes. Missing map keys mean NWS supplied no count for that code.

Schema: https://api.weather.gov/openapi.json#/paths/~1alerts~1active~1count/get/responses/200/content/application~1ld+json/schema

type Alert

type Alert struct {
	ID            string                       `json:"id"`
	AreaDesc      string                       `json:"areaDesc"`
	Geocode       *AlertGeocode                `json:"geocode"`
	AffectedZones []string                     `json:"affectedZones"`
	References    []AlertReference             `json:"references"`
	Sent          string                       `json:"sent"`
	Effective     string                       `json:"effective"`
	Onset         *string                      `json:"onset"`
	Expires       string                       `json:"expires"`
	Ends          *string                      `json:"ends"`
	Status        string                       `json:"status"`
	MessageType   string                       `json:"messageType"`
	Category      string                       `json:"category"`
	Severity      string                       `json:"severity"`
	Certainty     string                       `json:"certainty"`
	Urgency       string                       `json:"urgency"`
	Event         string                       `json:"event"`
	Sender        string                       `json:"sender"`
	SenderName    string                       `json:"senderName"`
	Headline      *string                      `json:"headline"`
	Description   string                       `json:"description"`
	Instruction   *string                      `json:"instruction"`
	Note          *string                      `json:"note"`
	Response      string                       `json:"response"`
	Parameters    map[string][]json.RawMessage `json:"parameters"`
	Scope         string                       `json:"scope"`
	Code          string                       `json:"code"`
	Language      string                       `json:"language"`
	Web           string                       `json:"web"`
	EventCode     map[string][]json.RawMessage `json:"eventCode"`
}

Alert models the NWS OpenAPI Alert schema used as AlertGeoJson.properties. It keeps NWS CAP-derived fields, with enums and timestamps left as strings.

Schema: https://api.weather.gov/openapi.json#/components/schemas/Alert

type AlertCollection

type AlertCollection struct {
	FeatureCollection[Alert]
	Title      string          `json:"title,omitempty"`
	Updated    string          `json:"updated,omitempty"`
	Pagination *PaginationInfo `json:"pagination,omitempty"`
}

AlertCollection models the NWS OpenAPI AlertCollectionGeoJson and ActiveAlertCollectionGeoJson schemas. Pagination is optional, belongs to AlertCollectionGeoJson, and is not followed automatically.

Schemas: https://api.weather.gov/openapi.json#/components/schemas/AlertCollectionGeoJson https://api.weather.gov/openapi.json#/components/schemas/ActiveAlertCollectionGeoJson

type AlertEventTypes

type AlertEventTypes struct {
	Types []string `json:"eventTypes"`
}

AlertEventTypes models the inline success-response schema for GET /alerts/types, containing NWS's recognized alert event names.

Schema: https://api.weather.gov/openapi.json#/paths/~1alerts~1types/get/responses/200/content/application~1ld+json/schema

type AlertGeocode

type AlertGeocode struct {
	UGC  []string `json:"UGC"`
	SAME []string `json:"SAME"`
}

AlertGeocode models the inline geocode object in the NWS OpenAPI Alert schema, listing NWS public zone/county identifiers and SAME codes.

Schema: https://api.weather.gov/openapi.json#/components/schemas/Alert/properties/geocode

type AlertReference

type AlertReference struct {
	ID         string `json:"@id"`
	Identifier string `json:"identifier"`
	Sender     string `json:"sender"`
	Sent       string `json:"sent"`
}

AlertReference models an inline references entry in the NWS OpenAPI Alert schema, identifying a prior alert that this alert updates or replaces.

Schema: https://api.weather.gov/openapi.json#/components/schemas/Alert/properties/references/items

type AlertsService

type AlertsService struct {
	// contains filtered or unexported fields
}

AlertsService provides access to NWS alerts endpoints. Use the corresponding field on a Client constructed with NewClient.

func (*AlertsService) CountActive

func (s *AlertsService) CountActive(ctx context.Context) (*ActiveAlertCounts, *Response, error)

CountActive returns the API's unfiltered active-alert counts.

func (*AlertsService) Get

func (s *AlertsService) Get(ctx context.Context, id string) (*Feature[Alert], *Response, error)

Get fetches an alert by its NWS identifier, not its API URL.

func (*AlertsService) List

List returns one page of alerts with NWS query parameters (including cursor). Follow AlertCollection.Pagination.Next with Client.Get into an AlertCollection.

Example
package main

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"time"

	nws "github.com/Three-Ships/national-weather-service-client"
)

func main() {
	// A local server supplies two pages so the example does not contact NWS.
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.RequestURI() {
		case "/alerts?area=DC&status=actual":
			io.WriteString(w, `{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"event":"Winter Weather Advisory"}}],"pagination":{"next":"/alerts?area=DC&cursor=next&status=actual"}}`)
		case "/alerts?area=DC&cursor=next&status=actual":
			io.WriteString(w, `{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"event":"Wind Advisory"}}]}`)
		default:
			http.Error(w, "unexpected request: "+r.URL.String(), http.StatusBadRequest)
		}
	}))
	defer server.Close()
	client, err := nws.NewClient("example-reader/1.0 (weather@example.com)",
		nws.WithRequestInterval(0), nws.WithBaseURL(server.URL))
	if err != nil {
		panic(err)
	}
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	page, _, err := client.Alerts.List(ctx, url.Values{"area": {"DC"}, "status": {"actual"}})
	if err != nil {
		panic(err)
	}
	for {
		for _, feature := range page.Features {
			fmt.Println(feature.Properties.Event)
		}
		if page.Pagination == nil || page.Pagination.Next == "" {
			break
		}
		// Next contains the continuation query; do not reapply initial filters.
		// Decode into a fresh collection so omitted metadata cannot remain stale.
		var next nws.AlertCollection
		if _, err := client.Get(ctx, page.Pagination.Next, nil, &next); err != nil {
			panic(err)
		}
		page = &next
	}
}
Output:
Winter Weather Advisory
Wind Advisory

func (*AlertsService) ListActive

func (s *AlertsService) ListActive(ctx context.Context, query url.Values) (*AlertCollection, *Response, error)

ListActive returns currently active alerts with caller-selected filters. No status filter is imposed; pass status=actual to exclude exercises/tests.

func (*AlertsService) ListTypes

func (s *AlertsService) ListTypes(ctx context.Context) (*AlertEventTypes, *Response, error)

ListTypes returns recognized event names without treating the list as a fixed enum enforced by the client.

type AstronomicalData

type AstronomicalData struct {
	Sunrise                   *string `json:"sunrise"`
	Sunset                    *string `json:"sunset"`
	Transit                   *string `json:"transit"`
	CivilTwilightBegin        *string `json:"civilTwilightBegin"`
	CivilTwilightEnd          *string `json:"civilTwilightEnd"`
	NauticalTwilightBegin     *string `json:"nauticalTwilightBegin"`
	NauticalTwilightEnd       *string `json:"nauticalTwilightEnd"`
	AstronomicalTwilightBegin *string `json:"astronomicalTwilightBegin"`
	AstronomicalTwilightEnd   *string `json:"astronomicalTwilightEnd"`
}

AstronomicalData models the NWS OpenAPI AstronomicalData schema, keeping event times as RFC3339 strings. Events may be null during polar day or night. The client neither calculates times nor converts time zones.

Schema: https://api.weather.gov/openapi.json#/components/schemas/AstronomicalData

type Client

type Client struct {
	Points     *PointsService
	Gridpoints *GridpointsService
	Alerts     *AlertsService
	Stations   *StationsService
	Zones      *ZonesService
	// contains filtered or unexported fields
}

Client is safe for concurrent requests when constructed with NewClient. Reuse an instance to share connection pooling and pacing; do not copy it after use. Resource services share this client's HTTP policy. Do not replace service fields while the client is in use.

func NewClient

func NewClient(userAgent string, options ...Option) (*Client, error)

NewClient validates request configuration before any requests. userAgent should identify the application and include contact details, for example "weather-reader/1.0 (weather@example.com)". Empty values and control characters are rejected. The default HTTP timeout is 30s per attempt; WithHTTPClient replaces it. Set a context deadline to also bound pacing and retry waits.

func (*Client) Fetch

func (c *Client) Fetch(ctx context.Context, resource string, query url.Values, accept string) (*Response, error)

Fetch retrieves a buffered response, including non-JSON representations. resource may be an API-relative path or a discovered same-origin URL; both must stay inside any configured path prefix. query replaces matching parameters and retains the rest. Malformed URL query encoding fails before HTTP rather than dropping filters; parameter names and values are otherwise passed through to NWS. An empty accept selects application/geo+json.

Each attempt follows at most three same-origin redirects within the base path. Attempts count Do calls, not redirect hops. Retries cover transport failures, incomplete successful-response reads, 429, and 5xx. Other HTTP statuses are terminal. Retry-After accepts delta-seconds and HTTP dates. On failure, Fetch returns a nil response; request failures are reported as RequestError.

func (*Client) Get

func (c *Client) Get(ctx context.Context, resource string, query url.Values, dst any) (*Response, error)

Get fetches JSON using Fetch's request policy and decodes it into dst, which must be a non-nil pointer. GeoJSON and JSON are preferred over JSON-LD so resource packages receive feature envelopes when the endpoint offers them. Use json.RawMessage or a caller-defined struct for unmodeled fields.

Get returns the buffered Response after a successful fetch, including when decoding fails. Fetch failures return a nil response. Decoding errors, including invalid destination types, return a non-retryable RequestError; dst may be partially populated and must not be used after a decoding error.

Example
client, err := NewClient("example-reader/1.0 (weather@example.com)",
	WithHTTPClient(&http.Client{Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
		return testResponse([]byte(`{"glossary":[{"term":"Dew Point","definition":"A temperature."}]}`)), nil
	})}))
if err != nil {
	panic(err)
}
var result struct {
	Glossary []struct{ Term, Definition string }
}
if _, err := client.Get(context.Background(), "/glossary", nil, &result); err != nil {
	panic(err)
}
fmt.Println(result.Glossary[0].Term)
Output:
Dew Point

type CloudLayer

type CloudLayer struct {
	Base   QuantitativeValue `json:"base"`
	Amount string            `json:"amount"`
}

CloudLayer models an inline cloudLayers entry in the NWS OpenAPI Observation schema, keeping NWS base-height units and the METAR sky-coverage code.

Schema: https://api.weather.gov/openapi.json#/components/schemas/Observation/properties/cloudLayers/items

type Feature

type Feature[T any] struct {
	Context    json.RawMessage `json:"@context,omitempty"`
	ID         string          `json:"id,omitempty"`
	Type       string          `json:"type"`
	Geometry   json.RawMessage `json:"geometry"`
	Properties T               `json:"properties"`
}

Feature models the NWS OpenAPI GeoJsonFeature schema with properties of type T. Geometry and Context retain their source representation because NWS serves multiple geometry and JSON-LD context shapes.

Schema: https://api.weather.gov/openapi.json#/components/schemas/GeoJsonFeature

type FeatureCollection

type FeatureCollection[T any] struct {
	Context  json.RawMessage `json:"@context,omitempty"`
	Type     string          `json:"type"`
	Features []Feature[T]    `json:"features"`
}

FeatureCollection models one page of the NWS OpenAPI GeoJsonFeatureCollection schema. Resource packages add collection-specific metadata, such as alert timestamps and station pagination.

Schema: https://api.weather.gov/openapi.json#/components/schemas/GeoJsonFeatureCollection

type Gridpoint

type Gridpoint struct {
	ID             string                              `json:"@id,omitempty"`
	Type           string                              `json:"@type,omitempty"`
	Context        json.RawMessage                     `json:"@context,omitempty"`
	Geometry       string                              `json:"geometry,omitempty"`
	UpdateTime     string                              `json:"updateTime"`
	ValidTimes     string                              `json:"validTimes"`
	Elevation      QuantitativeValue                   `json:"elevation"`
	ForecastOffice string                              `json:"forecastOffice"`
	GridID         string                              `json:"gridId"`
	GridX          int                                 `json:"gridX"`
	GridY          int                                 `json:"gridY"`
	Weather        *GridpointLayer[[]GridpointWeather] `json:"weather,omitempty"`
	Hazards        *GridpointLayer[[]GridpointHazard]  `json:"hazards,omitempty"`
	Layers         map[string]GridpointLayer[*float64] `json:"-"`
}

Gridpoint models the NWS OpenAPI Gridpoint schema used as GridpointGeoJson.properties. Numeric Layers are keyed by source names such as "temperature", "skyCover", and "quantitativePrecipitation". The schema makes these additional properties, so new numeric layers are retained without adding fields. Layers are stored in a map here but remain top-level properties in JSON. Missing layers remain absent; null values remain nil. Weather and Hazards have distinct categorical schemas.

Schema: https://api.weather.gov/openapi.json#/components/schemas/Gridpoint

func (Gridpoint) MarshalJSON

func (g Gridpoint) MarshalJSON() ([]byte, error)

MarshalJSON emits Layers as top-level properties to match the NWS JSON format. Layer names that conflict with metadata or categorical fields are errors.

func (*Gridpoint) UnmarshalJSON

func (g *Gridpoint) UnmarshalJSON(data []byte) error

UnmarshalJSON rejects additional properties that cannot be decoded as numeric layers.

type GridpointForecast

type GridpointForecast struct {
	Units             string                    `json:"units"`
	ForecastGenerator string                    `json:"forecastGenerator"`
	GeneratedAt       string                    `json:"generatedAt"`
	UpdateTime        string                    `json:"updateTime"`
	ValidTimes        string                    `json:"validTimes"`
	Elevation         QuantitativeValue         `json:"elevation"`
	Periods           []GridpointForecastPeriod `json:"periods"`
}

GridpointForecast models the shared properties of the NWS OpenAPI Gridpoint12hForecast and GridpointHourlyForecast schemas. Feature[GridpointForecast] supplies the GeoJSON envelope for Gridpoint12hForecastGeoJson or GridpointHourlyForecastGeoJson.

Schemas: https://api.weather.gov/openapi.json#/components/schemas/Gridpoint12hForecast https://api.weather.gov/openapi.json#/components/schemas/GridpointHourlyForecast

type GridpointForecastPeriod

type GridpointForecastPeriod struct {
	Number                     int                `json:"number"`
	Name                       string             `json:"name"`
	StartTime                  string             `json:"startTime"`
	EndTime                    string             `json:"endTime"`
	IsDaytime                  bool               `json:"isDaytime"`
	Temperature                json.RawMessage    `json:"temperature"`
	TemperatureUnit            string             `json:"temperatureUnit,omitempty"`
	TemperatureTrend           *string            `json:"temperatureTrend"`
	ProbabilityOfPrecipitation QuantitativeValue  `json:"probabilityOfPrecipitation"`
	Dewpoint                   *QuantitativeValue `json:"dewpoint,omitempty"`
	RelativeHumidity           *QuantitativeValue `json:"relativeHumidity,omitempty"`
	WindSpeed                  json.RawMessage    `json:"windSpeed"`
	WindGust                   json.RawMessage    `json:"windGust,omitempty"`
	WindDirection              string             `json:"windDirection"`
	Icon                       string             `json:"icon"`
	ShortForecast              string             `json:"shortForecast"`
	DetailedForecast           string             `json:"detailedForecast"`
}

GridpointForecastPeriod combines the NWS OpenAPI Gridpoint12hForecastPeriod and GridpointHourlyForecastPeriod schemas. Dewpoint and RelativeHumidity are hourly-only fields. It uses raw JSON for fields whose format depends on NWS feature flags: Temperature is an integer or QuantitativeValue; WindSpeed is a string or QuantitativeValue; WindGust also permits null. Keeping these fields raw leaves callers free to choose a format without converting values or units.

Schemas: https://api.weather.gov/openapi.json#/components/schemas/Gridpoint12hForecastPeriod https://api.weather.gov/openapi.json#/components/schemas/GridpointHourlyForecastPeriod

Example
package main

import (
	"encoding/json"
	"fmt"

	nws "github.com/Three-Ships/national-weather-service-client"
)

func main() {
	// Display both NWS formats without converting their values or units.
	formatValue := func(raw json.RawMessage, scalarUnit string) string {
		var value any
		if err := json.Unmarshal(raw, &value); err != nil {
			panic(err)
		}
		switch value := value.(type) {
		case nil:
			return "missing"
		case float64:
			return fmt.Sprintf("%g %s", value, scalarUnit)
		case string:
			return value
		default:
			var measurement nws.QuantitativeValue
			if err := json.Unmarshal(raw, &measurement); err != nil {
				panic(err)
			}
			if measurement.Value == nil {
				return "missing"
			}
			return fmt.Sprintf("%g %s", *measurement.Value, measurement.UnitCode)
		}
	}

	periods := []nws.GridpointForecastPeriod{
		{
			Temperature:     json.RawMessage(`32`),
			TemperatureUnit: "F",
			WindSpeed:       json.RawMessage(`"5 to 10 mph"`),
		},
		{
			Temperature: json.RawMessage(`{"value":0,"unitCode":"wmoUnit:degC"}`),
			WindSpeed:   json.RawMessage(`{"value":null,"unitCode":"wmoUnit:km_h-1"}`),
		},
	}
	for _, period := range periods {
		fmt.Println(formatValue(period.Temperature, period.TemperatureUnit))
		fmt.Println(formatValue(period.WindSpeed, ""))
	}
}
Output:
32 F
5 to 10 mph
0 wmoUnit:degC
missing

type GridpointHazard

type GridpointHazard struct {
	Phenomenon   string `json:"phenomenon"`
	Significance string `json:"significance"`
	EventNumber  *int   `json:"event_number"`
}

GridpointHazard models an inline hazard value in the NWS OpenAPI Gridpoint schema, retaining source P-VTEC codes and a nullable event number.

Schema: https://api.weather.gov/openapi.json#/components/schemas/Gridpoint/properties/hazards/properties/values/items/properties/value/items

type GridpointIntervalValue

type GridpointIntervalValue[T any] struct {
	ValidTime string `json:"validTime"`
	Value     T      `json:"value"`
}

GridpointIntervalValue generalizes validTime/value entries in NWS OpenAPI gridpoint layers. GridpointQuantitativeValueLayer entries use *float64 for T; weather and hazard entries use slices of their categorical values. ValidTime remains an ISO 8601 start/duration or start/end string. Callers decide how to expand or align the interval.

Schema: https://api.weather.gov/openapi.json#/components/schemas/GridpointQuantitativeValueLayer/properties/values/items

type GridpointLayer

type GridpointLayer[T any] struct {
	UOM    string                      `json:"uom,omitempty"`
	Values []GridpointIntervalValue[T] `json:"values"`
}

GridpointLayer generalizes the NWS OpenAPI GridpointQuantitativeValueLayer schema and the inline weather/hazard layers in Gridpoint. Numeric layers use GridpointLayer[*float64]. Values retain source order; UOM may be absent for categorical layers.

Schema: https://api.weather.gov/openapi.json#/components/schemas/GridpointQuantitativeValueLayer

type GridpointWeather

type GridpointWeather struct {
	Coverage   *string           `json:"coverage"`
	Weather    *string           `json:"weather"`
	Intensity  *string           `json:"intensity"`
	Visibility QuantitativeValue `json:"visibility"`
	Attributes []string          `json:"attributes"`
}

GridpointWeather models an inline weather value in the NWS OpenAPI Gridpoint schema, describing one expected weather phenomenon in a grid interval.

Schema: https://api.weather.gov/openapi.json#/components/schemas/Gridpoint/properties/weather/properties/values/items/properties/value/items

type GridpointsService

type GridpointsService struct {
	// contains filtered or unexported fields
}

GridpointsService provides access to NWS gridpoints endpoints. Use the corresponding field on a Client constructed with NewClient.

func (*GridpointsService) Get

func (s *GridpointsService) Get(ctx context.Context, resource string) (*Feature[Gridpoint], *Response, error)

Get fetches a discovered forecastGridData URL (or API path).

func (*GridpointsService) GetForecast

func (s *GridpointsService) GetForecast(ctx context.Context, resource string, query url.Values) (*Feature[GridpointForecast], *Response, error)

GetForecast fetches a forecast or forecastHourly link returned by PointsService.Get. query accepts NWS parameters such as units=si. Use a caller-owned transport for feature-flag headers, or Client.Get for caller-defined period types.

type MetarPhenomenon

type MetarPhenomenon struct {
	Intensity  *string `json:"intensity"`
	Modifier   *string `json:"modifier"`
	Weather    string  `json:"weather"`
	RawString  string  `json:"rawString"`
	InVicinity bool    `json:"inVicinity"`
}

MetarPhenomenon models the NWS OpenAPI MetarPhenomenon schema, retaining decoded METAR codes and raw text. Intensity and Modifier may be null; enums remain strings so new NWS codes are not discarded.

Schema: https://api.weather.gov/openapi.json#/components/schemas/MetarPhenomenon

type Observation

type Observation struct {
	Context                   json.RawMessage   `json:"@context,omitempty"`
	Geometry                  string            `json:"geometry,omitempty"`
	ID                        string            `json:"@id,omitempty"`
	Type                      string            `json:"@type,omitempty"`
	Elevation                 QuantitativeValue `json:"elevation"`
	Station                   string            `json:"station"`
	StationID                 string            `json:"stationId"`
	StationName               string            `json:"stationName"`
	Timestamp                 string            `json:"timestamp"`
	RawMessage                string            `json:"rawMessage"`
	TextDescription           string            `json:"textDescription"`
	Icon                      *string           `json:"icon"`
	PresentWeather            []MetarPhenomenon `json:"presentWeather"`
	Temperature               QuantitativeValue `json:"temperature"`
	Dewpoint                  QuantitativeValue `json:"dewpoint"`
	WindDirection             QuantitativeValue `json:"windDirection"`
	WindSpeed                 QuantitativeValue `json:"windSpeed"`
	WindGust                  QuantitativeValue `json:"windGust"`
	BarometricPressure        QuantitativeValue `json:"barometricPressure"`
	SeaLevelPressure          QuantitativeValue `json:"seaLevelPressure"`
	Visibility                QuantitativeValue `json:"visibility"`
	MaxTemperatureLast24Hours QuantitativeValue `json:"maxTemperatureLast24Hours"`
	MinTemperatureLast24Hours QuantitativeValue `json:"minTemperatureLast24Hours"`
	PrecipitationLastHour     QuantitativeValue `json:"precipitationLastHour"`
	PrecipitationLast3Hours   QuantitativeValue `json:"precipitationLast3Hours"`
	PrecipitationLast6Hours   QuantitativeValue `json:"precipitationLast6Hours"`
	RelativeHumidity          QuantitativeValue `json:"relativeHumidity"`
	WindChill                 QuantitativeValue `json:"windChill"`
	HeatIndex                 QuantitativeValue `json:"heatIndex"`
	CloudLayers               []CloudLayer      `json:"cloudLayers"`
}

Observation models the NWS OpenAPI Observation schema used as ObservationGeoJson.properties. It keeps NWS measurements, unit codes, quality-control flags, and timestamps unchanged. A measurement's nil Value means the reading is missing.

Schema: https://api.weather.gov/openapi.json#/components/schemas/Observation

type ObservationCollection

type ObservationCollection struct {
	FeatureCollection[Observation]
	Pagination *PaginationInfo `json:"pagination,omitempty"`
}

ObservationCollection models one page of the NWS OpenAPI ObservationCollectionGeoJson schema. Pagination is optional and is not followed automatically.

Schema: https://api.weather.gov/openapi.json#/components/schemas/ObservationCollectionGeoJson

type Option

type Option func(*Client)

Option configures a client. Repeated options replace earlier values.

func WithBaseURL

func WithBaseURL(baseURL string) Option

WithBaseURL sets an absolute HTTP(S) API origin, optionally with a path prefix. Use an httptest server URL for local tests. Discovered links must remain on this origin and inside that prefix.

func WithHTTPClient

func WithHTTPClient(client *http.Client) Option

WithHTTPClient uses client's transport, cookie jar, and timeout. Its redirect policy is replaced on a copy, leaving the supplied client's settings unchanged. Shared dependencies must be safe for concurrent use. The caller owns transport cleanup.

func WithRequestInterval

func WithRequestInterval(interval time.Duration) Option

WithRequestInterval sets the minimum spacing between HTTP attempt admissions, including retries and redirects. Zero disables pacing; the default is 200ms. It is not an in-flight limit or a quota shared between client instances.

func WithRetryPolicy

func WithRetryPolicy(policy RetryPolicy) Option

WithRetryPolicy replaces the default four attempts, 1s initial delay, and 30s maximum delay. All fields must be positive, with MaxDelay >= InitialDelay.

type PaginationInfo

type PaginationInfo struct {
	Next string `json:"next"`
}

PaginationInfo models the NWS OpenAPI PaginationInfo schema. Its next-page URL can be passed to Client.Get.

Schema: https://api.weather.gov/openapi.json#/components/schemas/PaginationInfo

type Point

type Point struct {
	ID                  string                     `json:"@id,omitempty"`
	Type                string                     `json:"type"`
	CWA                 string                     `json:"cwa"`
	ForecastOffice      string                     `json:"forecastOffice"`
	GridID              string                     `json:"gridId"`
	GridX               int                        `json:"gridX"`
	GridY               int                        `json:"gridY"`
	Forecast            string                     `json:"forecast"`
	ForecastHourly      string                     `json:"forecastHourly"`
	ForecastGridData    string                     `json:"forecastGridData"`
	ObservationStations string                     `json:"observationStations"`
	RelativeLocation    *Feature[RelativeLocation] `json:"relativeLocation,omitempty"`
	ForecastZone        string                     `json:"forecastZone"`
	County              string                     `json:"county"`
	FireWeatherZone     string                     `json:"fireWeatherZone"`
	TimeZone            string                     `json:"timeZone"`
	RadarStation        string                     `json:"radarStation"`
	AstronomicalData    *AstronomicalData          `json:"astronomicalData,omitempty"`
	NWR                 *WeatherRadio              `json:"nwr,omitempty"`
}

Point models the NWS OpenAPI Point schema used as PointGeoJson.properties. ForecastZone, County, FireWeatherZone, and ForecastOffice are URLs to fetch separately. AstronomicalData applies to the current day reported by NWS.

Schema: https://api.weather.gov/openapi.json#/components/schemas/Point

type PointsService

type PointsService struct {
	// contains filtered or unexported fields
}

PointsService provides access to NWS points endpoints. Use the corresponding field on a Client constructed with NewClient.

func (*PointsService) Get

func (s *PointsService) Get(ctx context.Context, latitude, longitude float64) (*Feature[Point], *Response, error)

Get resolves a coordinate to NWS metadata and resource links. Coordinates are rounded to four decimal places as expected by NWS. Non-finite or out-of-range inputs fail before any HTTP request; linked resources require separate calls.

Example
package main

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"

	nws "github.com/Three-Ships/national-weather-service-client"
)

func main() {
	// A local server keeps this example independent of live weather data.
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Cache-Control", "public, max-age=300")
		io.WriteString(w, `{"type":"Feature","properties":{"gridId":"LWX","timeZone":"America/New_York","forecastGridData":"https://api.weather.gov/gridpoints/LWX/97,71"}}`)
	}))
	defer server.Close()
	client, err := nws.NewClient("example-reader/1.0 (weather@example.com)", nws.WithBaseURL(server.URL))
	if err != nil {
		panic(err)
	}
	point, response, err := client.Points.Get(context.Background(), 38.8977, -77.0365)
	if err != nil {
		panic(err)
	}
	fmt.Println(response.Header.Get("Cache-Control"))
	fmt.Println(point.Properties.TimeZone)
	fmt.Println(point.Properties.ForecastGridData)
}
Output:
public, max-age=300
America/New_York
https://api.weather.gov/gridpoints/LWX/97,71

type ProblemDetail

type ProblemDetail struct {
	Type          string `json:"type"`
	Title         string `json:"title"`
	Status        int    `json:"status"`
	Detail        string `json:"detail"`
	Instance      string `json:"instance"`
	CorrelationID string `json:"correlationId"`
}

ProblemDetail models the NWS OpenAPI ProblemDetail schema, an RFC 7807 error document. Its Status may differ from the actual HTTP status; use RequestError.StatusCode for retry decisions.

Schema: https://api.weather.gov/openapi.json#/components/schemas/ProblemDetail

type QuantitativeValue

type QuantitativeValue struct {
	Value          *float64 `json:"value"`
	MaxValue       *float64 `json:"maxValue,omitempty"`
	MinValue       *float64 `json:"minValue,omitempty"`
	UnitCode       string   `json:"unitCode"`
	QualityControl string   `json:"qualityControl,omitempty"`
}

QuantitativeValue models the NWS OpenAPI QuantitativeValue measurement schema. It keeps NWS units and optional bounds. A nil Value means missing data; a non-nil pointer to zero is a real measurement. MinValue and MaxValue come from NWS and are never calculated here.

Schema: https://api.weather.gov/openapi.json#/components/schemas/QuantitativeValue

type RelativeLocation

type RelativeLocation struct {
	City     string            `json:"city"`
	State    string            `json:"state"`
	Distance QuantitativeValue `json:"distance"`
	Bearing  QuantitativeValue `json:"bearing"`
}

RelativeLocation models the NWS OpenAPI RelativeLocation schema used as RelativeLocationGeoJson.properties. It describes a nearby named location.

Schema: https://api.weather.gov/openapi.json#/components/schemas/RelativeLocation

type RequestError

type RequestError struct {
	URL        string
	Attempts   int
	StatusCode int
	Retryable  bool
	Problem    ProblemDetail
	Body       string
	Err        error
}

RequestError reports a failure after the client has applied its retry policy. Inspect it with errors.As; errors.Is can inspect the wrapped cause. Retryable describes the cause, not whether attempts remain. Attempts counts http.Client.Do calls, not redirect hops. StatusCode is zero when no HTTP status is available. Body contains at most 64 KiB of an HTTP error response.

func (*RequestError) Error

func (e *RequestError) Error() string

func (*RequestError) Unwrap

func (e *RequestError) Unwrap() error

Unwrap preserves transport, decoding, and context error identity.

type Response

type Response struct {
	URL        string
	StatusCode int
	Header     http.Header
	Body       []byte
	// contains filtered or unexported fields
}

Response contains the unmodified source body and final response headers. Successful bodies are buffered in full; there is nothing to close.

type RetryPolicy

type RetryPolicy struct {
	MaxAttempts  int
	InitialDelay time.Duration
	MaxDelay     time.Duration
}

RetryPolicy bounds total HTTP attempts, including the initial request. InitialDelay and MaxDelay bound exponential full-jitter backoff. Retry-After is a minimum wait and may exceed MaxDelay; the caller's context still wins.

type Station

type Station struct {
	ID                string             `json:"@id,omitempty"`
	Elevation         QuantitativeValue  `json:"elevation"`
	StationIdentifier string             `json:"stationIdentifier"`
	Name              string             `json:"name"`
	TimeZone          string             `json:"timeZone"`
	Provider          string             `json:"provider"`
	SubProvider       string             `json:"subProvider"`
	Forecast          string             `json:"forecast"`
	County            string             `json:"county"`
	FireWeatherZone   string             `json:"fireWeatherZone"`
	Distance          *QuantitativeValue `json:"distance,omitempty"`
	Bearing           *QuantitativeValue `json:"bearing,omitempty"`
}

Station models the NWS OpenAPI ObservationStation schema used as ObservationStationGeoJson.properties, containing observation-station metadata.

Schema: https://api.weather.gov/openapi.json#/components/schemas/ObservationStation

type StationCollection

type StationCollection struct {
	FeatureCollection[Station]
	Pagination          *PaginationInfo `json:"pagination,omitempty"`
	ObservationStations []string        `json:"observationStations,omitempty"`
}

StationCollection models one page of the NWS OpenAPI ObservationStationCollectionGeoJson schema. ObservationStations retains source station URLs when present; Pagination is not followed automatically.

Schema: https://api.weather.gov/openapi.json#/components/schemas/ObservationStationCollectionGeoJson

type StationsService

type StationsService struct {
	// contains filtered or unexported fields
}

StationsService provides access to NWS stations endpoints. Use the corresponding field on a Client constructed with NewClient.

func (*StationsService) Get

Get fetches station metadata by its NWS identifier.

func (*StationsService) GetLatestObservation

func (s *StationsService) GetLatestObservation(ctx context.Context, stationID string, query url.Values) (*Feature[Observation], *Response, error)

GetLatestObservation accepts the NWS require_qc query parameter. It does not impose a quality-control requirement on callers.

func (*StationsService) GetObservation

func (s *StationsService) GetObservation(ctx context.Context, stationID, timestamp string) (*Feature[Observation], *Response, error)

GetObservation requires an RFC3339 timestamp. Its original offset and precision are retained in the request; invalid timestamps fail before HTTP.

func (*StationsService) List

func (s *StationsService) List(ctx context.Context, resource string, query url.Values) (*StationCollection, *Response, error)

List fetches one station collection page. Resource may be "stations" or a discovered observationStations link; query accepts the endpoint's filters.

func (*StationsService) ListObservations

func (s *StationsService) ListObservations(ctx context.Context, stationID string, query url.Values) (*ObservationCollection, *Response, error)

ListObservations returns station history with NWS query parameters such as start, end, limit, and cursor. No time window or quality filter is imposed.

type WeatherRadio

type WeatherRadio struct {
	Transmitter    *string `json:"transmitter"`
	SAMECode       string  `json:"sameCode"`
	AreaBroadcast  *string `json:"areaBroadcast"`
	PointBroadcast *string `json:"pointBroadcast"`
}

WeatherRadio models the inline nwr object in the NWS OpenAPI Point schema, containing NOAA Weather Radio discovery metadata.

Schema: https://api.weather.gov/openapi.json#/components/schemas/Point/properties/nwr

type Zone

type Zone struct {
	ID                      string   `json:"id"`
	Type                    string   `json:"type"`
	Name                    string   `json:"name"`
	EffectiveDate           string   `json:"effectiveDate"`
	ExpirationDate          string   `json:"expirationDate"`
	State                   *string  `json:"state"`
	ForecastOffice          string   `json:"forecastOffice"`
	GridIdentifier          string   `json:"gridIdentifier"`
	AWIPSLocationIdentifier string   `json:"awipsLocationIdentifier"`
	CWA                     []string `json:"cwa"`
	ForecastOffices         []string `json:"forecastOffices"`
	TimeZone                []string `json:"timeZone"`
	ObservationStations     []string `json:"observationStations"`
	RadarStation            *string  `json:"radarStation"`
}

Zone models the NWS OpenAPI Zone schema used as ZoneGeoJson.properties. It describes a forecast, county, fire, or marine zone.

Schema: https://api.weather.gov/openapi.json#/components/schemas/Zone

type ZoneForecast

type ZoneForecast struct {
	Context  json.RawMessage      `json:"@context,omitempty"`
	Geometry string               `json:"geometry,omitempty"`
	Zone     string               `json:"zone"`
	Updated  string               `json:"updated"`
	Periods  []ZoneForecastPeriod `json:"periods"`
}

ZoneForecast models the NWS OpenAPI ZoneForecast schema used as ZoneForecastGeoJson.properties. It is a textual forecast for a zone, not a grid forecast. Periods do not include the numeric weather fields or start/end timestamps of grid forecasts.

Schema: https://api.weather.gov/openapi.json#/components/schemas/ZoneForecast

type ZoneForecastPeriod

type ZoneForecastPeriod struct {
	Number           int    `json:"number"`
	Name             string `json:"name"`
	DetailedForecast string `json:"detailedForecast"`
}

ZoneForecastPeriod models an inline periods entry in the NWS OpenAPI ZoneForecast schema, keeping the NWS period number, label, and forecast text.

Schema: https://api.weather.gov/openapi.json#/components/schemas/ZoneForecast/properties/periods/items

type ZonesService

type ZonesService struct {
	// contains filtered or unexported fields
}

ZonesService provides access to NWS zones endpoints. Use the corresponding field on a Client constructed with NewClient.

func (*ZonesService) Get

func (s *ZonesService) Get(ctx context.Context, zoneType, id string) (*Feature[Zone], *Response, error)

Get fetches metadata by NWS zone type and identifier. To use a discovered forecastZone URL directly, call Client.Get with an Feature[Zone].

func (*ZonesService) GetForecast

func (s *ZonesService) GetForecast(ctx context.Context, zoneType, id string) (*Feature[ZoneForecast], *Response, error)

GetForecast retrieves a zone's current forecast without converting its periods into grid forecast periods.

func (*ZonesService) List

List returns zones matching NWS query parameters such as area, type, point, and include_geometry. Multi-value filters use comma-separated strings.

func (*ZonesService) ListByType

func (s *ZonesService) ListByType(ctx context.Context, zoneType string, query url.Values) (*FeatureCollection[Zone], *Response, error)

ListByType selects the zone type in the route; query retains the list filters.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL