1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
|
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-v3.
const DefaultURL = "https://api.entur.io/journey-planner/v3/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 {
Operator operator `json:"operator"`
JourneyPattern journeyPattern `json:"journeyPattern"`
}
type operator struct {
Id string `json:"id"`
}
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(count, 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(numberOfDepartures:%d){realtime expectedDepartureTime actualDepartureTime destinationDisplay{frontText}serviceJourney{operator{id}journeyPattern{directionType line{publicCode}}}}}}"}`, stopID, count)
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-v3
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 (
operatorPrefix = "ATB:"
timeLayout = "2006-01-02T15:04:05-07:00"
)
departures := make([]Departure, 0, len(r.Data.StopPlace.EstimatedCalls))
for _, ec := range r.Data.StopPlace.EstimatedCalls {
if !strings.HasPrefix(ec.ServiceJourney.Operator.Id, operatorPrefix) {
continue // Skip other operators
}
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
}
|