diff --git a/gtfstidy.go b/gtfstidy.go index 02228fb..1b951e1 100755 --- a/gtfstidy.go +++ b/gtfstidy.go @@ -9,17 +9,18 @@ package main import ( "errors" "fmt" - "github.com/patrickbr/gtfsparser" - "github.com/patrickbr/gtfsparser/gtfs" - "github.com/patrickbr/gtfstidy/processors" - "github.com/patrickbr/gtfswriter" - "github.com/paulmach/go.geojson" - flag "github.com/spf13/pflag" "io/ioutil" "os" "path" "strconv" "strings" + + "github.com/patrickbr/gtfsparser" + "github.com/patrickbr/gtfsparser/gtfs" + "github.com/patrickbr/gtfstidy/processors" + "github.com/patrickbr/gtfswriter" + geojson "github.com/paulmach/go.geojson" + flag "github.com/spf13/pflag" ) func getGtfsPoly(poly [][][]float64) gtfsparser.Polygon { @@ -170,6 +171,7 @@ func main() { explicitCals := flag.BoolP("explicit-calendar", "", false, "add calendar.txt entry for every service, even irregular ones") ensureTripHeadsigns := flag.BoolP("ensure-trip-headsigns", "", false, "write trip headsigns if missing") ensureParents := flag.BoolP("ensure-stop-parents", "", false, "ensure that every stop (location_type=0) has a parent station") + fixTripHeadsigns := flag.BoolP("fix-trip-headsigns", "", false, "fixes trip headsigns pointing to previous stops") keepColOrder := flag.BoolP("keep-col-order", "", false, "keep the original column ordering of the input feed") keepFields := flag.BoolP("keep-additional-fields", "F", false, "keep all non-GTFS fields from the input") dropTooFast := flag.BoolP("drop-too-fast-trips", "", false, "drop trips that are too fast to realistically occur") @@ -641,6 +643,9 @@ func main() { if *ensureTripHeadsigns { minzers = append(minzers, processors.TripHeadsigner{}) } + if *fixTripHeadsigns { + minzers = append(minzers, processors.FixIntermediateHeadsigns{}) + } if *useRedTripMinimizer { // to convert calendar_dates based services into regular calendar.txt services diff --git a/processors/intermediateheadsign.go b/processors/intermediateheadsign.go new file mode 100644 index 0000000..4db755b --- /dev/null +++ b/processors/intermediateheadsign.go @@ -0,0 +1,72 @@ +package processors + +import ( + "fmt" + "os" + + "github.com/patrickbr/gtfsparser" +) + +// FixIntermediateHeadsigns checks if the trip headsign matches an intermediate stop. +// If so, it sets the stop_headsign for previous stops to that intermediate name +// and updates the trip_headsign to the final destination. + +type FixIntermediateHeadsigns struct{} + +func (pro FixIntermediateHeadsigns) Run(feed *gtfsparser.Feed) { + fmt.Fprintf(os.Stdout, "Fixing intermediate headsigns... ") + + count := 0 + + for _, trip := range feed.Trips { + if len(trip.StopTimes) < 2 { + continue + } + + currentHeadsign := trip.Headsign + if *currentHeadsign == "" { + continue + } + + lastStopIdx := len(trip.StopTimes) - 1 + lastStop := trip.StopTimes[lastStopIdx].Stop() + if lastStop == nil { + continue + } + + if *currentHeadsign == lastStop.Name { + continue + } + + matchIndex := -1 + + // 1. Check if headsign is equal to a stop along the trip (except the last one) + for i := len(trip.StopTimes) - 2; i >= 0; i-- { + st := trip.StopTimes[i] + if st.Stop() != nil && st.Stop().Name == *currentHeadsign { + matchIndex = i + break + } + } + + if matchIndex != -1 { + // Logic: + // Sequence: A -> B -> C (match) -> D -> E (last) + // Old Trip Headsign: C + // New Trip Headsign: E + // Stop Headsign for A, B: C + + // Update trip headsign to the actual last stop + trip.Headsign = &lastStop.Name + + // Update stop_headsign for all stops prior to the match + for j := 0; j < matchIndex; j++ { + trip.StopTimes[j].SetHeadsign(currentHeadsign) + } + + count++ + } + } + + fmt.Fprintf(os.Stdout, "done. Fixed headsigns for %d trips.\n", count) +}