@@ -3,20 +3,33 @@ package main
3
3
import (
4
4
"fmt"
5
5
"os"
6
+ "path/filepath"
6
7
"strings"
7
8
"time"
8
9
9
10
"github.com/fatih/color"
10
11
"github.com/jamespfennell/gtfs"
11
12
"github.com/jamespfennell/gtfs/extensions/nyctalerts"
12
13
"github.com/jamespfennell/gtfs/extensions/nycttrips"
14
+ "github.com/jamespfennell/gtfs/journal"
13
15
"github.com/urfave/cli/v2"
14
16
)
15
17
16
18
func main () {
17
19
app := & cli.App {
18
20
Name : "GTFS parser" ,
19
21
Usage : "parse GTFS static and realtime feeds" ,
22
+ Flags : []cli.Flag {
23
+ & cli.BoolFlag {
24
+ Name : "verbose" ,
25
+ Aliases : []string {"v" },
26
+ Usage : "print additional data about each trip and vehicle" ,
27
+ },
28
+ & cli.StringFlag {
29
+ Name : "extension" ,
30
+ Usage : "GTFS realtime extension to use: nycttrips, nyctalerts" ,
31
+ },
32
+ },
20
33
Commands : []* cli.Command {
21
34
{
22
35
Name : "static" ,
@@ -36,19 +49,8 @@ func main() {
36
49
},
37
50
},
38
51
{
39
- Name : "realtime" ,
40
- Usage : "parse a GTFS realtime message" ,
41
- Flags : []cli.Flag {
42
- & cli.BoolFlag {
43
- Name : "verbose" ,
44
- Aliases : []string {"v" },
45
- Usage : "print additional data about each trip and vehicle" ,
46
- },
47
- & cli.StringFlag {
48
- Name : "extension" ,
49
- Usage : "GTFS realtime extension to use: nycttrips, nyctalerts" ,
50
- },
51
- },
52
+ Name : "realtime" ,
53
+ Usage : "parse a GTFS realtime message" ,
52
54
ArgsUsage : "path" ,
53
55
Action : func (ctx * cli.Context ) error {
54
56
args := ctx .Args ()
@@ -60,27 +62,10 @@ func main() {
60
62
if err != nil {
61
63
return fmt .Errorf ("failed to read file %s: %w" , path , err )
62
64
}
63
-
64
65
opts := gtfs.ParseRealtimeOptions {}
65
- switch ctx .String ("extension" ) {
66
- case "nycttrips" :
67
- opts .Extension = nycttrips .Extension (nycttrips.ExtensionOpts {
68
- FilterStaleUnassignedTrips : true ,
69
- })
70
- americaNewYorkTimezone , err := time .LoadLocation ("America/New_York" )
71
- if err == nil {
72
- opts .Timezone = americaNewYorkTimezone
73
- }
74
- case "nyctalerts" :
75
- opts .Extension = nyctalerts .Extension (nyctalerts.ExtensionOpts {
76
- ElevatorAlertsDeduplicationPolicy : nyctalerts .DeduplicateInComplex ,
77
- ElevatorAlertsInformUsingStationIDs : true ,
78
- SkipTimetabledNoServiceAlerts : true ,
79
- })
80
- americaNewYorkTimezone , err := time .LoadLocation ("America/New_York" )
81
- if err == nil {
82
- opts .Timezone = americaNewYorkTimezone
83
- }
66
+ rawExtension := ctx .String ("extension" )
67
+ if err := readGtfsRealtimeExtension (rawExtension , & opts ); err != nil {
68
+ return err
84
69
}
85
70
realtime , err := gtfs .ParseRealtime (b , & opts )
86
71
if err != nil {
@@ -102,6 +87,65 @@ func main() {
102
87
return nil
103
88
},
104
89
},
90
+ {
91
+ Name : "journal" ,
92
+ Usage : "build a journal from a series of GTFS realtime messages" ,
93
+ Flags : []cli.Flag {
94
+ & cli.StringFlag {
95
+ Name : "output" ,
96
+ Aliases : []string {"o" },
97
+ Usage : "directory to output the CSV files" ,
98
+ },
99
+ },
100
+ ArgsUsage : "path" ,
101
+ Action : func (ctx * cli.Context ) error {
102
+ args := ctx .Args ()
103
+ if args .Len () == 0 {
104
+ return fmt .Errorf ("a path to the GTFS realtime messages was not provided" )
105
+ }
106
+ path := ctx .Args ().First ()
107
+ opts := gtfs.ParseRealtimeOptions {}
108
+ rawExtension := ctx .String ("extension" )
109
+ if err := readGtfsRealtimeExtension (rawExtension , & opts ); err != nil {
110
+ return err
111
+ }
112
+
113
+ source , err := journal .NewDirectoryGtfsrtSource (path )
114
+ if err != nil {
115
+ return fmt .Errorf ("failed to open %s: %w" , path , err )
116
+ }
117
+ fmt .Println ("Building journal..." )
118
+ j := journal .BuildJournal (source , time .Unix (0 , 0 ), time .Now ())
119
+ fmt .Println ("Exporting journal to CSV format..." )
120
+ export , err := j .ExportToCsv ()
121
+ if err != nil {
122
+ return fmt .Errorf ("failed to export journal: %w" , err )
123
+ }
124
+
125
+ outputDir := ctx .String ("output" )
126
+ for _ , f := range []struct {
127
+ file string
128
+ data []byte
129
+ }{
130
+ {
131
+ file : "trips.csv" ,
132
+ data : export .TripsCsv ,
133
+ },
134
+ {
135
+ file : "stop_times.csv" ,
136
+ data : export .StopTimesCsv ,
137
+ },
138
+ } {
139
+ fullPath := filepath .Join (outputDir , f .file )
140
+ fmt .Printf ("Writing %s to %s\n " , f .file , fullPath )
141
+ if err := os .WriteFile (fullPath , f .data , 0666 ); err != nil {
142
+ return fmt .Errorf ("failed to write %s: %w" , f .file , err )
143
+ }
144
+ }
145
+ fmt .Println ("Done" )
146
+ return nil
147
+ },
148
+ },
105
149
},
106
150
}
107
151
if err := app .Run (os .Args ); err != nil {
@@ -110,6 +154,33 @@ func main() {
110
154
}
111
155
}
112
156
157
+ func readGtfsRealtimeExtension (s string , opts * gtfs.ParseRealtimeOptions ) error {
158
+ switch s {
159
+ case "" :
160
+ case "nycttrips" :
161
+ opts .Extension = nycttrips .Extension (nycttrips.ExtensionOpts {
162
+ FilterStaleUnassignedTrips : true ,
163
+ })
164
+ americaNewYorkTimezone , err := time .LoadLocation ("America/New_York" )
165
+ if err == nil {
166
+ opts .Timezone = americaNewYorkTimezone
167
+ }
168
+ case "nyctalerts" :
169
+ opts .Extension = nyctalerts .Extension (nyctalerts.ExtensionOpts {
170
+ ElevatorAlertsDeduplicationPolicy : nyctalerts .DeduplicateInComplex ,
171
+ ElevatorAlertsInformUsingStationIDs : true ,
172
+ SkipTimetabledNoServiceAlerts : true ,
173
+ })
174
+ americaNewYorkTimezone , err := time .LoadLocation ("America/New_York" )
175
+ if err == nil {
176
+ opts .Timezone = americaNewYorkTimezone
177
+ }
178
+ default :
179
+ return fmt .Errorf ("unknown extension %q; supported extensions are nycttrips and nyctalerts" , s )
180
+ }
181
+ return nil
182
+ }
183
+
113
184
func formatTrip (trip gtfs.Trip , indent int , printStopTimes bool ) string {
114
185
var b strings.Builder
115
186
tc := color .New (color .FgCyan )
0 commit comments