218 lines
6.4 KiB
Go
218 lines
6.4 KiB
Go
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
|
|
// }
|