aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2021-08-11 21:15:42 +0200
committerMartin Polden <mpolden@mpolden.no>2021-08-11 22:34:12 +0200
commitef9d69d86c2ec40055c759dfde67b88fcf93db64 (patch)
treea822c65f58ee1b3e9863d800700e17446e62f0d2
parent7b0df649d84eaf95441f443d754a84f7b8369001 (diff)
entur: Implement client for new API
-rw-r--r--.gitattributes1
-rw-r--r--entur/entur.go131
-rw-r--r--entur/entur_test.go61
-rw-r--r--entur/testdata/ilsvika.json90
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