diff options
author | Martin Polden <mpolden@mpolden.no> | 2021-08-11 21:15:42 +0200 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2021-08-11 22:34:12 +0200 |
commit | ef9d69d86c2ec40055c759dfde67b88fcf93db64 (patch) | |
tree | a822c65f58ee1b3e9863d800700e17446e62f0d2 | |
parent | 7b0df649d84eaf95441f443d754a84f7b8369001 (diff) |
entur: Implement client for new API
-rw-r--r-- | .gitattributes | 1 | ||||
-rw-r--r-- | entur/entur.go | 131 | ||||
-rw-r--r-- | entur/entur_test.go | 61 | ||||
-rw-r--r-- | entur/testdata/ilsvika.json | 90 |
4 files changed, 283 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6767a69 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +**/testdata/* linguist-vendored diff --git a/entur/entur.go b/entur/entur.go new file mode 100644 index 0000000..4dfc60b --- /dev/null +++ b/entur/entur.go @@ -0,0 +1,131 @@ +package entur + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "time" +) + +// DefaultURL is the default Entur Journey Planner API URL. Documentation at +// https://developer.entur.org/pages-journeyplanner-journeyplanner-v2. +const DefaultURL = "https://api.entur.io/journey-planner/v2/graphql" + +// Client implements a client for the Entur Journey Planner API. +type Client struct{ URL string } + +// New creates a new client using the API found at url. +func New(url string) *Client { + if url == "" { + url = DefaultURL + } + return &Client{URL: url} +} + +// Departure represents a bus departure from a stop. +type Departure struct { + Line string + RegisteredDepartureTime time.Time + ScheduledDepartureTime time.Time + Destination string + IsRealtime bool + Inbound bool +} + +type response struct { + Data data `json:"data"` +} + +type data struct { + StopPlace stopPlace `json:"stopPlace"` +} + +type stopPlace struct { + ID string `json:"id"` + Name string `json:"name"` + EstimatedCalls []estimatedCall `json:"estimatedCalls"` +} + +type estimatedCall struct { + Realtime bool `json:"realtime"` + ExpectedDepartureTime string `json:"expectedDepartureTime"` + ActualDepartureTime string `json:"actualDepartureTime"` + DestinationDisplay destinationDisplay `json:"destinationDisplay"` + ServiceJourney serviceJourney `json:"serviceJourney"` +} + +type destinationDisplay struct { + FrontText string `json:"frontText"` +} + +type serviceJourney struct { + JourneyPattern journeyPattern `json:"journeyPattern"` +} + +type journeyPattern struct { + DirectionType string `json:"directionType"` + Line line `json:"line"` +} + +type line struct { + PublicCode string `json:"publicCode"` +} + +// Departures returns departures from the given stop ID. Use https://stoppested.entur.org/ to determine stop IDs. +func (c *Client) Departures(stopID int) ([]Departure, error) { + // https://api.entur.io/journey-planner/v2/ide/ for query testing + query := fmt.Sprintf(`{"query":"{stopPlace(id:\"NSR:StopPlace:%d\"){id name estimatedCalls{realtime expectedDepartureTime actualDepartureTime destinationDisplay{frontText}serviceJourney{journeyPattern{directionType line{publicCode}}}}}}"}`, stopID) + req, err := http.NewRequest("POST", c.URL, strings.NewReader(query)) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + // Identify this client. See https://developer.entur.org/pages-journeyplanner-journeyplanner-v2 + req.Header.Set("ET-Client-Name", "github_mpolden-atb") + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + json, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + return parseDepartures(json) +} + +func parseDepartures(jsonData []byte) ([]Departure, error) { + var r response + if err := json.Unmarshal(jsonData, &r); err != nil { + return nil, err + } + const timeLayout = "2006-01-02T15:04:05-0700" + departures := make([]Departure, 0, len(r.Data.StopPlace.EstimatedCalls)) + for _, ec := range r.Data.StopPlace.EstimatedCalls { + scheduledDepartureTime, err := time.Parse(timeLayout, ec.ExpectedDepartureTime) + if err != nil { + return nil, err + } + registeredDepartureTime := time.Time{} + if ec.ActualDepartureTime != "" { + t, err := time.Parse(timeLayout, ec.ActualDepartureTime) + if err != nil { + return nil, err + } + registeredDepartureTime = t + } + inbound := ec.ServiceJourney.JourneyPattern.DirectionType == "inbound" + d := Departure{ + Line: ec.ServiceJourney.JourneyPattern.Line.PublicCode, + RegisteredDepartureTime: registeredDepartureTime, + ScheduledDepartureTime: scheduledDepartureTime, + Destination: ec.DestinationDisplay.FrontText, + IsRealtime: ec.Realtime, + Inbound: inbound, + } + departures = append(departures, d) + } + return departures, nil +} diff --git a/entur/entur_test.go b/entur/entur_test.go new file mode 100644 index 0000000..dd98ea1 --- /dev/null +++ b/entur/entur_test.go @@ -0,0 +1,61 @@ +package entur + +import ( + "io/ioutil" + "path/filepath" + "testing" + "time" +) + +func TestParseDepartures(t *testing.T) { + testFile := filepath.Join("testdata", "ilsvika.json") + json, err := ioutil.ReadFile(testFile) + if err != nil { + t.Fatal(err) + } + d, err := parseDepartures(json) + if err != nil { + t.Fatal(err) + } + cest := time.FixedZone("CEST", 7200) + expected := []Departure{ + { + Line: "21", + RegisteredDepartureTime: time.Time{}, + ScheduledDepartureTime: time.Date(2021, 8, 11, 21, 19, 0, 0, cest), + Destination: "Pirbadet via sentrum", + IsRealtime: false, + Inbound: false, + }, + { + Line: "21", + RegisteredDepartureTime: time.Time{}, + ScheduledDepartureTime: time.Date(2021, 8, 11, 22, 19, 0, 0, cest), + Destination: "Pirbadet via sentrum", + IsRealtime: true, + Inbound: false, + }, + } + for i := 0; i < len(expected); i++ { + got := d[i] + want := expected[i] + if want.Line != got.Line { + t.Errorf("#%d: want Line = %q, got %q", i, want.Line, got.Line) + } + if !want.RegisteredDepartureTime.Equal(got.RegisteredDepartureTime) { + t.Errorf("#%d: want RegisteredDepartureTime = %q, got %q", i, want.RegisteredDepartureTime, got.RegisteredDepartureTime) + } + if !want.ScheduledDepartureTime.Equal(got.ScheduledDepartureTime) { + t.Errorf("#%d: want ScheduledDepartureTime = %q, got %q", i, want.ScheduledDepartureTime, got.ScheduledDepartureTime) + } + if want.Destination != got.Destination { + t.Errorf("#%d: want Destination = %q, got %q", i, want.Destination, got.Destination) + } + if want.IsRealtime != got.IsRealtime { + t.Errorf("#%d: want IsRealtime = %t, got %t", i, want.IsRealtime, got.IsRealtime) + } + if want.Inbound != got.Inbound { + t.Errorf("#%d: want Inbound = %t, got %t", i, want.Inbound, got.Inbound) + } + } +} diff --git a/entur/testdata/ilsvika.json b/entur/testdata/ilsvika.json new file mode 100644 index 0000000..2d6c32f --- /dev/null +++ b/entur/testdata/ilsvika.json @@ -0,0 +1,90 @@ +{ + "data": { + "stopPlace": { + "id": "NSR:StopPlace:42098", + "name": "Ilsvika", + "estimatedCalls": [ + { + "realtime": false, + "expectedDepartureTime": "2021-08-11T21:19:00+0200", + "actualDepartureTime": null, + "destinationDisplay": { + "frontText": "Pirbadet via sentrum" + }, + "serviceJourney": { + "journeyPattern": { + "directionType": "outbound", + "line": { + "publicCode": "21" + } + } + } + }, + { + "realtime": true, + "expectedDepartureTime": "2021-08-11T22:19:00+0200", + "actualDepartureTime": null, + "destinationDisplay": { + "frontText": "Pirbadet via sentrum" + }, + "serviceJourney": { + "journeyPattern": { + "directionType": "outbound", + "line": { + "publicCode": "21" + } + } + } + }, + { + "realtime": false, + "expectedDepartureTime": "2021-08-11T23:19:00+0200", + "actualDepartureTime": null, + "destinationDisplay": { + "frontText": "Pirbadet via sentrum" + }, + "serviceJourney": { + "journeyPattern": { + "directionType": "outbound", + "line": { + "publicCode": "21" + } + } + } + }, + { + "realtime": false, + "expectedDepartureTime": "2021-08-12T00:19:00+0200", + "actualDepartureTime": null, + "destinationDisplay": { + "frontText": "Pirbadet via sentrum" + }, + "serviceJourney": { + "journeyPattern": { + "directionType": "outbound", + "line": { + "publicCode": "21" + } + } + } + }, + { + "realtime": false, + "expectedDepartureTime": "2021-08-12T06:13:00+0200", + "actualDepartureTime": null, + "destinationDisplay": { + "frontText": "Pirbadet via sentrum" + }, + "serviceJourney": { + "journeyPattern": { + "directionType": "outbound", + "line": { + "publicCode": "21" + } + } + } + } + ] + } + } +}
\ No newline at end of file |