From b4c1e4226a16266ffe17a84ef87a0876b02d9ff9 Mon Sep 17 00:00:00 2001 From: Ben Burwell Date: Wed, 5 Feb 2020 22:17:05 -0500 Subject: Initial commit --- LICENSE | 21 ++++++ go.mod | 3 + metar/metar.go | 198 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ wx.go | 2 + 4 files changed, 224 insertions(+) create mode 100644 LICENSE create mode 100644 go.mod create mode 100644 metar/metar.go create mode 100644 wx.go diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e0485a4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Ben Burwell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4f4d0fa --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module bnbl.io/wx + +go 1.13 diff --git a/metar/metar.go b/metar/metar.go new file mode 100644 index 0000000..1bb57f5 --- /dev/null +++ b/metar/metar.go @@ -0,0 +1,198 @@ +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 +} diff --git a/wx.go b/wx.go new file mode 100644 index 0000000..648bc4f --- /dev/null +++ b/wx.go @@ -0,0 +1,2 @@ +// Package wx communicates with NOAA's Aviation Weather Center +package wx -- cgit v1.2.3