summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Burwell <ben@benburwell.com>2020-02-05 22:17:05 -0500
committerBen Burwell <ben@benburwell.com>2020-02-05 22:17:05 -0500
commitb4c1e4226a16266ffe17a84ef87a0876b02d9ff9 (patch)
tree3dde6860dd45b7a26c094d929ba2c10947bc992f
Initial commit
-rw-r--r--LICENSE21
-rw-r--r--go.mod3
-rw-r--r--metar/metar.go198
-rw-r--r--wx.go2
4 files changed, 224 insertions, 0 deletions
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