-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathnextbus.go
425 lines (370 loc) · 14.4 KB
/
nextbus.go
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
package nextbus
import (
"encoding/xml"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
// DefaultClient uses the default http client to make requests
var DefaultClient = &Client{http.DefaultClient}
// Client is used to make requests
type Client struct {
httpClient *http.Client
}
// NewClient creates a new nextbus client.
func NewClient(httpClient *http.Client) *Client {
return &Client{httpClient}
}
// AgencyResponse represents a list of transit agencies.
type AgencyResponse struct {
XMLName xml.Name `xml:"body"`
AgencyList []Agency `xml:"agency"`
}
// Agency represents a single transit agency.
type Agency struct {
XMLName xml.Name `xml:"agency"`
Tag string `xml:"tag,attr"`
Title string `xml:"title,attr"`
RegionTitle string `xml:"regionTitle,attr"`
}
// GetAgencyList fetches the list of supported transit agencies by nextbus.
func (c *Client) GetAgencyList() ([]Agency, error) {
resp, httpErr := c.httpClient.Get("http://webservices.nextbus.com/service/publicXMLFeed?command=agencyList")
if httpErr != nil {
return nil, fmt.Errorf("could not fetch agencies from nextbus: %v", httpErr)
}
defer resp.Body.Close()
body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil {
return nil, fmt.Errorf("could not parse agencies response body: %v", readErr)
}
var a AgencyResponse
if xmlErr := xml.Unmarshal(body, &a); xmlErr != nil {
return nil, fmt.Errorf("could not parse agencies XML: %v", xmlErr)
}
return a.AgencyList, nil
}
// RouteResponse is a set of transit routes.
type RouteResponse struct {
XMLName xml.Name `xml:"body"`
RouteList []Route `xml:"route"`
}
// Route is an individual transit route.
type Route struct {
XMLName xml.Name `xml:"route"`
Tag string `xml:"tag,attr"`
Title string `xml:"title,attr"`
}
// GetRouteList fetches the list of routes within the specified agency.
func (c *Client) GetRouteList(agencyTag string) ([]Route, error) {
resp, httpErr := c.httpClient.Get("http://webservices.nextbus.com/service/publicXMLFeed?command=routeList&a=" + agencyTag)
if httpErr != nil {
return nil, fmt.Errorf("could not fetch routes from nextbus: %v", httpErr)
}
defer resp.Body.Close()
body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil {
return nil, fmt.Errorf("could not parse routes response body: %v", readErr)
}
var a RouteResponse
xmlErr := xml.Unmarshal(body, &a)
if xmlErr != nil {
return nil, fmt.Errorf("could not parse routes XML: %v", xmlErr)
}
return a.RouteList, nil
}
// RouteConfigResponse is a collection of RouteConfigs.
type RouteConfigResponse struct {
XMLName xml.Name `xml:"body"`
RouteList []RouteConfig `xml:"route"`
}
// RouteConfig is the metadata for a particular transit route.
type RouteConfig struct {
XMLName xml.Name `xml:"route"`
StopList []Stop `xml:"stop"`
Tag string `xml:"tag,attr"`
Title string `xml:"title,attr"`
Color string `xml:"color,attr"`
OppositeColor string `xml:"oppositeColor,attr"`
LatMin string `xml:"latMin,attr"`
LatMax string `xml:"latMax,attr"`
LonMin string `xml:"lonMin,attr"`
LonMax string `xml:"lonMax,attr"`
DirList []Direction `xml:"direction"`
PathList []Path `xml:"path"`
}
// Stop is the metadata for a particular stop.
type Stop struct {
XMLName xml.Name `xml:"stop"`
Tag string `xml:"tag,attr"`
Title string `xml:"title,attr"`
Lat string `xml:"lat,attr"`
Lon string `xml:"lon,attr"`
StopID string `xml:"stopId,attr"`
}
// Direction is the metadata for one individual route direction. A transit route
// usually has at least two "directions": "inbound" and "outbound", for example.
type Direction struct {
XMLName xml.Name `xml:"direction"`
Tag string `xml:"tag,attr"`
Title string `xml:"title,attr"`
Name string `xml:"name,attr"`
UseForUI string `xml:"useForUI,attr"`
StopMarkerList []StopMarker `xml:"stop"`
}
// StopMarker identifies a particular stop for a direction of a route.
type StopMarker struct {
XMLName xml.Name `xml:"stop"`
Tag string `xml:"tag,attr"`
}
// Path contains a set of points that define the geographical path of a route.
type Path struct {
XMLName xml.Name `xml:"path"`
PointList []Point `xml:"point"`
}
// Point contains a latitude and longitude representing a geographical location.
type Point struct {
XMLName xml.Name `xml:"point"`
Lat string `xml:"lat,attr"`
Lon string `xml:"lon,attr"`
}
// RouteConfigParam is a configuration parameters for GetRouteConfig.
type RouteConfigParam func() string
// RouteConfigTag creates a RouteConfigParam that restricts a
// GetRouteConfig call to a single route.
func RouteConfigTag(tag string) RouteConfigParam {
return func() string {
return "r=" + url.QueryEscape(tag)
}
}
// RouteConfigTerse configures a GetRouteConfig call to avoid path results
func RouteConfigTerse() RouteConfigParam {
return func() string {
return "terse"
}
}
// RouteConfigVerbose configures a GetRouteConfig call to include directions
// not normally shown in UIs.
func RouteConfigVerbose() RouteConfigParam {
return func() string {
return "verbose"
}
}
// GetRouteConfig fetches the metadata for routes in a particular transit
// agency. Use the configParams to filter the requested data.
func (c *Client) GetRouteConfig(agencyTag string, configParams ...RouteConfigParam) ([]RouteConfig, error) {
params := []string{"command=routeConfig", "a=" + url.QueryEscape(agencyTag)}
for _, cp := range configParams {
params = append(params, cp())
}
resp, httpErr := c.httpClient.Get("http://webservices.nextbus.com/service/publicXMLFeed?" + strings.Join(params, "&"))
if httpErr != nil {
return nil, fmt.Errorf("could not fetch route config from nextbus: %v", httpErr)
}
defer resp.Body.Close()
body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil {
return nil, fmt.Errorf("could not parse route config response body: %v", readErr)
}
var a RouteConfigResponse
if xmlErr := xml.Unmarshal(body, &a); xmlErr != nil {
return nil, fmt.Errorf("could not parse route config XML: %v", xmlErr)
}
return a.RouteList, nil
}
// PredictionResponse contains a set of predictions.
type PredictionResponse struct {
XMLName xml.Name `xml:"body"`
PredictionDataList []PredictionData `xml:"predictions"`
}
// PredictionData represents a prediction for a particular route and stop. It
// contains a set of predictions arranged by direction.
type PredictionData struct {
XMLName xml.Name `xml:"predictions"`
PredictionDirectionList []PredictionDirection `xml:"direction"`
MessageList []Message `xml:"message"`
AgencyTitle string `xml:"agencyTitle,attr"`
RouteTitle string `xml:"routeTitle,attr"`
RouteTag string `xml:"routeTag,attr"`
StopTitle string `xml:"stopTitle,attr"`
StopTag string `xml:"stopTag,attr"`
}
// PredictionDirection contains a list of arrival predictions for a particular
// route and stop traveling in a specific direction.
type PredictionDirection struct {
XMLName xml.Name `xml:"direction"`
PredictionList []Prediction `xml:"prediction"`
Title string `xml:"title,attr"`
}
// Prediction is an individual arrival prediction for a particular route, stop,
// and direction.
type Prediction struct {
XMLName xml.Name `xml:"prediction"`
EpochTime string `xml:"epochTime,attr"`
Seconds string `xml:"seconds,attr"`
Minutes string `xml:"minutes,attr"`
IsDeparture string `xml:"isDeparture,attr"`
AffectedByLayover string `xml:"affectedByLayover,attr"`
DirTag string `xml:"dirTag,attr"`
Vehicle string `xml:"vehicle,attr"`
VehiclesInConsist string `xml:"vehiclesInConsist,attr"`
Block string `xml:"block,attr"`
TripTag string `xml:"tripTag,attr"`
}
// Message is an informational message provided by the transit agency.
type Message struct {
XMLName xml.Name `xml:"message"`
Text string `xml:"text,attr"`
Priority string `xml:"priority,attr"`
}
// GetStopPredictions fetches a set of predictions for a transit agency at the
// provided stop. Note that this requires the 'stopID' which is the unique
// identifier for a stop indepenedent of a route.
func (c *Client) GetStopPredictions(agencyTag string, stopID string) ([]PredictionData, error) {
resp, httpErr := c.httpClient.Get("http://webservices.nextbus.com/service/publicXMLFeed?command=predictions&a=" + agencyTag + "&stopId=" + stopID)
if httpErr != nil {
return nil, fmt.Errorf("could not fetch stop predictions from nextbus: %v", httpErr)
}
defer resp.Body.Close()
body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil {
return nil, fmt.Errorf("could not parse stop predictions response body: %v", readErr)
}
var a PredictionResponse
if xmlErr := xml.Unmarshal(body, &a); xmlErr != nil {
return nil, fmt.Errorf("could not parse stop predictions XML: %v", xmlErr)
}
return a.PredictionDataList, nil
}
// GetPredictions fetches a set of predictions for a transit agency at the
// provided route and stop.
func (c *Client) GetPredictions(agencyTag string, routeTag string, stopTag string) ([]PredictionData, error) {
resp, httpErr := c.httpClient.Get("http://webservices.nextbus.com/service/publicXMLFeed?command=predictions&a=" + agencyTag + "&r=" + routeTag + "&s=" + stopTag)
if httpErr != nil {
return nil, fmt.Errorf("could not fetch predictions from nextbus: %v", httpErr)
}
defer resp.Body.Close()
body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil {
return nil, fmt.Errorf("could not parse predictions response body: %v", readErr)
}
var a PredictionResponse
if xmlErr := xml.Unmarshal(body, &a); xmlErr != nil {
return nil, fmt.Errorf("could not parse predictions XML: %v", xmlErr)
}
return a.PredictionDataList, nil
}
// PredReqParam knows how to configure a request for a multi stop prediction.
type PredReqParam func() string
// PredReqStop specifies a route and stop which we want predictions for.
func PredReqStop(routeTag, stopTag string) PredReqParam {
return func() string {
return "stops=" + url.QueryEscape(fmt.Sprintf("%s|%s", routeTag, stopTag))
}
}
// PredReqShortTitles specifies that we want short titles in our
// predictions response.
func PredReqShortTitles() PredReqParam {
return func() string {
return "useShortTitles=true"
}
}
// GetPredictionsForMultiStops Issues a request to get predictions for multiple stops.
func (c *Client) GetPredictionsForMultiStops(agencyTag string, params ...PredReqParam) ([]PredictionData, error) {
queryParams := []string{
"command=predictionsForMultiStops",
"a=" + url.QueryEscape(agencyTag),
}
for _, p := range params {
queryParams = append(queryParams, p())
}
resp, httpErr := c.httpClient.Get("http://webservices.nextbus.com/service/publicXMLFeed?" + strings.Join(queryParams, "&"))
if httpErr != nil {
return nil, fmt.Errorf("could not fetch predictions for multiple stops from nextbus: %v", httpErr)
}
defer resp.Body.Close()
body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil {
return nil, fmt.Errorf("could not parse predictions for multiple stops response body: %v", readErr)
}
var a PredictionResponse
if xmlErr := xml.Unmarshal(body, &a); xmlErr != nil {
return nil, fmt.Errorf("could not parse predictions for multiple stops XML: %v", xmlErr)
}
return a.PredictionDataList, nil
}
// LocationResponse is a list of vehicle locations.
type LocationResponse struct {
XMLName xml.Name `xml:"body"`
VehicleList []VehicleLocation `xml:"vehicle"`
LastTime LocationLastTime `xml:"lastTime"`
}
// VehicleLocation represents the location of an individual vehicle traveling
// on a route.
type VehicleLocation struct {
XMLName xml.Name `xml:"vehicle"`
ID string `xml:"id,attr"`
RouteTag string `xml:"routeTag,attr"`
DirTag string `xml:"dirTag,attr"`
Lat string `xml:"lat,attr"`
Lon string `xml:"lon,attr"`
SecsSinceReport string `xml:"secsSinceReport,attr"`
Predictable string `xml:"predictable,attr"`
Heading string `xml:"heading,attr"`
SpeedKmHr string `xml:"speedKmHr,attr"`
LeadingVehicleID string `xml:"leadingVehicleId,attr"`
}
// LocationLastTime represents the last time that a location was reported.
type LocationLastTime struct {
XMLName xml.Name `xml:"lastTime"`
Time string `xml:"time,attr"`
}
// VehicleLocationParam is used to specify options when fetching vehicle
// locations.
type VehicleLocationParam func() string
// VehicleLocationRoute returns a VehicleLocationParam that indicates the
// desired route to filter vehicle locations by.
func VehicleLocationRoute(routeTag string) VehicleLocationParam {
return func() string {
return "r=" + url.QueryEscape(routeTag)
}
}
// VehicleLocationTime returns a VehicleLocationParam that indicates the
// desired time after which to fetch vehicle locations.
func VehicleLocationTime(t string) VehicleLocationParam {
return func() string {
return "t=" + url.QueryEscape(t)
}
}
// GetVehicleLocations fetches the set of vehicle locations for a transit
// agency. Use the configParams to filter the requested data.
func (c *Client) GetVehicleLocations(agencyTag string, configParams ...VehicleLocationParam) (*LocationResponse, error) {
params := []string{"command=vehicleLocations", "a=" + url.QueryEscape(agencyTag)}
timeWasSet := false
for _, cp := range configParams {
paramText := cp()
if strings.HasPrefix(paramText, "t=") {
timeWasSet = true
}
params = append(params, paramText)
}
if !timeWasSet {
params = append(params, VehicleLocationTime("0")())
}
resp, httpErr := c.httpClient.Get("http://webservices.nextbus.com/service/publicXMLFeed?" + strings.Join(params, "&"))
if httpErr != nil {
return nil, fmt.Errorf("could not fetch vehicle locations from nextbus: %v", httpErr)
}
defer resp.Body.Close()
body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil {
return nil, fmt.Errorf("could not parse vehicle locations response body: %v", readErr)
}
var result LocationResponse
if xmlErr := xml.Unmarshal(body, &result); xmlErr != nil {
return nil, fmt.Errorf("could not parse vehicle locations XML: %v", xmlErr)
}
return &result, nil
}