forked from npaton/gogtfs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
trip.go
339 lines (301 loc) · 10.4 KB
/
trip.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
package gtfs
import (
"fmt"
"strconv"
"time"
)
// Trip.Direction possible values: (There is no "in" or "out" directions, they symbolize two opposite directions)
const (
DirectionOut = iota // 0 - travel in one direction (e.g. outbound travel)
DirectionIn // 1 - travel in the opposite direction (e.g. inbound travel)
)
// trips.txt
type Trip struct {
// route_id - Required. The route_id field contains an ID that uniquely identifies a route.
// This value is referenced from the routes.txt file.
Route *Route
// service_id - Required. The service_id contains an ID that uniquely identifies a set of dates when service
// is available for one or more routes. This value is referenced from the calendar.txt or calendar_dates.txt file.
ServiceId string
// trip_id - Required. The trip_id field contains an ID that identifies a trip. The trip_id is dataset unique.
Id string
// trip_headsign - Optional. The trip_headsign field contains the text that appears on a sign that identifies the
// trip's destination to passengers. Use this field to distinguish between different patterns of service in the
// same route. If the headsign changes during a trip, you can override the trip_headsign by specifying values
// for the the stop_headsign field in stop_times.txt.
// See a Google Maps screenshot highlighting the headsign:
// http://code.google.com/transit/spec/transit_feed_specification.html#transitTripHeadsignScreenshot
Headsign string
// trip_short_name - Optional. The trip_short_name field contains the text that appears in schedules and sign boards
// to identify the trip to passengers, for example, to identify train numbers for commuter rail trips. If riders do
// not commonly rely on trip names, please leave this field blank.
// A trip_short_name value, if provided, should uniquely identify a trip within a service day; it should not be used
// for destination names or limited/express designations.
ShortName string
// direction_id - Optional. The direction_id field contains a binary value that indicates the direction of travel for
// a trip. Use this field to distinguish between bi-directional trips with the same route_id. This field is not used
// in routing; it provides a way to separate trips by direction when publishing time tables. You can specify names for
// each direction with the trip_headsign field.
// 0 - travel in one direction (e.g. outbound travel)
// 1 - travel in the opposite direction (e.g. inbound travel)
// For example, you could use the trip_headsign and direction_id fields together to assign a name to travel in each
// direction on trip "1234", the trips.txt file would contain these rows for use in time tables:
//
// trip_id, ... ,trip_headsign,direction_id
// 1234, ... , to Airport,0
// 1505, ... , to Downtown,1
//
// See DirectionIn/Out constants
Direction byte
// Check if direction is set
HasDirection bool
// block_id - Optional. The block_id field identifies the block to which the trip belongs. A block consists of two
// or more sequential trips made using the same vehicle, where a passenger can transfer from one trip to the next just
// by staying in the vehicle. The block_id must be referenced by two or more trips in trips.txt.
BlockId string
// shape_id - Optional. The shape_id field contains an ID that defines a shape for the trip. This value is referenced
// from the shapes.txt file. The shapes.txt file allows you to define how a line should be drawn on the map to represent a trip.
ShapeId string
//
DayRange
StopTimes []*StopTime
Frequencies []Frequency
feed *Feed
}
func (t *Trip) NextStopTimeWithTransfer(fromstop, after *Stop) (stopTime *StopTime, cost int, doesRun bool) {
// Looking for stop after fromstop in trip sequence that has attached stoptimes
if fromstop == nil {
return nil, 0, false
}
foundFrom := false
foundAfter := false
var previousStopTime *StopTime
for _, st := range t.StopTimes {
if foundFrom {
// We from departure to departure (and not arrival) to automatically include transfer/wait time. Kinda.
cost += int(st.DepartureTime - previousStopTime.DepartureTime)
previousStopTime = st
}
if st.Stop != nil {
if !foundFrom && st.Stop == fromstop {
foundFrom = true
previousStopTime = st
}
if len(st.Stop.StopTimes) > 1 && foundFrom && (after == nil || foundAfter) {
return st, cost, true
}
if after != nil && !foundAfter && st.Stop == after {
foundAfter = true
}
}
}
return nil, 0, false
}
func (t *Trip) RunsFromTo(fromstop, tostop *Stop) (stopTime *StopTime, cost int, doesRun bool) {
if fromstop == nil {
return nil, 0, false
}
if tostop == nil {
return nil, 0, false
}
foundFrom := false
var previousStopTime *StopTime
for _, st := range t.StopTimes {
if foundFrom {
// We from departure to departure (and not arrival) to automatically include transfer/wait time. Kinda.
cost += int(st.DepartureTime - previousStopTime.DepartureTime)
previousStopTime = st
}
if !foundFrom && st.Stop != nil && st.Stop == fromstop {
foundFrom = true
previousStopTime = st
}
if st.Stop != nil && st.Stop.Id == tostop.Id {
if !foundFrom {
return nil, 0, false
}
return st, cost, true
}
}
return nil, 0, false
}
func (t *Trip) RunsAccross(stop *Stop) bool {
for _, st := range t.StopTimes {
if st.Stop.Id == stop.Id {
return true
}
}
return false
}
func (t *Trip) InterpolateStopTimes() {
// Go through stoptime sequence
// remember time of last valid arrival/departure
// remember position of first missing arrival/departure
// interpolate when next valid arrival/departure is found
firstArrivalMissing := -1
firstDepartureMissing := -1
var lastArrival, lastDeparture uint
for i, st := range t.StopTimes {
if !st.ArrivalInterpolated {
if firstArrivalMissing != -1 {
avgTime := uint((st.ArrivalTime - lastArrival) / uint(i-firstArrivalMissing+1))
for j := firstArrivalMissing; j < i; j += 1 {
t.StopTimes[j].ArrivalTime = lastArrival + avgTime*uint(j-firstArrivalMissing+1)
}
firstArrivalMissing = -1
}
lastArrival = st.ArrivalTime
} else {
if firstArrivalMissing == -1 {
firstArrivalMissing = i
}
}
if !st.DepartureInterpolated {
if firstDepartureMissing != -1 {
avgTime := uint((st.DepartureTime - lastDeparture) / uint(i-firstDepartureMissing+1))
for j := firstDepartureMissing; j < i; j += 1 {
t.StopTimes[j].DepartureTime = lastDeparture + avgTime*uint(j-firstDepartureMissing+1)
}
firstDepartureMissing = -1
}
lastDeparture = st.DepartureTime
} else {
if firstDepartureMissing == -1 {
firstDepartureMissing = i
}
}
}
}
// AddStopTime adds StopTime to trip.StopTimes with respect to the stop_sequence order
func (t *Trip) AddStopTime(newStopTime *StopTime) {
if t.StopTimes == nil {
t.StopTimes = make([]*StopTime, 0, 5)
}
stopTimesLength := len(t.StopTimes)
if stopTimesLength == 0 {
// If first element simply append
t.StopTimes = append(t.StopTimes, newStopTime)
} else {
// If new stop time stop seq is superior to the last one on StopTimes array, append
if t.StopTimes[len(t.StopTimes)-1].StopSequence < newStopTime.StopSequence {
t.StopTimes = append(t.StopTimes, newStopTime)
} else {
// Otherwise rebuild new array, inserting the new stop time at right time
newStopTimes := make([]*StopTime, 0, stopTimesLength+1)
hasAppendedNewStopTime := false
for _, existingStopTime := range t.StopTimes {
if existingStopTime != nil {
if !hasAppendedNewStopTime && newStopTime.StopSequence < existingStopTime.StopSequence {
newStopTimes = append(newStopTimes, newStopTime)
hasAppendedNewStopTime = true
}
newStopTimes = append(newStopTimes, existingStopTime)
}
}
t.StopTimes = newStopTimes
}
}
}
type DayRange struct {
from uint // time of day in seconds since midnight
to uint // in seconds
}
func (a *DayRange) Intersects(b *DayRange) bool {
return (a.from <= b.from && a.to >= b.from) || (b.from <= a.from && b.to >= a.from)
}
func (a *DayRange) Contains(b *DayRange) bool {
return a.from <= b.from && a.to >= b.to
}
func (a *DayRange) Add(b *DayRange) {
if b.from < a.from {
a.from = b.from
}
if b.to > a.to {
a.to = b.to
}
}
func (t *Trip) calculateDayTimeRange() {
stopTimesLength := len(t.StopTimes)
if stopTimesLength > 0 {
dayrange := &DayRange{t.StopTimes[0].DepartureTime, t.StopTimes[stopTimesLength-1].ArrivalTime}
for _, freq := range t.Frequencies {
dayrange.Add(&freq.DayRange)
}
t.DayRange = *dayrange
} else {
t.DayRange = DayRange{0, 0}
}
}
func (t *Trip) HasShape() bool {
return t.ShapeId != "" && t.feed.Shapes[t.ShapeId] != nil
}
func (t *Trip) RunsOn(date *time.Time) (runs bool) {
runs = false // Unnecessary, default init to false, no?
intdate, _ := strconv.Atoi(fmt.Sprintf("%04d", date.Year()) + fmt.Sprintf("%02d", date.Month()) + fmt.Sprintf("%02d", date.Day()))
if calendar, ok := t.feed.Calendars[t.ServiceId]; ok {
if calendar.ValidOn(intdate, date) {
runs = true
}
}
if calendardates, ok := t.feed.CalendarDates[t.ServiceId]; ok {
// log.Println("calendardates", calendardates)
for _, cd := range calendardates {
if exceptionOnDay, shouldRun := cd.ExceptionOn(intdate); exceptionOnDay {
// log.Println("calendardate shouldRun", shouldRun)
runs = shouldRun
}
}
}
return
}
func (t *Trip) afterInit() {
t.Frequencies = make([]Frequency, 0)
}
func (t *Trip) setField(fieldName, val string) {
// log.Println("setField", fieldName, value)
switch fieldName {
case "trip_id":
t.Id = val
break
case "route_id":
t.Route = t.feed.Routes[val]
break
case "service_id":
t.ServiceId = val
break
case "trip_headsign":
t.Headsign = val
break
case "trip_short_name":
t.ShortName = val
break
case "direction_id":
t.HasDirection = true
v, _ := strconv.Atoi(val) // Should panic on error !
if v == 0 {
t.Direction = DirectionOut
} else if v == 1 {
t.Direction = DirectionIn
}
break
case "block_id":
t.BlockId = val
break
case "shape_id":
t.ShapeId = val
break
}
}
func (t *Trip) copyColorToShape() {
if t.Route == nil {
return
}
color := t.Route.Color
if color != "" && t.feed.Shapes[t.ShapeId] != nil {
// log.Println("INSPECT", color, "===", t.feed.Shapes[t.ShapeId])
t.feed.Shapes[t.ShapeId].Color = color
} else if t.feed.Shapes[t.ShapeId] != nil {
t.feed.Shapes[t.ShapeId].Color = "000000"
}
// log.Println("INSPECT", color, "===", t.feed.Shapes[t.ShapeId])
}