mirror of
https://github.com/Luzifer/go-metar.git
synced 2024-12-22 11:21:19 +00:00
Implemented fetching and parsing of METAR data
This commit is contained in:
commit
4db6747e3e
3 changed files with 193 additions and 0 deletions
13
go_metar_suite_test.go
Normal file
13
go_metar_suite_test.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package metar_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGoMetar(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "GoMetar Suite")
|
||||||
|
}
|
129
metar.go
Normal file
129
metar.go
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
package metar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
apiSource = "https://www.aviationweather.gov/adds/dataserver_current/httpparam?dataSource=metars&requestType=retrieve&format=xml&stationString=%s&hoursBeforeNow=2&mostRecent=true"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// HTTPClient is used to make requests, you can insert your own
|
||||||
|
HTTPClient = http.DefaultClient
|
||||||
|
)
|
||||||
|
|
||||||
|
// Result holds all the data from the METAR request
|
||||||
|
type Result struct {
|
||||||
|
XMLName xml.Name `xml:"METAR"`
|
||||||
|
RawText string `xml:"raw_text"` // The raw METAR
|
||||||
|
StationID string `xml:"station_id"` // Station identifier; Always a four character alphanumeric( A-Z, 0-9)
|
||||||
|
ObservationTime time.Time `xml:"observation_time"` // Time this METAR was observed
|
||||||
|
Latitude float64 `xml:"latitude"` // The latitude (in decimal degrees) of the station that reported this METAR
|
||||||
|
Longitude float64 `xml:"longitude"` // The longitude (in decimal degrees) of the station that reported this METAR
|
||||||
|
Temperature float64 `xml:"temp_c"` // Air temperature (celsius)
|
||||||
|
Dewpoint float64 `xml:"dewpoint_c"` // Dewpoint temperature (celsius)
|
||||||
|
WindDirDegrees int64 `xml:"wind_dir_degrees"` // Direction from which the wind is blowing. 0 degrees=variable wind direction.
|
||||||
|
WindSpeed int64 `xml:"wind_speed_kt"` // Wind speed; 0 degree wdir and 0 wspd = calm winds (kts)
|
||||||
|
WindGust int64 `xml:"wind_gust_kt"` // Wind gust
|
||||||
|
VisibilityStatute float64 `xml:"visibility_statute_mi"` // Horizontal visibility (statute miles)
|
||||||
|
Altimeter float64 `xml:"altim_in_hg"` // Altimeter (inches of Hg)
|
||||||
|
SeaLevelPressure float64 `xml:"sea_level_pressure_mb"` // Sea-level pressure (mb)
|
||||||
|
QualityControlFlags QualityControlFlags `xml:"quality_control_flags"` // Quality control flags provide useful information about the METAR station(s) that provide the data.
|
||||||
|
WXString string `xml:"wx_string"` // WX string descriptions (https://www.aviationweather.gov/static/adds/docs/metars/wxSymbols_anno2.pdf)
|
||||||
|
SkyCondition struct {
|
||||||
|
SkyCover SkyCover `xml:"sky_cover,attr"` // Sky cover, up to four levels of sky cover can be reported ; OVX present when vert_vis_ft is reported
|
||||||
|
} `xml:"sky_condition"`
|
||||||
|
FlightCategory FlightCategory `xml:"flight_category"` // Flight category of this METAR
|
||||||
|
// Fields 19 to 29 currently not implemented
|
||||||
|
MetarType string `xml:"metar_type"` // METAR or SPECI
|
||||||
|
Elevation float64 `xml:"elevation_m"` // The elevation of the station that reported this METAR (meters)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QualityControlFlags provide useful information about the METAR station(s) that provide the data.
|
||||||
|
type QualityControlFlags struct {
|
||||||
|
XMLName xml.Name `xml:"quality_control_flags"`
|
||||||
|
NoSignal bool `xml:"no_signal"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkyCover defines and explains possible sky coverage situations
|
||||||
|
type SkyCover string
|
||||||
|
|
||||||
|
// Common SkyCover situations
|
||||||
|
const (
|
||||||
|
SkyCoverSKC SkyCover = "SKC" // "No cloud/Sky clear" used worldwide but in North America is used to indicate a human generated report
|
||||||
|
SkyCoverCLR SkyCover = "CLR" // "No clouds below 12,000 ft (3,700 m) (U.S.) or 25,000 ft (7,600 m) (Canada)", used mainly within North America and indicates a station that is at least partly automated
|
||||||
|
SkyCoverNSC SkyCover = "NSC" // "No (nil) significant cloud", i.e., none below 5,000 ft (1,500 m) and no TCU or CB. Not used in North America.
|
||||||
|
SkyCoverFEW SkyCover = "FEW" // "Few" = 1–2 oktas
|
||||||
|
SkyCoverSCT SkyCover = "SCT" // "Scattered" = 3–4 oktas
|
||||||
|
SkyCoverBKN SkyCover = "BKN" // "Broken" = 5–7 oktas
|
||||||
|
SkyCoverOVC SkyCover = "OVC" // "Overcast" = 8 oktas, i.e., full cloud coverage
|
||||||
|
SkyCoverCAVOK SkyCover = "CAVOK" // Ceiling And Visibility OKay, indicating no cloud below 5,000 ft (1,500 m) or the highest minimum sector altitude and no cumulonimbus or towering cumulus at any level, a visibility of 10 km (6 mi) or more and no significant weather change
|
||||||
|
)
|
||||||
|
|
||||||
|
// FlightCategory defines and explains possible flight category situations
|
||||||
|
type FlightCategory string
|
||||||
|
|
||||||
|
// Common FlightCategory situations
|
||||||
|
const (
|
||||||
|
FlightCategoryVFR FlightCategory = "VFR" // Visual Flight Rules (Ceiling greater than 3,000 feet AGL and visibility greater than 5 miles)
|
||||||
|
FlightCategoryMVFR FlightCategory = "MVFR" // Marginal Visual Flight Rules (Ceiling 1,000 to 3,000 feet AGL and/or visibility 3 to 5 miles)
|
||||||
|
FlightCategoryIFR FlightCategory = "IFR" // Instrument Flight Rules (Ceiling 500 to below 1,000 feet AGL and/or visibility 1 mile to less than 3 miles)
|
||||||
|
FlightCategoryLIFR FlightCategory = "LIFR" // Low Instrument Flight Rules (Ceiling below 500 feet AGL and/or visibility less than 1 mile)
|
||||||
|
)
|
||||||
|
|
||||||
|
type response struct {
|
||||||
|
XMLName xml.Name `xml:"response"`
|
||||||
|
Data struct {
|
||||||
|
NumResults int `xml:"num_results,attr"`
|
||||||
|
Results []Result `xml:"METAR"`
|
||||||
|
} `xml:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchCurrentStationWeather fetches the last result from the specified station if it was reported during last 2 hours
|
||||||
|
func FetchCurrentStationWeather(station string) (*Result, error) {
|
||||||
|
req, _ := http.NewRequest("GET", fmt.Sprintf(apiSource, station), nil)
|
||||||
|
res, err := HTTPClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &response{}
|
||||||
|
if err = xml.NewDecoder(res.Body).Decode(r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Data.NumResults != len(r.Data.Results) {
|
||||||
|
return nil, errors.New("Got inconsistent number of results")
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Data.NumResults == 0 {
|
||||||
|
return nil, errors.New("Did not find any data for your station")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &r.Data.Results[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InHgTohPa converts "inch of mercury" to "hectopascal"
|
||||||
|
func InHgTohPa(inHg float64) float64 {
|
||||||
|
return inHg * 33.8638866667
|
||||||
|
}
|
||||||
|
|
||||||
|
// KtsToMs converts "knots" to "meters per second"
|
||||||
|
func KtsToMs(kts float64) float64 {
|
||||||
|
return kts * 0.514444
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatMileToKm converts "statute miles" to "kilometers"
|
||||||
|
func StatMileToKm(sm float64) float64 {
|
||||||
|
return sm * 1.60934
|
||||||
|
}
|
||||||
|
|
||||||
|
// MbTohPa converts "millibar" to "hectopascal"
|
||||||
|
func MbTohPa(mb float64) float64 {
|
||||||
|
return mb * 0.1
|
||||||
|
}
|
51
metar_test.go
Normal file
51
metar_test.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package metar_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
. "github.com/Luzifer/go-metar"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Metar", func() {
|
||||||
|
var (
|
||||||
|
station = ""
|
||||||
|
result *Result
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
JustBeforeEach(func() {
|
||||||
|
result, err = FetchCurrentStationWeather(station)
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("with station EDDH (HAM)", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
station = "EDDH"
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should not have errored", func() {
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should be at the expected position", func() {
|
||||||
|
Expect(result.Latitude).To(Equal(53.63))
|
||||||
|
Expect(result.Longitude).To(Equal(10.0))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should be a METAR station reporting", func() {
|
||||||
|
Expect(result.MetarType).To(Equal("METAR"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should be a fairly new result", func() {
|
||||||
|
Expect(time.Since(result.ObservationTime) < 1*time.Hour).To(BeTrue())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should have information about SkyCover and FlightCategory", func() {
|
||||||
|
Expect(result.SkyCondition.SkyCover).NotTo(Equal(SkyCover("")))
|
||||||
|
Expect(result.FlightCategory).NotTo(Equal(FlightCategory("")))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
Loading…
Reference in a new issue