gtfs-reader 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,72 @@
1
+ module GtfsReader
2
+ module Config
3
+ # Defines a single column in a {FileDefinition file}.
4
+ class Column
5
+ # A "parser" which simply returns its input. Used by default
6
+ IDENTITY_PARSER = ->(arg) { arg }
7
+
8
+ attr_reader :name
9
+
10
+ #@param name [String] the name of the column
11
+ #@option opts [Boolean] :required (false) If this column is required to
12
+ # appear in the given file
13
+ #@option opts [String] :alias an alternative name for this column. Many
14
+ # column names are needlessly prefixed with their filename:
15
+ # +Stop.stop_name+ could be aliased to +Stop.name+ for example.
16
+ #@option opts [Boolean] :unique (false) if values in this column need to be
17
+ # unique among all rows in the file.
18
+ def initialize(name, opts={}, &parser)
19
+ @name = name
20
+ @parser = block_given? ? parser : IDENTITY_PARSER
21
+
22
+ @opts = {
23
+ required: false,
24
+ unique: false,
25
+ alias: nil
26
+ }.merge (opts || {})
27
+ end
28
+
29
+ def parser(&block)
30
+ @parser = block if block_given?
31
+ @parser
32
+ end
33
+
34
+ #@return [Boolean] if this column is required to appear in the file
35
+ def required?
36
+ @opts[:required]
37
+ end
38
+
39
+ #@return [Boolean] if values in this column need to be unique among all rows
40
+ # in the file.
41
+ def unique?
42
+ @opts[:unique]
43
+ end
44
+
45
+ #@return [String,nil] this column's name's alias, if there is one
46
+ def alias
47
+ @opts[:alias]
48
+ end
49
+
50
+ #@return [Boolean]
51
+ def parser?
52
+ parser != IDENTITY_PARSER
53
+ end
54
+
55
+ def to_s
56
+ opts = @opts.collect do |key,value|
57
+ case value
58
+ when true then key
59
+ when false,nil then nil
60
+ else "#{key}=#{value}"
61
+ end
62
+ end.reject &:nil?
63
+
64
+ opts << 'has_parser' if parser?
65
+
66
+ str = name.to_s
67
+ str << ": #{opts.join ', '}" unless opts.empty?
68
+ "[#{str}]"
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,208 @@
1
+ require_relative '../feed_definition'
2
+
3
+ module GtfsReader
4
+ module Config
5
+ module Defaults
6
+ FEED_DEFINITION = FeedDefinition.new.tap do |feed|
7
+ feed.instance_exec do
8
+ file :agency, required: true do
9
+ prefix :agency do
10
+ col :name, required: true
11
+ col :url, required: true
12
+ col :timezone, required: true
13
+ col :id, unique: true
14
+ col :lang
15
+ col :phone
16
+ col :fare_url
17
+ end
18
+ end
19
+
20
+ file :stops, required: true do
21
+ prefix :stop do
22
+ col :id, required: true, unique: true
23
+ col :code
24
+ col :name, required: true
25
+ col :desc
26
+ col :lat, required: true
27
+ col :lon, required: true
28
+ col :url
29
+ col :timezone
30
+ end
31
+
32
+ col :zone_id
33
+ col :location_type, &output_map( :stop, station: ?1 )
34
+ col :parent_station
35
+
36
+ col :wheelchair_boarding do |val|
37
+ if parent_station
38
+ case val
39
+ when ?2 then :no
40
+ when ?1 then :yes
41
+ else :inherit
42
+ end
43
+ else
44
+ case val
45
+ when ?2 then :no_vehicles
46
+ when ?1 then :some_vehicles
47
+ else :unknown
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ file :routes, required: true do
54
+ prefix :route do
55
+ col :id, required: true, unique: true
56
+ col :short_name, required: true
57
+ col :long_name, required: true
58
+ col :desc
59
+ col :type, required: true,
60
+ &output_map( :unknown,
61
+ tram: ?0,
62
+ subway: ?1,
63
+ rail: ?2,
64
+ bus: ?3,
65
+ ferry: ?4,
66
+ cable_car: ?5,
67
+ gondola: ?6,
68
+ funicular: ?7 )
69
+ col :url
70
+ col :color
71
+ col :text_color
72
+ end
73
+
74
+ col :agency_id
75
+ end
76
+
77
+ file :trips, required: true do
78
+ prefix :trip do
79
+ col :id, required: true, unique: true
80
+ col :headsign
81
+ col :short_name
82
+ col :long_name
83
+ end
84
+
85
+ col :route_id, required: true
86
+ col :service_id, required: true
87
+ col :direction_id,
88
+ &output_map( primary: ?0, opposite: ?1 )
89
+ col :block_id
90
+ col :shape_id
91
+ col :wheelchair_accessible,
92
+ &output_map( :unknown, yes: ?1, no: ?2 )
93
+ col :bikes_allowed,
94
+ &output_map( :unknown, yes: ?1, no: ?2 )
95
+ end
96
+
97
+ file :stop_times, required: true do
98
+ col :trip_id, required: true
99
+ col :arrival_time, required: true
100
+ col :departure_time, required: true
101
+
102
+ prefix :stop do
103
+ col :id, required: true
104
+ col :sequence, required: true
105
+ col :headsign
106
+ end
107
+
108
+ col :pickup_type,
109
+ &output_map( :regular,
110
+ none: ?1,
111
+ phone_agency: ?2,
112
+ coordinate_with_driver: ?3 )
113
+
114
+ col :drop_off_type,
115
+ &output_map( :regular,
116
+ none: ?1,
117
+ phone_agency: ?2,
118
+ coordinate_with_driver: ?3 )
119
+
120
+ col :shape_dist_traveled
121
+ end
122
+
123
+ file :calendar, required: true do
124
+ col :service_id, required: true, unique: true
125
+
126
+ col :monday, required: true, &output_map( yes: ?1, no: ?0 )
127
+ col :tuesday, required: true, &output_map( yes: ?1, no: ?0 )
128
+ col :wednesday, required: true, &output_map( yes: ?1, no: ?0 )
129
+ col :thursday, required: true, &output_map( yes: ?1, no: ?0 )
130
+ col :friday, required: true, &output_map( yes: ?1, no: ?0 )
131
+ col :saturday, required: true, &output_map( yes: ?1, no: ?0 )
132
+ col :sunday, required: true, &output_map( yes: ?1, no: ?0 )
133
+
134
+ col :start_date
135
+ col :end_date
136
+ end
137
+
138
+ file :calendar_dates do
139
+ col :service_id, required: true
140
+ col :date, required: true
141
+ col :exception_type, required: true,
142
+ &output_map( added: ?1, removed: ?2 )
143
+ end
144
+
145
+ file :fare_attributes do
146
+ col :fare_id, required: true, unique: true
147
+ col :price, required: true
148
+ col :currency_type, required: true
149
+ col :payment_method, required: true,
150
+ &output_map( on_board: 0, before: 1 )
151
+ col :transfers, required: true,
152
+ &output_map( :unlimited, none: 0, once: 1, twice: 2 )
153
+ col :transfer_duration
154
+ end
155
+
156
+ file :fare_rules do
157
+ col :fare_id, required: true
158
+ col :route_id
159
+ col :origin_id
160
+ col :destination_id
161
+ col :contains_id
162
+ end
163
+
164
+ file :shapes do
165
+ prefix :shape do
166
+ col :id, required: true
167
+ col :pt_lat, required: true
168
+ col :pt_lon, required: true
169
+ col :pt_sequence, required: true
170
+ col :dist_traveled
171
+ end
172
+ end
173
+
174
+ file :frequencies do
175
+ col :trip_id, required: true
176
+ col :start_time, required: true
177
+ col :end_time, required: true
178
+ col :headway_secs, required: true
179
+ col :exact_times,
180
+ &output_map( :inexact, exact: 1 )
181
+ end
182
+
183
+ file :transfers do
184
+ col :from_stop_id, required: true
185
+ col :to_stop_id, required: true
186
+ col :transfer_type, required: true,
187
+ &output_map( :recommended,
188
+ timed_transfer: 1,
189
+ minimum_time_required: 2,
190
+ impossible: 3 )
191
+ col :min_transfer_time
192
+ end
193
+
194
+ file :feed_info do
195
+ prefix :scope do
196
+ col :publisher_name, required: true
197
+ col :publisher_url, required: true
198
+ col :lang, required: true
199
+ col :start_date
200
+ col :end_date
201
+ col :version
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,55 @@
1
+ require_relative 'file_definition'
2
+
3
+ module GtfsReader
4
+ module Config
5
+ # Describes a GTFS feed and the {FileDefinition files} it is expected to
6
+ # provide.
7
+ class FeedDefinition
8
+ def initialize
9
+ @file_definition = {}
10
+ end
11
+
12
+ #@return [Array<FileDefinition>] All of the defined files.
13
+ def files
14
+ @file_definition.values
15
+ end
16
+
17
+ def required_files
18
+ files.select &:required?
19
+ end
20
+
21
+ def optional_files
22
+ files.reject &:required?
23
+ end
24
+
25
+ #@overload file(name, *args, &block)
26
+ # Defines a new file in the feed.
27
+ #
28
+ # @param name [String] the name of this file within the feed. This name
29
+ # should not include a file extension (like +.txt+)
30
+ # @param args [Array] the first argument is used as a +Hash+ of options
31
+ # to create the new file definition
32
+ # @param block [Proc] this block is +instance_eval+ed on the new
33
+ # {FileDefinition file}
34
+ # @return [FileDefinition] the newly created file
35
+ #
36
+ #@overload file(name)
37
+ # @param name [String] the name of the file to return
38
+ # @return [FileDefinition] the previously created file with the given name
39
+ #@see FileDefinition
40
+ def file(name, *args, &block)
41
+ return @file_definition[name] unless block_given?
42
+
43
+ definition_for!( name, args.first ).tap do |d|
44
+ d.instance_exec &block if block
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def definition_for!(name, opts)
51
+ @file_definition[name] ||= FileDefinition.new( name, opts )
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,111 @@
1
+ require_relative 'column'
2
+ require_relative 'prefixed_column_setter'
3
+
4
+ module GtfsReader
5
+ module Config
6
+ # Describes a single file in a {FeedDefinition GTFS feed}.
7
+ class FileDefinition
8
+ attr_reader :name
9
+
10
+ #@param name [String] The name of the file within the feed.
11
+ #@option opts [Boolean] :required (false)
12
+ # If this file is required to be in the feed.
13
+ def initialize(name, opts={})
14
+ @name, @columns = name, {}
15
+ @opts = { required: false }.merge (opts || {})
16
+ @aliases = {}
17
+ end
18
+
19
+ #@return [Boolean] If this file is required to be in the feed.
20
+ def required?
21
+ @opts[:required]
22
+ end
23
+
24
+ #@return [String] The filename of this file within the GTFS feed.
25
+ def filename
26
+ "#{name}.txt"
27
+ end
28
+
29
+ #@return [Column] The column with the given name
30
+ def [](name)
31
+ @columns[name]
32
+ end
33
+
34
+ def columns
35
+ @columns.values
36
+ end
37
+
38
+ #@return [Array<Column>] The columns required to appear in this file.
39
+ def required_columns
40
+ columns.select &:required?
41
+ end
42
+
43
+ #@return [Array<Column>] The columns not required to appear in this file.
44
+ def optional_columns
45
+ columns.reject &:required?
46
+ end
47
+
48
+ #@return [Array<Column>] The columns which cannot have two rows with the
49
+ # same value.
50
+ def unique_columns
51
+ columns.select &:unique?
52
+ end
53
+
54
+ # Creates a column with the given name.
55
+ #
56
+ #@param name [String] The name of the column to define.
57
+ #@param args [Array] The first element of this args list is used as a
58
+ # +Hash+ of options to create the new column with.
59
+ #@param block [Proc] An optional block used to parse the values of this
60
+ # column on each row.
61
+ #@yieldparam input [String] The value of this column for a particular row.
62
+ #@yieldreturn Any kind of object.
63
+ #@return [Column] The newly created column.
64
+ def col(name, *args, &block)
65
+ name = @aliases[name] if @aliases.key? name
66
+
67
+ if @columns.key? name
68
+ @columns[name].parser &block if block_given?
69
+ return @columns[name]
70
+ end
71
+
72
+ (@columns[name] = Column.new name, args.first, &block).tap do |col|
73
+ @aliases[col.alias] = name if col.alias
74
+ end
75
+ end
76
+
77
+ # Starts a new block within which any defined columns will have the given
78
+ # +sym+ prefixed to its name (joined with an underscore). Also, the
79
+ # defined name given within the block will be aliased to the column.
80
+ #@param sym the prefix to prefixed to each column within the block
81
+ #
82
+ #@example Create a column +route_name+ with the alias +name+
83
+ # prefix( :route ) { name }
84
+ def prefix(sym, &blk)
85
+ PrefixedColumnSetter.new(self, sym.to_s).instance_exec &blk
86
+ end
87
+
88
+ # Creates an input-output proc to convert column values from one form to
89
+ # another.
90
+ #
91
+ # Many parser procs simply map a set of known values to another set of
92
+ # known values. This helper creates such a proc from a given hash and
93
+ # optional default.
94
+ #
95
+ #@param default [] The value to return if there is no mapping for a given
96
+ # input.
97
+ #@param reverse_map [Hash] A map of returns values to their input values.
98
+ # This is in reverse because it looks better, like a list of labels:
99
+ # +{bus: 3, ferry: 4}+
100
+ def output_map(default=nil, reverse_map)
101
+ if reverse_map.values.uniq.length != reverse_map.values.length
102
+ raise FileDefinitionError, "Duplicate values given: #{reverse_map}"
103
+ end
104
+
105
+ map = default.nil? ? {} : Hash.new( default )
106
+ reverse_map.each { |k,v| map[v] = k }
107
+ map.method( :[] ).to_proc
108
+ end
109
+ end
110
+ end
111
+ end