summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Burwell <ben@benburwell.com>2020-02-05 23:28:40 -0500
committerBen Burwell <ben@benburwell.com>2020-02-05 23:28:40 -0500
commit380002c0cfd213b8d0bb70a387181e424aa969aa (patch)
tree7b33731cfd44a2a42e006d569e8345cd4a941b69
parentb4c1e4226a16266ffe17a84ef87a0876b02d9ff9 (diff)
Flesh out docs
-rw-r--r--metar/metar.go281
1 files changed, 221 insertions, 60 deletions
diff --git a/metar/metar.go b/metar/metar.go
index 1bb57f5..d273e2b 100644
--- a/metar/metar.go
+++ b/metar/metar.go
@@ -1,9 +1,13 @@
+// Package metar fetches METAR observations from the Aviation Weather Center's
+// Text Data Server (TDS).
+//
+// Note that the TDS stores only the past 3 days of data.
package metar
import (
"context"
"encoding/xml"
- "errors"
+ "fmt"
"net/http"
"net/url"
"strconv"
@@ -11,16 +15,27 @@ import (
"time"
)
+// A Client can fetch METARs from the TDS.
type Client struct {
hc http.Client
}
const (
+ // MostRecentConstraint requests the most recent observation for each METAR
+ // station in the fastest fashion. Not appropriate for historical data
+ // retrieval.
MostRecentConstraint = "constraint"
+
+ // MostRecentPostFilter requests the most recent observation for each METAR
+ // station by filtering results after applying all other constraints.
+ //
+ // This is the older, slower filtering method.
MostRecentPostFilter = "postfilter"
- MostRecentTrue = "true"
)
+// Response is the top-level XML document returned from the TDS. It contains
+// some metadata about the request handling, as well as zero or more METARs
+// (individual station observations).
type Response struct {
RequestIndex int `xml:"request_index"`
DataSource struct {
@@ -38,60 +53,155 @@ type Response struct {
} `xml:"data"`
}
+// A METAR holds the raw observation data from a station included in a
+// Response.
type METAR struct {
- RawText string `xml:"raw_text"`
- StationID string `xml:"station_id"`
- ObservationTime string `xml:"observation_time"`
- Latitude float32 `xml:"latitude"`
- Longitude float32 `xml:"longitude"`
- TempC float32 `xml:"temp_c"`
- DewpointC float32 `xml:"dewpoint_c"`
- WindDirDegrees int `xml:"wind_dir_degress"`
- WindSpeedKt int `xml:"wind_speed_kt"`
- WindGustKt int `xml:"wind_gust_kt"`
+ // The raw METAR
+ RawText string `xml:"raw_text"`
+
+ // Station identifier, always a four character alphanumeric
+ StationID string `xml:"station_id"`
+
+ // Time this METAR was observed (ISO 8601 date/time format)
+ ObservationTime string `xml:"observation_time"`
+
+ // The latitude of the station that reported this METAR (decimal degrees)
+ Latitude float32 `xml:"latitude"`
+
+ // The longitude of the station that reported this METAR (decimal degrees)
+ Longitude float32 `xml:"longitude"`
+
+ // Air temperature (C)
+ TempC float32 `xml:"temp_c"`
+
+ // Dewpoint temperature (C)
+ DewpointC float32 `xml:"dewpoint_c"`
+
+ // Direction from which the wind is blowing (degrees)
+ WindDirDegrees int `xml:"wind_dir_degress"`
+
+ // Wind speed, 0 degree direction and 0 speed = calm winds (kts)
+ WindSpeedKt int `xml:"wind_speed_kt"`
+
+ // Wind gust (kts)
+ WindGustKt int `xml:"wind_gust_kt"`
+
+ // Horizontal visibility (statute miles)
VisibilityStatuteMi float32 `xml:"visibility_statute_mi"`
- AltimInHg float32 `xml:"altim_in_hg"`
- SeaLevelPressureMb float32 `xml:"sea_level_pressure_mb"`
- QualityControlFlags struct {
- Corrected string `xml:"corrected"`
- Auto string `xml:"auto"`
- AutoStation string `xml:"auto_station"`
- MaintenanceIndicatorOn string `xml:"maintenance_indicator_on"`
- NoSignal string `xml:"no_signal"`
- LightningSensorOff string `xml:"lightning_sensor_off"`
- FreezingRainSensorOff string `xml:"freezing_rain_sensor_off"`
- PresentWeatherSensorOff string `xml:"present_weather_sensor_off"`
- } `xml:"quality_control_flags"`
- WxString string `xml:"wx_string"`
- SkyConditions []SkyCondition `xml:"sky_condition"`
- FlightCategory string `xml:"flight_category"`
- ThreeHrPressureTendencyMb float32 `xml:"three_hr_pressure_tendency_mb"`
- MaxTC float32 `xml:"maxT_c"`
- MinTC float32 `xml:"minT_c"`
- MaxT24HrC float32 `xml:"maxT24hr_c"`
- MinT24HrC float32 `xml:"minT24hr_c"`
- PrecipIn float32 `xml:"precip_in"`
- Pcp3HrIn float32 `xml:"pcp3hr_in"`
- Pcp6HrIn float32 `xml:"pcp6hr_in"`
- Pcp24HrIn float32 `xml:"pcp24hr_in"`
- SnowIn float32 `xml:"snow_in"`
- VertVisFt int `xml:"vert_vis_ft"`
- METARType string `xml:"metar_type"`
- ElevationM float32 `xml:"elevation_m"`
+
+ // Altimeter (inches of Hg)
+ AltimInHg float32 `xml:"altim_in_hg"`
+
+ // Sea-level pressure (mb)
+ SeaLevelPressureMb float32 `xml:"sea_level_pressure_mb"`
+
+ // Quality control flags
+ QualityControlFlags QCFlags `xml:"quality_control_flags"`
+
+ // Present weather string
+ WxString string `xml:"wx_string"`
+
+ // Up to four levels of sky cover and base can be reported
+ SkyConditions []SkyCondition `xml:"sky_condition"`
+
+ // Flight category of this METAR: VFR, MVFR, IFR, or LIFR
+ FlightCategory string `xml:"flight_category"`
+
+ // Pressure change in the past 3 hours
+ ThreeHrPressureTendencyMb float32 `xml:"three_hr_pressure_tendency_mb"`
+
+ // Maximum air temperature from the past 6 hours
+ MaxTC float32 `xml:"maxT_c"`
+
+ // Minimum air temperature from the past 6 hours
+ MinTC float32 `xml:"minT_c"`
+
+ // Maximum air temperature from the past 24 hours
+ MaxT24HrC float32 `xml:"maxT24hr_c"`
+
+ // Minimum air temperature from the past 24 hours
+ MinT24HrC float32 `xml:"minT24hr_c"`
+
+ // Liquid precipitation since the last regular METAR
+ PrecipIn float32 `xml:"precip_in"`
+
+ // Liquid precipitation from the past 3 hours. 0.0005 in = trace precipitation.
+ Pcp3HrIn float32 `xml:"pcp3hr_in"`
+
+ // Liquid precipitation from the past 6 hours. 0.0005 in = trace precipitation.
+ Pcp6HrIn float32 `xml:"pcp6hr_in"`
+
+ // Liquid precipitation from the past 24 hours. 0.0005 in = trace precipitation.
+ Pcp24HrIn float32 `xml:"pcp24hr_in"`
+
+ // Snow depth on the ground (in)
+ SnowIn float32 `xml:"snow_in"`
+
+ // Vertical visibility (ft)
+ VertVisFt int `xml:"vert_vis_ft"`
+
+ // METAR or SPECI
+ METARType string `xml:"metar_type"`
+
+ // The elevation of the station that reported this METAR
+ ElevationM float32 `xml:"elevation_m"`
}
+// A QCFlags holds quality control flags indicating the status of the station
+// and information about the METAR.
+type QCFlags struct {
+ // Corrected
+ Corrected string `xml:"corrected"`
+
+ // Fully automated
+ Auto string `xml:"auto"`
+
+ // Indicates that the automated station type is one of the following: A01,
+ // A01A, A02, A02A, AOA, AWOS.
+ //
+ // Note: The type of station is not returned. This simply indicates that
+ // this station is one of the six stations enumerated above.
+ AutoStation string `xml:"auto_station"`
+
+ // Maintenance check indicator - maintenance is needed
+ MaintenanceIndicatorOn string `xml:"maintenance_indicator_on"`
+
+ // No signal
+ NoSignal string `xml:"no_signal"`
+
+ // The lightning detection sensor is not operating - thunderstorm
+ // information is not available.
+ LightningSensorOff string `xml:"lightning_sensor_off"`
+
+ // The freezing rain sensor is not operating
+ FreezingRainSensorOff string `xml:"freezing_rain_sensor_off"`
+
+ // The present weather sensor is not operating
+ PresentWeatherSensorOff string `xml:"present_weather_sensor_off"`
+}
+
+// A SkyCondition describes the condition of the sky at a particular altitude.
type SkyCondition struct {
- SkyCover string `xml:"sky_cover,attr"`
- CloudBaseFtAGL int `xml:"cloud_base_ft_agl,attr"`
+ // SKC, CLR, CAVOK, FEW, SCT, BKN, OVC, OVX
+ SkyCover string `xml:"sky_cover,attr"`
+
+ // Height of cloud base in feet AGL. A value exists when SkyCover is FEW,
+ // SCT, BKN, or OVC.
+ CloudBaseFtAGL int `xml:"cloud_base_ft_agl,attr"`
}
+// A Request holds options for a METAR request such as a station, area, or
+// flight path to get METARs for, the time range for which to request
+// observations, etc.
type Request struct {
v url.Values
}
-type requestArg func(*Request)
-
-func NewRequest(args ...requestArg) *Request {
+// NewRequest builds a Request with the provided arguments. Supported Arguments
+// include StationString, TimeRange, HoursBeforeNow, MostRecent,
+// MostRecentForEachStation, LatLongRect, RadialDistance, FlightPath,
+// MinDegreeDistance, and Fields.
+func NewRequest(args ...func(*Request)) *Request {
var r Request
for _, arg := range args {
arg(&r)
@@ -99,38 +209,66 @@ func NewRequest(args ...requestArg) *Request {
return &r
}
-func StationString(s string) requestArg {
+// StationString sets which station(s) METARs are fetched from. A Station
+// String can contain one or more complete or partial ICAO IDs separated by
+// whitespace and/or commas, US states or Canadian provinces prefixed with '@',
+// or two-letter country abbrevations prefixed with '~'.
+//
+// Examples:
+// - "KDEN KSEA, PHNL" obtains all available METARs for KDEN, KSEA, and PHNL
+// - "KSEA KDE" obtains all available METARs for KSEA and all ICAO IDs
+// beginning with KDE (i.e. KDEN, KDEH, KDEW, etc)
+// - "KSEA KDE*" is equivalent to "KSEA KDE"
+// - "@WA" obtains METARs for all ICAO IDs from Washington state
+// - "@BC" obtains METARs for all ICAO IDs from British Columbia, Canada
+// - "~au" obtains METARs for all ICAO IDs from Australia
+func StationString(s string) func(*Request) {
return func(r *Request) {
r.v.Add("stationString", s)
}
}
-func TimeRange(from, to time.Time) requestArg {
+// TimeRange requests METARs observed between the from and to times.
+func TimeRange(from, to time.Time) func(*Request) {
return func(r *Request) {
r.v.Add("startTime", string(from.Unix()))
r.v.Add("endTime", string(to.Unix()))
}
}
-func HoursBeforeNow(h float64) requestArg {
+// HoursBeforeNow requests METARs observed during the previous h hours.
+func HoursBeforeNow(h float64) func(*Request) {
return func(r *Request) {
r.v.Add("hoursBeforeNow", strconv.FormatFloat(h, 'f', 64, -1))
}
}
-func MostRecent(mr bool) requestArg {
+// MostRecent sets whether only the most recent METAR should be fetched. When
+// requesting multiple stations, this means that only the METAR from one of
+// them will be returned.
+//
+// Use MostRecentForEachStation to apply a most-recent constraint to multiple
+// stations.
+func MostRecent(mr bool) func(*Request) {
return func(r *Request) {
r.v.Add("mostRecent", strconv.FormatBool(mr))
}
}
-func MostRecentForEachStation(s string) requestArg {
+// MostRecentForEachStation sets whether to fetch only the most recent METAR
+// for each requested station. The provided string should be either
+// MostRecentConstraint or MostRecentPostFilter.
+func MostRecentForEachStation(s string) func(*Request) {
return func(r *Request) {
r.v.Add("mostRecentForEachStation", s)
}
}
-func LatLongRect(minLat, minLon, maxLat, maxLon float64) requestArg {
+// LonLatRect specifies a rectangular bounding box of geographic coordinates in
+// which to fetch METARs.
+//
+// Does not support bounding boxes that encompass a pole.
+func LonLatRect(minLat, minLon, maxLat, maxLon float64) func(*Request) {
return func(r *Request) {
r.v.Add("minLat", strconv.FormatFloat(minLat, 'f', 64, -1))
r.v.Add("minLon", strconv.FormatFloat(minLon, 'f', 64, -1))
@@ -139,25 +277,45 @@ func LatLongRect(minLat, minLon, maxLat, maxLon float64) requestArg {
}
}
-func RadialDistance(s string) requestArg {
+// RadialDistance specifies a radius around a point in which to fetch METARs.
+//
+// Described area may not cross the international date line or either pole.
+func RadialDistance(r, lat, lon float64) func(*Request) {
return func(r *Request) {
- r.v.Add("radialDistance", s)
+ r.v.Add("radialDistance", fmt.Sprintf("%f;%f,%f", r, lon, lon))
}
}
-func FlightPath(s string) requestArg {
+// FlightPath obtains all METARs for the specified flight path, up to a maximum
+// distance.
+//
+// Waypoints may take the form of "lon,lat" or ICAO station IDs, and may be
+// mixed interchangably. The ordering of waypoints is significant, always start
+// with the origin and end with the destination.
+//
+// Note: Flight path results are sorted by distance along the flight path from
+// origin to destination. The flight path constraint does not support flight
+// paths that cross the poles or flight paths that cross the international date
+// line.
+func FlightPath(maxDist float64, waypoints ...string) func(*Request) {
return func(r *Request) {
- r.v.Add("flightPath", s)
+ r.v.Add("flightPath", fmt.Sprintf("%f;%s", maxDist, strings.Join(waypoints, ";")))
}
}
-func MinDegreeDistance(d float64) requestArg {
+// MinDegreeDistance can be used to fetch fewer results by specifying a minimum
+// degree distance (based on longitude and latitude) between stations.
+//
+// A large MinDegreeDistance will yield less dense results. Duplicate stations
+// are filtered and the most recent of duplicate stations is reported.
+func MinDegreeDistance(d float64) func(*Request) {
return func(r *Request) {
r.v.Add("minDegreeDistance", strconv.FormatFloat(d, 'f', 64, -1))
}
}
-func Fields(ss ...string) requestArg {
+// Fields specifies a subset of METAR fields to be collected.
+func Fields(ss ...string) func(*Request) {
return func(r *Request) {
r.v.Add("fields", strings.Join(ss, ","))
}
@@ -170,6 +328,12 @@ func requestToQueryString(m *Request) string {
return m.v.Encode()
}
+// GetMETARs fetches METARs from the TDS based on the constraints specified by
+// the Request and returns the Response and an error.
+//
+// Note that an error is only returned if there is a failure at the HTTP layer
+// or the response could not be interpreted. The response may additionally
+// contain error messages if the request was unable to be processed.
func (c *Client) GetMETARs(ctx context.Context, req *Request) (*Response, error) {
u := url.URL{
Scheme: "https",
@@ -186,9 +350,6 @@ func (c *Client) GetMETARs(ctx context.Context, req *Request) (*Response, error)
if err != nil {
return nil, err
}
- if resp.StatusCode != http.StatusOK {
- return nil, errors.New(resp.Status)
- }
defer resp.Body.Close()
var r Response
if err := xml.NewDecoder(resp.Body).Decode(&r); err != nil {