package main // import ( // "sort" // "time" // "git.tornberg.me/go-gtfs/pkg/reader" // "git.tornberg.me/go-gtfs/pkg/types" // ) // // Connection represents a single leg of a trip between two stops. // type CSAConnection struct { // DepartureStopID string // ArrivalStopID string // DepartureTime types.SecondsAfterMidnight // ArrivalTime types.SecondsAfterMidnight // TripID string // } // // CSAPlanner uses the Connection Scan Algorithm for routing. // type CSAPlanner struct { // *reader.TripData // connections []CSAConnection // } // // NewCSAPlanner creates and preprocesses data for the Connection Scan Algorithm. // func NewCSAPlanner(data *reader.TripData) *CSAPlanner { // p := &CSAPlanner{ // TripData: data, // } // p.preprocess() // return p // } // // preprocess creates a sorted list of all connections. // func (p *CSAPlanner) preprocess() { // p.connections = make([]CSAConnection, 0) // for tripID, trip := range p.Trips { // sts := trip.Stops // sort.Slice(sts, func(i, j int) bool { // return sts[i].StopSequence < sts[j].StopSequence // }) // for i := 0; i < len(sts)-1; i++ { // from := sts[i] // to := sts[i+1] // if from.DepartureTime < to.ArrivalTime { // p.connections = append(p.connections, CSAConnection{ // DepartureStopID: from.StopId, // ArrivalStopID: to.StopId, // DepartureTime: from.DepartureTime, // ArrivalTime: to.ArrivalTime, // TripID: tripID, // }) // } // } // } // // Sort connections by departure time, which is crucial for the algorithm. // sort.Slice(p.connections, func(i, j int) bool { // return p.connections[i].DepartureTime < p.connections[j].DepartureTime // }) // } // // FindRoute finds the best route using the Connection Scan Algorithm. // func (p *CSAPlanner) FindRoute(startStopID, endStopID string, when time.Time) *Route { // earliestArrival := make(map[string]time.Time) // journeyPointers := make(map[string]CSAConnection) // To reconstruct the path // startTime := types.AsSecondsAfterMidnight(when) // day := when.Truncate(24 * time.Hour) // // Initialize earliest arrival times // for stopID := range p.Stops { // earliestArrival[stopID] = time.Time{} // Zero time represents infinity // } // earliestArrival[startStopID] = when // // Find the starting point in the connections array // firstConnectionIdx := sort.Search(len(p.connections), func(i int) bool { // return p.connections[i].DepartureTime >= startTime // }) // // Scan through connections // for i := firstConnectionIdx; i < len(p.connections); i++ { // conn := p.connections[i] // depStopArrival, reachable := earliestArrival[conn.DepartureStopID] // if !reachable || depStopArrival.IsZero() { // continue // Cannot reach the departure stop of this connection yet // } // connDepartureTime := day.Add(time.Duration(conn.DepartureTime) * time.Second) // if connDepartureTime.Before(depStopArrival) { // connDepartureTime = connDepartureTime.Add(24 * time.Hour) // Next day // } // if !depStopArrival.IsZero() && connDepartureTime.After(depStopArrival) { // // We can catch this connection // connArrivalTime := day.Add(time.Duration(conn.ArrivalTime) * time.Second) // if connArrivalTime.Before(connDepartureTime) { // connArrivalTime = connArrivalTime.Add(24 * time.Hour) // } // // Check if this connection offers a better arrival time at the destination stop // currentBestArrival, hasArrival := earliestArrival[conn.ArrivalStopID] // if !hasArrival || currentBestArrival.IsZero() || connArrivalTime.Before(currentBestArrival) { // earliestArrival[conn.ArrivalStopID] = connArrivalTime // journeyPointers[conn.ArrivalStopID] = conn // } // } // } // // Reconstruct the path if the destination was reached // if _, ok := journeyPointers[endStopID]; !ok { // return nil // No path found // } // return p.reconstructCSAPath(startStopID, endStopID, journeyPointers) // } // // reconstructCSAPath builds the route from the journey pointers. // func (p *CSAPlanner) reconstructCSAPath(startStopID, endStopID string, pointers map[string]CSAConnection) *Route { // var path []CSAConnection // currentStopID := endStopID // for currentStopID != startStopID { // conn, ok := pointers[currentStopID] // if !ok { // break // Should not happen if a path was found // } // path = append([]CSAConnection{conn}, path...) // currentStopID = conn.DepartureStopID // } // if len(path) == 0 { // return nil // } // // Group connections into legs // var legs []Leg // if len(path) > 0 { // currentLeg := p.connectionToLeg(path[0]) // for i := 1; i < len(path); i++ { // if path[i].TripID == currentLeg.TripID { // // Continue the current leg // currentLeg.To = path[i].ArrivalStopID // currentLeg.ToStop = p.GetStop(currentLeg.To) // currentLeg.Stops = append(currentLeg.Stops, currentLeg.To) // } else { // // New leg // legs = append(legs, *currentLeg) // currentLeg = p.connectionToLeg(path[i]) // } // } // legs = append(legs, *currentLeg) // } // return &Route{Legs: legs} // } // func (p *CSAPlanner) connectionToLeg(conn CSAConnection) *Leg { // trip := p.GetTrip(conn.TripID) // route := p.GetRoute(trip.RouteId) // return &Leg{ // TripID: conn.TripID, // From: conn.DepartureStopID, // To: conn.ArrivalStopID, // FromStop: p.GetStop(conn.DepartureStopID), // ToStop: p.GetStop(conn.ArrivalStopID), // Trip: trip, // Agency: p.GetAgency(route.AgencyID), // Route: route, // Stops: []string{conn.DepartureStopID, conn.ArrivalStopID}, // } // } // func (p *CSAPlanner) GetRoute(routeId string) *types.Route { // if routeId == "" { // return nil // } // route, ok := p.Routes[routeId] // if !ok { // return nil // } // return route // } // func (p *CSAPlanner) GetAgency(agencyId string) *types.Agency { // if agencyId == "" { // return nil // } // agency, ok := p.Agencies[agencyId] // if !ok { // return nil // } // return agency // } // func (p *CSAPlanner) GetTrip(tripId string) *types.Trip { // if tripId == "" { // return nil // } // trip, ok := p.Trips[tripId] // if !ok { // return nil // } // return trip // } // func (p *CSAPlanner) GetStop(stopID string) *types.Stop { // if stopID == "" { // return nil // } // stop, ok := p.Stops[stopID] // if !ok { // return nil // } // return stop // }