123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- // SPDX-FileCopyrightText: Adam Evyčędo
- //
- // SPDX-License-Identifier: AGPL-3.0-or-later
- package traffic
- import (
- pb "apiote.xyz/p/szczanieckiej/gtfs_rt/transit_realtime"
- "errors"
- "fmt"
- "log"
- "os"
- "strings"
- "sync"
- "time"
- "git.sr.ht/~sircmpwn/go-bare"
- "golang.org/x/text/language"
- )
- type BlockingError struct {
- cause error
- }
- func (e BlockingError) Error() string {
- return e.cause.Error()
- }
- type Alerts struct {
- ByLine map[string][]uint
- ByTrip map[string][]uint
- ByLineType map[LineType][]uint
- ByStop map[string][]uint
- ByAgency map[string][]uint
- Alerts []Alert
- }
- type SpecificAlert struct {
- Header string
- Description string
- URL string
- Cause AlertCause
- Effect AlertEffect
- }
- type Alert struct {
- TimeRanges [][2]time.Time
- Headers map[language.Tag]string
- Descriptions map[language.Tag]string
- URLs map[language.Tag]string
- Cause AlertCause
- Effect AlertEffect
- }
- type AlertCause uint
- const (
- CAUSE_UNKNOWN AlertCause = 0
- CAUSE_OTHER AlertCause = 1
- CAUSE_TECHNICAL_PROBLEM AlertCause = 2
- CAUSE_STRIKE AlertCause = 3
- CAUSE_DEMONSTRATION AlertCause = 4
- CAUSE_ACCIDENT AlertCause = 5
- CAUSE_HOLIDAY AlertCause = 6
- CAUSE_WEATHER AlertCause = 7
- CAUSE_MAINTENANCE AlertCause = 8
- CAUSE_CONSTRUCTION AlertCause = 9
- CAUSE_POLICE_ACTIVITY AlertCause = 10
- CAUSE_MEDICAL_EMERGENCY AlertCause = 11
- )
- func alertCauseOfGtfs(v *pb.Alert_Cause) AlertCause {
- switch v {
- case pb.Alert_UNKNOWN_CAUSE.Enum():
- return CAUSE_UNKNOWN
- case pb.Alert_OTHER_CAUSE.Enum():
- return CAUSE_OTHER
- case pb.Alert_TECHNICAL_PROBLEM.Enum():
- return CAUSE_TECHNICAL_PROBLEM
- case pb.Alert_STRIKE.Enum():
- return CAUSE_STRIKE
- case pb.Alert_DEMONSTRATION.Enum():
- return CAUSE_DEMONSTRATION
- case pb.Alert_ACCIDENT.Enum():
- return CAUSE_ACCIDENT
- case pb.Alert_HOLIDAY.Enum():
- return CAUSE_HOLIDAY
- case pb.Alert_WEATHER.Enum():
- return CAUSE_WEATHER
- case pb.Alert_MAINTENANCE.Enum():
- return CAUSE_MAINTENANCE
- case pb.Alert_CONSTRUCTION.Enum():
- return CAUSE_CONSTRUCTION
- case pb.Alert_POLICE_ACTIVITY.Enum():
- return CAUSE_POLICE_ACTIVITY
- case pb.Alert_MEDICAL_EMERGENCY.Enum():
- return CAUSE_MEDICAL_EMERGENCY
- default:
- return CAUSE_UNKNOWN
- }
- }
- type AlertEffect uint
- const (
- EFFECT_UNKNOWN AlertEffect = 0
- EFFECT_OTHER AlertEffect = 1
- EFFECT_NO_SERVICE AlertEffect = 2
- EFFECT_REDUCED_SERVICE AlertEffect = 3
- EFFECT_SIGNIFICANT_DELAYS AlertEffect = 4
- EFFECT_DETOUR AlertEffect = 5
- EFFECT_ADDITIONAL_SERVICE AlertEffect = 6
- EFFECT_MODIFIED_SERVICE AlertEffect = 7
- EFFECT_STOP_MOVED AlertEffect = 8
- EFFECT_NONE AlertEffect = 9
- EFFECT_ACCESSIBILITY_ISSUE AlertEffect = 10
- )
- func alertEffectOfGtfs(v *pb.Alert_Effect) AlertEffect {
- switch v {
- case pb.Alert_UNKNOWN_EFFECT.Enum():
- return EFFECT_UNKNOWN
- case pb.Alert_OTHER_EFFECT.Enum():
- return EFFECT_OTHER
- case pb.Alert_NO_SERVICE.Enum():
- return EFFECT_NO_SERVICE
- case pb.Alert_REDUCED_SERVICE.Enum():
- return EFFECT_REDUCED_SERVICE
- case pb.Alert_SIGNIFICANT_DELAYS.Enum():
- return EFFECT_SIGNIFICANT_DELAYS
- case pb.Alert_DETOUR.Enum():
- return EFFECT_DETOUR
- case pb.Alert_ADDITIONAL_SERVICE.Enum():
- return EFFECT_ADDITIONAL_SERVICE
- case pb.Alert_MODIFIED_SERVICE.Enum():
- return EFFECT_MODIFIED_SERVICE
- case pb.Alert_STOP_MOVED.Enum():
- return EFFECT_STOP_MOVED
- case pb.Alert_NO_EFFECT.Enum():
- return EFFECT_NONE
- case pb.Alert_ACCESSIBILITY_ISSUE.Enum():
- return EFFECT_ACCESSIBILITY_ISSUE
- default:
- return EFFECT_UNKNOWN
- }
- }
- // ............ feedID trip/stop
- var updates map[string]map[string][]Update
- var alerts map[string]Alerts
- var vehicleStatuses map[string]map[string]VehicleStatus
- var cacheMx sync.Mutex
- 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 departuresFromNoTripUpdates(updates []Update, alerts map[string][]Alert, pickups, dropoffs map[string]Boarding, timezone *time.Location, languages []language.Tag) ([]DepartureRealtime, error) {
- departures := []DepartureRealtime{}
- now := time.Now().In(timezone)
- for _, update := range updates {
- if update.Time == "" {
- log.Printf("update time is empty, update is %+v\n", update)
- continue
- }
- departureTime, err := time.Parse("150405", update.Time)
- if err != nil {
- return departures, fmt.Errorf("while parsing time: %w", err)
- }
- departureTime = time.Date(now.Year(), now.Month(), now.Day(), departureTime.Hour(), departureTime.Minute(), departureTime.Second(), 0, timezone)
- departures = append(departures, DepartureRealtime{
- Time: departureTime,
- Departure: Departure{
- Pickup: pickups[update.VehicleStatus.LineID],
- Dropoff: dropoffs[update.VehicleStatus.LineID],
- },
- Headsign: update.VehicleStatus.Headsign,
- LineID: update.VehicleStatus.LineID,
- Order: StopOrder{
- uint(departureTime.Unix()),
- 0,
- },
- Update: update, // NOTE delay must be 0
- Alerts: selectSpecificAlerts(alerts[update.VehicleStatus.TripID], languages),
- })
- }
- return departures, nil
- }
- func enrichDepartures(stopID, stopCode string, departures []DepartureRealtime, datetime time.Time, departuresType DeparturesType, ctx Context, tripsFile *os.File, timezone *time.Location, languages []language.Tag) ([]DepartureRealtime, error) { // TODO tripsFile -> map[tripOffset]tripID
- 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)
- feedInfo = FeedInfo{}
- }
- var enrichMethod func(string, int, string, string, Context) (map[string][]Update, map[string][]Alert, bool, error)
- if feedInfo.Name != "" {
- if _, ok := feedInfo.RealtimeFeeds[TRIP_UPDATES]; ok {
- enrichMethod = getGtfsRealtimeUpdates
- // log.Println("GTFS")
- } else if isLuaUpdatesScript(ctx) {
- enrichMethod = getLuaRealtimeUpdates
- // log.Println("Lua")
- } else {
- // log.Println("none")
- }
- }
- offsets := make([]uint, len(departures))
- pickups := map[string]Boarding{}
- dropoffs := map[string]Boarding{}
- for i, departure := range departures {
- offsets[i] = departure.Order.TripOffset
- pickups[departure.LineID] = departure.Departure.Pickup
- dropoffs[departure.LineID] = departure.Departure.Dropoff
- }
- trips, err := GetTripsByOffset(offsets, ctx, func(Trip) bool { return true })
- if err != nil {
- return departures, fmt.Errorf("while getting trips: %w", err)
- }
- 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) {
- var (
- updates map[string][]Update
- alerts map[string][]Alert
- areTripsInTimetable bool
- )
- if enrichMethod != nil {
- updates, alerts, areTripsInTimetable, err = enrichMethod(trips[departure.Order.TripOffset].Id, departure.Order.Sequence, stopID, stopCode, ctx)
- if err != nil {
- var ber BlockingError
- if isTimeout(err) || errors.As(err, &ber) || strings.Contains(err.Error(), "connection refused") { // TODO or any other connection problem
- log.Printf("blocking error while enriching departure %s -> %s (%v): %v", departure.LineID, departure.Headsign, departure.Time, err)
- update := Update{}
- update.VehicleStatus.LineID = trips[departure.Order.TripOffset].LineID
- update.VehicleStatus.Headsign = trips[departure.Order.TripOffset].Headsign
- enrichedDepartures[i] = departure.WithUpdate(update)
- enrichMethod = nil
- continue
- } else {
- log.Printf("while enriching departure %s -> %s (%v): %v\n", departure.LineID, departure.Headsign, departure.Time, err)
- enrichedDepartures[i] = departure
- continue
- }
- }
- if areTripsInTimetable {
- tripUpdates := updates[trips[departure.Order.TripOffset].Id]
- var validTripUpdate Update
- for _, tripUpdate := range tripUpdates {
- if tripUpdate.StopSequence > uint32(departure.Order.Sequence) {
- break
- }
- validTripUpdate.Time = tripUpdate.Time
- validTripUpdate.Delay = tripUpdate.Delay
- validTripUpdate.StopID = tripUpdate.StopID
- validTripUpdate.StopSequence = tripUpdate.StopSequence
- validTripUpdate.TimetableRelationship = tripUpdate.TimetableRelationship
- validTripUpdate.VehicleStatus = tripUpdate.VehicleStatus
- if tripUpdate.StopSequence != uint32(departure.Order.Sequence) {
- validTripUpdate.VehicleStatus.Status = IN_TRANSIT
- }
- }
- validTripUpdate.VehicleStatus.LineID = trips[departure.Order.TripOffset].LineID
- validTripUpdate.VehicleStatus.Headsign = trips[departure.Order.TripOffset].Headsign
- enrichedDepartures[i] = departure.WithUpdate(validTripUpdate)
- enrichedDepartures[i] = enrichedDepartures[i].WithAlerts(alerts[trips[departure.Order.TripOffset].Id], languages)
- } else {
- var err error
- enrichedDepartures, err = departuresFromNoTripUpdates(updates[stopCode], alerts, pickups, dropoffs, timezone, languages)
- if err != nil {
- return departures, fmt.Errorf("while creating departures without trip: %w", err)
- }
- break
- }
- } else {
- update := Update{}
- update.VehicleStatus.LineID = trips[departure.Order.TripOffset].LineID
- update.VehicleStatus.Headsign = trips[departure.Order.TripOffset].Headsign
- enrichedDepartures[i] = departure.WithUpdate(update)
- }
- }
- }
- } else {
- for i, departure := range departures {
- enrichedDepartures[i] = departure.WithUpdate(Update{
- VehicleStatus: VehicleStatus{
- LineID: trips[departure.Order.TripOffset].LineID,
- Headsign: trips[departure.Order.TripOffset].Headsign,
- },
- })
- }
- }
- return enrichedDepartures, nil
- }
- func GetAlerts(stopID, stopCode string, tripOffset int, ctx Context, t *Traffic, languages []language.Tag) []SpecificAlert {
- feedInfo, err := getFeedInfo(ctx.DataHome, ctx.FeedID, ctx.Version)
- if err != nil {
- log.Printf("while getting feedInfo: %v\n", err)
- feedInfo = FeedInfo{}
- }
- var function func(string, string, string, Context, *Traffic) ([]Alert, error)
- if feedInfo.Name != "" {
- if _, ok := feedInfo.RealtimeFeeds[ALERTS]; ok {
- function = getGtfsRealtimeAlerts
- } else if isLuaAlertsScript(ctx) {
- function = getLuaRealtimeAlerts
- } else {
- return []SpecificAlert{}
- }
- }
- tripID := ""
- if tripOffset > 0 {
- trip, err := GetTripByOffset(uint(tripOffset), ctx, t)
- if err != nil {
- log.Printf("while getting trip: %v\n", err)
- return []SpecificAlert{}
- }
- tripID = trip.Id
- }
- if function != nil {
- alerts, err := function(stopID, stopCode, tripID, ctx, t)
- if err != nil {
- log.Printf("while getting alerts: %v\n", err)
- return []SpecificAlert{}
- }
- return selectSpecificAlerts(alerts, languages)
- }
- return []SpecificAlert{}
- }
- func getVehiclePositions(ctx Context, t *Traffic, lb, rt Position) []VehicleStatus {
- feedInfo, err := getFeedInfo(ctx.DataHome, ctx.FeedID, ctx.Version)
- if err != nil {
- log.Printf("while getting feedInfo: %v\n", err)
- feedInfo = FeedInfo{}
- }
- var function func(Context, Position, Position) ([]VehicleStatus, error)
- if feedInfo.Name != "" {
- if _, ok := feedInfo.RealtimeFeeds[VEHICLE_POSITIONS]; ok {
- function = getGtfsRealtimeVehicles
- } else if isLuaVehiclesScript(ctx) {
- function = getLuaRealtimeVehicles
- }
- }
- if function != nil {
- statuses, err := function(ctx, lb, rt)
- if err != nil {
- log.Printf("while getting vehicle positions: %v\n", err)
- return []VehicleStatus{}
- }
- ids := []string{}
- for _, status := range statuses {
- if status.LineID == "" || status.Headsign == "" {
- ids = append(ids, status.TripID)
- }
- }
- trips, err := GetTrips(ids, ctx, t)
- if err != nil {
- log.Printf("while getting trips: %v", err)
- }
- statusesWithLine := make([]VehicleStatus, len(statuses))
- for i, status := range statuses {
- if status.LineID != "" && status.Headsign != "" {
- statusesWithLine[i] = status
- } else {
- if _, ok := trips[status.TripID]; !ok {
- continue
- }
- status.LineID = trips[status.TripID].LineID
- status.Headsign = trips[status.TripID].Headsign
- statusesWithLine[i] = status
- }
- }
- return statusesWithLine
- }
- return []VehicleStatus{}
- }
|