1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207 |
- package traffic
- import (
- "apiote.xyz/p/szczanieckiej/config"
- "apiote.xyz/p/szczanieckiej/file"
- "apiote.xyz/p/szczanieckiej/gtfs_rt"
- traffic_errors "apiote.xyz/p/szczanieckiej/traffic/errors"
- "apiote.xyz/p/szczanieckiej/transformers"
- "errors"
- "fmt"
- "io"
- "log"
- "net"
- "os"
- "path/filepath"
- "sort"
- "strconv"
- "strings"
- "time"
- "golang.org/x/text/language"
- "golang.org/x/text/runes"
- "golang.org/x/text/transform"
- "git.sr.ht/~sircmpwn/go-bare"
- "github.com/dhconnelly/rtreego"
- "github.com/sahilm/fuzzy"
- "notabug.org/apiote/gott"
- )
- type OlcError struct {
- Value string
- Err error
- }
- func (e OlcError) Error() string {
- return e.Err.Error()
- }
- type _Result struct {
- Filename string
- Offset uint
- Date time.Time
- LineName string
- TimetableHome string
- Calendar []Schedule
- DeparturesType DeparturesType
- Vehicles Vehicles
- Feed Feed
- Location *time.Location
- Datetime time.Time
- MinuteB4Datetime time.Time
- Midnight time.Time
- TodaySchedule string
- YesterdaySchedule string
- file *os.File
- TripsFile *os.File
- Trips map[string]Trip
- Departures []DepartureRealtime
- Stop Stop
- Line Line
- Trip Trip
- FeedInfo FeedInfo
- }
- var lastUpdatedGtfsRt = map[string]uint64{}
- func isTimeout(err error) bool {
- var e net.Error
- return errors.As(err, &e) && e.Timeout()
- }
- func CleanQuery(query string, feed Feed) (string, error) {
- t := transform.Chain(runes.Remove(runes.Predicate(transformers.IsNonAlphanum)), feed.Transformer())
- queryCleaned, _, err := transform.String(t, query)
- return strings.ToLower(queryCleaned), err
- }
- func findSchedule(home string, time time.Time, calendar []Schedule) (string,
- error) {
- weekday := uint8(1 << time.Weekday())
- date := time.Format(DateFormat)
- for _, schedule := range calendar {
- if schedule.StartDate <= date && date <= schedule.EndDate &&
- (schedule.Weekdays&weekday != 0) {
- return schedule.ScheduleID, nil
- }
- }
- return "", traffic_errors.NoSchedule{Date: date}
- }
- func getRealtimeOffset(tripID string, stopSequence int,
- feed Feed) (gtfs_rt.Update, error) {
- updates, lastUpdated, err := gtfs_rt.GetRt(lastUpdatedGtfsRt[feed.String()],
- feed.RealtimeFeeds(), feed.String())
- if err != nil {
- return gtfs_rt.Update{}, err
- }
- lastUpdatedGtfsRt[feed.String()] = lastUpdated
- update := updates[tripID]
- return update, nil
- }
- func getRealtimeUpdates(feed Feed) (map[string]gtfs_rt.Update, error) {
- updates, lastUpdated, err := gtfs_rt.GetRt(lastUpdatedGtfsRt[feed.String()],
- feed.RealtimeFeeds(), feed.String())
- if err != nil {
- return map[string]gtfs_rt.Update{}, err
- }
- lastUpdatedGtfsRt[feed.String()] = lastUpdated
- return updates, nil
- }
- func calculateGtfsTime(gtfsTime uint, delay int32, date time.Time,
- timezone *time.Location) (time.Time, error) {
- 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
- }
- func loadLocation(input ...interface{}) (interface{}, error) {
- result := input[0].(_Result)
- var err error = nil
- result.Location, err = GetTimezone(result.Stop, result.Feed)
- return result, err
- }
- 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)
- return result
- }
- func loadTodaySchedule(input ...interface{}) (interface{}, error) {
- result := input[0].(_Result)
- todaySchedule, err := findSchedule(result.TimetableHome, result.Date,
- result.Calendar)
- result.TodaySchedule = todaySchedule
- return result, err
- }
- func loadYesterdaySchedule(input ...interface{}) (interface{}, error) {
- result := input[0].(_Result)
- yesterday := result.Date.AddDate(0, 0, -1)
- yesterdaySchedule, err := findSchedule(result.TimetableHome, yesterday,
- result.Calendar)
- result.YesterdaySchedule = yesterdaySchedule
- return result, err
- }
- func recoverYesterdaySchedule(input ...interface{}) (interface{}, error) {
- result := input[0].(_Result)
- err := input[1].(error)
- dayBefore := result.Date.AddDate(0, 0, -1).Format(DateFormat)
- if err, ok := err.(traffic_errors.NoSchedule); ok && err.Date == dayBefore {
- result.YesterdaySchedule = ""
- return gott.Tuple{result}, nil
- }
- return gott.Tuple{result}, err
- }
- func openFile(input ...interface{}) (interface{}, error) {
- result := input[0].(_Result)
- file, err := os.Open(filepath.Join(result.TimetableHome, result.Filename))
- result.file = file
- return result, err
- }
- func seek(input ...interface{}) (interface{}, error) {
- result := input[0].(_Result)
- _, err := result.file.Seek(int64(result.Offset), 0)
- return result, err
- }
- func unmarshalStop(input ...interface{}) (interface{}, error) {
- result := input[0].(_Result)
- result.Stop = Stop{}
- err := bare.UnmarshalReader(result.file, &result.Stop)
- result.file.Close()
- return result, err
- }
- func unmarshalFeedInfo(input ...interface{}) (interface{}, error) {
- result := input[0].(_Result)
- result.FeedInfo = FeedInfo{}
- err := bare.UnmarshalReader(result.file, &result.FeedInfo)
- result.file.Close()
- return result, err
- }
- func unmarshalLine(input ...interface{}) (interface{}, error) {
- result := input[0].(_Result)
- result.Line = Line{}
- err := bare.UnmarshalReader(result.file, &result.Line)
- result.file.Close()
- return result, err
- }
- func unmarshalTrip(input ...interface{}) (interface{}, error) {
- result := input[0].(_Result)
- result.Trip = Trip{}
- err := bare.UnmarshalReader(result.file, &result.Trip)
- result.file.Close()
- return result, err
- }
- func openTripsFile(input ...interface{}) (interface{}, error) {
- result := input[0].(_Result)
- tripsFile, err := os.Open(filepath.Join(result.TimetableHome, "trips.bare"))
- result.TripsFile = tripsFile
- return result, err
- }
- func readTrips(input ...interface{}) (interface{}, error) {
- result := input[0].(_Result)
- trips := map[string]Trip{}
- orders := []StopOrder{}
- for _, order := range result.Stop.Order {
- _, err := result.TripsFile.Seek(int64(order.TripOffset), 0)
- if err != nil {
- return result, err
- }
- trip := Trip{}
- err = bare.UnmarshalReader(result.TripsFile, &trip)
- if err != nil {
- return result, err
- }
- if trip.ScheduleID == result.TodaySchedule ||
- trip.ScheduleID == result.YesterdaySchedule {
- trips[trip.ID] = trip
- order.TripID = trip.ID
- orders = append(orders, order)
- }
- }
- result.Stop.Order = orders
- result.Trips = trips
- return result, nil
- }
- func getUpdates(input ...interface{}) (interface{}, error) {
- result := input[0].(_Result)
- departures := []DepartureRealtime{}
- timedOut := false
- for _, order := range result.Stop.Order {
- trip := result.Trips[order.TripID]
- var date time.Time
- if trip.ScheduleID == result.TodaySchedule {
- date = result.Date
- } else if trip.ScheduleID == result.YesterdaySchedule {
- date = result.Date.AddDate(0, 0, -1)
- } else {
- continue
- }
- departure, err := getDeparture(date, result, order, trip, result.Feed, timedOut)
- if err != nil {
- if isTimeout(err) {
- timedOut = true
- err = nil
- } else {
- return result, err
- }
- }
- departures = append(departures, departure)
- }
- result.Departures = departures
- result.TripsFile.Close()
- return result, nil
- }
- func getDeparture(date time.Time, result _Result, order StopOrder,
- trip Trip, feed Feed, timedOut bool) (DepartureRealtime, error) {
- found := false
- departureRt := DepartureRealtime{}
- var finalErr error
- for _, departure := range trip.Departures {
- if departure.StopSeq == order.Order {
- departureRt.Departure = departure
- departureRt.Headsign = trip.Headsign
- departureRt.LineName = trip.LineName
- departureRt.Order = order
- departureRt.Update = gtfs_rt.Update{}
- departureTime, err := 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(order.TripID,
- order.Order, 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
- }
- }
- if !found {
- return departureRt, traffic_errors.NoStopOrder{
- TripID: trip.ID,
- Order: order.Order,
- }
- }
- return departureRt, finalErr
- }
- 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) {
- departures = append(departures, departure)
- }
- }
- result.Departures = departures
- return result
- }
- func filterDeparturesByLine(input ...interface{}) interface{} {
- result := input[0].(_Result)
- departures := []DepartureRealtime{}
- if result.LineName != "" {
- for _, departure := range result.Departures {
- if departure.LineName == result.LineName {
- departures = append(departures, departure)
- }
- }
- result.Departures = departures
- }
- return result
- }
- 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 result
- }
- func closeFiles(input ...interface{}) (interface{}, error) {
- result := input[0].(_Result)
- err := input[1].(error)
- if result.file != nil {
- result.file.Close()
- }
- if result.TripsFile != nil {
- result.TripsFile.Close()
- }
- return result, err
- }
- func unmarshalCodeIndex(timetableHome string) (CodeIndex, error) {
- ix := CodeIndex{}
- ixFile, err := os.Open(filepath.Join(timetableHome, "ix_stop_codes.bare"))
- if err != nil {
- return ix, fmt.Errorf("while opening file: %w\n", err)
- }
- defer ixFile.Close()
- r := bare.NewReader(ixFile)
- num, err := r.ReadUint()
- if err != nil {
- return ix, fmt.Errorf("while reading length: %w\n", err)
- }
- for i := uint64(0); i < num; i++ {
- k, err := r.ReadString()
- if err != nil {
- return ix, fmt.Errorf("while reading key at %d: %w\n", i, err)
- }
- v, err := r.ReadUint()
- if err != nil {
- return ix, fmt.Errorf("while reading value at %d: %w\n", i, err)
- }
- ix[ID(k)] = uint(v)
- }
- return ix, nil
- }
- func unmarshalNameIndex(timetableHome, filename string) (NameIndex, error) {
- ix := NameIndex{}
- ixFile, err := os.Open(filepath.Join(timetableHome, filename))
- if err != nil {
- return ix, fmt.Errorf("while opening file: %w", err)
- }
- defer ixFile.Close()
- for err == nil {
- nameOffset := NameOffset{}
- err = bare.UnmarshalReader(ixFile, &nameOffset)
- if err != nil {
- if err == io.EOF {
- break
- } else {
- return ix, fmt.Errorf("while unmarshaling: %w", err)
- }
- }
- ix = append(ix, nameOffset)
- }
- return ix, nil
- }
- func unmarshalLineIndex(timetableHome string) (NameIndex, error) {
- return unmarshalNameIndex(timetableHome, "ix_lines.bare")
- }
- func unmarshalStopNameIndex(timetableHome string) (NameIndex, error) {
- return unmarshalNameIndex(timetableHome, "ix_stop_names.bare")
- }
- func unmarshalTripIndex(timetableHome string) (NameIndex, error) {
- return unmarshalNameIndex(timetableHome, "ix_trips.bare")
- }
- func readIndexes(feedHome string, versions []Version) (FeedCodeIndex,
- FeedNameIndex, FeedNameIndex, FeedNameIndex, error) {
- codeIndex := FeedCodeIndex{}
- nameIndex := FeedNameIndex{}
- lineIndex := FeedNameIndex{}
- tripIndex := FeedNameIndex{}
- for _, v := range versions {
- validity := Validity(v.String())
- timetableHome := filepath.Join(feedHome, string(validity))
- cIx, err := unmarshalCodeIndex(timetableHome)
- if err != nil {
- return codeIndex, nameIndex, lineIndex, tripIndex,
- fmt.Errorf("while unmarshalling code index: %w\n", err)
- }
- nIx, err := unmarshalStopNameIndex(timetableHome)
- if err != nil {
- return codeIndex, nameIndex, lineIndex, tripIndex,
- fmt.Errorf("while unmarshalling name index: %w\n", err)
- }
- lIx, err := unmarshalLineIndex(timetableHome)
- if err != nil {
- return codeIndex, nameIndex, lineIndex, tripIndex,
- fmt.Errorf("while unmarshalling line index: %w\n", err)
- }
- tIx, err := unmarshalTripIndex(timetableHome)
- if err != nil {
- return codeIndex, nameIndex, lineIndex, tripIndex,
- fmt.Errorf("while unmarshalling trip index: %w\n", err)
- }
- codeIndex[validity] = cIx
- nameIndex[validity] = nIx
- lineIndex[validity] = lIx
- tripIndex[validity] = tIx
- }
- return codeIndex, nameIndex, lineIndex, tripIndex, nil
- }
- func unmarshalCalendar(timetableHome string) ([]Schedule, error) {
- calendar := []Schedule{}
- calendarFile, err := os.Open(filepath.Join(timetableHome, "calendar.bare"))
- if err != nil {
- return calendar, fmt.Errorf("while opening file: %w", err)
- }
- defer calendarFile.Close()
- for err == nil {
- schedule := Schedule{}
- err = bare.UnmarshalReader(calendarFile, &schedule)
- if err != nil {
- if err == io.EOF {
- break
- } else {
- return calendar, fmt.Errorf("while unmarshaling: %w", err)
- }
- }
- calendar = append(calendar, schedule)
- }
- return calendar, nil
- }
- func readCalendar(feedHome string, versions []Version) (FeedCalendar, error) {
- calendars := FeedCalendar{}
- for _, v := range versions {
- validity := Validity(v.String())
- timetableHome := filepath.Join(feedHome, string(validity))
- schedule, err := unmarshalCalendar(timetableHome)
- if err != nil {
- return calendars, fmt.Errorf("while unmarshaling for %s: %w", v, err)
- }
- calendars[validity] = schedule
- }
- return calendars, nil
- }
- func unmarshalVehicles(timetableHome string) (Vehicles, error) {
- vehicles := Vehicles{}
- vehiclesFile, err := os.Open(filepath.Join(timetableHome, "vehicles.bare"))
- if err != nil {
- return vehicles, fmt.Errorf("while opening file: %w", err)
- }
- defer vehiclesFile.Close()
- for err == nil {
- vehicle := Vehicle{}
- err = bare.UnmarshalReader(vehiclesFile, &vehicle)
- if err != nil {
- if err == io.EOF {
- break
- } else {
- return vehicles, fmt.Errorf("while unmarshaling: %w", err)
- }
- }
- vehicles[vehicle.Id] = vehicle
- }
- return vehicles, nil
- }
- func readVehicles(feedHome string, versions []Version) (FeedVehicles, error) {
- vehicles := FeedVehicles{}
- for _, v := range versions {
- validity := Validity(v.String())
- timetableHome := filepath.Join(feedHome, string(validity))
- versionVehicles, err := unmarshalVehicles(timetableHome)
- if err != nil {
- return vehicles, fmt.Errorf("while unmarshaling for %s: %w", v, err)
- }
- vehicles[validity] = versionVehicles
- }
- return vehicles, nil
- }
- func createPositionIndex(feedHome string, versions []Version) (FeedPositionIndex, error) {
- feedPositionIndex := FeedPositionIndex{}
- for _, v := range versions {
- positionIndex := rtreego.NewTree(2, 25, 50)
- validity := Validity(v.String())
- timetableHome := filepath.Join(feedHome, string(validity))
- stopsFile, err := os.Open(filepath.Join(timetableHome, "stops.bare"))
- if err != nil {
- return feedPositionIndex, fmt.Errorf("while opening stops file: %w", err)
- }
- defer stopsFile.Close()
- for err == nil {
- stop := Stop{}
- err = bare.UnmarshalReader(stopsFile, &stop)
- if err != nil {
- if err == io.EOF {
- break
- } else {
- return feedPositionIndex, fmt.Errorf("while unmarshaling: %w", err)
- }
- }
- stop.Name = ""
- stop.ChangeOptions = nil
- stop.Zone = ""
- stop.Order = nil
- positionIndex.Insert(stop)
- feedPositionIndex[validity] = positionIndex
- }
- }
- return feedPositionIndex, nil
- }
- func unmarshalTripFromFile(tripsFile *os.File) Trip {
- trip := Trip{}
- _ = bare.UnmarshalReader(tripsFile, &trip)
- return trip
- }
- func EnableFeeds(cfg config.Config, traffic *Traffic) {
- feedsMap := RegisterFeeds()
- feeds := map[string]Feed{}
- for _, enabledFeed := range cfg.EnabledFeeds {
- feeds[enabledFeed] = feedsMap[enabledFeed]
- }
- traffic.Feeds = feeds
- }
- func Initialise(sigChan chan os.Signal, doneChan chan bool, cfg config.Config,
- traffic *Traffic) {
- for {
- sig := <-sigChan
- if sig == os.Interrupt {
- break
- }
- allVersions := GlobalVersions{}
- codeIndexes := GlobalCodeIndex{}
- nameIndexes := GlobalNameIndex{}
- lineIndexes := GlobalNameIndex{}
- tripIndexes := GlobalNameIndex{}
- calendars := GlobalCalendar{}
- vehicles := GlobalVehicles{}
- positionIndexes := GlobalPositionIndex{}
- for _, feed := range traffic.Feeds {
- feedHome := filepath.Join(cfg.FeedsPath, feed.String())
- err := file.UnpackTraffic(cfg.FeedsPath, feed.String())
- if err != nil {
- log.Printf("while unpacking TRAFFIC in feed %s: %v\n", feed, err)
- continue
- }
- err = CleanOldVersions(cfg, feed)
- if err != nil {
- log.Printf("while cleaning old TRAFFIC versions in feed %s: %v\n",
- feed, err)
- continue
- }
- feedVersions, err := ListVersions(cfg, feed)
- if err != nil {
- log.Printf("while listing TRAFFIC versions in feed %s: %v\n", feed,
- err)
- continue
- }
- feedName := feed.String()
- allVersions[feedName] = feedVersions
- codeIndexes[feedName], nameIndexes[feedName], lineIndexes[feedName], tripIndexes[feedName],
- err = readIndexes(feedHome, feedVersions)
- if err != nil {
- log.Printf("while reading indexes in feed %s: %v\n", feed, err)
- continue
- }
- calendars[feedName], err = readCalendar(feedHome, feedVersions)
- if err != nil {
- log.Printf("while reading calendars in feed %s: %v\n", feed, err)
- continue
- }
- vehicles[feedName], err = readVehicles(feedHome, feedVersions)
- if err != nil {
- log.Printf("while reading vehicles in feed %s: %v\n", feed, err)
- continue
- }
- positionIndexes[feedName], err = createPositionIndex(feedHome, feedVersions)
- }
- traffic.CodeIndexes = codeIndexes
- traffic.NameIndexes = nameIndexes
- traffic.LineIndexes = lineIndexes
- traffic.TripIndexes = tripIndexes
- traffic.Versions = allVersions
- traffic.Calendars = calendars
- traffic.Vehicles = vehicles
- traffic.PositionIndexes = positionIndexes
- log.Println("Initialised")
- }
- doneChan <- true
- }
- func GetDepartures(stopCode ID, lineName, dataHome, feedName string,
- versionCode Validity, traffic *Traffic, date time.Time,
- departuresType DeparturesType) ([]DepartureRealtime, error) {
- codeIndex := traffic.CodeIndexes[feedName][versionCode]
- calendar := traffic.Calendars[feedName][versionCode]
- vehicles := traffic.Vehicles[feedName][versionCode]
- result := _Result{
- Offset: codeIndex[stopCode],
- Filename: "stops.bare",
- Date: date,
- LineName: lineName,
- TimetableHome: filepath.Join(dataHome, feedName, string(versionCode)),
- Calendar: calendar,
- DeparturesType: departuresType,
- Vehicles: vehicles,
- Feed: traffic.Feeds[feedName],
- }
- r, e := gott.NewResult(result).
- Bind(loadLocation).
- Map(loadTime).
- Bind(loadTodaySchedule).
- Bind(loadYesterdaySchedule).
- Recover(recoverYesterdaySchedule).
- Bind(openFile).
- Bind(seek).
- Bind(unmarshalStop).
- Bind(openTripsFile).
- Bind(readTrips).
- Bind(getUpdates).
- Map(filterDepartures).
- Map(filterDeparturesByLine).
- Map(sortDepartures).
- Recover(closeFiles).
- Finish()
- if e != nil {
- return []DepartureRealtime{}, e
- } else {
- return r.(_Result).Departures, nil
- }
- }
- func GetTripFromStop(tripID string, stopCode ID, context Context, traffic *Traffic) ([]TimedStopStub, error) {
- stubs := []TimedStopStub{}
- var (
- order = -1
- trip Trip
- err error
- baseTime uint = 0
- time uint = 0
- )
- if stopCode != "" {
- startingStop, err := GetStop(stopCode, context, traffic)
- if err != nil {
- return stubs, fmt.Errorf("while getting starting stop: %w", err)
- }
- tripOffset := -1
- order = -1
- for _, o := range startingStop.Order {
- if o.TripID == tripID {
- tripOffset = int(o.TripOffset)
- order = o.Order
- }
- }
- if tripOffset == -1 {
- return stubs, fmt.Errorf("trip for starting stop not found")
- }
- trip, err = GetTripByOffset(uint(tripOffset), context, traffic)
- if err != nil {
- return stubs, fmt.Errorf("while getting trip: %w", err)
- }
- } else {
- trip, err = GetTrip(tripID, context, traffic)
- if err != nil {
- return stubs, fmt.Errorf("while getting trip: %w", err)
- }
- }
- for _, departure := range trip.Departures {
- if departure.StopSeq >= order {
- stop, err := getStopByOffset(uint(departure.StopOffset), context, traffic)
- if err != nil {
- return stubs, fmt.Errorf("while getting stop: %w", err)
- }
- if baseTime != 0 {
- time = departure.Time - baseTime
- }
- stubs = append(stubs, TimedStopStub{
- StopStub: StopStub{
- Code: stop.Code,
- Name: stop.Name,
- Zone: stop.Zone,
- OnDemand: departure.Pickup == 3, // todo BAF10
- },
- Time: time,
- })
- }
- }
- return stubs, nil
- }
- 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)),
- }
- r, e := gott.NewResult(result).
- Bind(openFile).
- Bind(seek).
- Bind(unmarshalStop).
- Finish()
- if e != nil {
- return Stop{}, e
- } else {
- return r.(_Result).Stop, nil
- }
- }
- func getLineByOffset(offset uint, dataHome string, feedName string,
- versionCode Validity, traffic *Traffic) (Line, error) {
- result := _Result{
- Filename: "lines.bare",
- Offset: offset,
- TimetableHome: filepath.Join(dataHome, feedName, string(versionCode)),
- }
- r, e := gott.NewResult(result).
- Bind(openFile).
- Bind(seek).
- Bind(unmarshalLine).
- Finish()
- if e != nil {
- return Line{}, e
- } else {
- return r.(_Result).Line, nil
- }
- }
- func getFeedInfo(dataHome string, feedName string,
- versionCode Validity, traffic *Traffic) (FeedInfo, error) {
- result := _Result{
- Filename: "feed_info.bare",
- TimetableHome: filepath.Join(dataHome, feedName, string(versionCode)),
- }
- r, e := gott.NewResult(result).
- Bind(openFile).
- Bind(unmarshalFeedInfo).
- Finish()
- if e != nil {
- return FeedInfo{}, e
- } else {
- return r.(_Result).FeedInfo, nil
- }
- }
- 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)),
- }
- r, e := gott.NewResult(result).
- Bind(openFile).
- Bind(seek).
- Bind(unmarshalTrip).
- Finish()
- if e != nil {
- return Trip{}, e
- } else {
- return r.(_Result).Trip, nil
- }
- }
- func GetStop(stopCode ID, context Context, traffic *Traffic) (Stop, error) {
- codeIndex := traffic.CodeIndexes[context.FeedName][context.Version]
- return getStopByOffset(codeIndex[stopCode], context, traffic)
- }
- func GetStopStub(stopCode ID, lineName string, context Context, traffic *Traffic) (StopStub, error) {
- stop, err := GetStop(stopCode, context, traffic)
- if err != nil {
- return StopStub{}, err
- }
- var trip Trip
- var stopOrder = -1
- for _, order := range stop.Order {
- offset := order.TripOffset
- trip, _ = GetTripByOffset(offset, context, traffic)
- if trip.LineName == lineName {
- stopOrder = order.Order
- break
- }
- }
- stopStub := StopStub{
- Code: stop.Code,
- Name: stop.Name,
- Zone: stop.Zone,
- OnDemand: trip.Departures[stopOrder].Pickup == 3, // todo BAF10
- }
- return stopStub, nil
- }
- func GetLine(name string, context Context, traffic *Traffic) (Line, error) {
- index := traffic.LineIndexes[context.FeedName][context.Version]
- for _, o := range index {
- if o.Name == name {
- return getLineByOffset(o.Offsets[0], context.DataHome, context.FeedName, context.Version, traffic)
- }
- }
- return Line{}, nil
- }
- func GetTrip(id string, context Context, traffic *Traffic) (Trip, error) {
- tripIndex := traffic.TripIndexes[context.FeedName][context.Version]
- for _, o := range tripIndex {
- if o.Name == id {
- return GetTripByOffset(o.Offsets[0], context, traffic)
- }
- }
- return Trip{}, fmt.Errorf("trip by id %s not found", id)
- }
- func QueryLines(query string, dataHome string, feedName string,
- versionCode Validity, traffic *Traffic) ([]Line, error) {
- lines := []Line{}
- index := traffic.LineIndexes[feedName][versionCode]
- cleanQuery, err := CleanQuery(query, traffic.Feeds[feedName])
- if err != nil {
- return lines, fmt.Errorf("while cleaning query: %w", err)
- }
- results := fuzzy.FindFrom(cleanQuery, index)
- for _, result := range results {
- for _, offset := range index[result.Index].Offsets {
- line, err := getLineByOffset(offset, dataHome, feedName,
- versionCode, traffic)
- if err != nil {
- return lines, fmt.Errorf("while getting line for %s: %w", result.Str, err)
- }
- lines = append(lines, line)
- }
- }
- return lines, nil
- }
- func QueryStops(query string, context Context, traffic *Traffic) ([]Stop, error) {
- stops := []Stop{}
- nameIndex := traffic.NameIndexes[context.FeedName][context.Version]
- results := fuzzy.FindFrom(query, nameIndex)
- for _, result := range results {
- for _, offset := range nameIndex[result.Index].Offsets {
- stop, err := getStopByOffset(offset, context, traffic)
- if err != nil {
- return stops, err
- }
- stops = append(stops, stop)
- }
- }
- return stops, nil
- }
- 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]
- spatials := positionIndex.NearestNeighbors(12, rtreego.Point{location.Lat, location.Lon})
- for _, spatial := range spatials {
- stop, err := getStopByOffset(codeIndex[ID(spatial.(Stop).Code)], context, traffic)
- if err != nil {
- return stops, fmt.Errorf("while getting stop by offset for %s: %w", spatial.(Stop).Code, err)
- }
- stops = append(stops, stop)
- }
- return stops, nil
- }
- func GetAlerts(stopCode string, preferredLanguages []language.Tag, context Context, traffic *Traffic) ([]Alert, error) {
- feed := traffic.Feeds[context.FeedName]
- 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(ID(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)
- 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.Type] {
- 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]
- 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
- }
- }
- 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)
- }
- 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]
- 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]
- i++
- }
- return alerts, nil
- }
- func GetTimezone(stop Stop, feed Feed) (*time.Location, error) {
- if stop.Timezone != "" {
- return time.LoadLocation(stop.Timezone)
- }
- return feed.GetLocation(), nil
- }
- func GetLanguage(ctx Context, t *Traffic) (string, error) {
- feedInfo, err := getFeedInfo(ctx.DataHome, ctx.FeedName, ctx.Version, t)
- return feedInfo.Language, err
- }
- func CleanOldVersions(cfg config.Config, feed Feed) error {
- allVersions, err := ListVersions(cfg, feed)
- if err != nil {
- return err
- }
- now := time.Now().In(feed.GetLocation())
- versionsMap := map[string]Version{}
- for _, version := range allVersions {
- versionsMap[version.String()] = version
- }
- sort.Slice(allVersions, func(i, j int) bool {
- return allVersions[i].ValidFrom.Before(allVersions[j].ValidFrom)
- })
- validVersions := FindValidVersions(allVersions, now)
- validVersionsMap := map[string]bool{}
- for _, version := range validVersions {
- validVersionsMap[version.String()] = true
- }
- err = file.CleanOldVersions(FeedPath(cfg, feed), validVersionsMap)
- return err
- }
- func convertVehicle(update gtfs_rt.Update, context Context, t *Traffic) (Vehicle, error) {
- vehicles := t.Vehicles[context.FeedName][context.Version]
- tripID := update.TripUpdate.GetTrip().GetTripId()
- trip, err := GetTrip(tripID, context, t)
- if err != nil {
- return Vehicle{}, fmt.Errorf("while converting vehicle: %w", err)
- }
- return Vehicle{
- Id: ID(update.VehicleID),
- Coordinates: Position{float64(update.Latitude), float64(update.Longitude)},
- Capabilities: vehicles[ID(update.VehicleID)].Capabilities,
- Speed: update.Speed,
- Headsign: trip.Headsign,
- LineName: trip.LineName,
- }, nil
- }
- func GetVehicle(tripID string, context Context, t *Traffic) (Vehicle, error) {
- vehicle := Vehicle{}
- update, err := getRealtimeOffset(tripID, 0, t.Feeds[context.FeedName])
- if err != nil {
- return vehicle, fmt.Errorf("while getting realtime update: %w", err)
- }
- if update.TripUpdate == nil {
- return vehicle, fmt.Errorf("empty realtime update")
- }
- return convertVehicle(update, context, t)
- }
- 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]
- 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)
- }
- spatials := positionIndex.SearchIntersect(rect)
- for _, spatial := range spatials {
- stop, err := getStopByOffset(codeIndex[ID(spatial.(Stop).Code)], context, traffic)
- if err != nil {
- return stops, fmt.Errorf("while getting stop by offset for %s: %w", spatial.(Stop).Code, err)
- }
- stops = append(stops, stop)
- }
- return stops, nil
- }
- func GetVehiclesIn(lb, rt Position, context Context, t *Traffic) ([]Vehicle, error) {
- // todo limit rect size
- vehicles := []Vehicle{}
- updates, err := getRealtimeUpdates(t.Feeds[context.FeedName])
- if err != nil {
- return vehicles, err
- }
- for _, update := range updates {
- if rt.Lon < float64(update.Longitude) || lb.Lon > float64(update.Longitude) {
- continue
- }
- lat := float64(update.Latitude)
- if lb.Lat < rt.Lat {
- if lb.Lat < lat && lat < rt.Lat {
- vehicle, err := convertVehicle(update, context, t)
- if err != nil {
- return vehicles, fmt.Errorf("while converting vehicle: %w", err)
- }
- vehicles = append(vehicles, vehicle)
- }
- } else {
- if lat > lb.Lat || lat < rt.Lat {
- vehicle, err := convertVehicle(update, context, t)
- if err != nil {
- return vehicles, fmt.Errorf("while converting vehicle: %w", err)
- }
- vehicles = append(vehicles, vehicle)
- }
- }
- }
- return vehicles, nil
- }
|