12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232 |
- // SPDX-FileCopyrightText: Adam Evyčędo
- //
- // SPDX-License-Identifier: AGPL-3.0-or-later
- package traffic
- import (
- "apiote.xyz/p/szczanieckiej/config"
- "apiote.xyz/p/szczanieckiej/file"
- traffic_errors "apiote.xyz/p/szczanieckiej/traffic/errors"
- "apiote.xyz/p/szczanieckiej/transformers"
- "errors"
- "fmt"
- "io"
- "log"
- "net"
- "os"
- "path/filepath"
- "sort"
- "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
- LineID string
- TimetableHome string
- Calendar []Schedule
- DeparturesType DeparturesType
- Vehicles Vehicles
- Feed Feed
- Ctx Context
- Traffic *Traffic
- Languages []language.Tag
- Location *time.Location
- Datetime time.Time
- MinuteB4Datetime time.Time
- TodaySchedule map[string]struct{}
- YesterdaySchedule map[string]struct{}
- file *os.File
- TripsFile *os.File
- Trips map[string]Trip
- Departures []DepartureRealtime
- Stop Stop
- Line Line
- Trip Trip
- FeedInfo FeedInfo
- }
- 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) (map[string]struct{},
- error) {
- schedules := map[string]struct{}{}
- weekday := uint8(1 << time.Weekday())
- date := time.Format(DateFormat)
- for _, schedule := range calendar {
- for _, dateRange := range schedule.DateRanges {
- if dateRange.Start <= date && date <= dateRange.End &&
- (dateRange.Weekdays&weekday != 0) {
- schedules[schedule.Id] = struct{}{}
- break
- }
- }
- }
- var err error
- if len(schedules) == 0 {
- err = traffic_errors.NoSchedule{Date: date}
- }
- return schedules, err
- }
- func calculateGtfsTime(gtfsTime uint, delay int32, date time.Time,
- timezone *time.Location) time.Time {
- noon := time.Date(date.Year(), date.Month(), date.Day(), 12, 0, 0, 0,
- timezone)
- 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) {
- result := input[0].(_Result)
- var err error = nil
- result.Location, err = GetTimezone(result.Stop, result.Traffic, result.Ctx.FeedID)
- return result, err
- }
- func loadTime(input ...interface{}) interface{} {
- result := input[0].(_Result)
- now := time.Now()
- datetime := time.Date(result.Date.Year(), result.Date.Month(),
- result.Date.Day(), now.Hour(), now.Minute(), now.Second(), 0, now.Location()).In(result.Location)
- result.Datetime = datetime
- result.MinuteB4Datetime = datetime.Add(time.Duration(-1) * time.Minute)
- 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 = map[string]struct{}{}
- 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 := map[string]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
- }
- _, inToday := result.TodaySchedule[trip.ScheduleID]
- _, inYesterday := result.YesterdaySchedule[trip.ScheduleID]
- if inToday || inYesterday {
- trips[trip.Id] = trip
- orders[trip.Id] = order
- }
- }
- result.Stop.Order = orders
- result.Trips = trips
- return result, nil
- }
- func getDepartures(input ...interface{}) (interface{}, error) {
- result := input[0].(_Result)
- departures := []DepartureRealtime{}
- timedOut := false
- for tripID, order := range result.Stop.Order {
- trip := result.Trips[tripID]
- var date time.Time
- if _, ok := result.TodaySchedule[trip.ScheduleID]; ok {
- date = result.Date
- } else if _, ok := result.YesterdaySchedule[trip.ScheduleID]; ok {
- 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
- return result, nil
- }
- func makeDeparturesRealtime(input ...interface{}) (interface{}, error) {
- result := input[0].(_Result)
- departures, err := enrichDepartures(result.Stop.Id, result.Stop.Code, result.Departures, result.Datetime, result.DeparturesType, result.Ctx, result.TripsFile, result.Location, result.Languages)
- result.TripsFile.Close()
- result.Departures = departures
- return result, err
- }
- func addAlerts(input ...interface{}) interface{} {
- result := input[0].(_Result)
- alertedDepartures := make([]DepartureRealtime, len(result.Departures))
- for i, d := range result.Departures {
- if len(d.Alerts) == 0 {
- d.Alerts = GetAlerts("", "", int(d.Order.TripOffset), result.Ctx, result.Traffic, result.Languages)
- }
- alertedDepartures[i] = d
- }
- result.Departures = alertedDepartures
- return result
- }
- 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.StopSequence == order.Sequence {
- departureRt.Departure = departure
- departureRt.Headsign = trip.Headsign
- departureRt.LineID = trip.LineID
- departureRt.Order = order
- departureRt.Update = Update{}
- departureRt.Time = calculateGtfsTime(departure.Time, 0, date,
- result.Location)
- found = true
- break
- }
- }
- if !found {
- return departureRt, traffic_errors.NoStopOrder{
- TripID: trip.Id,
- Order: order.Sequence,
- }
- }
- return departureRt, finalErr
- }
- func GetTimeWithDelay(departure DepartureRealtime) time.Time {
- if departure.Update.TimeUTC != "" {
- updateTimeUTC, err := time.Parse("150405", departure.Update.Time)
- if err != nil {
- panic("departure update time ‘" + departure.Update.Time + "’ not in format 150405")
- }
- updateTime := time.Date(departure.Time.Year(), departure.Time.Month(), departure.Time.Day(), updateTimeUTC.Hour(), updateTimeUTC.Minute(), updateTimeUTC.Second(), 0, time.UTC)
- return updateTime.In(departure.Time.Location())
- } else if departure.Update.Time != "" {
- updateTime, err := time.Parse("150405", departure.Update.Time)
- if err != nil {
- panic("departure update time ‘" + departure.Update.Time + "’ not in format 150405")
- }
- updateDateTime := time.Date(departure.Time.Year(), departure.Time.Month(), departure.Time.Day(), updateTime.Hour(), updateTime.Minute(), updateTime.Second(), 0, departure.Time.Location())
- return updateDateTime
- } else {
- delay := int(departure.Update.Delay)
- return departure.Time.Add(time.Duration(delay) * time.Second)
- }
- }
- func filterDepartures(input ...interface{}) interface{} {
- result := input[0].(_Result)
- departures := []DepartureRealtime{}
- midnight := result.Date
- for _, departure := range result.Departures {
- if (result.DeparturesType == DEPARTURES_FULL && GetTimeWithDelay(departure).After(midnight)) || (result.DeparturesType == DEPARTURES_HYBRID && GetTimeWithDelay(departure).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.LineID != "" {
- for _, departure := range result.Departures {
- if departure.LineID == result.LineID {
- 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 GetTimeWithDelay(result.Departures[i]).Before(GetTimeWithDelay(result.Departures[j]))
- })
- 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, filename string) (CodeIndex, error) {
- ix := CodeIndex{}
- ixFile, err := os.Open(filepath.Join(timetableHome, filename))
- if err != nil {
- return ix, fmt.Errorf("while opening file: %w", err)
- }
- defer ixFile.Close()
- r := bare.NewReader(ixFile)
- num, err := r.ReadUint()
- if err != nil {
- return ix, fmt.Errorf("while reading length: %w", 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", i, err)
- }
- v, err := r.ReadUint()
- if err != nil {
- return ix, fmt.Errorf("while reading value at %d: %w", i, err)
- }
- ix[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 unmarshalStopCodeIndex(timetableHome string) (CodeIndex, error) {
- return unmarshalCodeIndex(timetableHome, "ix_stop_codes.bare")
- }
- func unmarshalLineCodeIndex(timetableHome string) (CodeIndex, error) {
- return unmarshalCodeIndex(timetableHome, "ix_line_codes.bare")
- }
- 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, FeedCodeIndex, FeedNameIndex, error) {
- codeIndex := FeedCodeIndex{}
- nameIndex := FeedNameIndex{}
- lineIndex := FeedNameIndex{}
- tripIndex := FeedNameIndex{}
- lineIdIndex := FeedCodeIndex{}
- for _, v := range versions {
- validity := Validity(v.String())
- timetableHome := filepath.Join(feedHome, string(validity))
- cIx, err := unmarshalStopCodeIndex(timetableHome)
- if err != nil {
- return codeIndex, nameIndex, lineIndex, lineIdIndex, tripIndex,
- fmt.Errorf("while unmarshalling code index: %w", err)
- }
- liIx, err := unmarshalLineCodeIndex(timetableHome)
- if err != nil {
- return codeIndex, nameIndex, lineIndex, lineIdIndex, tripIndex,
- fmt.Errorf("while unmarshalling code index: %w", err)
- }
- nIx, err := unmarshalStopNameIndex(timetableHome)
- if err != nil {
- return codeIndex, nameIndex, lineIndex, lineIdIndex, tripIndex,
- fmt.Errorf("while unmarshalling name index: %w", err)
- }
- lIx, err := unmarshalLineIndex(timetableHome)
- if err != nil {
- return codeIndex, nameIndex, lineIndex, lineIdIndex, tripIndex,
- fmt.Errorf("while unmarshalling line index: %w", err)
- }
- tIx, err := unmarshalTripIndex(timetableHome)
- if err != nil {
- return codeIndex, nameIndex, lineIndex, lineIdIndex, tripIndex,
- fmt.Errorf("while unmarshalling trip index: %w", err)
- }
- codeIndex[validity] = cIx
- nameIndex[validity] = nIx
- lineIndex[validity] = lIx
- lineIdIndex[validity] = liIx
- tripIndex[validity] = tIx
- }
- return codeIndex, nameIndex, lineIndex, lineIdIndex, 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.NodeName = ""
- 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 {
- if _, ok := feedsMap[enabledFeed]; !ok {
- log.Printf("feed %s not registered, ignoring\n", enabledFeed)
- continue
- }
- feeds[enabledFeed] = feedsMap[enabledFeed]
- }
- traffic.Feeds = feeds
- }
- func Initialise(sigChan chan os.Signal, doneChan chan bool, initedChan chan bool, cfg config.Config,
- traffic *Traffic) {
- bare.MaxMapSize(8192)
- alreadyInitialised := false
- for {
- sig := <-sigChan
- if sig == os.Interrupt {
- break
- } // else it's SIGUSR1, reloading config
- allVersions := GlobalVersions{}
- codeIndexes := GlobalCodeIndex{}
- nameIndexes := GlobalNameIndex{}
- lineIndexes := GlobalNameIndex{}
- lineIdIndexes := GlobalCodeIndex{}
- tripIndexes := GlobalNameIndex{}
- calendars := GlobalCalendar{}
- vehicles := GlobalVehicles{}
- positionIndexes := GlobalPositionIndex{}
- feedInfos := map[Validity]map[string]FeedInfo{}
- for _, feed := range traffic.Feeds {
- feedID := feed.String()
- feedHome := filepath.Join(cfg.FeedsPath, feedID)
- err := file.UnpackTraffic(cfg.FeedsPath, feedID)
- if err != nil {
- log.Printf("while unpacking TRAFFIC in feed %s: %v\n", feed, err)
- continue
- }
- feedValidities, err := ListVersions(cfg, feed)
- if err != nil {
- log.Printf("while listing TRAFFIC versions in feed %s: %v\n", feed, err)
- continue
- }
- for _, version := range feedValidities {
- feedInfo, err := getFeedInfo(cfg.FeedsPath, feedID, Validity(version))
- if err != nil {
- log.Printf("while getting feed info for feed %s, version %s: %v\n", feed, version, err)
- continue
- }
- if feedInfos[Validity(feedInfo.ValidSince+"_"+feedInfo.ValidTill)] == nil {
- feedInfos[Validity(feedInfo.ValidSince+"_"+feedInfo.ValidTill)] = map[string]FeedInfo{}
- }
- feedInfos[Validity(feedInfo.ValidSince+"_"+feedInfo.ValidTill)][feedID] = feedInfo
- }
- traffic.FeedInfos = feedInfos
- feedVersions, deletedValidities, err := CleanOldVersions(cfg, feed, traffic, feedID, feedValidities)
- if err != nil {
- log.Printf("while cleaning old TRAFFIC versions in feed %s: %v\n",
- feed, err)
- continue
- }
- for _, deletedVersion := range deletedValidities {
- delete(feedInfos, Validity(deletedVersion))
- }
- allVersions[feedID] = feedVersions
- codeIndexes[feedID], nameIndexes[feedID], lineIndexes[feedID], lineIdIndexes[feedID], tripIndexes[feedID],
- err = readIndexes(feedHome, feedVersions)
- if err != nil {
- log.Printf("while reading indexes in feed %s: %v\n", feed, err)
- continue
- }
- calendars[feedID], err = readCalendar(feedHome, feedVersions)
- if err != nil {
- log.Printf("while reading calendars in feed %s: %v\n", feed, err)
- continue
- }
- vehicles[feedID], err = readVehicles(feedHome, feedVersions)
- if err != nil {
- log.Printf("while reading vehicles in feed %s: %v\n", feed, err)
- continue
- }
- positionIndexes[feedID], err = createPositionIndex(feedHome, feedVersions)
- if err != nil {
- log.Printf("while creating position index in feed %s: %v\n", feed, err)
- continue
- }
- }
- traffic.CodeIndexes = codeIndexes
- traffic.NameIndexes = nameIndexes
- traffic.LineIndexes = lineIndexes
- traffic.LineIdIndexes = lineIdIndexes
- traffic.TripIndexes = tripIndexes
- traffic.Versions = allVersions
- traffic.Calendars = calendars
- traffic.Vehicles = vehicles
- traffic.PositionIndexes = positionIndexes
- traffic.FeedInfos = feedInfos
- log.Println("Initialised")
- if !alreadyInitialised {
- initedChan <- true
- }
- }
- doneChan <- true
- }
- func GetDepartures(stopCode, lineID string, ctx Context, traffic *Traffic, date time.Time,
- departuresType DeparturesType, languages []language.Tag) ([]DepartureRealtime, error) {
- 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,
- LineID: lineID,
- TimetableHome: filepath.Join(ctx.DataHome, ctx.FeedID, string(ctx.Version)),
- Calendar: calendar,
- DeparturesType: departuresType,
- Vehicles: vehicles,
- Feed: traffic.Feeds[ctx.FeedID],
- Ctx: ctx,
- Traffic: traffic,
- Languages: languages,
- }
- 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(getDepartures).
- Bind(makeDeparturesRealtime).
- Map(addAlerts).
- 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 string, 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
- o := startingStop.Order[tripID]
- tripOffset = int(o.TripOffset)
- order = o.Sequence
- 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.StopSequence >= 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,
- NodeName: stop.NodeName,
- Zone: stop.Zone,
- OnDemand: departure.Pickup == BY_DRIVER || departure.Dropoff == BY_DRIVER,
- },
- 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.FeedID, 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) (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) (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 GetTrips(ids []string, ctx Context, t *Traffic) (map[string]Trip, error) { // TODO optimise
- trips := map[string]Trip{}
- e := []error{}
- for _, id := range ids {
- trip, err := GetTrip(id, ctx, t)
- if err != nil {
- e = append(e, err)
- } else {
- trips[trip.Id] = trip
- }
- }
- return trips, errors.Join(e...)
- }
- func GetTripsByOffset(offsets []uint, context Context, filter func(Trip) bool) (map[uint]Trip, error) {
- trips := map[uint]Trip{}
- 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)
- }
- defer file.Close()
- offsetsSet := map[uint]struct{}{}
- for _, offset := range offsets {
- offsetsSet[offset] = struct{}{}
- }
- for offset := range offsetsSet {
- _, err = file.Seek(int64(offset), 0)
- if err != nil {
- return trips, fmt.Errorf("while seeking to %d: %w", offset, err)
- }
- trip := Trip{}
- err = bare.UnmarshalReader(file, &trip)
- if err != nil {
- return trips, fmt.Errorf("while unmarshalling at %d: %w", offset, err)
- }
- if filter(trip) {
- trips[offset] = trip
- }
- }
- return trips, nil
- }
- func GetTripByOffset(offset uint, context Context, t *Traffic) (Trip, error) {
- result := _Result{
- Filename: "trips.bare",
- Offset: offset,
- TimetableHome: filepath.Join(context.DataHome, context.FeedID, 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 string, context Context, traffic *Traffic) (Stop, error) {
- codeIndex := traffic.CodeIndexes[context.FeedID][context.Version]
- return getStopByOffset(codeIndex[stopCode], context, traffic)
- }
- func GetStopStub(stopCode string, lineID 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.LineID == lineID {
- stopOrder = order.Sequence
- break
- }
- }
- if stopOrder == -1 {
- return StopStub{}, fmt.Errorf("cannot the stop on given line")
- }
- var departure *Departure
- for _, d := range trip.Departures {
- if d.StopSequence == stopOrder { // todo binary search
- departure = &d
- break
- }
- }
- if departure == nil {
- return StopStub{}, fmt.Errorf("cannot find departure at sequence %d", stopOrder)
- }
- stopStub := StopStub{
- Code: stop.Code,
- Name: stop.Name,
- NodeName: stop.NodeName,
- Zone: stop.Zone,
- OnDemand: departure.Pickup == BY_DRIVER || departure.Dropoff == BY_DRIVER,
- }
- return stopStub, nil
- }
- func GetLine(id string, context Context, traffic *Traffic) (Line, error) {
- index := traffic.LineIdIndexes[context.FeedID][context.Version]
- return getLineByOffset(index[id], context.DataHome, context.FeedID, context.Version)
- }
- func GetLineOld(name string, context Context, traffic *Traffic) (Line, error) {
- index := traffic.LineIndexes[context.FeedID][context.Version]
- for _, o := range index {
- 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.FeedID, context.Version)
- }
- }
- return Line{}, nil
- }
- func GetTrip(id string, context Context, traffic *Traffic) (Trip, error) {
- tripIndex := traffic.TripIndexes[context.FeedID][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) {
- linesSet := map[string]Line{}
- index := traffic.LineIndexes[feedName][versionCode]
- cleanQuery, err := CleanQuery(query, traffic.Feeds[feedName])
- if err != nil {
- return []Line{}, 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)
- if err != nil {
- return []Line{}, fmt.Errorf("while getting line for %s: %w", result.Str, err)
- }
- linesSet[line.Id] = line
- }
- }
- lines := make([]Line, len(linesSet))
- i := 0
- for _, line := range linesSet {
- lines[i] = line
- i++
- }
- return lines, nil
- }
- func QueryStops(query string, context Context, traffic *Traffic) ([]Stop, error) {
- stopsSet := map[string]Stop{}
- nameIndex := traffic.NameIndexes[context.FeedID][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 []Stop{}, err
- }
- stopsSet[stop.Id] = stop
- }
- }
- stops := make([]Stop, len(stopsSet))
- i := 0
- for _, stop := range stopsSet {
- stops[i] = stop
- i++
- }
- return stops, nil
- }
- func GetStopsNear(location Position, context Context, traffic *Traffic) ([]Stop, error) {
- stops := []Stop{}
- 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)
- 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 GetLanguage(ctx Context) (string, error) {
- feedInfo, err := getFeedInfo(ctx.DataHome, ctx.FeedID, ctx.Version)
- return feedInfo.Language, err
- }
- func CleanOldVersions(cfg config.Config, feed Feed, t *Traffic, feedID string, allValidities []string) ([]Version, []string, error) {
- feedVersions := []Version{}
- deletedValidities := []string{}
- timezone, err := GetTimezone(Stop{}, t, feedID)
- if err != nil {
- return feedVersions, deletedValidities, fmt.Errorf("while getting timezone: %w", err)
- }
- now := time.Now().In(timezone)
- versionsMap := map[string]Version{}
- allVersions := []Version{}
- for _, validity := range allValidities {
- version, err := MakeVersionTimezone(validity, timezone)
- if err != nil {
- return feedVersions, deletedValidities, fmt.Errorf("while making version of %s: %w", version, err)
- }
- allVersions = append(allVersions, version)
- versionsMap[validity] = version
- }
- validVersions := FindValidVersions(allVersions, now)
- validVersionsMap := map[string]bool{}
- for _, version := range validVersions {
- validVersionsMap[version.String()] = true
- }
- err = file.CleanOldVersions(FeedPath(cfg, feed), validVersionsMap)
- if err != nil {
- return feedVersions, deletedValidities, fmt.Errorf("while removing files: %w", err)
- }
- for _, version := range validVersions {
- feedVersions = append(feedVersions, version)
- }
- for _, version := range allVersions {
- if _, ok := validVersionsMap[version.String()]; !ok {
- deletedValidities = append(deletedValidities, version.String())
- }
- }
- return feedVersions, deletedValidities, nil
- }
- func createSmallerRect(side float64, rect *rtreego.Rect) (*rtreego.Rect, Position, Position, error) {
- halfSide := side / 2
- latMid := rect.PointCoord(0) + (rect.LengthsCoord(0) / 2)
- lonMid := rect.PointCoord(1) + (rect.LengthsCoord(1) / 2)
- lb := Position{Lat: latMid - halfSide, Lon: lonMid - halfSide}
- rt := Position{Lat: latMid + halfSide, Lon: lonMid + halfSide}
- rect, err := rtreego.NewRectFromPoints(rtreego.Point{lb.Lat, lb.Lon}, rtreego.Point{rt.Lat, rt.Lon})
- return rect, lb, rt, err
- }
- func GetStopsIn(lb, rt Position, context Context, traffic *Traffic) ([]Stop, error) {
- limit := 0.0005
- side := 0.0224 // sqrt(0.0005)
- stops := []Stop{}
- // TODO does it take into account rect 179 -> -179 latitude?
- 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)
- }
- if rect.Size() > limit {
- rect, _, _, err = createSmallerRect(side, rect)
- if err != nil {
- return stops, fmt.Errorf("while creating the smaller rect: %w", err)
- }
- }
- positionIndex := traffic.PositionIndexes[context.FeedID][context.Version]
- codeIndex := traffic.CodeIndexes[context.FeedID][context.Version]
- spatials := positionIndex.SearchIntersect(rect)
- for _, spatial := range spatials {
- stop, err := getStopByOffset(codeIndex[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) ([]VehicleStatus, error) {
- limit := 0.0005
- side := 0.0224 // sqrt(0.0005)
- vehicles := []VehicleStatus{}
- rect, err := rtreego.NewRectFromPoints(rtreego.Point{lb.Lat, lb.Lon}, rtreego.Point{rt.Lat, rt.Lon})
- if err != nil {
- return vehicles, fmt.Errorf("while creating a rect: %w", err)
- }
- if rect.Size() > limit {
- rect, lb, rt, err = createSmallerRect(side, rect)
- if err != nil {
- return vehicles, fmt.Errorf("while creating the smaller rect: %w", err)
- }
- }
- vehiclesRt := getVehiclePositions(context, t, lb, rt)
- for _, vehicleRt := range vehiclesRt {
- if rt.Lon < float64(vehicleRt.Longitude) || lb.Lon > float64(vehicleRt.Longitude) {
- continue
- }
- lat := float64(vehicleRt.Latitude)
- if lb.Lat < rt.Lat {
- if lb.Lat < lat && lat < rt.Lat {
- vehicles = append(vehicles, vehicleRt)
- }
- } else {
- if lat > lb.Lat || lat < rt.Lat {
- vehicles = append(vehicles, vehicleRt)
- }
- }
- }
- return vehicles, nil
- }
|