From d2b9e9c9ada4fe1608f2d1687085cabc593dac57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mats=20T=C3=B6rnberg?= Date: Sun, 16 Nov 2025 12:12:23 +0100 Subject: [PATCH] more --- cmd/planner/planner.go | 97 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 90 insertions(+), 7 deletions(-) diff --git a/cmd/planner/planner.go b/cmd/planner/planner.go index 0c6b196..37ac2f3 100644 --- a/cmd/planner/planner.go +++ b/cmd/planner/planner.go @@ -13,7 +13,6 @@ import ( // TripPlanner handles preprocessed transit data for efficient routing type TripPlanner struct { *reader.TripData - graph map[string][]Edge } const ( @@ -22,6 +21,8 @@ const ( maxWaitBetweenTrips = 1 * time.Hour //trajectoryAngleTolerance = 220.0 maxTravelDuration = 12 * time.Hour + maxInitialWait = 90 * time.Minute + searchWindow = 4 * time.Hour ) // 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 func (tp *TripPlanner) FindRoute(from, to string, when time.Time) ([]*Route, error) { - fromStop := tp.GetStop(from) toStop := tp.GetStop(to) - if fromStop == nil || toStop == nil { 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{} heap.Init(&open) bestCost := make(map[string]float64) startSeconds := types.AsSecondsAfterMidnight(when) + startSecondsFloat := float64(startSeconds) initCount := 0 + initialWaitLimit := maxInitialWait.Seconds() + searchWindowLimit := searchWindow.Seconds() for tripWithDeparture := range fromStop.GetTripsAfter(when) { trip := tripWithDeparture.Trip @@ -138,10 +172,17 @@ func (tp *TripPlanner) FindRoute(from, to string, when time.Time) ([]*Route, err if idx < 0 { continue } - wait := float64(startStopTime.DepartureTime - startSeconds) + departureDelta := float64(startStopTime.DepartureTime) - startSecondsFloat + if searchWindow > 0 && departureDelta > searchWindowLimit { + continue + } + wait := departureDelta if wait < 0 { wait = 0 } + if maxInitialWait > 0 && wait > initialWaitLimit { + continue + } node := &searchNode{ stopTime: startStopTime, index: idx, @@ -155,23 +196,41 @@ func (tp *TripPlanner) FindRoute(from, to string, when time.Time) ([]*Route, err } 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 if current.stopTime.StopId == toStop.StopId { route := buildRouteFromNode(current) if route != nil { + signature := routeSignature(route) + if signature == "" { + continue + } + if _, seen := exclude[signature]; seen { + continue + } + exclude[signature] = struct{}{} routes = append(routes, route) + if len(routes) >= limit { + break + } } continue } if current.g > maxTravelDuration.Seconds() { continue } + if searchWindow > 0 { + currentDelta := float64(current.stopTime.DepartureTime) - startSecondsFloat + if currentDelta > searchWindowLimit { + continue + } + } + tpp := tp.Trips[current.stopTime.TripId] if tpp != nil { 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() { continue } + if searchWindow > 0 { + nextDepartureDelta := float64(next.DepartureTime) - startSecondsFloat + if nextDepartureDelta > searchWindowLimit { + continue + } + } key := nodeKey(next) if prev, ok := bestCost[key]; ok && newCost >= prev { continue @@ -225,6 +290,12 @@ func (tp *TripPlanner) FindRoute(from, to string, when time.Time) ([]*Route, err if newCost > maxTravelDuration.Seconds() { continue } + if searchWindow > 0 { + transferDepartureDelta := float64(transferStopTime.DepartureTime) - startSecondsFloat + if transferDepartureDelta > searchWindowLimit { + continue + } + } idx := tripStopIndex(otherTrip, transferStopTime) if idx < 0 { continue @@ -281,6 +352,18 @@ func buildRouteFromNode(goal *searchNode) *Route { 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 { trip, ok := toStop.Stop.Trips[toStop.TripId] if !ok {