This commit is contained in:
2025-11-16 12:12:23 +01:00
parent 716bd0816c
commit d2b9e9c9ad

View File

@@ -13,7 +13,6 @@ import (
// TripPlanner handles preprocessed transit data for efficient routing // TripPlanner handles preprocessed transit data for efficient routing
type TripPlanner struct { type TripPlanner struct {
*reader.TripData *reader.TripData
graph map[string][]Edge
} }
const ( const (
@@ -22,6 +21,8 @@ const (
maxWaitBetweenTrips = 1 * time.Hour maxWaitBetweenTrips = 1 * time.Hour
//trajectoryAngleTolerance = 220.0 //trajectoryAngleTolerance = 220.0
maxTravelDuration = 12 * time.Hour maxTravelDuration = 12 * time.Hour
maxInitialWait = 90 * time.Minute
searchWindow = 4 * time.Hour
) )
// NewTripPlanner creates a new trip planner instance // NewTripPlanner creates a new trip planner instance
@@ -115,18 +116,51 @@ func tripStopIndex(trip *types.Trip, st *types.StopTime) int {
// FindRoutes finds the best routes (up to num) between two stops starting at the given time // FindRoutes finds the best routes (up to num) between two stops starting at the given time
func (tp *TripPlanner) FindRoute(from, to string, when time.Time) ([]*Route, error) { func (tp *TripPlanner) FindRoute(from, to string, when time.Time) ([]*Route, error) {
fromStop := tp.GetStop(from) fromStop := tp.GetStop(from)
toStop := tp.GetStop(to) toStop := tp.GetStop(to)
if fromStop == nil || toStop == nil { if fromStop == nil || toStop == nil {
return nil, fmt.Errorf("invalid from or to stop") return nil, fmt.Errorf("invalid from or to stop")
} }
exclude := make(map[string]struct{})
results := make([]*Route, 0, 3)
timeCursor := when
const maxAttempts = 5
var lastErr error
for attempts := 0; attempts < maxAttempts && len(results) < 3; attempts++ {
limit := 3 - len(results)
routes, err := tp.searchRoutesAStar(fromStop, toStop, timeCursor, exclude, limit)
if err == nil {
results = append(results, routes...)
} else {
lastErr = err
}
timeCursor = timeCursor.Add(10 * time.Minute)
}
if len(results) == 0 {
if lastErr != nil {
return nil, lastErr
}
return nil, fmt.Errorf("no route found")
}
return results, nil
}
func (tp *TripPlanner) searchRoutesAStar(fromStop, toStop *types.Stop, when time.Time, exclude map[string]struct{}, limit int) ([]*Route, error) {
if limit <= 0 {
return nil, nil
}
open := priorityQueue{} open := priorityQueue{}
heap.Init(&open) heap.Init(&open)
bestCost := make(map[string]float64) bestCost := make(map[string]float64)
startSeconds := types.AsSecondsAfterMidnight(when) startSeconds := types.AsSecondsAfterMidnight(when)
startSecondsFloat := float64(startSeconds)
initCount := 0 initCount := 0
initialWaitLimit := maxInitialWait.Seconds()
searchWindowLimit := searchWindow.Seconds()
for tripWithDeparture := range fromStop.GetTripsAfter(when) { for tripWithDeparture := range fromStop.GetTripsAfter(when) {
trip := tripWithDeparture.Trip trip := tripWithDeparture.Trip
@@ -138,10 +172,17 @@ func (tp *TripPlanner) FindRoute(from, to string, when time.Time) ([]*Route, err
if idx < 0 { if idx < 0 {
continue continue
} }
wait := float64(startStopTime.DepartureTime - startSeconds) departureDelta := float64(startStopTime.DepartureTime) - startSecondsFloat
if searchWindow > 0 && departureDelta > searchWindowLimit {
continue
}
wait := departureDelta
if wait < 0 { if wait < 0 {
wait = 0 wait = 0
} }
if maxInitialWait > 0 && wait > initialWaitLimit {
continue
}
node := &searchNode{ node := &searchNode{
stopTime: startStopTime, stopTime: startStopTime,
index: idx, index: idx,
@@ -155,23 +196,41 @@ func (tp *TripPlanner) FindRoute(from, to string, when time.Time) ([]*Route, err
} }
if initCount == 0 { if initCount == 0 {
return nil, fmt.Errorf("no departures from %s after %s", from, when.Format(time.RFC3339)) return nil, fmt.Errorf("no departures from %s after %s", fromStop.StopName, when.Format(time.RFC3339))
} }
routes := make([]*Route, 0, 3) routes := make([]*Route, 0, limit)
for open.Len() > 0 && len(routes) < 3 { for open.Len() > 0 && len(routes) < limit {
current := heap.Pop(&open).(*pqItem).node current := heap.Pop(&open).(*pqItem).node
if current.stopTime.StopId == toStop.StopId { if current.stopTime.StopId == toStop.StopId {
route := buildRouteFromNode(current) route := buildRouteFromNode(current)
if route != nil { if route != nil {
signature := routeSignature(route)
if signature == "" {
continue
}
if _, seen := exclude[signature]; seen {
continue
}
exclude[signature] = struct{}{}
routes = append(routes, route) routes = append(routes, route)
if len(routes) >= limit {
break
}
} }
continue continue
} }
if current.g > maxTravelDuration.Seconds() { if current.g > maxTravelDuration.Seconds() {
continue continue
} }
if searchWindow > 0 {
currentDelta := float64(current.stopTime.DepartureTime) - startSecondsFloat
if currentDelta > searchWindowLimit {
continue
}
}
tpp := tp.Trips[current.stopTime.TripId] tpp := tp.Trips[current.stopTime.TripId]
if tpp != nil { if tpp != nil {
for i := current.index + 1; i < len(tpp.Stops); i++ { for i := current.index + 1; i < len(tpp.Stops); i++ {
@@ -187,6 +246,12 @@ func (tp *TripPlanner) FindRoute(from, to string, when time.Time) ([]*Route, err
if newCost > maxTravelDuration.Seconds() { if newCost > maxTravelDuration.Seconds() {
continue continue
} }
if searchWindow > 0 {
nextDepartureDelta := float64(next.DepartureTime) - startSecondsFloat
if nextDepartureDelta > searchWindowLimit {
continue
}
}
key := nodeKey(next) key := nodeKey(next)
if prev, ok := bestCost[key]; ok && newCost >= prev { if prev, ok := bestCost[key]; ok && newCost >= prev {
continue continue
@@ -225,6 +290,12 @@ func (tp *TripPlanner) FindRoute(from, to string, when time.Time) ([]*Route, err
if newCost > maxTravelDuration.Seconds() { if newCost > maxTravelDuration.Seconds() {
continue continue
} }
if searchWindow > 0 {
transferDepartureDelta := float64(transferStopTime.DepartureTime) - startSecondsFloat
if transferDepartureDelta > searchWindowLimit {
continue
}
}
idx := tripStopIndex(otherTrip, transferStopTime) idx := tripStopIndex(otherTrip, transferStopTime)
if idx < 0 { if idx < 0 {
continue continue
@@ -281,6 +352,18 @@ func buildRouteFromNode(goal *searchNode) *Route {
return &Route{Legs: legs} return &Route{Legs: legs}
} }
func routeSignature(route *Route) string {
if route == nil {
return ""
}
if len(route.Legs) == 0 {
return ""
}
start := int(route.StartTime())
duration := route.Duration()
return fmt.Sprintf("%d:%d", start, duration)
}
func NewLeg(fromStop, toStop *types.StopTime) Leg { func NewLeg(fromStop, toStop *types.StopTime) Leg {
trip, ok := toStop.Stop.Trips[toStop.TripId] trip, ok := toStop.Stop.Trips[toStop.TripId]
if !ok { if !ok {