123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474 |
- // SPDX-FileCopyrightText: Adam Evyčędo
- //
- // SPDX-License-Identifier: AGPL-3.0-or-later
- package traffic
- import (
- "strings"
- "apiote.xyz/p/szczanieckiej/gtfs_rt"
- pb "apiote.xyz/p/szczanieckiej/gtfs_rt/transit_realtime"
- "golang.org/x/text/language"
- "fmt"
- "math"
- "time"
- )
- // ........................ feedID
- var lastUpdatedGtfsRt = map[string]map[RealtimeFeedType]uint64{}
- func makeTimetableRelationshipFromTripTimetable(r pb.TripDescriptor_ScheduleRelationship) TimetableRelationship {
- switch r {
- case pb.TripDescriptor_ADDED:
- return TRIP_ADDED
- case pb.TripDescriptor_CANCELED:
- return TRIP_CANCELED
- 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_TRIP_DATA
- case pb.TripUpdate_StopTimeUpdate_SKIPPED:
- return STOP_SKIPPED
- default:
- return TRIP_SCHEDULED
- }
- }
- func makeDepartureStatus(s pb.VehiclePosition_VehicleStopStatus) DepartureStatus {
- switch s {
- case pb.VehiclePosition_STOPPED_AT:
- return AT_STOP
- case pb.VehiclePosition_INCOMING_AT:
- return INCOMING
- default:
- return IN_TRANSIT
- }
- }
- func makeCongestionLevel(l pb.VehiclePosition_CongestionLevel) CongestionLevel {
- switch l {
- case pb.VehiclePosition_RUNNING_SMOOTHLY:
- return CONGESTION_SMOOTH
- case pb.VehiclePosition_STOP_AND_GO:
- return CONGESTION_STOP_AND_GO
- case pb.VehiclePosition_CONGESTION:
- return CONGESTION_SIGNIFICANT
- case pb.VehiclePosition_SEVERE_CONGESTION:
- return CONGESTION_SEVERE
- default:
- return CONGESTION_UNKNOWN
- }
- }
- // XXX default occupancy status in GTFS-realtime is VehiclePosition_EMPTY while VehiclePosition_NO_DATA exists
- func makeOccupancyStatus(s *pb.VehiclePosition_OccupancyStatus) OccupancyStatus {
- if s == nil {
- return OCCUPANCY_UNKNOWN
- }
- switch *s {
- case pb.VehiclePosition_EMPTY:
- return OCCUPANCY_EMPTY
- case pb.VehiclePosition_MANY_SEATS_AVAILABLE:
- return OCCUPANCY_MANY_AVAILABLE
- case pb.VehiclePosition_FEW_SEATS_AVAILABLE:
- return OCCUPANCY_FEW_AVAILABLE
- case pb.VehiclePosition_STANDING_ROOM_ONLY:
- return OCCUPANCY_STANDING_ONLY
- case pb.VehiclePosition_CRUSHED_STANDING_ROOM_ONLY:
- return OCCUPANCY_CRUSHED
- case pb.VehiclePosition_FULL:
- return OCCUPANCY_FULL
- case pb.VehiclePosition_NOT_ACCEPTING_PASSENGERS:
- fallthrough
- case pb.VehiclePosition_NOT_BOARDABLE:
- return OCCUPANCY_NOT_ACCEPTING
- default:
- return OCCUPANCY_UNKNOWN
- }
- }
- func makeWheelchairAccessibility(a pb.VehicleDescriptor_WheelchairAccessible) WheelchairAccessibility {
- switch a {
- case pb.VehicleDescriptor_NO_VALUE:
- return WHEELCHAIR_NO_DATA
- case pb.VehicleDescriptor_WHEELCHAIR_ACCESSIBLE:
- return WHEELCHAIR_INACCESSIBLE
- case pb.VehicleDescriptor_WHEELCHAIR_INACCESSIBLE:
- return WHEELCHAIR_INACCESSIBLE
- default:
- return WHEELCHAIR_UNKNOWN
- }
- }
- func makeLineType(routeType int32) LineType {
- switch routeType {
- case 0:
- return TRAM
- case 1:
- return METRO
- case 2:
- return RAIL
- case 3:
- return BUS
- case 4:
- return FERRY
- case 5:
- return CABLE_TRAM
- case 6:
- return CABLE_CAR
- case 7:
- return FUNICULAR
- case 11:
- return TROLLEYBUS
- case 12:
- return MONORAIL
- default:
- panic(fmt.Sprintf("unknown GTFS route type: %d", routeType))
- }
- }
- func getGtfsRtData(entities []*pb.FeedEntity, feedID string) map[RealtimeFeedType]int {
- cacheMx.Lock()
- if vehicleStatuses == nil {
- vehicleStatuses = map[string]map[string]VehicleStatus{}
- }
- if vehicleStatuses[feedID] == nil {
- vehicleStatuses[feedID] = map[string]VehicleStatus{}
- }
- if updates == nil {
- updates = map[string]map[string][]Update{}
- }
- if updates[feedID] == nil {
- updates[feedID] = map[string][]Update{}
- }
- if alerts == nil {
- alerts = map[string]Alerts{}
- }
- var alertNumber uint = 0
- which := map[RealtimeFeedType]int{}
- for _, entity := range entities {
- a := entity.Alert
- v := entity.Vehicle
- t := entity.TripUpdate
- if a != nil {
- if alertNumber == 0 {
- alerts[feedID] = Alerts{
- ByLine: map[string][]uint{},
- ByTrip: map[string][]uint{},
- ByLineType: map[LineType][]uint{},
- ByStop: map[string][]uint{},
- ByAgency: map[string][]uint{},
- }
- }
- which[ALERTS] = 1
- alert := Alert{
- Headers: map[language.Tag]string{},
- Descriptions: map[language.Tag]string{},
- URLs: map[language.Tag]string{},
- }
- for _, period := range a.ActivePeriod {
- if period != nil {
- timeRange := [2]time.Time{
- time.Unix(int64(*period.Start), 0),
- time.Unix(int64(*period.End), 0),
- }
- alert.TimeRanges = append(alert.TimeRanges, timeRange)
- }
- }
- if translations := a.HeaderText.GetTranslation(); translations != nil {
- for _, t := range translations {
- if t == nil || t.GetText() == "" {
- continue
- }
- if t.Language == nil || t.GetLanguage() == "" {
- alert.Headers[language.Und] = *t.Text
- }
- tag, err := language.Parse(t.GetLanguage())
- if err == nil {
- alert.Headers[tag] = *t.Text
- }
- }
- }
- if translations := a.DescriptionText.GetTranslation(); translations != nil {
- for _, t := range translations {
- if t == nil || t.GetText() == "" {
- continue
- }
- if t.Language == nil || t.GetLanguage() == "" {
- alert.Descriptions[language.Und] = *t.Text
- }
- tag, err := language.Parse(t.GetLanguage())
- if err == nil {
- alert.Descriptions[tag] = *t.Text
- }
- }
- }
- if translations := a.Url.GetTranslation(); translations != nil {
- for _, t := range translations {
- if t == nil || t.GetText() == "" {
- continue
- }
- if t.Language == nil || t.GetLanguage() == "" {
- alert.URLs[language.Und] = *t.Text
- }
- tag, err := language.Parse(t.GetLanguage())
- if err == nil {
- alert.URLs[tag] = *t.Text
- }
- }
- }
- alert.Cause = alertCauseOfGtfs(a.Cause)
- alert.Effect = alertEffectOfGtfs(a.Effect)
- for _, e := range a.InformedEntity {
- if e == nil {
- continue
- }
- if e.AgencyId != nil {
- alerts[feedID].ByAgency[*e.AgencyId] = append(alerts[feedID].ByAgency[*e.AgencyId], alertNumber)
- }
- if e.RouteId != nil {
- alerts[feedID].ByLine[*e.RouteId] = append(alerts[feedID].ByLine[*e.RouteId], alertNumber)
- }
- if e.StopId != nil {
- alerts[feedID].ByStop[*e.StopId] = append(alerts[feedID].ByStop[*e.StopId], alertNumber)
- }
- if e.Trip != nil && e.Trip.TripId != nil {
- alerts[feedID].ByTrip[*e.Trip.TripId] = append(alerts[feedID].ByTrip[*e.Trip.TripId], alertNumber)
- }
- if e.RouteType != nil {
- alerts[feedID].ByLineType[makeLineType(*e.RouteType)] = append(alerts[feedID].ByLineType[makeLineType(*e.RouteType)], alertNumber)
- }
- }
- alertNumber++
- alertsStruct := alerts[feedID]
- alertsStruct.Alerts = append(alerts[feedID].Alerts, alert)
- alerts[feedID] = alertsStruct
- }
- if v != nil {
- which[VEHICLE_POSITIONS] = 1
- tripUpdates := updates[feedID][*v.Trip.TripId]
- vehicleUpdate := VehicleStatus{
- Status: makeDepartureStatus(v.GetCurrentStatus()),
- CongestionLevel: makeCongestionLevel(v.GetCongestionLevel()),
- OccupancyStatus: makeOccupancyStatus(v.OccupancyStatus),
- VehicleID: v.GetVehicle().GetId(),
- Latitude: float64(v.GetPosition().GetLatitude()),
- Longitude: float64(v.GetPosition().GetLongitude()),
- Speed: v.GetPosition().GetSpeed(),
- Bearing: float64(v.GetPosition().GetBearing() * math.Pi / 180),
- TripID: *v.Trip.TripId,
- WheelchairAccessibility: makeWheelchairAccessibility(v.GetVehicle().GetWheelchairAccessible()),
- }
- vehicleStatuses[feedID][*v.Trip.TripId] = vehicleUpdate
- for i, tripUpdate := range tripUpdates {
- tripUpdate.VehicleStatus = vehicleUpdate
- tripUpdates[i] = tripUpdate
- }
- updates[feedID][*v.Trip.TripId] = tripUpdates
- }
- if t != nil {
- tripUpdates := make([]Update, len(t.StopTimeUpdate))
- for i, stopTimeUpdate := range t.StopTimeUpdate {
- which[TRIP_UPDATES] = 1
- update := Update{
- StopSequence: stopTimeUpdate.GetStopSequence(),
- StopID: stopTimeUpdate.GetStopId(),
- VehicleStatus: vehicleStatuses[feedID][*t.Trip.TripId],
- }
- update.VehicleStatus.TripID = *t.Trip.TripId
- update.Delay = stopTimeUpdate.GetArrival().GetDelay()
- if update.StopSequence == 0 && update.Delay < 0 {
- update.Delay = 0
- }
- updateTime := stopTimeUpdate.GetArrival().GetTime()
- if updateTime != 0 {
- update.TimeUTC = time.Unix(updateTime, 0).In(time.UTC).Format("150405")
- }
- stopTripRelationship := stopTimeUpdate.GetScheduleRelationship()
- if stopTripRelationship == pb.TripUpdate_StopTimeUpdate_SCHEDULED {
- tripTimetableRelationship := t.Trip.GetScheduleRelationship()
- update.TimetableRelationship = makeTimetableRelationshipFromTripTimetable(tripTimetableRelationship)
- } else {
- update.TimetableRelationship = makeTimetableRelationshipFromStopTrip(stopTripRelationship)
- }
- tripUpdates[i] = update
- }
- updates[feedID][*t.Trip.TripId] = tripUpdates
- }
- }
- cacheMx.Unlock()
- return which
- }
- func getGtfsRealtimeMessages(feedType RealtimeFeedType, feedID string, feeds map[RealtimeFeedType]string) error {
- now := uint64(time.Now().Unix())
- if lastUpdatedGtfsRt[feedID] == nil {
- lastUpdatedGtfsRt[feedID] = map[RealtimeFeedType]uint64{}
- }
- if passed := now - lastUpdatedGtfsRt[feedID][feedType]; passed > 30 {
- for _, feedURL := range strings.Split(feeds[feedType], " ") {
- message, err := gtfs_rt.GetMessages(feedID, feedURL)
- if err != nil {
- return BlockingError{fmt.Errorf("while getting messages: %w", err)}
- }
- /*
- cacheMx.Lock()
- switch feedType {
- case TRIP_UPDATES:
- updates = map[string]map[string][]Update{}
- case VEHICLE_POSITIONS:
- vehicleStatuses = map[string]map[string]VehicleStatus{}
- case ALERTS:
- alerts = map[string]Alerts{}
- }
- cacheMx.Unlock()
- */
- whichUpdated := getGtfsRtData(message.Entity, feedID)
- for key, value := range whichUpdated {
- if value == 1 {
- lastUpdatedGtfsRt[feedID][key] = now
- }
- }
- }
- }
- return nil
- }
- func getGtfsRealtimeUpdates(_ string, _ int, _, _ string, ctx Context) (map[string][]Update, map[string][]Alert, bool, error) {
- feedInfo, err := getFeedInfo(ctx.DataHome, ctx.FeedID, ctx.Version)
- if err != nil {
- return map[string][]Update{}, map[string][]Alert{}, true, fmt.Errorf("while getting feedInfo: %w", err)
- }
- err = getGtfsRealtimeMessages(TRIP_UPDATES, ctx.FeedID, feedInfo.RealtimeFeeds)
- if err != nil {
- return map[string][]Update{}, map[string][]Alert{}, true, fmt.Errorf("while getting updates: %w", err)
- }
- if _, ok := feedInfo.RealtimeFeeds[VEHICLE_POSITIONS]; ok {
- // TODO should be moved to enrichDepartures and conditional (this, or custom API)
- err = getGtfsRealtimeMessages(VEHICLE_POSITIONS, ctx.FeedID, feedInfo.RealtimeFeeds)
- if err != nil {
- return map[string][]Update{}, map[string][]Alert{}, true, fmt.Errorf("while getting vehicles: %w", err)
- }
- }
- cacheMx.Lock()
- resultUpdates := updates[ctx.FeedID]
- cacheMx.Unlock()
- return resultUpdates, map[string][]Alert{}, true, nil
- }
- func getGtfsRealtimeVehicles(ctx Context, lb, rt Position) ([]VehicleStatus, error) {
- feedInfo, err := getFeedInfo(ctx.DataHome, ctx.FeedID, ctx.Version)
- if err != nil {
- return []VehicleStatus{}, fmt.Errorf("while getting feedInfo: %w", err)
- }
- getGtfsRealtimeMessages(VEHICLE_POSITIONS, ctx.FeedID, feedInfo.RealtimeFeeds)
- cacheMx.Lock()
- vehicles := make([]VehicleStatus, len(vehicleStatuses[ctx.FeedID]))
- i := 0
- for _, status := range vehicleStatuses[ctx.FeedID] {
- vehicles[i] = status
- i++
- }
- cacheMx.Unlock()
- vehiclesFiltered := []VehicleStatus{}
- for _, vehicleRt := range vehicles {
- if rt.Lat < float64(vehicleRt.Latitude) || lb.Lat > float64(vehicleRt.Latitude) {
- continue
- }
- lon := float64(vehicleRt.Longitude)
- if lb.Lon < rt.Lon {
- if lb.Lon < lon && lon < rt.Lon {
- vehiclesFiltered = append(vehiclesFiltered, vehicleRt)
- }
- } else {
- if lon > lb.Lon || lon < rt.Lon {
- vehiclesFiltered = append(vehiclesFiltered, vehicleRt)
- }
- }
- }
- return vehiclesFiltered, nil
- }
- func getGtfsRealtimeAlerts(stopID, _, tripID string, ctx Context, t *Traffic) ([]Alert, error) {
- feedInfo, err := getFeedInfo(ctx.DataHome, ctx.FeedID, ctx.Version)
- if err != nil {
- return []Alert{}, fmt.Errorf("while getting feedInfo: %w", err)
- }
- getGtfsRealtimeMessages(ALERTS, ctx.FeedID, feedInfo.RealtimeFeeds)
- cacheMx.Lock()
- resultAlertsMap := map[uint]Alert{}
- if tripID != "" {
- trip, err := GetTrip(tripID, ctx, t)
- if err != nil {
- return []Alert{}, fmt.Errorf("while getting trip: %w", err)
- }
- line, err := GetLine(trip.LineID, ctx, t)
- if err != nil {
- return []Alert{}, fmt.Errorf("while getting line: %w", err)
- }
- for _, i := range alerts[ctx.FeedID].ByAgency[line.AgencyID] {
- resultAlertsMap[i] = alerts[ctx.FeedID].Alerts[i]
- }
- for _, i := range alerts[ctx.FeedID].ByLine[line.Id] {
- resultAlertsMap[i] = alerts[ctx.FeedID].Alerts[i]
- }
- for _, i := range alerts[ctx.FeedID].ByLine[line.Id] {
- resultAlertsMap[i] = alerts[ctx.FeedID].Alerts[i]
- }
- for _, i := range alerts[ctx.FeedID].ByLineType[line.Kind] {
- resultAlertsMap[i] = alerts[ctx.FeedID].Alerts[i]
- }
- }
- if stopID != "" {
- for _, i := range alerts[ctx.FeedID].ByStop[stopID] {
- resultAlertsMap[i] = alerts[ctx.FeedID].Alerts[i]
- }
- for _, i := range alerts[ctx.FeedID].ByTrip[tripID] {
- resultAlertsMap[i] = alerts[ctx.FeedID].Alerts[i]
- }
- }
- cacheMx.Unlock()
- resultAlerts := make([]Alert, len(resultAlertsMap))
- i := 0
- for _, alert := range resultAlertsMap {
- resultAlerts[i] = alert
- i++
- }
- return resultAlerts, nil
- }
|