fix
This commit is contained in:
@@ -144,13 +144,17 @@ func main() {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
agencyName := ""
|
||||||
|
if trip.Agency != nil {
|
||||||
|
agencyName = trip.Agency.AgencyName
|
||||||
|
}
|
||||||
tripData := map[string]interface{}{
|
tripData := map[string]interface{}{
|
||||||
"trip_id": trip.TripId,
|
"trip_id": trip.TripId,
|
||||||
"headsign": trip.TripHeadsign,
|
"headsign": trip.TripHeadsign,
|
||||||
"short_name": trip.TripShortName,
|
"short_name": trip.TripShortName,
|
||||||
"route_id": trip.RouteID,
|
"route_id": trip.RouteId,
|
||||||
"agency_id": trip.AgencyID,
|
"agency_id": trip.AgencyId,
|
||||||
"agency_name": trip.Route.Agency.AgencyName,
|
"agency_name": agencyName,
|
||||||
"stops": []map[string]interface{}{},
|
"stops": []map[string]interface{}{},
|
||||||
}
|
}
|
||||||
for _, st := range trip.Stops[startIdx:] {
|
for _, st := range trip.Stops[startIdx:] {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"container/heap"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"slices"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.tornberg.me/go-gtfs/pkg/reader"
|
"git.tornberg.me/go-gtfs/pkg/reader"
|
||||||
@@ -16,24 +16,12 @@ type TripPlanner struct {
|
|||||||
graph map[string][]Edge
|
graph map[string][]Edge
|
||||||
}
|
}
|
||||||
|
|
||||||
type StopWithPossibleConnections struct {
|
|
||||||
*types.Stop
|
|
||||||
PossibleConnections []Connection
|
|
||||||
}
|
|
||||||
|
|
||||||
type Connection struct {
|
|
||||||
*types.Stop
|
|
||||||
Distance float64
|
|
||||||
Time time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
transferPenalty = 90 * time.Minute
|
transferPenalty = 90 * time.Minute
|
||||||
maxTransfers = 4
|
maxTransfers = 4
|
||||||
maxWaitBetweenTrips = 1 * time.Hour
|
maxWaitBetweenTrips = 1 * time.Hour
|
||||||
//trajectoryAngleTolerance = 220.0
|
//trajectoryAngleTolerance = 220.0
|
||||||
maxTravelDuration = 12 * time.Hour
|
maxTravelDuration = 12 * time.Hour
|
||||||
maxDetourFactor = 2
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewTripPlanner creates a new trip planner instance
|
// NewTripPlanner creates a new trip planner instance
|
||||||
@@ -88,23 +76,41 @@ func (tp *TripPlanner) Preprocess() error {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// tp.stopTimes = nil
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type History struct {
|
type searchNode struct {
|
||||||
*types.StopTime
|
stopTime *types.StopTime
|
||||||
DistanceToEnd float64
|
index int
|
||||||
TravelTime types.SecondsAfterMidnight
|
g float64
|
||||||
|
f float64
|
||||||
|
transfers int
|
||||||
|
parent *searchNode
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHistory(st *types.StopTime, distanceToEnd float64, travelTime types.SecondsAfterMidnight) History {
|
func nodeKey(st *types.StopTime) string {
|
||||||
return History{
|
return fmt.Sprintf("%s:%d", st.TripId, st.StopSequence)
|
||||||
StopTime: st,
|
}
|
||||||
DistanceToEnd: distanceToEnd,
|
|
||||||
TravelTime: travelTime,
|
func heuristicSeconds(from, to *types.Stop) float64 {
|
||||||
|
if from == nil || to == nil {
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
const avgSpeedKmh = 60.0
|
||||||
|
distanceKm := from.HaversineDistance(to)
|
||||||
|
return (distanceKm / avgSpeedKmh) * 3600
|
||||||
|
}
|
||||||
|
|
||||||
|
func tripStopIndex(trip *types.Trip, st *types.StopTime) int {
|
||||||
|
if trip == nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
for i, candidate := range trip.Stops {
|
||||||
|
if candidate == st {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@@ -116,160 +122,163 @@ func (tp *TripPlanner) FindRoute(from, to string, when time.Time) ([]*Route, err
|
|||||||
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")
|
||||||
}
|
}
|
||||||
routes := make([]*Route, 0)
|
open := priorityQueue{}
|
||||||
usedRouteIds := make(map[string]struct{})
|
heap.Init(&open)
|
||||||
|
bestCost := make(map[string]float64)
|
||||||
|
startSeconds := types.AsSecondsAfterMidnight(when)
|
||||||
|
initCount := 0
|
||||||
|
|
||||||
for trip := range fromStop.GetTripsAfter(when) {
|
for tripWithDeparture := range fromStop.GetTripsAfter(when) {
|
||||||
if _, used := usedRouteIds[trip.RouteId]; used {
|
trip := tripWithDeparture.Trip
|
||||||
|
startStopTime, ok := trip.Has(fromStop)
|
||||||
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for i := len(trip.Stops) - 1; i >= 0; i-- {
|
idx := tripStopIndex(trip, startStopTime)
|
||||||
stop := trip.Stops[i]
|
if idx < 0 {
|
||||||
if stop.StopId == toStop.StopId {
|
continue
|
||||||
usedRouteIds[trip.RouteId] = struct{}{}
|
|
||||||
routes = append(routes, &Route{
|
|
||||||
Legs: []Leg{NewLeg(trip.Stops[0], trip.Stops[i])},
|
|
||||||
})
|
|
||||||
break
|
|
||||||
} else if stop.StopId == fromStop.StopId {
|
|
||||||
break
|
|
||||||
} else if stop.PickupType == 0 {
|
|
||||||
distance := stop.Stop.HaversineDistance(toStop)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
wait := float64(startStopTime.DepartureTime - startSeconds)
|
||||||
|
if wait < 0 {
|
||||||
|
wait = 0
|
||||||
|
}
|
||||||
|
node := &searchNode{
|
||||||
|
stopTime: startStopTime,
|
||||||
|
index: idx,
|
||||||
|
g: wait,
|
||||||
|
transfers: 0,
|
||||||
|
}
|
||||||
|
node.f = node.g + heuristicSeconds(startStopTime.Stop, toStop)
|
||||||
|
heap.Push(&open, &pqItem{node: node, priority: node.f})
|
||||||
|
bestCost[nodeKey(startStopTime)] = node.g
|
||||||
|
initCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
for start, stop := range fromStop.GetStopsAfter(when) {
|
if initCount == 0 {
|
||||||
if stop.StopId == toStop.StopId {
|
return nil, fmt.Errorf("no departures from %s after %s", from, when.Format(time.RFC3339))
|
||||||
routes = append(routes, &Route{
|
}
|
||||||
Legs: []Leg{NewLeg(start, stop)},
|
|
||||||
})
|
|
||||||
} else if from != stop.StopId {
|
|
||||||
// startTime = start
|
|
||||||
route, err := tp.findRoute(*start, toStop, NewHistory(start, start.Stop.HaversineDistance(toStop), 0), NewHistory(stop, stop.Stop.HaversineDistance(toStop), stop.ArrivalTime-start.DepartureTime))
|
|
||||||
|
|
||||||
if err == nil && route != nil {
|
routes := make([]*Route, 0, 3)
|
||||||
|
|
||||||
|
for open.Len() > 0 && len(routes) < 3 {
|
||||||
|
current := heap.Pop(&open).(*pqItem).node
|
||||||
|
if current.stopTime.StopId == toStop.StopId {
|
||||||
|
route := buildRouteFromNode(current)
|
||||||
|
if route != nil {
|
||||||
routes = append(routes, route)
|
routes = append(routes, route)
|
||||||
}
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
if current.g > maxTravelDuration.Seconds() {
|
||||||
// slices.SortFunc(possibleNextStops, byDistanceTo(*toStop))
|
continue
|
||||||
// for _, nextStop := range possibleNextStops {
|
|
||||||
// route, err := tp.findRoute(*nextStop, toStop, *NewHistory(startTime, startTime.Stop.HaversineDistance(toStop), types.AsSecondsAfterMidnight(when)), *NewHistory(nextStop, nextStop.Stop.HaversineDistance(toStop), nextStop.ArrivalTime-startTime.ArrivalTime))
|
|
||||||
// if err == nil && route != nil {
|
|
||||||
// return route, nil
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
slices.SortFunc(routes, func(a, b *Route) int {
|
|
||||||
transfersA := len(a.Legs) - 1
|
|
||||||
transfersB := len(b.Legs) - 1
|
|
||||||
if transfersA != transfersB {
|
|
||||||
return transfersA - transfersB
|
|
||||||
}
|
}
|
||||||
return a.Duration() - b.Duration() - (transfersA-transfersB)*int(transferPenalty.Seconds())
|
tpp := tp.Trips[current.stopTime.TripId]
|
||||||
})
|
if tpp != nil {
|
||||||
return routes[:min(len(routes), 10)], nil
|
for i := current.index + 1; i < len(tpp.Stops); i++ {
|
||||||
}
|
next := tpp.Stops[i]
|
||||||
|
if next.DropOffType == 1 {
|
||||||
func byDistanceTo(end types.Stop) func(a, b *types.StopTime) int {
|
continue
|
||||||
return func(a, b *types.StopTime) int {
|
}
|
||||||
distanceA := haversine(a.Stop.StopLat, a.Stop.StopLon, end.StopLat, end.StopLon)
|
travel := next.ArrivalTime - current.stopTime.DepartureTime
|
||||||
distanceB := haversine(b.Stop.StopLat, b.Stop.StopLon, end.StopLat, end.StopLon)
|
if travel <= 0 {
|
||||||
return (int(distanceA) - int(distanceB)) // + (int(b.ArrivalTime - a.ArrivalTime))
|
continue
|
||||||
}
|
}
|
||||||
}
|
newCost := current.g + float64(travel)
|
||||||
|
if newCost > maxTravelDuration.Seconds() {
|
||||||
func isInCorrectDirection(from, possible, end *types.Stop) bool {
|
continue
|
||||||
if from.StopId == end.StopId || possible.StopId == end.StopId {
|
}
|
||||||
return true
|
key := nodeKey(next)
|
||||||
}
|
if prev, ok := bestCost[key]; ok && newCost >= prev {
|
||||||
if from.StopId == possible.StopId {
|
continue
|
||||||
return false
|
}
|
||||||
}
|
nextNode := &searchNode{
|
||||||
startToEndLat := end.StopLat - from.StopLat
|
stopTime: next,
|
||||||
startToEndLon := end.StopLon - from.StopLon
|
index: i,
|
||||||
startToPossibleLat := possible.StopLat - from.StopLat
|
g: newCost,
|
||||||
startToPossibleLon := possible.StopLon - from.StopLon
|
transfers: current.transfers,
|
||||||
dotProduct := startToEndLat*startToPossibleLat + startToEndLon*startToPossibleLon
|
parent: current,
|
||||||
return dotProduct > -0.4 && dotProduct < 0.4
|
}
|
||||||
}
|
nextNode.f = newCost + heuristicSeconds(next.Stop, toStop)
|
||||||
|
bestCost[key] = newCost
|
||||||
func shouldTryStop(end *types.Stop, visited ...History) func(possible *types.StopTime) bool {
|
heap.Push(&open, &pqItem{node: nextNode, priority: nextNode.f})
|
||||||
lastDistance := visited[len(visited)-1].Stop.HaversineDistance(end)
|
|
||||||
return func(possible *types.StopTime) bool {
|
|
||||||
if end.StopId == possible.StopId {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if possible.DepartureTime > visited[len(visited)-1].DepartureTime+types.SecondsAfterMidnight(maxWaitBetweenTrips.Seconds()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if possible.DropOffType == 1 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// if !isInCorrectDirection(visited[len(visited)-1].Stop, possible.Stop, end) {
|
|
||||||
// return false
|
|
||||||
// }
|
|
||||||
distance := possible.Stop.HaversineDistance(end)
|
|
||||||
|
|
||||||
for _, v := range visited {
|
|
||||||
if v.DistanceToEnd <= distance*1.2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v.TripId == possible.TripId || v.StopId == possible.StopId {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if current.transfers >= maxTransfers {
|
||||||
return distance <= lastDistance*1.2
|
continue
|
||||||
}
|
}
|
||||||
}
|
for _, otherTrip := range current.stopTime.Stop.Trips {
|
||||||
|
if otherTrip.TripId == current.stopTime.TripId {
|
||||||
func (tp *TripPlanner) findRoute(start types.StopTime, end *types.Stop, changes ...History) (*Route, error) {
|
continue
|
||||||
if len(changes) >= maxTransfers {
|
|
||||||
return nil, fmt.Errorf("max transfers reached")
|
|
||||||
}
|
|
||||||
isOk := shouldTryStop(end, changes...)
|
|
||||||
possibleNextStops := make([]*types.StopTime, 0)
|
|
||||||
for stop := range start.Stop.GetUpcomingStops(&start) {
|
|
||||||
if stop.StopId == end.StopId {
|
|
||||||
return &Route{
|
|
||||||
Legs: CreateLegs(changes, stop),
|
|
||||||
}, nil
|
|
||||||
} else {
|
|
||||||
if isOk(stop) {
|
|
||||||
possibleNextStops = append(possibleNextStops, stop)
|
|
||||||
}
|
}
|
||||||
|
transferStopTime, ok := otherTrip.Has(current.stopTime.Stop)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if transferStopTime.DepartureTime <= current.stopTime.ArrivalTime {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
wait := transferStopTime.DepartureTime - current.stopTime.ArrivalTime
|
||||||
|
if wait > types.SecondsAfterMidnight(maxWaitBetweenTrips.Seconds()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newCost := current.g + float64(wait) + transferPenalty.Seconds()
|
||||||
|
if newCost > maxTravelDuration.Seconds() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
idx := tripStopIndex(otherTrip, transferStopTime)
|
||||||
|
if idx < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := nodeKey(transferStopTime)
|
||||||
|
if prev, ok := bestCost[key]; ok && newCost >= prev {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nextNode := &searchNode{
|
||||||
|
stopTime: transferStopTime,
|
||||||
|
index: idx,
|
||||||
|
g: newCost,
|
||||||
|
transfers: current.transfers + 1,
|
||||||
|
parent: current,
|
||||||
|
}
|
||||||
|
nextNode.f = newCost + heuristicSeconds(transferStopTime.Stop, toStop)
|
||||||
|
bestCost[key] = newCost
|
||||||
|
heap.Push(&open, &pqItem{node: nextNode, priority: nextNode.f})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
slices.SortFunc(possibleNextStops, byDistanceTo(*end))
|
|
||||||
|
|
||||||
tries := 15
|
if len(routes) == 0 {
|
||||||
for _, nextStop := range possibleNextStops {
|
return nil, fmt.Errorf("no route found")
|
||||||
route, err := tp.findRoute(*nextStop, end, append(changes, NewHistory(nextStop, nextStop.Stop.HaversineDistance(end), nextStop.ArrivalTime-start.ArrivalTime))...)
|
|
||||||
if err == nil && route != nil {
|
|
||||||
return route, nil
|
|
||||||
}
|
|
||||||
tries--
|
|
||||||
if tries <= 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no route found")
|
|
||||||
|
return routes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateLegs(stops []History, finalStop *types.StopTime) []Leg {
|
func buildRouteFromNode(goal *searchNode) *Route {
|
||||||
legs := make([]Leg, 0, len(stops)+1)
|
if goal == nil {
|
||||||
var previousStop *types.StopTime
|
return nil
|
||||||
for _, stop := range stops {
|
|
||||||
if previousStop != nil {
|
|
||||||
legs = append(legs, NewLeg(previousStop, stop.StopTime))
|
|
||||||
}
|
|
||||||
previousStop = stop.StopTime
|
|
||||||
}
|
}
|
||||||
legs = append(legs, NewLeg(previousStop, finalStop))
|
nodes := make([]*searchNode, 0)
|
||||||
return legs
|
for current := goal; current != nil; current = current.parent {
|
||||||
|
nodes = append(nodes, current)
|
||||||
|
}
|
||||||
|
for i, j := 0, len(nodes)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
nodes[i], nodes[j] = nodes[j], nodes[i]
|
||||||
|
}
|
||||||
|
if len(nodes) < 2 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
legs := make([]Leg, 0)
|
||||||
|
runStart := nodes[0]
|
||||||
|
for i := 1; i < len(nodes); i++ {
|
||||||
|
prev := nodes[i-1]
|
||||||
|
curr := nodes[i]
|
||||||
|
if curr.stopTime.TripId != prev.stopTime.TripId {
|
||||||
|
legs = append(legs, NewLeg(runStart.stopTime, prev.stopTime))
|
||||||
|
runStart = curr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
legs = append(legs, NewLeg(runStart.stopTime, nodes[len(nodes)-1].stopTime))
|
||||||
|
return &Route{Legs: legs}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLeg(fromStop, toStop *types.StopTime) Leg {
|
func NewLeg(fromStop, toStop *types.StopTime) Leg {
|
||||||
@@ -281,13 +290,17 @@ func NewLeg(fromStop, toStop *types.StopTime) Leg {
|
|||||||
To: toStop,
|
To: toStop,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
agencyName := ""
|
||||||
|
if trip.Agency != nil {
|
||||||
|
agencyName = trip.Agency.AgencyName
|
||||||
|
}
|
||||||
return Leg{
|
return Leg{
|
||||||
From: fromStop,
|
From: fromStop,
|
||||||
To: toStop,
|
To: toStop,
|
||||||
Trip: &JSONTrip{
|
Trip: &JSONTrip{
|
||||||
TripId: trip.TripId,
|
TripId: trip.TripId,
|
||||||
RouteId: trip.RouteID,
|
RouteId: trip.RouteId,
|
||||||
AgencyName: trip.Agency.AgencyName,
|
AgencyName: agencyName,
|
||||||
TripHeadsign: trip.TripHeadsign,
|
TripHeadsign: trip.TripHeadsign,
|
||||||
TripShortName: trip.TripShortName,
|
TripShortName: trip.TripShortName,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type pqItem struct {
|
type pqItem struct {
|
||||||
Stop string
|
node *searchNode
|
||||||
Cost time.Time
|
priority float64
|
||||||
index int
|
index int
|
||||||
}
|
}
|
||||||
|
|
||||||
type priorityQueue []*pqItem
|
type priorityQueue []*pqItem
|
||||||
|
|
||||||
func (pq priorityQueue) Len() int { return len(pq) }
|
func (pq priorityQueue) Len() int { return len(pq) }
|
||||||
|
|
||||||
func (pq priorityQueue) Less(i, j int) bool { return pq[i].Cost.Before(pq[j].Cost) }
|
func (pq priorityQueue) Less(i, j int) bool { return pq[i].priority < pq[j].priority }
|
||||||
|
|
||||||
func (pq priorityQueue) Swap(i, j int) {
|
func (pq priorityQueue) Swap(i, j int) {
|
||||||
pq[i], pq[j] = pq[j], pq[i]
|
pq[i], pq[j] = pq[j], pq[i]
|
||||||
|
|||||||
@@ -136,8 +136,8 @@ func ParseRoutes(r io.Reader, callback func(types.Route)) error {
|
|||||||
routeType, _ := strconv.Atoi(record[4])
|
routeType, _ := strconv.Atoi(record[4])
|
||||||
route := types.Route{
|
route := types.Route{
|
||||||
Trips: make([]*types.Trip, 0),
|
Trips: make([]*types.Trip, 0),
|
||||||
RouteID: record[0],
|
RouteId: record[0],
|
||||||
AgencyID: record[1],
|
AgencyId: record[1],
|
||||||
RouteShortName: record[2],
|
RouteShortName: record[2],
|
||||||
RouteLongName: record[3],
|
RouteLongName: record[3],
|
||||||
RouteType: routeType,
|
RouteType: routeType,
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ func LoadTripData(path string) (*TripData, error) {
|
|||||||
})
|
})
|
||||||
case "routes":
|
case "routes":
|
||||||
err = ParseRoutes(f, func(r types.Route) {
|
err = ParseRoutes(f, func(r types.Route) {
|
||||||
tp.Routes[r.RouteID] = &r
|
tp.Routes[r.RouteId] = &r
|
||||||
if ag, ok := tp.Agencies[r.AgencyID]; ok {
|
if ag, ok := tp.Agencies[r.AgencyId]; ok {
|
||||||
r.SetAgency(ag)
|
r.SetAgency(ag)
|
||||||
// ag.AddRoute(&r)
|
// ag.AddRoute(&r)
|
||||||
}
|
}
|
||||||
@@ -58,10 +58,10 @@ func LoadTripData(path string) (*TripData, error) {
|
|||||||
} else {
|
} else {
|
||||||
log.Printf("route %s not found", trip.RouteId)
|
log.Printf("route %s not found", trip.RouteId)
|
||||||
}
|
}
|
||||||
if agency, ok := tp.Agencies[trip.AgencyID]; ok {
|
if agency, ok := tp.Agencies[trip.AgencyId]; ok {
|
||||||
trip.Agency = agency
|
trip.Agency = agency
|
||||||
} else {
|
} else {
|
||||||
log.Printf("agency %s not found", trip.AgencyID)
|
log.Printf("agency %s not found", trip.AgencyId)
|
||||||
}
|
}
|
||||||
tp.Trips[trip.TripId] = &trip
|
tp.Trips[trip.TripId] = &trip
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user