Author: Adam Evyčędo <git@apiote.xyz>
get gtfs updates refactor
api/api.go | 279 +++++++++++++++++------------------------- api/feedsResponse.go | 11 + gtfs_rt/main.go | 20 +- gtfs_rt/new.go | 30 ++++ server/handler_vars.go | 2 server/router.go | 20 +- traffic/access.go | 286 +++++++++++++++++++------------------------ traffic/feed_info.go | 2 traffic/gtfs_rt.go | 91 ++++++++++++++ traffic/realtime.go | 153 +++++++++++++++++++++++ traffic/structs.go | 20 +- traffic/structs_gen.go | 21 +-
diff --git a/api/api.go b/api/api.go index 0d8b4dabfb1fc8dacdcf0571161ed7304ff04765..7b7a58aec618f4fee3f974afe59341327978454d 100644 --- a/api/api.go +++ b/api/api.go @@ -3,9 +3,8 @@ // todo(BAF10) direction (0|1) to const (TO|BACK) import ( - "apiote.xyz/p/szczanieckiej/gtfs_rt" - pb "apiote.xyz/p/szczanieckiej/gtfs_rt/transit_realtime" "apiote.xyz/p/szczanieckiej/traffic" + "golang.org/x/text/language" "encoding/hex" "errors" @@ -20,13 +19,16 @@ "github.com/adrg/strutil/metrics" "github.com/dhconnelly/rtreego" ) -func convertTrafficAlerts(trafficAlerts []traffic.Alert) []AlertV1 { +func convertTrafficAlerts(trafficAlerts []traffic.Alert, preferredLanguages []language.Tag) []AlertV1 { alerts := []AlertV1{} for _, alert := range trafficAlerts { + headerTag := selectLanguageByTag(alert.Headers, preferredLanguages) + descriptionTag := selectLanguageByTag(alert.Descriptions, preferredLanguages) + urlTag := selectLanguageByTag(alert.URLs, preferredLanguages) a := AlertV1{ - Header: alert.Header, - Description: alert.Header, - Url: alert.URL, + Header: alert.Headers[headerTag], + Description: alert.Descriptions[descriptionTag], + Url: alert.URLs[urlTag], Cause: makeAlertCauseV1(alert), Effect: makeAlertEffectV1(alert), } @@ -284,10 +286,10 @@ Queryables: []QueryableV2{}, } for _, item := range items { if stop, ok := item.(traffic.Stop); ok { - s := convertTrafficStopV2(stop, context.FeedName) + s := convertTrafficStopV2(stop, context.FeedID) success.Queryables = append(success.Queryables, QueryableV2(s)) } else if line, ok := item.(traffic.Line); ok { - l := convertTrafficLine(line, context.FeedName) + l := convertTrafficLine(line, context.FeedID) success.Queryables = append(success.Queryables, QueryableV2(l)) } else { // todo error @@ -312,10 +314,10 @@ Queryables: []QueryableV3{}, } for _, item := range items { if stop, ok := item.(traffic.Stop); ok { - s := convertTrafficStopV2(stop, context.FeedName) + s := convertTrafficStopV2(stop, context.FeedID) success.Queryables = append(success.Queryables, QueryableV3(s)) } else if line, ok := item.(traffic.Line); ok { - l := convertTrafficLineV2(line, context.FeedName) + l := convertTrafficLineV2(line, context.FeedID) success.Queryables = append(success.Queryables, QueryableV3(l)) } else { // todo error @@ -516,7 +518,7 @@ Locatables: []LocatableV2{}, } for _, locatable := range locatables { if stop, ok := locatable.(traffic.Stop); ok { - s := convertTrafficStopV2(stop, context.FeedName) + s := convertTrafficStopV2(stop, context.FeedID) success.Locatables = append(success.Locatables, LocatableV2(s)) } else if vehicle, ok := locatable.(traffic.VehicleStatus); ok { v, err := convertTrafficVehicleV2(vehicle, context, t) @@ -540,7 +542,7 @@ Locatables: []LocatableV3{}, } for _, locatable := range locatables { if stop, ok := locatable.(traffic.Stop); ok { - s := convertTrafficStopV2(stop, context.FeedName) + s := convertTrafficStopV2(stop, context.FeedID) success.Locatables = append(success.Locatables, LocatableV3(s)) } else if vehicle, ok := locatable.(traffic.VehicleStatus); ok { v, err := convertTrafficVehicleV3(vehicle, context, t) @@ -559,7 +561,7 @@ return success, nil } func CreateSuccessLine(line traffic.Line, context traffic.Context, t *traffic.Traffic) (LineResponse, error) { - l := convertTrafficLine(line, context.FeedName) + l := convertTrafficLine(line, context.FeedID) l, err := convertTrafficLineGraphs(line, l, context, t) if err != nil { return LineResponseV1{}, fmt.Errorf("while converting graph: %w", err) @@ -572,7 +574,7 @@ return success, nil } func CreateSuccessLineV2(line traffic.Line, context traffic.Context, t *traffic.Traffic) (LineResponse, error) { - l := convertTrafficLineV2(line, context.FeedName) + l := convertTrafficLineV2(line, context.FeedID) l, err := convertTrafficLineGraphsV1forLineV2(line, l, context, t) if err != nil { return LineResponseDev{}, fmt.Errorf("while converting graph: %w", err) @@ -584,10 +586,61 @@ return success, nil } -func convertVehicle(update gtfs_rt.Update, vehicles map[string]traffic.Vehicle, line traffic.Line, headsign string) VehicleV1 { +func convertVehicleStatusV1(status traffic.DepartureStatus, timeToArrival float64) VehicleStatusV1 { + if status == traffic.AT_STOP { + return STATUS_AT_STOP + } else if timeToArrival < 0 { + return STATUS_DEPARTED + } else if timeToArrival < 1 { + return STATUS_INCOMING + } + return STATUS_IN_TRANSIT +} + +func convertCongestionLevelV1(level traffic.CongestionLevel) CongestionLevelV1 { + switch level { + case traffic.CONGESTION_UNKNOWN: + return CONGESTION_UNKNOWN + case traffic.CONGESTION_SMOOTH: + return CONGESTION_SMOOTH + case traffic.CONGESTION_STOP_AND_GO: + return CONGESTION_STOP_AND_GO + case traffic.CONGESTION_SIGNIFICANT: + return CONGESTION_SIGNIFICANT + case traffic.CONGESTION_SEVERE: + return CONGESTION_SEVERE + default: + return CONGESTION_UNKNOWN + } +} + +func convertOccupancyStatusV1(status traffic.OccupancyStatus) OccupancyStatusV1 { + switch status { + case traffic.OCCUPANCY_UNKNOWN: + return OCCUPANCY_UNKNOWN + case traffic.OCCUPANCY_EMPTY: + return OCCUPANCY_EMPTY + case traffic.OCCUPANCY_MANY_AVAILABLE: + return OCCUPANCY_MANY_AVAILABLE + case traffic.OCCUPANCY_FEW_AVAILABLE: + return OCCUPANCY_FEW_AVAILABLE + case traffic.OCCUPANCY_STANDING_ONLY: + return OCCUPANCY_STANDING_ONLY + case traffic.OCCUPANCY_CRUSHED: + return OCCUPANCY_CRUSHED + case traffic.OCCUPANCY_FULL: + return OCCUPANCY_FULL + case traffic.OCCUPANCY_NOT_ACCEPTING: + return OCCUPANCY_NOT_ACCEPTING + default: + return OCCUPANCY_UNKNOWN + } +} + +func convertVehicle(update traffic.Update, vehicles map[string]traffic.Vehicle, line traffic.Line, headsign string) VehicleV1 { return VehicleV1{ Id: update.VehicleID, - Position: PositionV1{Lat: float64(update.Latitude), Lon: float64(update.Longitude)}, + Position: PositionV1{Lat: update.Latitude, Lon: update.Longitude}, Capabilities: vehicles[update.VehicleID].Capabilities, Speed: update.Speed, Line: LineStubV1{Name: line.Name, Kind: makeLineTypeV1(line), Colour: fromColor(line.Colour)}, @@ -597,7 +650,7 @@ // todo OccupancyStatus } } -func convertVehicleV2(update gtfs_rt.Update, vehicles map[string]traffic.Vehicle, line traffic.Line, headsign string) VehicleV2 { +func convertVehicleV2(update traffic.Update, vehicles map[string]traffic.Vehicle, line traffic.Line, headsign string) VehicleV2 { return VehicleV2{ Id: update.VehicleID, Position: PositionV1{Lat: float64(update.Latitude), Lon: float64(update.Longitude)}, @@ -610,7 +663,7 @@ // todo OccupancyStatus } } -func convertVehicleV3(update gtfs_rt.Update, vehicles map[string]traffic.Vehicle, line traffic.Line, headsign string) VehicleV3 { +func convertVehicleV3(update traffic.Update, vehicles map[string]traffic.Vehicle, line traffic.Line, headsign string) VehicleV3 { return VehicleV3{ Id: update.VehicleID, Position: PositionV1{Lat: float64(update.Latitude), Lon: float64(update.Longitude)}, @@ -623,19 +676,18 @@ // todo OccupancyStatus } } -func CreateSuccessDeparturesV1(stop traffic.Stop, departures []traffic.DepartureRealtime, - date time.Time, vehicles map[string]traffic.Vehicle, alerts []traffic.Alert, ctx traffic.Context, t *traffic.Traffic, accept uint) (DeparturesResponse, error) { +func CreateSuccessDeparturesV1(stop traffic.Stop, departures []traffic.DepartureRealtime, date time.Time, vehicles map[string]traffic.Vehicle, alerts []traffic.Alert, ctx traffic.Context, t *traffic.Traffic, accept uint, preferredLanguages []language.Tag) (DeparturesResponse, error) { d := []DepartureV1{} var success DeparturesResponse now := time.Now() - timezone, err := traffic.GetTimezone(stop, t.Feeds[ctx.FeedName]) + timezone, err := traffic.GetTimezone(stop, t.Feeds[ctx.FeedID]) if err != nil { return success, err } datetime := time.Date(date.Year(), date.Month(), date.Day(), now.Hour(), now.Minute(), now.Second(), 0, timezone) for _, trafficDeparture := range departures { - zoneAbbr := trafficDeparture.Update.Time.Location().String() + zoneAbbr := trafficDeparture.Time.Location().String() stopOrder, err := marshalStopOrder(trafficDeparture.Order.TripOffset, trafficDeparture.Order.Sequence) if err != nil { return success, err @@ -644,83 +696,49 @@ line, err := traffic.GetLine(trafficDeparture.LineName, ctx, t) if err != nil { return success, fmt.Errorf("while getting line %s: %w", trafficDeparture.LineName, err) } + departureTime := traffic.GetTimeWithDelay(trafficDeparture) departure := DepartureV1{ Id: stopOrder, Time: TimeV1{ - DayOffset: getDayOffset(datetime, trafficDeparture.Update.Time), - Hour: uint8(trafficDeparture.Update.Time.Hour()), - Minute: uint8(trafficDeparture.Update.Time.Minute()), - Second: uint8(trafficDeparture.Update.Time.Second()), + DayOffset: getDayOffset(datetime, departureTime), + Hour: uint8(departureTime.Hour()), + Minute: uint8(departureTime.Minute()), + Second: uint8(departureTime.Second()), Zone: zoneAbbr, }, Status: STATUS_IN_TRANSIT, - IsRealtime: trafficDeparture.Update.TripUpdate != nil, + IsRealtime: trafficDeparture.Update.Delay != nil, Vehicle: convertVehicle(trafficDeparture.Update, vehicles, line, trafficDeparture.Headsign), Boarding: makeBoardingV1(trafficDeparture.Departure.Pickup, trafficDeparture.Departure.Dropoff), } - timeToArrival := trafficDeparture.Update.Time.Sub(datetime).Minutes() + timeToArrival := departureTime.Sub(datetime).Minutes() if departure.IsRealtime { - if trafficDeparture.Update.Status == pb.VehiclePosition_STOPPED_AT { - departure.Status = STATUS_AT_STOP - } else if timeToArrival < 0 { - departure.Status = STATUS_DEPARTED - } else if timeToArrival < 1 { - departure.Status = STATUS_INCOMING - } - - if trafficDeparture.Update.CongestionLevel == nil { - departure.Vehicle.CongestionLevel = CONGESTION_UNKNOWN - } else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_RUNNING_SMOOTHLY { - departure.Vehicle.CongestionLevel = CONGESTION_SMOOTH - } else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_STOP_AND_GO { - departure.Vehicle.CongestionLevel = CONGESTION_STOP_AND_GO - } else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_CONGESTION { - departure.Vehicle.CongestionLevel = CONGESTION_SIGNIFICANT - } else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_SEVERE_CONGESTION { - departure.Vehicle.CongestionLevel = CONGESTION_SEVERE - } - - if trafficDeparture.Update.OccupancyStatus == nil { - departure.Vehicle.OccupancyStatus = OCCUPANCY_UNKNOWN - } else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_EMPTY { - departure.Vehicle.OccupancyStatus = OCCUPANCY_EMPTY - } else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_MANY_SEATS_AVAILABLE { - departure.Vehicle.OccupancyStatus = OCCUPANCY_MANY_AVAILABLE - } else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_FEW_SEATS_AVAILABLE { - departure.Vehicle.OccupancyStatus = OCCUPANCY_FEW_AVAILABLE - } else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_STANDING_ROOM_ONLY { - departure.Vehicle.OccupancyStatus = OCCUPANCY_STANDING_ONLY - } else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_CRUSHED_STANDING_ROOM_ONLY { - departure.Vehicle.OccupancyStatus = OCCUPANCY_CRUSHED - } else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_FULL { - departure.Vehicle.OccupancyStatus = OCCUPANCY_FULL - } else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_NOT_ACCEPTING_PASSENGERS { - departure.Vehicle.OccupancyStatus = OCCUPANCY_NOT_ACCEPTING - } + departure.Status = convertVehicleStatusV1(trafficDeparture.Update.Status, timeToArrival) + departure.Vehicle.CongestionLevel = convertCongestionLevelV1(trafficDeparture.Update.CongestionLevel) + departure.Vehicle.OccupancyStatus = convertOccupancyStatusV1(trafficDeparture.Update.OccupancyStatus) } d = append(d, departure) } success = DeparturesResponseV1{ Stop: convertTrafficStop(stop), Departures: d, - Alerts: convertTrafficAlerts(alerts), + Alerts: convertTrafficAlerts(alerts, preferredLanguages), } return success, nil } -func CreateSuccessDeparturesV2(stop traffic.Stop, departures []traffic.DepartureRealtime, - date time.Time, vehicles map[string]traffic.Vehicle, alerts []traffic.Alert, ctx traffic.Context, t *traffic.Traffic, accept uint) (DeparturesResponse, error) { +func CreateSuccessDeparturesV2(stop traffic.Stop, departures []traffic.DepartureRealtime, date time.Time, vehicles map[string]traffic.Vehicle, alerts []traffic.Alert, ctx traffic.Context, t *traffic.Traffic, accept uint, preferredLanguages []language.Tag) (DeparturesResponse, error) { d := []DepartureV2{} var success DeparturesResponse now := time.Now() - timezone, err := traffic.GetTimezone(stop, t.Feeds[ctx.FeedName]) + timezone, err := traffic.GetTimezone(stop, t.Feeds[ctx.FeedID]) if err != nil { return success, err } datetime := time.Date(date.Year(), date.Month(), date.Day(), now.Hour(), now.Minute(), now.Second(), 0, timezone) for _, trafficDeparture := range departures { - zoneAbbr := trafficDeparture.Update.Time.Location().String() + zoneAbbr := trafficDeparture.Time.Location().String() stopOrder, err := marshalStopOrder(trafficDeparture.Order.TripOffset, trafficDeparture.Order.Sequence) if err != nil { return success, err @@ -729,83 +747,49 @@ line, err := traffic.GetLine(trafficDeparture.LineName, ctx, t) if err != nil { return success, fmt.Errorf("while getting line %s: %w", trafficDeparture.LineName, err) } + departureTime := traffic.GetTimeWithDelay(trafficDeparture) departure := DepartureV2{ Id: stopOrder, Time: TimeV1{ - DayOffset: getDayOffset(datetime, trafficDeparture.Update.Time), - Hour: uint8(trafficDeparture.Update.Time.Hour()), - Minute: uint8(trafficDeparture.Update.Time.Minute()), - Second: uint8(trafficDeparture.Update.Time.Second()), + DayOffset: getDayOffset(datetime, departureTime), + Hour: uint8(departureTime.Hour()), + Minute: uint8(departureTime.Minute()), + Second: uint8(departureTime.Second()), Zone: zoneAbbr, }, Status: STATUS_IN_TRANSIT, - IsRealtime: trafficDeparture.Update.TripUpdate != nil, + IsRealtime: trafficDeparture.Update.Delay != nil, Vehicle: convertVehicleV2(trafficDeparture.Update, vehicles, line, trafficDeparture.Headsign), Boarding: makeBoardingV1(trafficDeparture.Departure.Pickup, trafficDeparture.Departure.Dropoff), } - timeToArrival := trafficDeparture.Update.Time.Sub(datetime).Minutes() + timeToArrival := departureTime.Sub(datetime).Minutes() if departure.IsRealtime { - if trafficDeparture.Update.Status == pb.VehiclePosition_STOPPED_AT { - departure.Status = STATUS_AT_STOP - } else if timeToArrival < 0 { - departure.Status = STATUS_DEPARTED - } else if timeToArrival < 1 { - departure.Status = STATUS_INCOMING - } - - if trafficDeparture.Update.CongestionLevel == nil { - departure.Vehicle.CongestionLevel = CONGESTION_UNKNOWN - } else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_RUNNING_SMOOTHLY { - departure.Vehicle.CongestionLevel = CONGESTION_SMOOTH - } else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_STOP_AND_GO { - departure.Vehicle.CongestionLevel = CONGESTION_STOP_AND_GO - } else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_CONGESTION { - departure.Vehicle.CongestionLevel = CONGESTION_SIGNIFICANT - } else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_SEVERE_CONGESTION { - departure.Vehicle.CongestionLevel = CONGESTION_SEVERE - } - - if trafficDeparture.Update.OccupancyStatus == nil { - departure.Vehicle.OccupancyStatus = OCCUPANCY_UNKNOWN - } else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_EMPTY { - departure.Vehicle.OccupancyStatus = OCCUPANCY_EMPTY - } else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_MANY_SEATS_AVAILABLE { - departure.Vehicle.OccupancyStatus = OCCUPANCY_MANY_AVAILABLE - } else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_FEW_SEATS_AVAILABLE { - departure.Vehicle.OccupancyStatus = OCCUPANCY_FEW_AVAILABLE - } else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_STANDING_ROOM_ONLY { - departure.Vehicle.OccupancyStatus = OCCUPANCY_STANDING_ONLY - } else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_CRUSHED_STANDING_ROOM_ONLY { - departure.Vehicle.OccupancyStatus = OCCUPANCY_CRUSHED - } else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_FULL { - departure.Vehicle.OccupancyStatus = OCCUPANCY_FULL - } else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_NOT_ACCEPTING_PASSENGERS { - departure.Vehicle.OccupancyStatus = OCCUPANCY_NOT_ACCEPTING - } + departure.Status = convertVehicleStatusV1(trafficDeparture.Update.Status, timeToArrival) + departure.Vehicle.CongestionLevel = convertCongestionLevelV1(trafficDeparture.Update.CongestionLevel) + departure.Vehicle.OccupancyStatus = convertOccupancyStatusV1(trafficDeparture.Update.OccupancyStatus) } d = append(d, departure) } success = DeparturesResponseV2{ - Stop: convertTrafficStopV2(stop, ctx.FeedName), + Stop: convertTrafficStopV2(stop, ctx.FeedID), Departures: d, - Alerts: convertTrafficAlerts(alerts), + Alerts: convertTrafficAlerts(alerts, preferredLanguages), } return success, nil } -func CreateSuccessDeparturesV3(stop traffic.Stop, departures []traffic.DepartureRealtime, - date time.Time, vehicles map[string]traffic.Vehicle, alerts []traffic.Alert, ctx traffic.Context, t *traffic.Traffic, accept uint) (DeparturesResponse, error) { +func CreateSuccessDeparturesV3(stop traffic.Stop, departures []traffic.DepartureRealtime, date time.Time, vehicles map[string]traffic.Vehicle, alerts []traffic.Alert, ctx traffic.Context, t *traffic.Traffic, accept uint, preferredLanguages []language.Tag) (DeparturesResponse, error) { d := []DepartureV3{} var success DeparturesResponse now := time.Now() - timezone, err := traffic.GetTimezone(stop, t.Feeds[ctx.FeedName]) + timezone, err := traffic.GetTimezone(stop, t.Feeds[ctx.FeedID]) if err != nil { return success, err } datetime := time.Date(date.Year(), date.Month(), date.Day(), now.Hour(), now.Minute(), now.Second(), 0, timezone) for _, trafficDeparture := range departures { - zoneAbbr := trafficDeparture.Update.Time.Location().String() + zoneAbbr := trafficDeparture.Time.Location().String() stopOrder, err := marshalStopOrder(trafficDeparture.Order.TripOffset, trafficDeparture.Order.Sequence) if err != nil { return success, err @@ -814,66 +798,33 @@ line, err := traffic.GetLine(trafficDeparture.LineName, ctx, t) if err != nil { return success, fmt.Errorf("while getting line %s: %w", trafficDeparture.LineName, err) } + departureTime := traffic.GetTimeWithDelay(trafficDeparture) departure := DepartureV3{ Id: stopOrder, Time: TimeV1{ - DayOffset: getDayOffset(datetime, trafficDeparture.Update.Time), - Hour: uint8(trafficDeparture.Update.Time.Hour()), - Minute: uint8(trafficDeparture.Update.Time.Minute()), - Second: uint8(trafficDeparture.Update.Time.Second()), + DayOffset: getDayOffset(datetime, departureTime), + Hour: uint8(departureTime.Hour()), + Minute: uint8(departureTime.Minute()), + Second: uint8(departureTime.Second()), Zone: zoneAbbr, }, Status: STATUS_IN_TRANSIT, - IsRealtime: trafficDeparture.Update.TripUpdate != nil, + IsRealtime: trafficDeparture.Update.Delay != nil, Vehicle: convertVehicleV3(trafficDeparture.Update, vehicles, line, trafficDeparture.Headsign), Boarding: makeBoardingV1(trafficDeparture.Departure.Pickup, trafficDeparture.Departure.Dropoff), } - timeToArrival := trafficDeparture.Update.Time.Sub(datetime).Minutes() + timeToArrival := departureTime.Sub(datetime).Minutes() if departure.IsRealtime { - if trafficDeparture.Update.Status == pb.VehiclePosition_STOPPED_AT { - departure.Status = STATUS_AT_STOP - } else if timeToArrival < 0 { - departure.Status = STATUS_DEPARTED - } else if timeToArrival < 1 { - departure.Status = STATUS_INCOMING - } - - if trafficDeparture.Update.CongestionLevel == nil { - departure.Vehicle.CongestionLevel = CONGESTION_UNKNOWN - } else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_RUNNING_SMOOTHLY { - departure.Vehicle.CongestionLevel = CONGESTION_SMOOTH - } else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_STOP_AND_GO { - departure.Vehicle.CongestionLevel = CONGESTION_STOP_AND_GO - } else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_CONGESTION { - departure.Vehicle.CongestionLevel = CONGESTION_SIGNIFICANT - } else if *trafficDeparture.Update.CongestionLevel == pb.VehiclePosition_SEVERE_CONGESTION { - departure.Vehicle.CongestionLevel = CONGESTION_SEVERE - } - - if trafficDeparture.Update.OccupancyStatus == nil { - departure.Vehicle.OccupancyStatus = OCCUPANCY_UNKNOWN - } else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_EMPTY { - departure.Vehicle.OccupancyStatus = OCCUPANCY_EMPTY - } else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_MANY_SEATS_AVAILABLE { - departure.Vehicle.OccupancyStatus = OCCUPANCY_MANY_AVAILABLE - } else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_FEW_SEATS_AVAILABLE { - departure.Vehicle.OccupancyStatus = OCCUPANCY_FEW_AVAILABLE - } else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_STANDING_ROOM_ONLY { - departure.Vehicle.OccupancyStatus = OCCUPANCY_STANDING_ONLY - } else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_CRUSHED_STANDING_ROOM_ONLY { - departure.Vehicle.OccupancyStatus = OCCUPANCY_CRUSHED - } else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_FULL { - departure.Vehicle.OccupancyStatus = OCCUPANCY_FULL - } else if *trafficDeparture.Update.OccupancyStatus == pb.VehiclePosition_NOT_ACCEPTING_PASSENGERS { - departure.Vehicle.OccupancyStatus = OCCUPANCY_NOT_ACCEPTING - } + departure.Status = convertVehicleStatusV1(trafficDeparture.Update.Status, timeToArrival) + departure.Vehicle.CongestionLevel = convertCongestionLevelV1(trafficDeparture.Update.CongestionLevel) + departure.Vehicle.OccupancyStatus = convertOccupancyStatusV1(trafficDeparture.Update.OccupancyStatus) } d = append(d, departure) } success = DeparturesResponseDev{ - Stop: convertTrafficStopV2(stop, ctx.FeedName), + Stop: convertTrafficStopV2(stop, ctx.FeedID), Departures: d, - Alerts: convertTrafficAlerts(alerts), + Alerts: convertTrafficAlerts(alerts, preferredLanguages), } return success, nil } diff --git a/api/feedsResponse.go b/api/feedsResponse.go index 3bf3d9b458f05bcd1c1e2875928f14054987027c..062fdf8aa36093d8344aa3fd9faebe61f6e56195 100644 --- a/api/feedsResponse.go +++ b/api/feedsResponse.go @@ -35,6 +35,17 @@ return descriptionTag, attributionTag } +func selectLanguageByTag(messages map[language.Tag]string, preferredLanguages []language.Tag) language.Tag { + tags := []language.Tag{} + for t := range messages { + tags = append(tags, t) + } + + matcher := language.NewMatcher(preferredLanguages) + _, index, _ := matcher.Match(tags...) + return preferredLanguages[index] +} + func MakeFeedsResponse(feedInfos map[string]traffic.FeedInfo, lastUpdates map[string]time.Time, accept uint, preferredLanguages []language.Tag) (FeedsResponse, error) { switch accept { case 0: diff --git a/gtfs_rt/main.go b/gtfs_rt/main.go index f2085b0e496df8c81547a810c3c12c13b46f7f44..7cdedd16593fa6d81c1a671d2ef565d5690886f9 100644 --- a/gtfs_rt/main.go +++ b/gtfs_rt/main.go @@ -1,4 +1,4 @@ -package gtfs_rt +package gtfs_rt // TODO move to traffic import ( pb "apiote.xyz/p/szczanieckiej/gtfs_rt/transit_realtime" @@ -13,7 +13,7 @@ "google.golang.org/protobuf/proto" ) -type Update struct { +type Update struct { // TODO use traffic/Update VehicleID string Latitude float32 Longitude float32 @@ -169,9 +169,10 @@ } return nil } -func getMessages(previouslyUpdated uint64, feedName string, feedURLs []string) (uint64, error) { +func GetMessages(previouslyUpdated uint64, feedName string, feedURLs []string) (uint64, []*pb.FeedMessage, error) { now := uint64(time.Now().Unix()) lastUpdated := previouslyUpdated + updates := cache[feedName] if now-previouslyUpdated > 30 { if cache == nil { cache = map[string][]*pb.FeedMessage{} @@ -181,27 +182,28 @@ for _, url := range feedURLs { client := http.Client{Timeout: 5 * time.Second} response, err := client.Get(url) if err != nil { - return lastUpdated, fmt.Errorf("cannot download from ‘%s’: %w", url, err) + return lastUpdated, updates, fmt.Errorf("cannot download from ‘%s’: %w", url, err) } message := &pb.FeedMessage{} bytes, err := ioutil.ReadAll(response.Body) if err != nil { - return lastUpdated, fmt.Errorf("cannot read response for ‘%s’: %w", url, err) + return lastUpdated, updates, fmt.Errorf("cannot read response for ‘%s’: %w", url, err) } if err := proto.Unmarshal(bytes, message); err != nil { - return lastUpdated, fmt.Errorf("Failed to parse message: %w", err) + return lastUpdated, updates, fmt.Errorf("Failed to parse message: %w", err) } cache[feedName] = append(cache[feedName], message) } lastUpdated = now + updates = cache[feedName] } - return lastUpdated, nil + return lastUpdated, updates, nil } func GetRt(previouslyUpdated uint64, feedUrls []string, feedName string) (map[string]Update, uint64, error) { updates := map[string]Update{} - lastUpdated, err := getMessages(previouslyUpdated, feedName, feedUrls) + lastUpdated, _, err := GetMessages(previouslyUpdated, feedName, feedUrls) if err != nil { return updates, lastUpdated, fmt.Errorf("while getting messages: %w", err) } @@ -222,7 +224,7 @@ func GetAlerts(previouslyUpdated uint64, feedUrls []string, feedName string) (Alerts, uint64, error) { alerts := Alerts{} - lastUpdated, err := getMessages(previouslyUpdated, feedName, feedUrls) + lastUpdated, _, err := GetMessages(previouslyUpdated, feedName, feedUrls) if err != nil { return alerts, lastUpdated, fmt.Errorf("while getting messages: %w", err) } diff --git a/gtfs_rt/new.go b/gtfs_rt/new.go new file mode 100644 index 0000000000000000000000000000000000000000..78e4cf47c04f4553172808a6f44e0dff0619149b --- /dev/null +++ b/gtfs_rt/new.go @@ -0,0 +1,30 @@ +package gtfs_rt + +import ( + pb "apiote.xyz/p/szczanieckiej/gtfs_rt/transit_realtime" + + "fmt" + "io" + "net/http" + "time" + + "google.golang.org/protobuf/proto" +) + +func GetMessages2(feedID string, feedURL string) (*pb.FeedMessage, error) { + var message *pb.FeedMessage + client := http.Client{Timeout: 5 * time.Second} + response, err := client.Get(feedURL) + if err != nil { + return nil, fmt.Errorf("cannot download from ‘%s’: %w", feedURL, err) + } + message = new(pb.FeedMessage) + bytes, err := io.ReadAll(response.Body) + if err != nil { + return nil, fmt.Errorf("cannot read response for ‘%s’: %w", feedURL, err) + } + if err := proto.Unmarshal(bytes, message); err != nil { + return nil, fmt.Errorf("Failed to parse message: %w", err) + } + return message, nil +} diff --git a/server/handler_vars.go b/server/handler_vars.go index 2d04b58d6209de2404c1a0fb82f81389191770f3..09cc1d9afa50591159e1e6925cc65c667298b7ae 100644 --- a/server/handler_vars.go +++ b/server/handler_vars.go @@ -131,7 +131,7 @@ func createContext(v AbstractHandlerVars) AbstractHandlerVars { v.setContext( traffic.Context{ DataHome: v.getConfig().FeedsPath, - FeedName: v.getFeedName(), + FeedID: v.getFeedName(), Version: v.getVersionCode(), }, ) diff --git a/server/router.go b/server/router.go index ba34211f7a60da274f71223e70d4304b5b39c133..92af52952bf75359bda9d522c766d7ec73276352 100644 --- a/server/router.go +++ b/server/router.go @@ -114,7 +114,7 @@ } context := traffic.Context{ DataHome: cfg.FeedsPath, - FeedName: feedName, + FeedID: feedName, Version: versionCode, } stops, err := traffic.GetStopsIn(lb, rt, context, t) @@ -210,7 +210,7 @@ } context := traffic.Context{ DataHome: cfg.FeedsPath, - FeedName: feedName, + FeedID: feedName, Version: versionCode, } @@ -364,7 +364,7 @@ } context := traffic.Context{ DataHome: cfg.FeedsPath, - FeedName: feedName, + FeedID: feedName, Version: versionCode, } @@ -384,7 +384,7 @@ if err != nil { return fmt.Errorf("while getting stop: %w", err) } - departures, err := traffic.GetDepartures(code, line, cfg.FeedsPath, feedName, versionCode, t, date, departuresType) + departures, err := traffic.GetDepartures(code, line, context, t, date, departuresType) if err != nil { if _, ok := err.(traffic_errors.NoSchedule); ok { return ServerError{ @@ -407,7 +407,7 @@ } acceptLanguage := r.Header.Get("Accept-Language") if acceptLanguage == "" { - acceptLanguage, err = traffic.GetLanguage(context, t) + acceptLanguage, err = traffic.GetLanguage(context) if err != nil { log.Printf("while gettng default language: %v\n", err) acceptLanguage = "und" @@ -423,7 +423,7 @@ err: err, } } - alerts, err := traffic.GetAlerts(string(code), preferredLanguages, context, t) + alerts, err := traffic.GetGtfsAlerts(string(code), preferredLanguages, context, t) if err != nil { return fmt.Errorf("while getting alerts: %w", err) } @@ -431,11 +431,11 @@ var success api.DeparturesResponse switch accept { case 0: - success, err = api.CreateSuccessDeparturesV3(stop, departures, date, t.Vehicles[feedName][versionCode], alerts, context, t, accept) + success, err = api.CreateSuccessDeparturesV3(stop, departures, date, t.Vehicles[feedName][versionCode], alerts, context, t, accept, preferredLanguages) case 1: - success, err = api.CreateSuccessDeparturesV1(stop, departures, date, t.Vehicles[feedName][versionCode], alerts, context, t, accept) + success, err = api.CreateSuccessDeparturesV1(stop, departures, date, t.Vehicles[feedName][versionCode], alerts, context, t, accept, preferredLanguages) case 2: - success, err = api.CreateSuccessDeparturesV2(stop, departures, date, t.Vehicles[feedName][versionCode], alerts, context, t, accept) + success, err = api.CreateSuccessDeparturesV2(stop, departures, date, t.Vehicles[feedName][versionCode], alerts, context, t, accept, preferredLanguages) } if err != nil { return fmt.Errorf("while creating departuresSuccess: %w", err) @@ -464,7 +464,7 @@ stopCode := r.Form.Get("stop") context := traffic.Context{ DataHome: cfg.FeedsPath, - FeedName: feedName, + FeedID: feedName, Version: versionCode, } diff --git a/traffic/access.go b/traffic/access.go index 7ab8c8550f41157969dd9c69ce8448d92e110a37..a3bd8279211959fd7313aca1212c8f965603f7b2 100644 --- a/traffic/access.go +++ b/traffic/access.go @@ -15,7 +15,6 @@ "net" "os" "path/filepath" "sort" - "strconv" "strings" "time" @@ -48,11 +47,11 @@ Calendar []Schedule DeparturesType DeparturesType Vehicles Vehicles Feed Feed + Ctx Context Location *time.Location Datetime time.Time MinuteB4Datetime time.Time - Midnight time.Time TodaySchedule map[string]struct{} YesterdaySchedule map[string]struct{} file *os.File @@ -67,6 +66,7 @@ FeedInfo FeedInfo } var lastUpdatedGtfsRt = map[string]uint64{} +var lastUpdatedGtfsRt2 = map[string]map[string]uint64{} func isTimeout(err error) bool { var e net.Error @@ -123,23 +123,10 @@ return updates, nil } func calculateGtfsTime(gtfsTime uint, delay int32, date time.Time, - timezone *time.Location) (time.Time, error) { + timezone *time.Location) time.Time { noon := time.Date(date.Year(), date.Month(), date.Day(), 12, 0, 0, 0, timezone) - twelve, _ := time.ParseDuration("-12h") - midnight := noon.Add(twelve) - departureDuration, err := time.ParseDuration( - strconv.FormatInt(int64(gtfsTime), 10) + "m") - if err != nil { - return midnight, err - } - delayDuration, err := time.ParseDuration(strconv.FormatInt(int64(delay), - 10) + "s") - if err != nil { - return midnight, err - } - t := midnight.Add(departureDuration).Add(delayDuration) - return t, nil + return noon.Add(time.Duration(-12) * time.Hour).Add(time.Duration(gtfsTime) * time.Minute).Add(time.Duration(delay) * time.Second) } func loadLocation(input ...interface{}) (interface{}, error) { @@ -152,15 +139,12 @@ func loadTime(input ...interface{}) interface{} { result := input[0].(_Result) - deadzone, _ := time.ParseDuration("-1m") now := time.Now() datetime := time.Date(result.Date.Year(), result.Date.Month(), result.Date.Day(), now.Hour(), now.Minute(), now.Second(), 0, result.Location) result.Datetime = datetime - result.MinuteB4Datetime = datetime.Add(deadzone) - result.Midnight = time.Date(datetime.Year(), datetime.Month(), - datetime.Day(), 0, 0, 0, 0, result.Location) + result.MinuteB4Datetime = datetime.Add(time.Duration(-1) * time.Minute) return result } @@ -282,7 +266,7 @@ result.Trips = trips return result, nil } -func getUpdates(input ...interface{}) (interface{}, error) { +func getDepartures(input ...interface{}) (interface{}, error) { result := input[0].(_Result) departures := []DepartureRealtime{} timedOut := false @@ -309,7 +293,7 @@ } } departures = append(departures, departure) } - result.Departures = departures + result.Departures = enrichDepartures(departures, result.Datetime, result.DeparturesType, result.Ctx, result.TripsFile, result.Location) result.TripsFile.Close() return result, nil } @@ -326,35 +310,10 @@ departureRt.Departure = departure departureRt.Headsign = trip.Headsign departureRt.LineName = trip.LineName departureRt.Order = order - departureRt.Update = gtfs_rt.Update{} + departureRt.Update = Update{} - departureTime, err := calculateGtfsTime(departure.Time, 0, date, + departureRt.Time = calculateGtfsTime(departure.Time, 0, date, result.Location) - if err != nil { - return departureRt, err - } - departureRt.Update.Time = departureTime - - if departureTime.After(result.Midnight) { - if result.DeparturesType == DEPARTURES_HYBRID && !timedOut { - departureRt.Update, err = getRealtimeOffset(trip.Id, - order.Sequence, feed) - if err != nil { - if isTimeout(err) { - timedOut = true - finalErr = err - } else { - log.Printf("while getting realtime departures: %v\n", err) - } - } - } - departureTime, err := calculateGtfsTime(departure.Time, - departureRt.Update.Delay, date, result.Location) - if err != nil { - return departureRt, err - } - departureRt.Update.Time = departureTime - } found = true break } @@ -368,12 +327,20 @@ } return departureRt, finalErr } +func GetTimeWithDelay(departure DepartureRealtime) time.Time { + var delay int + if departure.Update.Delay != nil { + delay = int(*departure.Update.Delay) + } + return departure.Time.Add(time.Duration(delay) * time.Nanosecond) +} + func filterDepartures(input ...interface{}) interface{} { result := input[0].(_Result) departures := []DepartureRealtime{} for _, departure := range result.Departures { if result.DeparturesType == DEPARTURES_FULL || - departure.Update.Time.After(result.MinuteB4Datetime) { + GetTimeWithDelay(departure).After(result.MinuteB4Datetime) { departures = append(departures, departure) } } @@ -397,10 +364,8 @@ } func sortDepartures(input ...interface{}) interface{} { result := input[0].(_Result) - sort.Slice(result.Departures, func(i, j int) bool { - return result.Departures[i].Update.Time.Before( - result.Departures[j].Update.Time) + return GetTimeWithDelay(result.Departures[i]).Before(GetTimeWithDelay(result.Departures[j])) }) return result @@ -720,24 +685,24 @@ } doneChan <- true } -func GetDepartures(stopCode string, lineName, dataHome, feedName string, - versionCode Validity, traffic *Traffic, date time.Time, +func GetDepartures(stopCode, lineName string, ctx Context, traffic *Traffic, date time.Time, departuresType DeparturesType) ([]DepartureRealtime, error) { - codeIndex := traffic.CodeIndexes[feedName][versionCode] - calendar := traffic.Calendars[feedName][versionCode] - vehicles := traffic.Vehicles[feedName][versionCode] + codeIndex := traffic.CodeIndexes[ctx.FeedID][ctx.Version] + calendar := traffic.Calendars[ctx.FeedID][ctx.Version] + vehicles := traffic.Vehicles[ctx.FeedID][ctx.Version] result := _Result{ Offset: codeIndex[stopCode], Filename: "stops.bare", Date: date, LineName: lineName, - TimetableHome: filepath.Join(dataHome, feedName, string(versionCode)), + TimetableHome: filepath.Join(ctx.DataHome, ctx.FeedID, string(ctx.Version)), Calendar: calendar, DeparturesType: departuresType, Vehicles: vehicles, - Feed: traffic.Feeds[feedName], + Feed: traffic.Feeds[ctx.FeedID], + Ctx: ctx, } r, e := gott.NewResult(result). @@ -751,7 +716,7 @@ Bind(seek). Bind(unmarshalStop). Bind(openTripsFile). Bind(readTrips). - Bind(getUpdates). + Bind(getDepartures). Map(filterDepartures). Map(filterDeparturesByLine). Map(sortDepartures). @@ -830,7 +795,7 @@ func getStopByOffset(offset uint, context Context, traffic *Traffic) (Stop, error) { // todo offset should be uint64 everywhere result := _Result{ Filename: "stops.bare", Offset: offset, - TimetableHome: filepath.Join(context.DataHome, context.FeedName, string(context.Version)), + TimetableHome: filepath.Join(context.DataHome, context.FeedID, string(context.Version)), } r, e := gott.NewResult(result). Bind(openFile). @@ -864,7 +829,7 @@ } } func getFeedInfo(dataHome string, feedName string, - versionCode Validity, traffic *Traffic) (FeedInfo, error) { + versionCode Validity) (FeedInfo, error) { result := _Result{ Filename: "feed_info.bare", TimetableHome: filepath.Join(dataHome, feedName, string(versionCode)), @@ -882,7 +847,7 @@ } func GetTripsByOffset(offsets []uint, context Context, t *Traffic, filter func(Trip) bool) ([]Trip, error) { trips := []Trip{} - file, err := os.Open(filepath.Join(context.DataHome, context.FeedName, string(context.Version), "trips.bare")) + file, err := os.Open(filepath.Join(context.DataHome, context.FeedID, string(context.Version), "trips.bare")) if err != nil { return trips, fmt.Errorf("while opening file: %w", err) } @@ -914,7 +879,7 @@ func GetTripByOffset(offset uint, context Context, t *Traffic) (Trip, error) { result := _Result{ Filename: "trips.bare", Offset: offset, - TimetableHome: filepath.Join(context.DataHome, context.FeedName, string(context.Version)), + TimetableHome: filepath.Join(context.DataHome, context.FeedID, string(context.Version)), } r, e := gott.NewResult(result). Bind(openFile). @@ -929,7 +894,7 @@ } } func GetStop(stopCode string, context Context, traffic *Traffic) (Stop, error) { - codeIndex := traffic.CodeIndexes[context.FeedName][context.Version] + codeIndex := traffic.CodeIndexes[context.FeedID][context.Version] return getStopByOffset(codeIndex[stopCode], context, traffic) } @@ -975,21 +940,21 @@ return stopStub, nil } func GetLine(name string, context Context, traffic *Traffic) (Line, error) { - index := traffic.LineIndexes[context.FeedName][context.Version] + index := traffic.LineIndexes[context.FeedID][context.Version] for _, o := range index { - cleanedName, err := CleanQuery(name, traffic.Feeds[context.FeedName]) + cleanedName, err := CleanQuery(name, traffic.Feeds[context.FeedID]) if err != nil { return Line{}, err } if o.Name == cleanedName { - return getLineByOffset(o.Offsets[0], context.DataHome, context.FeedName, context.Version, traffic) + return getLineByOffset(o.Offsets[0], context.DataHome, context.FeedID, context.Version, traffic) } } return Line{}, nil } func GetTrip(id string, context Context, traffic *Traffic) (Trip, error) { - tripIndex := traffic.TripIndexes[context.FeedName][context.Version] + tripIndex := traffic.TripIndexes[context.FeedID][context.Version] for _, o := range tripIndex { if o.Name == id { return GetTripByOffset(o.Offsets[0], context, traffic) @@ -1022,7 +987,7 @@ } func QueryStops(query string, context Context, traffic *Traffic) ([]Stop, error) { stops := []Stop{} - nameIndex := traffic.NameIndexes[context.FeedName][context.Version] + nameIndex := traffic.NameIndexes[context.FeedID][context.Version] results := fuzzy.FindFrom(query, nameIndex) for _, result := range results { for _, offset := range nameIndex[result.Index].Offsets { @@ -1038,8 +1003,8 @@ } func GetStopsNear(location Position, context Context, traffic *Traffic) ([]Stop, error) { stops := []Stop{} - positionIndex := traffic.PositionIndexes[context.FeedName][context.Version] - codeIndex := traffic.CodeIndexes[context.FeedName][context.Version] + positionIndex := traffic.PositionIndexes[context.FeedID][context.Version] + codeIndex := traffic.CodeIndexes[context.FeedID][context.Version] spatials := positionIndex.NearestNeighbors(12, rtreego.Point{location.Lat, location.Lon}) for _, spatial := range spatials { stop, err := getStopByOffset(codeIndex[spatial.(Stop).Code], context, traffic) @@ -1051,101 +1016,104 @@ } return stops, nil } -func GetAlerts(stopCode string, preferredLanguages []language.Tag, context Context, traffic *Traffic) ([]Alert, error) { - feed := traffic.Feeds[context.FeedName] +func GetGtfsAlerts(stopCode string, preferredLanguages []language.Tag, context Context, traffic *Traffic) ([]Alert, error) { + return []Alert{}, nil + /* + feed := traffic.Feeds[context.FeedID] - alertPositions := map[uint]struct{}{} + alertPositions := map[uint]struct{}{} - gtfsAlerts, lastUpdated, err := gtfs_rt.GetAlerts(lastUpdatedGtfsRt[feed.String()], feed.RealtimeFeeds(), feed.String()) - if err != nil { - if isTimeout(err) { - return []Alert{}, nil - } - return []Alert{}, fmt.Errorf("while geting alerts: %w", err) - } - lastUpdatedGtfsRt[feed.String()] = lastUpdated - - stop, err := GetStop(stopCode, context, traffic) - for _, pos := range gtfsAlerts.ByStop[stop.Id] { - alertPositions[pos] = struct{}{} - } - for _, option := range stop.ChangeOptions { - line, err := GetLine(option.LineName, context, traffic) + gtfsAlerts, lastUpdated, err := gtfs_rt.GetAlerts(lastUpdatedGtfsRt[feed.String()], feed.RealtimeFeeds(), feed.String()) if err != nil { - return []Alert{}, fmt.Errorf("while getting line %s: %w", option.LineName, err) - } - for _, pos := range gtfsAlerts.ByRoute[line.Id] { - alertPositions[pos] = struct{}{} + if isTimeout(err) { + return []Alert{}, nil + } + return []Alert{}, fmt.Errorf("while geting alerts: %w", err) } - for _, pos := range gtfsAlerts.ByType[line.Kind.Value()] { + lastUpdatedGtfsRt[feed.String()] = lastUpdated + + stop, err := GetStop(stopCode, context, traffic) + for _, pos := range gtfsAlerts.ByStop[stop.Id] { alertPositions[pos] = struct{}{} } - } - for _, order := range stop.Order { - trip, err := GetTripByOffset(order.TripOffset, context, traffic) - if err != nil { - return []Alert{}, fmt.Errorf("while getting trip at %d: %w", order.TripOffset, err) + for _, option := range stop.ChangeOptions { + line, err := GetLine(option.LineName, context, traffic) + if err != nil { + return []Alert{}, fmt.Errorf("while getting line %s: %w", option.LineName, err) + } + for _, pos := range gtfsAlerts.ByRoute[line.Id] { + alertPositions[pos] = struct{}{} + } + for _, pos := range gtfsAlerts.ByType[line.Kind.Value()] { + alertPositions[pos] = struct{}{} + } } - for _, pos := range gtfsAlerts.ByTrip[trip.Id] { - alertPositions[pos] = struct{}{} + for _, order := range stop.Order { + trip, err := GetTripByOffset(order.TripOffset, context, traffic) + if err != nil { + return []Alert{}, fmt.Errorf("while getting trip at %d: %w", order.TripOffset, err) + } + for _, pos := range gtfsAlerts.ByTrip[trip.Id] { + alertPositions[pos] = struct{}{} + } } - } - alerts := make([]Alert, len(alertPositions)) - i := 0 - for pos := range alertPositions { - gtfsAlert := gtfsAlerts.Alerts[pos] + alerts := make([]Alert, len(alertPositions)) + i := 0 + for pos := range alertPositions { + gtfsAlert := gtfsAlerts.Alerts[pos] - includeAlert := false - for _, timeRange := range gtfsAlert.TimeRanges { - start := time.Unix(int64(timeRange.GetStart()), 0) - var end time.Time - if timeRange.GetEnd() == 0 { - end = time.Date(9999, 12, 31, 23, 59, 59, 0, time.Local) - } else { - end = time.Unix(int64(timeRange.GetEnd()), 0) + includeAlert := false + for _, timeRange := range gtfsAlert.TimeRanges { + start := time.Unix(int64(timeRange.GetStart()), 0) + var end time.Time + if timeRange.GetEnd() == 0 { + end = time.Date(9999, 12, 31, 23, 59, 59, 0, time.Local) + } else { + end = time.Unix(int64(timeRange.GetEnd()), 0) + } + now := time.Now() + if !now.Before(start) && !now.After(end) { + includeAlert = true + } } - now := time.Now() - if !now.Before(start) && !now.After(end) { - includeAlert = true + if !includeAlert { + continue + } + + alerts[i] = Alert{ + Cause: int32(gtfsAlert.Cause), // todo(BAF10) + Effect: int32(gtfsAlert.Effect), // todo(BAF10) + } + alertHeaderLangs := []language.Tag{} + for lang := range gtfsAlert.Headers { + alertHeaderLangs = append(alertHeaderLangs, lang) } - } - if !includeAlert { - continue - } + m := language.NewMatcher(alertHeaderLangs) + tag, _, _ := m.Match(preferredLanguages...) + alerts[i].Header = gtfsAlert.Headers[tag] - alerts[i] = Alert{ - Cause: int32(gtfsAlert.Cause), // todo(BAF10) - Effect: int32(gtfsAlert.Effect), // todo(BAF10) - } - alertHeaderLangs := []language.Tag{} - for lang := range gtfsAlert.Headers { - alertHeaderLangs = append(alertHeaderLangs, lang) - } - m := language.NewMatcher(alertHeaderLangs) - tag, _, _ := m.Match(preferredLanguages...) - alerts[i].Header = gtfsAlert.Headers[tag] + alertDescriptionLangs := []language.Tag{} + for lang := range gtfsAlert.Descriptions { + alertDescriptionLangs = append(alertDescriptionLangs, lang) + } + m = language.NewMatcher(alertDescriptionLangs) + tag, _, _ = m.Match(preferredLanguages...) + alerts[i].Description = gtfsAlert.Descriptions[tag] - alertDescriptionLangs := []language.Tag{} - for lang := range gtfsAlert.Descriptions { - alertDescriptionLangs = append(alertDescriptionLangs, lang) - } - m = language.NewMatcher(alertDescriptionLangs) - tag, _, _ = m.Match(preferredLanguages...) - alerts[i].Description = gtfsAlert.Descriptions[tag] + alertUrlLangs := []language.Tag{} + for lang := range gtfsAlert.URLs { + alertUrlLangs = append(alertUrlLangs, lang) + } + m = language.NewMatcher(alertUrlLangs) + tag, _, _ = m.Match(preferredLanguages...) + alerts[i].URL = gtfsAlert.URLs[tag] - alertUrlLangs := []language.Tag{} - for lang := range gtfsAlert.URLs { - alertUrlLangs = append(alertUrlLangs, lang) + i++ } - m = language.NewMatcher(alertUrlLangs) - tag, _, _ = m.Match(preferredLanguages...) - alerts[i].URL = gtfsAlert.URLs[tag] - i++ - } - - return alerts, nil + return alerts, nil + */ } func GetTimezone(stop Stop, feed Feed) (*time.Location, error) { @@ -1155,8 +1123,8 @@ } return feed.GetLocation(), nil } -func GetLanguage(ctx Context, t *Traffic) (string, error) { - feedInfo, err := getFeedInfo(ctx.DataHome, ctx.FeedName, ctx.Version, t) +func GetLanguage(ctx Context) (string, error) { + feedInfo, err := getFeedInfo(ctx.DataHome, ctx.FeedID, ctx.Version) return feedInfo.Language, err } @@ -1184,7 +1152,7 @@ return err } func convertVehicle(update gtfs_rt.Update, context Context, t *Traffic) (VehicleStatus, error) { - vehicles := t.Vehicles[context.FeedName][context.Version] + vehicles := t.Vehicles[context.FeedID][context.Version] tripID := update.TripUpdate.GetTrip().GetTripId() trip, err := GetTrip(tripID, context, t) if err != nil { @@ -1202,7 +1170,7 @@ } func GetVehicle(tripID string, context Context, t *Traffic) (VehicleStatus, error) { vehicle := VehicleStatus{} - update, err := getRealtimeOffset(tripID, 0, t.Feeds[context.FeedName]) + update, err := getRealtimeOffset(tripID, 0, t.Feeds[context.FeedID]) if err != nil { return vehicle, fmt.Errorf("while getting realtime update: %w", err) } @@ -1216,8 +1184,8 @@ func GetStopsIn(lb, rt Position, context Context, traffic *Traffic) ([]Stop, error) { // todo limit rect size // todo does it take into account rect 179 -> -179 latitude? stops := []Stop{} - positionIndex := traffic.PositionIndexes[context.FeedName][context.Version] - codeIndex := traffic.CodeIndexes[context.FeedName][context.Version] + positionIndex := traffic.PositionIndexes[context.FeedID][context.Version] + codeIndex := traffic.CodeIndexes[context.FeedID][context.Version] rect, err := rtreego.NewRectFromPoints(rtreego.Point{lb.Lat, lb.Lon}, rtreego.Point{rt.Lat, rt.Lon}) if err != nil { return stops, fmt.Errorf("while creating a rect: %w", err) @@ -1236,7 +1204,7 @@ func GetVehiclesIn(lb, rt Position, context Context, t *Traffic) ([]VehicleStatus, error) { // todo limit rect size vehicles := []VehicleStatus{} - updates, err := getRealtimeUpdates(t.Feeds[context.FeedName]) + updates, err := getRealtimeUpdates(t.Feeds[context.FeedID]) if err != nil { return vehicles, err } diff --git a/traffic/feed_info.go b/traffic/feed_info.go index b457feeb854f6b22e6d8c810b91561b504886912..ee3703236093274287967fcdbbd8505c11fe07c1 100644 --- a/traffic/feed_info.go +++ b/traffic/feed_info.go @@ -53,7 +53,7 @@ log.Printf("while parsing date for %s: %v", id, err) continue } - feedInfo, err := getFeedInfo(cfg.FeedsPath, id, versionCode, t) + feedInfo, err := getFeedInfo(cfg.FeedsPath, id, versionCode) if err != nil { log.Printf("while getting feed info for %s: %v", id, err) continue diff --git a/traffic/gtfs_rt.go b/traffic/gtfs_rt.go new file mode 100644 index 0000000000000000000000000000000000000000..107582df7a5d1ace6311c74ada2fa8510ef8401b --- /dev/null +++ b/traffic/gtfs_rt.go @@ -0,0 +1,91 @@ +package traffic + +import ( + "apiote.xyz/p/szczanieckiej/gtfs_rt" + pb "apiote.xyz/p/szczanieckiej/gtfs_rt/transit_realtime" + + "fmt" + "time" +) + +func makeTimetableRelationshipFromTripTimetable(r pb.TripDescriptor_ScheduleRelationship) TimetableRelationship { + switch r { + case pb.TripDescriptor_ADDED: + return TRIP_ADDED + case pb.TripDescriptor_CANCELED: + return TRIP_CANCELED + /* TODO update pb schema + case pb.TripDescriptor_DELETED: + return TRIP_DELETED*/ + default: + return TRIP_SCHEDULED + } +} + +func makeTimetableRelationshipFromStopTrip(r pb.TripUpdate_StopTimeUpdate_ScheduleRelationship) TimetableRelationship { + switch r { + case pb.TripUpdate_StopTimeUpdate_NO_DATA: + return NO_DATA + case pb.TripUpdate_StopTimeUpdate_SKIPPED: + return STOP_SKIPPED + default: + return TRIP_SCHEDULED + } +} + +func getGtfsRtData(entities []*pb.FeedEntity) error { + updates = map[string]Update{} + for _, entity := range entities { + t := entity.TripUpdate + // TODO v := entity.Vehicle + // TODO a := entity.Alert + if t != nil { + u := updates[*t.Trip.TripId] + u.StopSequence = t.StopTimeUpdate[0].GetStopSequence() + u.StopID = t.StopTimeUpdate[0].GetStopId() + if arrival := t.StopTimeUpdate[0].GetArrival(); arrival != nil { + u.Delay = arrival.Delay + } + if u.StopSequence == 0 && *u.Delay < 0 { + *u.Delay = 0 + } + + stopTripRelationship := t.StopTimeUpdate[0].GetScheduleRelationship() + if stopTripRelationship == pb.TripUpdate_StopTimeUpdate_SCHEDULED { + tripTimetableRelationship := t.Trip.GetScheduleRelationship() + u.TimetableRelationship = makeTimetableRelationshipFromTripTimetable(tripTimetableRelationship) + } else { + u.TimetableRelationship = makeTimetableRelationshipFromStopTrip(stopTripRelationship) + } + + updates[*t.Trip.TripId] = u + } + } + + return nil +} + +func getGtfsRealtimeUpdates(tripID string, sequence int, ctx Context) (Update, error) { + feedInfo, err := getFeedInfo(ctx.DataHome, ctx.FeedID, ctx.Version) + if err != nil { + return Update{}, fmt.Errorf("while getting feedInfo: %w\n", err) + } + + now := uint64(time.Now().Unix()) + lastUpdated := lastUpdatedGtfsRt2[ctx.FeedID] + if passed := now - lastUpdated["updates"]; passed > 30 { + message, err := gtfs_rt.GetMessages2(ctx.FeedID, feedInfo.RealtimeFeeds["updates"]) + if err != nil { + return Update{}, fmt.Errorf("while getting messages: %w\n", err) + } + lastUpdatedGtfsRt2[ctx.FeedID]["updates"] = now + + err = getGtfsRtData(message.Entity) + if err != nil { + return Update{}, fmt.Errorf("while converting to Update: %v\n", err) + } + } + + update := updates[tripID] + return update, nil +} diff --git a/traffic/realtime.go b/traffic/realtime.go new file mode 100644 index 0000000000000000000000000000000000000000..5cfe2dd50682df9e763c0b092618d320f669637b --- /dev/null +++ b/traffic/realtime.go @@ -0,0 +1,153 @@ +package traffic + +import ( + "fmt" + "log" + "os" + "time" + + "git.sr.ht/~sircmpwn/go-bare" + "golang.org/x/text/language" +) + +type TimetableRelationship uint + +const ( + TRIP_SCHEDULED TimetableRelationship = iota + TRIP_CANCELED + TRIP_DELETED + TRIP_ADDED + STOP_SKIPPED + NO_DATA +) + +type Update struct { + StopSequence uint32 + StopID string + Delay *int32 // seconds, if nil -> unknown + TimetableRelationship TimetableRelationship // TODO better name + Status DepartureStatus + CongestionLevel CongestionLevel + OccupancyStatus OccupancyStatus + VehicleID string + Latitude float64 + Longitude float64 + Speed float32 + // TODO … +} + +type CongestionLevel uint + +const ( + CONGESTION_UNKNOWN CongestionLevel = iota + CONGESTION_SMOOTH + CONGESTION_STOP_AND_GO + CONGESTION_SIGNIFICANT + CONGESTION_SEVERE +) + +type OccupancyStatus uint + +const ( + OCCUPANCY_UNKNOWN OccupancyStatus = iota + OCCUPANCY_EMPTY + OCCUPANCY_MANY_AVAILABLE + OCCUPANCY_FEW_AVAILABLE + OCCUPANCY_STANDING_ONLY + OCCUPANCY_CRUSHED + OCCUPANCY_FULL + OCCUPANCY_NOT_ACCEPTING +) + +type DepartureStatus uint + +const ( + AT_STOP DepartureStatus = iota + DEPARTED + INCOMING +) + +type Alerts struct { + ByRoute map[string][]uint + ByTrip map[string][]uint + ByType map[uint][]uint + ByStop map[string][]uint + ByAgency map[string][]uint + Alerts []Alert +} + +type Alert struct { + TimeRanges [][2]time.Time + Headers map[language.Tag]string + Descriptions map[language.Tag]string + URLs map[language.Tag]string + Cause int32 // todo(BAF10) + Effect int32 // todo(BAF10) +} + +var updates map[string]Update +var alerts Alerts + +// TODO vehicles cache + +func getTripID(tripsFile *os.File, offset int64) (string, error) { + _, err := tripsFile.Seek(offset, 0) + if err != nil { + return "", fmt.Errorf("while seeking: %w", err) + } + trip := Trip{} + err = bare.UnmarshalReader(tripsFile, &trip) + if err != nil { + return "", fmt.Errorf("while unmarshalling: %w", err) + } + return trip.Id, nil +} + +func enrichDepartures(departures []DepartureRealtime, datetime time.Time, departuresType DeparturesType, ctx Context, tripsFile *os.File, timezone *time.Location) []DepartureRealtime { + enrichedDepartures := make([]DepartureRealtime, len(departures)) + + feedInfo, err := getFeedInfo(ctx.DataHome, ctx.FeedID, ctx.Version) + if err != nil { + log.Printf("while getting feedInfo: %v\n", err) + return departures + } + + var enrichMethod func(string, int, Context) (Update, error) + if _, ok := feedInfo.RealtimeFeeds["updates"]; ok { + enrichMethod = getGtfsRealtimeUpdates + } else { + // TODO enrichMethod = getLuaRealtieUpdates + } + midnight := time.Date(datetime.Year(), datetime.Month(), + datetime.Day(), 0, 0, 0, 0, timezone) + if departuresType == DEPARTURES_HYBRID { + for i, departure := range departures { + if departure.Time.After(midnight) { + tripID, err := getTripID(tripsFile, int64(departure.Order.TripOffset)) + if err != nil { + log.Printf("while getting trip id for %s -> %s (%v): %v\n", departure.LineName, departure.Headsign, departure.Time, err) + continue + } + update, err := enrichMethod(tripID, departure.Order.Sequence, ctx) + if err != nil { + if isTimeout(err) { + break + } else { + log.Printf("while enriching departure %s -> %s (%v): %v\n", departure.LineName, departure.Headsign, departure.Time, err) + } + } else { + enrichedDepartures[i] = departure.WithUpdate(update) + } + } + } + } + return departures +} + +func GetAlerts(languages []language.Tag /*, feed*/) []Alert { + return []Alert{} +} + +func GetVehiclePositions(position Position) []VehicleStatus { + return []VehicleStatus{} +} diff --git a/traffic/structs.go b/traffic/structs.go index d24c3178859578d155d3195779e29f751a4677ec..f063919590af4ca7350d53fc0eeb11e99a82fc4c 100644 --- a/traffic/structs.go +++ b/traffic/structs.go @@ -1,8 +1,6 @@ package traffic import ( - "apiote.xyz/p/szczanieckiej/gtfs_rt" - //"image/color" "strings" "time" @@ -75,11 +73,17 @@ StopOffset uint64 }*/ type DepartureRealtime struct { + Time time.Time Departure Departure Headsign string LineName string Order StopOrder - Update gtfs_rt.Update + Update Update +} + +func (d DepartureRealtime) WithUpdate(update Update) DepartureRealtime { + d.Update = update + return d } /*type Trip struct { @@ -210,14 +214,6 @@ Zone string OnDemand bool } -type Alert struct { - Header string - Description string - URL string - Cause int32 // todo(BAF10) - Effect int32 // todo(BAF10) -} - // type ID string type Validity string // 20060102_20060102 func (v Validity) Start() string { @@ -288,6 +284,6 @@ } type Context struct { DataHome string - FeedName string + FeedID string Version Validity } diff --git a/traffic/structs_gen.go b/traffic/structs_gen.go index 24ffd86ac14bc285c20de14dd93c79a732ecaba6..8a4db233185b074d8a53b47fdeb7a8297547a9ba 100644 --- a/traffic/structs_gen.go +++ b/traffic/structs_gen.go @@ -178,16 +178,17 @@ return bare.Marshal(t) } type FeedInfo struct { - Name string `bare:"name"` - Website string `bare:"website"` - Language string `bare:"language"` - ValidSince string `bare:"validSince"` - ValidTill string `bare:"validTill"` - QrHost string `bare:"qrHost"` - QrLocation QRLocation `bare:"qrLocation"` - QrSelector string `bare:"qrSelector"` - Attributions map[string]string `bare:"attributions"` - Descriptions map[string]string `bare:"descriptions"` + Name string `bare:"name"` + Website string `bare:"website"` + Language string `bare:"language"` + ValidSince string `bare:"validSince"` + ValidTill string `bare:"validTill"` + QrHost string `bare:"qrHost"` + QrLocation QRLocation `bare:"qrLocation"` + QrSelector string `bare:"qrSelector"` + Attributions map[string]string `bare:"attributions"` + Descriptions map[string]string `bare:"descriptions"` + RealtimeFeeds map[string]string `bare:"realtimeFeeds"` } func (t *FeedInfo) Decode(data []byte) error {