package metar import ( "context" "encoding/xml" "errors" "net/http" "net/url" "strconv" "strings" "time" ) type Client struct { hc http.Client } const ( MostRecentConstraint = "constraint" MostRecentPostFilter = "postfilter" MostRecentTrue = "true" ) type Response struct { RequestIndex int `xml:"request_index"` DataSource struct { Name string `xml:"name,attr"` } `xml:"data_source"` Request struct { Type string `xml:"type,attr"` } `xml:"request"` Errors []string `xml:"errors>error"` Warnings []string `xml:"warnings>warning"` TimeTakenMs int `xml:"time_taken_ms"` Data struct { NumResults int `xml:"num_results,attr"` METARs []METAR `xml:"METAR"` } `xml:"data"` } 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"` 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"` } type SkyCondition struct { SkyCover string `xml:"sky_cover,attr"` CloudBaseFtAGL int `xml:"cloud_base_ft_agl,attr"` } type Request struct { v url.Values } type requestArg func(*Request) func NewRequest(args ...requestArg) *Request { var r Request for _, arg := range args { arg(&r) } return &r } func StationString(s string) requestArg { return func(r *Request) { r.v.Add("stationString", s) } } func TimeRange(from, to time.Time) requestArg { return func(r *Request) { r.v.Add("startTime", string(from.Unix())) r.v.Add("endTime", string(to.Unix())) } } func HoursBeforeNow(h float64) requestArg { return func(r *Request) { r.v.Add("hoursBeforeNow", strconv.FormatFloat(h, 'f', 64, -1)) } } func MostRecent(mr bool) requestArg { return func(r *Request) { r.v.Add("mostRecent", strconv.FormatBool(mr)) } } func MostRecentForEachStation(s string) requestArg { return func(r *Request) { r.v.Add("mostRecentForEachStation", s) } } func LatLongRect(minLat, minLon, maxLat, maxLon float64) requestArg { return func(r *Request) { r.v.Add("minLat", strconv.FormatFloat(minLat, 'f', 64, -1)) r.v.Add("minLon", strconv.FormatFloat(minLon, 'f', 64, -1)) r.v.Add("maxLat", strconv.FormatFloat(maxLat, 'f', 64, -1)) r.v.Add("maxLon", strconv.FormatFloat(maxLon, 'f', 64, -1)) } } func RadialDistance(s string) requestArg { return func(r *Request) { r.v.Add("radialDistance", s) } } func FlightPath(s string) requestArg { return func(r *Request) { r.v.Add("flightPath", s) } } func MinDegreeDistance(d float64) requestArg { return func(r *Request) { r.v.Add("minDegreeDistance", strconv.FormatFloat(d, 'f', 64, -1)) } } func Fields(ss ...string) requestArg { return func(r *Request) { r.v.Add("fields", strings.Join(ss, ",")) } } func requestToQueryString(m *Request) string { m.v.Add("dataSource", "metars") m.v.Add("requestType", "retrieve") m.v.Add("format", "xml") return m.v.Encode() } func (c *Client) GetMETARs(ctx context.Context, req *Request) (*Response, error) { u := url.URL{ Scheme: "https", Host: "www.aviationweather.gov", Path: "/adds/dataserver_current/httpparam", RawQuery: requestToQueryString(req), } hr, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) if err != nil { return nil, err } hr.Header.Add("user-agent", "wxbot/1.0 +bnbl.io/wx") resp, err := c.hc.Do(hr) 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 { return nil, err } return &r, nil }