departures.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. // SPDX-FileCopyrightText: Adam Evyčędo
  2. //
  3. // SPDX-License-Identifier: AGPL-3.0-or-later
  4. package traffic
  5. import (
  6. "errors"
  7. "fmt"
  8. "log"
  9. "os"
  10. "path/filepath"
  11. "sort"
  12. "strings"
  13. "time"
  14. "apiote.xyz/p/gott/v2"
  15. traffic_errors "apiote.xyz/p/szczanieckiej/traffic/errors"
  16. "git.sr.ht/~sircmpwn/go-bare"
  17. "golang.org/x/text/language"
  18. )
  19. type DepartureRealtimeNew struct {
  20. Departure
  21. Order StopOrder
  22. Update Update
  23. Time time.Time
  24. Alerts []SpecificAlert
  25. startTime *uint
  26. }
  27. func (d DepartureRealtimeNew) IsEmpty() bool {
  28. return d.Time.IsZero()
  29. }
  30. func (d DepartureRealtimeNew) WithUpdate(update Update) DepartureRealtimeNew {
  31. d.Update = update
  32. return d
  33. }
  34. func (d DepartureRealtimeNew) WithAlerts(alerts []Alert, languages []language.Tag) DepartureRealtimeNew {
  35. d.Alerts = selectSpecificAlerts(alerts, languages)
  36. return d
  37. }
  38. func (d DepartureRealtimeNew) GetTimeWithDelay() time.Time {
  39. if d.Update.TimeUTC != "" {
  40. updateTimeUTC, err := time.Parse("150405", d.Update.Time)
  41. if err != nil {
  42. panic("departure update time ‘" + d.Update.Time + "’ not in format 150405")
  43. }
  44. updateTime := time.Date(d.Time.Year(), d.Time.Month(), d.Time.Day(), updateTimeUTC.Hour(), updateTimeUTC.Minute(), updateTimeUTC.Second(), 0, time.UTC)
  45. return updateTime.In(d.Time.Location())
  46. } else if d.Update.Time != "" {
  47. updateTime, err := time.Parse("150405", d.Update.Time)
  48. if err != nil {
  49. panic("departure update time ‘" + d.Update.Time + "’ not in format 150405")
  50. }
  51. updateDateTime := time.Date(d.Time.Year(), d.Time.Month(), d.Time.Day(), updateTime.Hour(), updateTime.Minute(), updateTime.Second(), 0, d.Time.Location())
  52. return updateDateTime
  53. } else {
  54. delay := int(d.Update.Delay)
  55. return d.Time.Add(time.Duration(delay) * time.Second)
  56. }
  57. }
  58. type DeparturesResult struct {
  59. traffic *Traffic
  60. context Context
  61. date time.Time
  62. timetableHome string
  63. calendar []Schedule
  64. stopOffset uint
  65. languages []language.Tag
  66. departuresType DeparturesType
  67. lineID string
  68. timezone *time.Location
  69. datetime time.Time
  70. minuteB4Datetime time.Time
  71. scheduleIDs map[string]map[string]struct{}
  72. stopsFile *os.File
  73. stop Stop
  74. tripsFile *os.File
  75. trips map[string][]Trip
  76. feedInfo FeedInfo
  77. enrichMethod func(string, int, string, string, Context) (map[string][]Update, map[string][]Alert, bool, error)
  78. departures []DepartureRealtimeNew
  79. }
  80. func (r DeparturesResult) getTraffic() *Traffic {
  81. return r.traffic
  82. }
  83. func (r DeparturesResult) getContext() Context {
  84. return r.context
  85. }
  86. func (r *DeparturesResult) setTimezone(l *time.Location) {
  87. r.timezone = l
  88. }
  89. func (r *DeparturesResult) setStopsFile(f *os.File) {
  90. r.stopsFile = f
  91. }
  92. func (r DeparturesResult) getTimetableHome() string {
  93. return r.timetableHome
  94. }
  95. func (r DeparturesResult) getStopsFile() *os.File {
  96. return r.stopsFile
  97. }
  98. func (r DeparturesResult) getStopOffset() uint {
  99. return r.stopOffset
  100. }
  101. func (r *DeparturesResult) setStop(s Stop) {
  102. r.stop = s
  103. }
  104. func (r *DeparturesResult) setTripsFile(f *os.File) {
  105. r.tripsFile = f
  106. }
  107. func (r DeparturesResult) getTripsFile() *os.File {
  108. return r.tripsFile
  109. }
  110. func loadTime(r TrafficResult) TrafficResult {
  111. result := r.(*DeparturesResult)
  112. now := time.Now()
  113. datetime := time.Date(result.date.Year(), result.date.Month(),
  114. result.date.Day(), now.Hour(), now.Minute(), now.Second(), 0, now.Location()).In(result.timezone)
  115. result.datetime = datetime
  116. result.minuteB4Datetime = datetime.Add(time.Duration(-1) * time.Minute)
  117. return result
  118. }
  119. func loadSchedules(r TrafficResult) (TrafficResult, error) {
  120. result := r.(*DeparturesResult)
  121. schedules := map[string]map[string]struct{}{}
  122. for _, offset := range []int{0, -1} {
  123. date := result.date.AddDate(0, 0, offset)
  124. scheduleIDs, err := findSchedule(result.timetableHome, date, result.calendar)
  125. if err != nil {
  126. log.Printf("no schedule for %s: %v\n", date, err)
  127. } else {
  128. schedules[date.Format(DateFormat)] = scheduleIDs
  129. }
  130. }
  131. result.scheduleIDs = schedules
  132. if len(schedules) == 0 {
  133. return result, fmt.Errorf("no schedules found")
  134. }
  135. return result, nil
  136. }
  137. func selectTrips(r TrafficResult) (TrafficResult, error) {
  138. result := r.(*DeparturesResult)
  139. trips := map[string][]Trip{}
  140. for _, order := range result.stop.Order {
  141. _, err := result.tripsFile.Seek(int64(order.TripOffset), 0)
  142. if err != nil {
  143. return result, fmt.Errorf("while seeking to StopOrder %v: %w", order, err)
  144. }
  145. trip := Trip{}
  146. err = bare.UnmarshalReader(result.tripsFile, &trip)
  147. if err != nil {
  148. return result, fmt.Errorf("while unmarshalling trip at offset %d: %w", order.TripOffset, err)
  149. }
  150. for startDate, scheduleIDs := range result.scheduleIDs {
  151. if _, ok := scheduleIDs[trip.ScheduleID]; ok {
  152. trips[startDate] = append(trips[startDate], trip)
  153. }
  154. }
  155. }
  156. result.trips = trips
  157. result.tripsFile.Close()
  158. return result, nil
  159. }
  160. func getFeedInfo2(r TrafficResult) (TrafficResult, error) {
  161. result := r.(*DeparturesResult)
  162. feedInfo, err := getFeedInfo(result.context.DataHome, result.context.FeedID, result.context.Version)
  163. result.feedInfo = feedInfo
  164. return result, err
  165. }
  166. func selectEnrichMethod(r TrafficResult) TrafficResult {
  167. result := r.(*DeparturesResult)
  168. if result.feedInfo.Name != "" {
  169. _, tripUpdatesRtFeed := result.feedInfo.RealtimeFeeds[TRIP_UPDATES]
  170. _, vehiclePositionsRtFeed := result.feedInfo.RealtimeFeeds[VEHICLE_POSITIONS]
  171. if tripUpdatesRtFeed || vehiclePositionsRtFeed {
  172. result.enrichMethod = getGtfsRealtimeUpdates
  173. // log.Println("GTFS")
  174. } else if isLuaUpdatesScript(result.context) {
  175. result.enrichMethod = getLuaRealtimeUpdates
  176. // log.Println("Lua")
  177. } else {
  178. result.enrichMethod = nil
  179. // log.Println("none")
  180. }
  181. } else {
  182. result.enrichMethod = nil
  183. // log.Println("else")
  184. }
  185. return result
  186. }
  187. func getNoTripsDepartures(r TrafficResult) (TrafficResult, error) {
  188. result := r.(*DeparturesResult)
  189. if !isLuaUpdatesScript(result.context) {
  190. return r, nil
  191. }
  192. updates, alerts, areTripsInTimetable, err := result.enrichMethod("", -1, result.stop.Id, result.stop.Code, result.context)
  193. if areTripsInTimetable {
  194. return r, nil
  195. }
  196. if err != nil {
  197. return r, fmt.Errorf("while getting departures: %w", err)
  198. }
  199. pickups := map[string]Boarding{}
  200. dropoffs := map[string]Boarding{}
  201. for _, trips := range result.trips {
  202. for _, trip := range trips {
  203. if _, ok := pickups[trip.LineID]; ok {
  204. continue
  205. }
  206. for _, d := range trip.Departures {
  207. if d.StopSequence == result.stop.Order[trip.Id].Sequence {
  208. pickups[trip.LineID] = d.Pickup
  209. dropoffs[trip.LineID] = d.Dropoff
  210. }
  211. }
  212. }
  213. }
  214. departures, err := makeDeparturesFromNoTripUpdates(updates[result.stop.Code], alerts, pickups, dropoffs, result.timezone, result.languages)
  215. if err != nil {
  216. return r, fmt.Errorf("while creating departures without trip: %w", err)
  217. }
  218. result.departures = departures
  219. return result, nil
  220. }
  221. func makeDeparturesFromNoTripUpdates(updates []Update, alerts map[string][]Alert, pickups, dropoffs map[string]Boarding, timezone *time.Location, languages []language.Tag) ([]DepartureRealtimeNew, error) {
  222. departures := []DepartureRealtimeNew{}
  223. now := time.Now().In(timezone)
  224. for _, update := range updates {
  225. if update.Time == "" {
  226. log.Printf("update time is empty, update is %+v\n", update)
  227. continue
  228. }
  229. departureTime, err := time.Parse("150405", update.Time)
  230. if err != nil {
  231. return departures, fmt.Errorf("while parsing time: %w", err)
  232. }
  233. update.Delay = 0
  234. departureTime = time.Date(now.Year(), now.Month(), now.Day(), departureTime.Hour(), departureTime.Minute(), departureTime.Second(), 0, timezone)
  235. departures = append(departures, DepartureRealtimeNew{
  236. Time: departureTime,
  237. Departure: Departure{
  238. Pickup: pickups[update.VehicleStatus.LineID],
  239. Dropoff: dropoffs[update.VehicleStatus.LineID],
  240. },
  241. Order: StopOrder{
  242. uint(departureTime.Unix()),
  243. 0,
  244. },
  245. Update: update,
  246. Alerts: selectSpecificAlerts(alerts[update.VehicleStatus.TripID], languages),
  247. })
  248. }
  249. return departures, nil
  250. }
  251. func getTripsDepartures(r TrafficResult) (TrafficResult, error) {
  252. result := r.(*DeparturesResult)
  253. departures := []DepartureRealtimeNew{}
  254. if len(result.departures) > 0 {
  255. return r, nil
  256. }
  257. var ber BlockingError
  258. timedOut := false
  259. for startDate, trips := range result.trips {
  260. for _, trip := range trips {
  261. tripStartDate, _ := time.Parse(DateFormat, startDate) // NOTE internally formatted date, will alway succeed
  262. if len(trip.Headways) == 0 { // NOTE departures based on schedule
  263. scheduleDeparture, err := getScheduleDeparture(trip, result.stop.Order[trip.Id], tripStartDate, result.timezone, nil, result.context)
  264. if err != nil {
  265. log.Printf("no departures found for trip %s, order %v\n", trip.Id, result.stop.Order[trip.Id])
  266. continue
  267. }
  268. if result.enrichMethod != nil && !timedOut {
  269. departure, err := enrichDeparture(result, scheduleDeparture[0], trip)
  270. if err != nil {
  271. if isTimeout(err) || errors.As(err, &ber) || strings.Contains(err.Error(), "connection refused") { // TODO or any other connection problem
  272. timedOut = true
  273. log.Printf("blocking error while enriching departure %s -> %s (%v): %v", trip.LineID, trip.Headsign, departure.Time, err)
  274. } else {
  275. log.Printf("while enriching departure %s -> %s (%v): %v\n", trip.LineID, trip.Headsign, departure.Time, err)
  276. }
  277. departures = append(departures, scheduleDeparture[0])
  278. } else {
  279. departures = append(departures, departure)
  280. }
  281. } else {
  282. departures = append(departures, scheduleDeparture[0])
  283. }
  284. } else { // departures based on frequencies
  285. for _, headway := range trip.Headways {
  286. if headway.Exact { // departures based on frequencies; exact times
  287. headwayDepartures, err := getScheduleDeparture(trip, result.stop.Order[trip.Id], tripStartDate, result.timezone, &headway, result.context)
  288. if err != nil {
  289. log.Printf("no departures found for trip %s, order %v\n", trip.Id, result.stop.Order[trip.Id])
  290. break
  291. }
  292. for _, headwayDeparture := range headwayDepartures {
  293. if result.enrichMethod != nil && !timedOut {
  294. departure, err := enrichDeparture(result, headwayDeparture, trip)
  295. if err != nil {
  296. if isTimeout(err) || errors.As(err, &ber) || strings.Contains(err.Error(), "connection refused") { // TODO or any other connection problem
  297. timedOut = true
  298. log.Printf("blocking error while enriching departure %s -> %s (%v): %v", trip.LineID, trip.Headsign, departure.Time, err)
  299. } else {
  300. log.Printf("while enriching departure %s -> %s (%v): %v\n", trip.LineID, trip.Headsign, departure.Time, err)
  301. }
  302. departures = append(departures, headwayDeparture)
  303. } else {
  304. departures = append(departures, departure)
  305. }
  306. } else {
  307. departures = append(departures, headwayDeparture)
  308. }
  309. }
  310. } else { // departures based on frequencies; inexact times
  311. headwayDepartures, err := getScheduleDeparture(trip, result.stop.Order[trip.Id], tripStartDate, result.timezone, &headway, result.context)
  312. if err != nil {
  313. log.Printf("no departures found for trip %s, order %v\n", trip.Id, result.stop.Order[trip.Id])
  314. break
  315. }
  316. var headwayUpdates []DepartureRealtimeNew
  317. if !timedOut {
  318. headwayUpdates, err = getUpdatesDepartures(result, trip, headwayDepartures[0].Pickup, headwayDepartures[0].Dropoff, false, tripStartDate, headwayDepartures[0].Departure.Time)
  319. if err != nil {
  320. if isTimeout(err) || errors.As(err, &ber) || strings.Contains(err.Error(), "connection refused") { // TODO or any other connection problem
  321. timedOut = true
  322. log.Printf("blocking error while getting updates departures: %v", err)
  323. } else {
  324. log.Printf("blocking error while getting updates departures: %v", err)
  325. }
  326. }
  327. }
  328. headwayUpdatesNumber := len(headwayUpdates)
  329. if headwayUpdatesNumber == 0 {
  330. departures = append(departures, headwayDepartures...)
  331. } else {
  332. departures = append(departures, headwayUpdates...)
  333. templateDeparture := headwayUpdates[0]
  334. for i := uint(0); ; i++ {
  335. singleDepartureTime := templateDeparture.Time.Add(time.Duration(headway.Interval*i) * -time.Second)
  336. if singleDepartureTime.Before(headwayDepartures[0].Time) {
  337. break
  338. }
  339. singleDeparture := templateDeparture
  340. singleDeparture.Update = Update{
  341. VehicleStatus: VehicleStatus{
  342. LineID: trip.LineID,
  343. Headsign: trip.Headsign,
  344. },
  345. }
  346. singleDeparture.Time = singleDepartureTime
  347. departures = append(departures, singleDeparture)
  348. }
  349. templateDeparture = headwayUpdates[headwayUpdatesNumber-1]
  350. for i := uint(0); ; i++ {
  351. singleDepartureTime := templateDeparture.Time.Add(time.Duration(headway.Interval*i) * time.Second)
  352. if singleDepartureTime.After(headwayDepartures[len(headwayDepartures)-1].Time) {
  353. break
  354. }
  355. singleDeparture := templateDeparture
  356. singleDeparture.Update = Update{
  357. VehicleStatus: VehicleStatus{
  358. LineID: trip.LineID,
  359. Headsign: trip.Headsign,
  360. },
  361. }
  362. singleDeparture.Time = singleDepartureTime
  363. departures = append(departures, singleDeparture)
  364. }
  365. }
  366. }
  367. }
  368. }
  369. }
  370. }
  371. result.departures = departures
  372. return result, nil
  373. }
  374. func getScheduleDeparture(trip Trip, order StopOrder, tripStartDate time.Time, timezone *time.Location, headway *Headway, context Context) ([]DepartureRealtimeNew, error) {
  375. templateDeparture := DepartureRealtimeNew{}
  376. found := false
  377. for _, departure := range trip.Departures {
  378. if departure.StopSequence == order.Sequence {
  379. templateDeparture.Departure = departure
  380. templateDeparture.Order = order
  381. templateDeparture.Update = Update{
  382. VehicleStatus: VehicleStatus{
  383. Headsign: trip.Headsign,
  384. LineID: trip.LineID,
  385. },
  386. }
  387. templateDeparture.Exact = departure.Exact
  388. templateDeparture.Time = calculateGtfsTime(departure.Time, 0, tripStartDate,
  389. timezone)
  390. found = true
  391. break
  392. }
  393. }
  394. if !found {
  395. return []DepartureRealtimeNew{}, traffic_errors.NoStopOrder{
  396. TripID: trip.Id,
  397. Order: order.Sequence,
  398. }
  399. }
  400. if headway == nil {
  401. return []DepartureRealtimeNew{templateDeparture}, nil
  402. }
  403. departures := []DepartureRealtimeNew{}
  404. for i := uint(0); ; i++ {
  405. if headway.StartTime+(headway.Interval*i) > headway.EndTime {
  406. break
  407. }
  408. singleDeparture := templateDeparture
  409. singleDeparture.Exact = (headway.Exact && templateDeparture.Exact)
  410. singleDeparture.Time = singleDeparture.Time.Add(time.Duration(headway.StartTime) * time.Second).Add(time.Duration(headway.Interval*i) * time.Second)
  411. startTime := templateDeparture.Departure.Time + headway.StartTime + (headway.Interval * i)
  412. singleDeparture.startTime = &startTime
  413. departures = append(departures, singleDeparture)
  414. }
  415. return departures, nil
  416. }
  417. func enrichDeparture(r *DeparturesResult, scheduleDeparture DepartureRealtimeNew, trip Trip) (DepartureRealtimeNew, error) {
  418. if r.departuresType == DEPARTURES_HYBRID {
  419. var (
  420. updates map[string][]Update
  421. alerts map[string][]Alert
  422. err error
  423. )
  424. updates, alerts, _, err = r.enrichMethod(trip.Id, scheduleDeparture.Order.Sequence, r.stop.Id, r.stop.Code, r.context)
  425. if err != nil {
  426. return scheduleDeparture, err
  427. }
  428. tripUpdates := updates[trip.Id]
  429. var validTripUpdate Update
  430. for _, tripUpdate := range tripUpdates {
  431. if scheduleDeparture.startTime != nil && tripUpdate.StartTime != nil && *scheduleDeparture.startTime != *tripUpdate.StartTime {
  432. // TODO if update.relationship is UNSCHEDULED -> add this update as new departure
  433. continue
  434. }
  435. if tripUpdate.StopSequence > uint32(scheduleDeparture.Order.Sequence) {
  436. break
  437. }
  438. validTripUpdate.Time = tripUpdate.Time
  439. validTripUpdate.Delay = tripUpdate.Delay
  440. validTripUpdate.StopID = tripUpdate.StopID
  441. validTripUpdate.StopSequence = tripUpdate.StopSequence
  442. validTripUpdate.TimetableRelationship = tripUpdate.TimetableRelationship
  443. validTripUpdate.VehicleStatus = tripUpdate.VehicleStatus
  444. }
  445. validTripUpdate.VehicleStatus.LineID = trip.LineID
  446. validTripUpdate.VehicleStatus.Headsign = trip.Headsign
  447. departure := scheduleDeparture.WithUpdate(validTripUpdate).WithAlerts(alerts[trip.Id], r.languages)
  448. return departure, nil
  449. } else {
  450. update := Update{}
  451. update.VehicleStatus.LineID = trip.LineID
  452. update.VehicleStatus.Headsign = trip.Headsign
  453. return scheduleDeparture.WithUpdate(update), nil
  454. }
  455. }
  456. func getUpdatesDepartures(r *DeparturesResult, trip Trip, pickup, dropoff Boarding, exact bool, tripStartDate time.Time, departureTime uint) ([]DepartureRealtimeNew, error) {
  457. departures := []DepartureRealtimeNew{}
  458. updates, alerts, _, err := r.enrichMethod(trip.Id, r.stop.Order[trip.Id].Sequence, r.stop.Id, r.stop.Code, r.context)
  459. if err != nil {
  460. return departures, err
  461. }
  462. tripUpdates := updates[trip.Id]
  463. updatesMap := map[uint]Update{}
  464. for _, tripUpdate := range tripUpdates {
  465. if tripUpdate.StopSequence > uint32(r.stop.Order[trip.Id].Sequence) {
  466. break
  467. }
  468. if tripUpdate.StartTime == nil {
  469. log.Printf("invalid update for frequency-based inexact-time trip: without start time")
  470. }
  471. validTripUpdate := updatesMap[*tripUpdate.StartTime]
  472. validTripUpdate.Time = tripUpdate.Time
  473. validTripUpdate.Delay = tripUpdate.Delay
  474. validTripUpdate.StopID = tripUpdate.StopID
  475. validTripUpdate.StopSequence = tripUpdate.StopSequence
  476. validTripUpdate.TimetableRelationship = tripUpdate.TimetableRelationship
  477. validTripUpdate.VehicleStatus = tripUpdate.VehicleStatus
  478. validTripUpdate.VehicleStatus.LineID = trip.LineID
  479. validTripUpdate.VehicleStatus.Headsign = trip.Headsign
  480. updatesMap[*tripUpdate.StartTime] = validTripUpdate
  481. }
  482. for _, validTripUpdate := range updatesMap {
  483. scheduleDeparture := DepartureRealtimeNew{
  484. Departure: Departure{
  485. StopSequence: r.stop.Order[trip.Id].Sequence,
  486. Pickup: pickup,
  487. Dropoff: dropoff,
  488. Exact: exact,
  489. },
  490. Order: r.stop.Order[trip.Id],
  491. Update: validTripUpdate,
  492. Time: calculateGtfsTime(*validTripUpdate.StartTime, 0, tripStartDate, r.timezone).Add(time.Duration(departureTime) * time.Second),
  493. startTime: validTripUpdate.StartTime,
  494. }
  495. departure := scheduleDeparture.WithUpdate(validTripUpdate).WithAlerts(alerts[trip.Id], r.languages)
  496. departures = append(departures, departure)
  497. }
  498. return departures, nil
  499. }
  500. func dropTrips(r TrafficResult) TrafficResult {
  501. result := r.(*DeparturesResult)
  502. result.trips = map[string][]Trip{}
  503. return result
  504. }
  505. func filterDepartures(r TrafficResult) TrafficResult {
  506. result := r.(*DeparturesResult)
  507. departures := []DepartureRealtimeNew{}
  508. midnight := result.date // TODO should be in client timezone
  509. for _, departure := range result.departures {
  510. if (result.departuresType == DEPARTURES_FULL && departure.GetTimeWithDelay().After(midnight)) || (result.departuresType == DEPARTURES_HYBRID && departure.GetTimeWithDelay().After(result.minuteB4Datetime)) {
  511. departures = append(departures, departure)
  512. }
  513. }
  514. result.departures = departures
  515. return result
  516. }
  517. func filterDeparturesByLine(r TrafficResult) TrafficResult {
  518. result := r.(*DeparturesResult)
  519. departures := []DepartureRealtimeNew{}
  520. if result.lineID != "" {
  521. for _, departure := range result.departures {
  522. if departure.Update.VehicleStatus.LineID == result.lineID {
  523. departures = append(departures, departure)
  524. }
  525. }
  526. result.departures = departures
  527. }
  528. return result
  529. }
  530. func addAlerts(r TrafficResult) TrafficResult {
  531. result := r.(*DeparturesResult)
  532. alertedDepartures := make([]DepartureRealtimeNew, len(result.departures))
  533. for i, d := range result.departures {
  534. if len(d.Alerts) == 0 {
  535. d.Alerts = GetAlerts("", "", int(d.Order.TripOffset), result.context, result.traffic, result.languages)
  536. }
  537. alertedDepartures[i] = d
  538. }
  539. result.departures = alertedDepartures
  540. return result
  541. }
  542. func sortDepartures(r TrafficResult) TrafficResult {
  543. result := r.(*DeparturesResult)
  544. sort.Slice(result.departures, func(i, j int) bool {
  545. return result.departures[i].GetTimeWithDelay().Before(result.departures[j].GetTimeWithDelay())
  546. })
  547. return result
  548. }
  549. func GetDepartures(stopCode, lineID string, ctx Context, traffic *Traffic, date time.Time, departuresType DeparturesType, languages []language.Tag) ([]DepartureRealtimeNew, error) {
  550. codeIndex := traffic.CodeIndexes[ctx.FeedID][ctx.Version]
  551. calendar := traffic.Calendars[ctx.FeedID][ctx.Version]
  552. result := &DeparturesResult{
  553. traffic: traffic,
  554. context: ctx,
  555. date: date, // has timezone of feed
  556. timetableHome: filepath.Join(ctx.DataHome, ctx.FeedID, string(ctx.Version)),
  557. calendar: calendar,
  558. stopOffset: codeIndex[stopCode],
  559. languages: languages,
  560. departuresType: departuresType,
  561. lineID: lineID,
  562. }
  563. r := gott.R[TrafficResult]{S: result}.
  564. Bind(loadTimezone).
  565. Map(loadTime).
  566. Bind(loadSchedules).
  567. Bind(openStopsFile).
  568. Bind(seekStopsFile).
  569. Bind(unmarshallStop).
  570. Bind(openTripsFile).
  571. Bind(selectTrips).
  572. Bind(getFeedInfo2).
  573. Map(selectEnrichMethod).
  574. Bind(getNoTripsDepartures).
  575. Bind(getTripsDepartures).
  576. Map(dropTrips).
  577. Map(filterDepartures).
  578. Map(filterDeparturesByLine).
  579. Map(addAlerts).
  580. Map(sortDepartures).
  581. Recover(closeFiles)
  582. return r.S.(*DeparturesResult).departures, r.E
  583. }