gtfs_df 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.conform.yaml +25 -0
  3. data/.envrc +12 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +2 -0
  6. data/.solargraph.yml +26 -0
  7. data/.standard.yml +3 -0
  8. data/CHANGELOG.md +3 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +105 -0
  11. data/Rakefile +10 -0
  12. data/devenv.lock +171 -0
  13. data/devenv.nix +13 -0
  14. data/devenv.yaml +8 -0
  15. data/examples/split-by-agency/.gitignore +1 -0
  16. data/examples/split-by-agency/Gemfile +5 -0
  17. data/examples/split-by-agency/Gemfile.lock +52 -0
  18. data/examples/split-by-agency/README.md +26 -0
  19. data/examples/split-by-agency/split_by_agency.rb +52 -0
  20. data/lib/gtfs_df/base_gtfs_table.rb +45 -0
  21. data/lib/gtfs_df/feed.rb +152 -0
  22. data/lib/gtfs_df/graph.rb +131 -0
  23. data/lib/gtfs_df/reader.rb +28 -0
  24. data/lib/gtfs_df/schema/agency.rb +26 -0
  25. data/lib/gtfs_df/schema/areas.rb +18 -0
  26. data/lib/gtfs_df/schema/attributions.rb +28 -0
  27. data/lib/gtfs_df/schema/booking_rules.rb +19 -0
  28. data/lib/gtfs_df/schema/calendar.rb +32 -0
  29. data/lib/gtfs_df/schema/calendar_dates.rb +19 -0
  30. data/lib/gtfs_df/schema/enum_values.rb +147 -0
  31. data/lib/gtfs_df/schema/fare_attributes.rb +25 -0
  32. data/lib/gtfs_df/schema/fare_leg_join_rules.rb +20 -0
  33. data/lib/gtfs_df/schema/fare_leg_rules.rb +21 -0
  34. data/lib/gtfs_df/schema/fare_media.rb +18 -0
  35. data/lib/gtfs_df/schema/fare_products.rb +24 -0
  36. data/lib/gtfs_df/schema/fare_rules.rb +19 -0
  37. data/lib/gtfs_df/schema/fare_transfer_rules.rb +23 -0
  38. data/lib/gtfs_df/schema/feed_info.rb +21 -0
  39. data/lib/gtfs_df/schema/frequencies.rb +22 -0
  40. data/lib/gtfs_df/schema/levels.rb +15 -0
  41. data/lib/gtfs_df/schema/location_group_stops.rb +17 -0
  42. data/lib/gtfs_df/schema/location_groups.rb +17 -0
  43. data/lib/gtfs_df/schema/networks.rb +17 -0
  44. data/lib/gtfs_df/schema/pathways.rb +29 -0
  45. data/lib/gtfs_df/schema/rider_categories.rb +18 -0
  46. data/lib/gtfs_df/schema/route_networks.rb +17 -0
  47. data/lib/gtfs_df/schema/routes.rb +33 -0
  48. data/lib/gtfs_df/schema/shapes.rb +24 -0
  49. data/lib/gtfs_df/schema/stop_areas.rb +19 -0
  50. data/lib/gtfs_df/schema/stop_attributes.rb +17 -0
  51. data/lib/gtfs_df/schema/stop_times.rb +38 -0
  52. data/lib/gtfs_df/schema/stops.rb +34 -0
  53. data/lib/gtfs_df/schema/transfers.rb +20 -0
  54. data/lib/gtfs_df/schema/translations.rb +24 -0
  55. data/lib/gtfs_df/schema/trips.rb +30 -0
  56. data/lib/gtfs_df/schema_validator.rb +89 -0
  57. data/lib/gtfs_df/utils.rb +52 -0
  58. data/lib/gtfs_df/version.rb +5 -0
  59. data/lib/gtfs_df/writer.rb +26 -0
  60. data/lib/gtfs_df.rb +49 -0
  61. data/sig/gtfs-df.rbs +4 -0
  62. metadata +148 -0
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GtfsDf
4
+ class Feed
5
+ GTFS_FILES = %w[
6
+ agency
7
+ stops
8
+ routes
9
+ trips
10
+ stop_times
11
+ calendar
12
+ calendar_dates
13
+ pathways
14
+ levels
15
+ feed_info
16
+ shapes
17
+ frequencies
18
+ transfers
19
+ fare_attributes
20
+ fare_rules
21
+ attributions
22
+ translations
23
+ stop_areas
24
+ stop_attributes
25
+ rider_categories
26
+ fare_media
27
+ fare_products
28
+ fare_leg_rules
29
+ fare_leg_join_rules
30
+ fare_transfer_rules
31
+ areas
32
+ networks
33
+ route_networks
34
+ location_groups
35
+ location_group_stops
36
+ booking_rules
37
+ ].freeze
38
+
39
+ attr_reader(*GTFS_FILES)
40
+
41
+ # Initialize with a hash of DataFrames
42
+ REQUIRED_GTFS_FILES = %w[agency stops routes trips stop_times].freeze
43
+
44
+ def initialize(data = {})
45
+ missing = REQUIRED_GTFS_FILES.reject { |file| data[file].is_a?(Polars::DataFrame) }
46
+ # At least one of calendar or calendar_dates must be present
47
+ unless data["calendar"].is_a?(Polars::DataFrame) || data["calendar_dates"].is_a?(Polars::DataFrame)
48
+ missing << "calendar.txt or calendar_dates.txt"
49
+ end
50
+ unless missing.empty?
51
+ raise GtfsDf::Error, "Missing required GTFS files: #{missing.map do |f|
52
+ f.end_with?(".txt") ? f : f + ".txt"
53
+ end.join(", ")}"
54
+ end
55
+
56
+ GTFS_FILES.each do |file|
57
+ df = data[file]
58
+ schema_class_name = file.split("_").map(&:capitalize).join
59
+ schema_class = begin
60
+ GtfsDf::Schema.const_get(schema_class_name)
61
+ rescue
62
+ nil
63
+ end
64
+ if df.is_a?(Polars::DataFrame) && schema_class && schema_class.const_defined?(:SCHEMA)
65
+ df = schema_class.new(df).df
66
+ end
67
+ instance_variable_set("@#{file}", df.is_a?(Polars::DataFrame) ? df : nil)
68
+ end
69
+ end
70
+
71
+ # Load from a directory of GTFS CSV files
72
+ def self.load_from_dir(dir)
73
+ data = {}
74
+ GTFS_FILES.each do |file|
75
+ path = File.join(dir, "#{file}.txt")
76
+ next unless File.exist?(path)
77
+
78
+ schema_class_name = file.split("_").map(&:capitalize).join
79
+
80
+ data[file] = GtfsDf::Schema.const_get(schema_class_name).new(path)
81
+ end
82
+ new(data)
83
+ end
84
+
85
+ # Filter the feed using a view hash
86
+ # Example view: { 'routes' => { 'route_id' => '123' }, 'trips' => { 'service_id' => 'A' } }
87
+ def filter(view)
88
+ filtered = {}
89
+ graph = GtfsDf::Graph.build
90
+ # Step 1: Apply view filters
91
+ GTFS_FILES.each do |file|
92
+ df = send(file)
93
+ next unless df
94
+
95
+ filters = view[file]
96
+ if filters && !filters.empty?
97
+ filters.each do |col, val|
98
+ df = if val.is_a?(Array)
99
+ df.filter(Polars.col(col).is_in(val))
100
+ elsif val.respond_to?(:call)
101
+ df.filter(val.call(Polars.col(col)))
102
+ else
103
+ df.filter(Polars.col(col).eq(val))
104
+ end
105
+ end
106
+ end
107
+ filtered[file] = df
108
+ end
109
+ # Step 2: Cascade filters following the directed edges
110
+ # An edge from parent->child means: filter child based on valid parent IDs
111
+ changed = true
112
+ while changed
113
+ changed = false
114
+ GTFS_FILES.each do |parent_file|
115
+ parent_df = filtered[parent_file]
116
+ next unless parent_df && parent_df.height > 0
117
+
118
+ # For each outgoing edge from parent_file to child_file
119
+ graph.adj[parent_file]&.each do |child_file, attrs|
120
+ child_df = filtered[child_file]
121
+ next unless child_df && child_df.height > 0
122
+
123
+ attrs[:dependencies].each do |dep|
124
+ parent_col = dep[parent_file]
125
+ child_col = dep[child_file]
126
+
127
+ next unless parent_col && child_col &&
128
+ parent_df.columns.include?(parent_col) && child_df.columns.include?(child_col)
129
+
130
+ # Get valid values from parent
131
+ valid_values = parent_df[parent_col].to_a.uniq.compact
132
+ next if valid_values.empty?
133
+
134
+ # Filter child to only include rows that reference valid parent values
135
+ before = child_df.height
136
+ child_df = child_df.filter(Polars.col(child_col).is_in(valid_values))
137
+
138
+ if child_df.height < before
139
+ filtered[child_file] = child_df
140
+ changed = true
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ # Remove files that are empty, but keep required files even if empty
148
+ filtered.delete_if { |file, df| (!df || df.height == 0) && !REQUIRED_GTFS_FILES.include?(file) }
149
+ self.class.new(filtered)
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GtfsDf
4
+ class Graph
5
+ # Returns a directed graph of GTFS file dependencies
6
+ def self.build
7
+ g = NetworkX::DiGraph.new
8
+ # Nodes: GTFS files
9
+ files = %w[
10
+ agency routes trips stop_times stops calendar calendar_dates shapes transfers frequencies fare_attributes fare_rules
11
+ fare_leg_join_rules fare_transfer_rules areas networks route_networks location_groups location_group_stops booking_rules
12
+ ]
13
+ files.each { |f| g.add_node(f) }
14
+
15
+ # Edges: dependencies
16
+ edges = [
17
+ ["agency", "routes", {dependencies: [
18
+ {"agency" => "agency_id", "routes" => "agency_id"}
19
+ ]}],
20
+ ["fare_attributes", "fare_rules", {dependencies: [
21
+ {"fare_attributes" => "fare_id",
22
+ "fare_rules" => "fare_id"}
23
+ ]}],
24
+ ["fare_rules", "routes", {dependencies: [
25
+ {"fare_rules" => "route_id", "routes" => "route_id"}
26
+ ]}],
27
+ ["routes", "trips", {dependencies: [
28
+ {"routes" => "route_id", "trips" => "route_id"}
29
+ ]}],
30
+ ["trips", "stop_times", {dependencies: [
31
+ {"trips" => "trip_id", "stop_times" => "trip_id"}
32
+ ]}],
33
+ ["stop_times", "stops", {dependencies: [
34
+ {"stop_times" => "stop_id", "stops" => "stop_id"}
35
+ ]}],
36
+ ["stops", "transfers", {dependencies: [
37
+ {"stops" => "stop_id", "transfers" => "from_stop_id"},
38
+ {"stops" => "stop_id", "transfers" => "to_stop_id"}
39
+ ]}],
40
+ ["trips", "calendar", {dependencies: [
41
+ {"trips" => "service_id", "calendar" => "service_id"}
42
+ ]}],
43
+ ["trips", "calendar_dates", {dependencies: [
44
+ {"trips" => "service_id", "calendar_dates" => "service_id"}
45
+ ]}],
46
+ ["trips", "shapes", {dependencies: [
47
+ {"trips" => "shape_id", "shapes" => "shape_id"}
48
+ ]}],
49
+ ["trips", "frequencies", {dependencies: [
50
+ {"trips" => "trip_id", "frequencies" => "trip_id"}
51
+ ]}],
52
+
53
+ # --- GTFS Extensions ---
54
+ ["stops", "fare_leg_join_rules",
55
+ {dependencies: [
56
+ {"stops" => "stop_id", "fare_leg_join_rules" => "from_stop_id"},
57
+ {"stops" => "stop_id", "fare_leg_join_rules" => "to_stop_id"}
58
+ ]}],
59
+ ["fare_leg_join_rules", "networks", {dependencies: [
60
+ {"fare_leg_join_rules" => "from_network_id", "networks" => "network_id"},
61
+ {"fare_leg_join_rules" => "to_network_id", "networks" => "network_id"}
62
+ ]}],
63
+ ["fare_leg_join_rules", "fare_leg_rules",
64
+ {dependencies: [
65
+ {"fare_leg_join_rules" => "fare_leg_rule_id", "fare_leg_rules" => "fare_leg_rule_id"}
66
+ ]}],
67
+ ["fare_transfer_rules", "fare_leg_rules",
68
+ {dependencies: [
69
+ {"fare_transfer_rules" => "from_leg_group_id", "fare_leg_rules" => "leg_group_id"},
70
+ {"fare_transfer_rules" => "to_leg_group_id", "fare_leg_rules" => "leg_group_id"}
71
+ ]}],
72
+ ["fare_transfer_rules", "fare_products",
73
+ {dependencies: [
74
+ {"fare_transfer_rules" => "fare_product_id", "fare_products" => "fare_product_id"}
75
+ ]}],
76
+ ["areas", "stop_areas", {dependencies: [
77
+ {"areas" => "area_id", "stop_areas" => "area_id"}
78
+ ]}],
79
+ ["stops", "areas", {dependencies: [
80
+ {"stops" => "area_id", "areas" => "area_id"}
81
+ ]}],
82
+ ["areas", "fare_leg_rules", {dependencies: [
83
+ {"areas" => "area_id", "fare_leg_rules" => "from_area_id"},
84
+ {"areas" => "area_id", "fare_leg_rules" => "to_area_id"}
85
+ ]}],
86
+ ["networks", "route_networks", {dependencies: [
87
+ {"networks" => "network_id", "route_networks" => "network_id"}
88
+ ]}],
89
+ ["networks", "routes", {dependencies: [
90
+ {"networks" => "network_id", "routes" => "network_id"}
91
+ ]}],
92
+ ["networks", "fare_leg_rules", {dependencies: [
93
+ {"networks" => "network_id", "fare_leg_rules" => "network_id"}
94
+ ]}],
95
+ ["route_networks", "routes", {dependencies: [
96
+ {"route_networks" => "route_id", "routes" => "route_id"}
97
+ ]}],
98
+ ["route_networks", "networks", {dependencies: [
99
+ {"route_networks" => "network_id", "networks" => "network_id"}
100
+ ]}],
101
+ ["location_groups", "location_group_stops", {dependencies: [
102
+ {"location_groups" => "location_group_id", "location_group_stops" => "location_group_id"}
103
+ ]}],
104
+ ["location_groups", "stops", {dependencies: [
105
+ {"location_groups" => "location_group_id", "stops" => "location_group_id"}
106
+ ]}],
107
+ ["location_group_stops", "stops", {dependencies: [
108
+ {"location_group_stops" => "stop_id", "stops" => "stop_id"}
109
+ ]}],
110
+ ["stops", "location_group_stops", {dependencies: [
111
+ {"stops" => "stop_id", "location_group_stops" => "stop_id"}
112
+ ]}],
113
+ ["location_group_stops", "location_groups", {dependencies: [
114
+ {"location_group_stops" => "location_group_id", "location_groups" => "location_group_id"}
115
+ ]}],
116
+ ["booking_rules", "stop_times", {dependencies: [
117
+ {"booking_rules" => "booking_rule_id", "stop_times" => "pickup_booking_rule_id"},
118
+ {"booking_rules" => "booking_rule_id", "stop_times" => "drop_off_booking_rule_id"}
119
+ ]}],
120
+ ["stops", "booking_rules", {dependencies: [
121
+ {"stops" => "stop_id", "booking_rules" => "stop_id"}
122
+ ]}]
123
+ ]
124
+
125
+ edges.each do |from, to, attrs|
126
+ g.add_edge(from, to, **attrs)
127
+ end
128
+ g
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GtfsDf
4
+ class Reader
5
+ # Loads a GTFS zip file and returns a Feed
6
+ def self.load_from_zip(zip_path)
7
+ data = {}
8
+ Dir.mktmpdir do |tmpdir|
9
+ Zip::File.open(zip_path) do |zip_file|
10
+ zip_file.each do |entry|
11
+ next unless entry.file?
12
+
13
+ GtfsDf::Feed::GTFS_FILES.each do |file|
14
+ next unless entry.name == "#{file}.txt"
15
+
16
+ out_path = File.join(tmpdir, entry.name)
17
+ entry.extract(out_path)
18
+ schema_class_name = file.split("_").map(&:capitalize).join
19
+
20
+ data[file] = GtfsDf::Schema.const_get(schema_class_name).new(out_path).df
21
+ end
22
+ end
23
+ end
24
+ end
25
+ GtfsDf::Feed.new(data)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GtfsDf
4
+ module Schema
5
+ class Agency < BaseGtfsTable
6
+ SCHEMA = {
7
+ "agency_id" => Polars::String,
8
+ "agency_name" => Polars::String,
9
+ "agency_url" => Polars::String,
10
+ "agency_timezone" => Polars::String,
11
+ "agency_lang" => Polars::String,
12
+ "agency_phone" => Polars::String,
13
+ "agency_fare_url" => Polars::String,
14
+ "agency_email" => Polars::String,
15
+ "ticketing_deep_link_id" => Polars::String, # Google extension, optional
16
+ "cemv_support" => Polars::Enum.new(EnumValues::CEMV_SUPPORT.map(&:first)) # GTFS extension, optional
17
+ }
18
+
19
+ REQUIRED_FIELDS = %w[agency_name agency_url agency_timezone].freeze
20
+
21
+ ENUM_VALUE_MAP = {
22
+ "cemv_support" => :CEMV_SUPPORT
23
+ }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GtfsDf
4
+ module Schema
5
+ class Areas < BaseGtfsTable
6
+ SCHEMA = {
7
+ "area_id" => Polars::String,
8
+ "area_name" => Polars::String,
9
+ "area_type" => Polars::String
10
+ }.freeze
11
+
12
+ REQUIRED_FIELDS = %w[
13
+ area_id
14
+ area_name
15
+ ].freeze
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GtfsDf
4
+ module Schema
5
+ class Attributions < BaseGtfsTable
6
+ SCHEMA = {
7
+ "attribution_id" => Polars::String,
8
+ "agency_id" => Polars::String,
9
+ "route_id" => Polars::String,
10
+ "trip_id" => Polars::String,
11
+ "organization_name" => Polars::String,
12
+ "is_producer" => Polars::Int64,
13
+ "is_operator" => Polars::Int64,
14
+ "is_authority" => Polars::Int64,
15
+ "attribution_url" => Polars::String,
16
+ "attribution_email" => Polars::String,
17
+ "attribution_phone" => Polars::String
18
+ }.freeze
19
+
20
+ REQUIRED_FIELDS = %w[
21
+ organization_name
22
+ is_producer
23
+ is_operator
24
+ is_authority
25
+ ].freeze
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GtfsDf
4
+ module Schema
5
+ class BookingRules < BaseGtfsTable
6
+ SCHEMA = {
7
+ "booking_rule_id" => Polars::String,
8
+ "booking_type" => Polars::String,
9
+ "min_advance_book_time" => Polars::Int64,
10
+ "max_advance_book_time" => Polars::Int64
11
+ }.freeze
12
+
13
+ REQUIRED_FIELDS = %w[
14
+ booking_rule_id
15
+ booking_type
16
+ ].freeze
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GtfsDf
4
+ module Schema
5
+ class Calendar < BaseGtfsTable
6
+ SCHEMA = {
7
+ "service_id" => Polars::String,
8
+ "monday" => Polars::Enum.new(EnumValues::SERVICE_DAY.map(&:first)),
9
+ "tuesday" => Polars::Enum.new(%w[0 1]),
10
+ "wednesday" => Polars::Enum.new(%w[0 1]),
11
+ "thursday" => Polars::Enum.new(EnumValues::SERVICE_DAY.map(&:first)),
12
+ "friday" => Polars::Enum.new(%w[0 1]),
13
+ "saturday" => Polars::Enum.new(%w[0 1]),
14
+ "sunday" => Polars::Enum.new(%w[0 1]),
15
+ "start_date" => Polars::String,
16
+ "end_date" => Polars::String
17
+ }
18
+
19
+ REQUIRED_FIELDS = %w[service_id monday tuesday wednesday thursday friday saturday sunday start_date end_date]
20
+
21
+ ENUM_VALUE_MAP = {
22
+ "monday" => :SERVICE_DAY,
23
+ "tuesday" => :SERVICE_DAY,
24
+ "wednesday" => :SERVICE_DAY,
25
+ "thursday" => :SERVICE_DAY,
26
+ "friday" => :SERVICE_DAY,
27
+ "saturday" => :SERVICE_DAY,
28
+ "sunday" => :SERVICE_DAY
29
+ }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GtfsDf
4
+ module Schema
5
+ class CalendarDates < BaseGtfsTable
6
+ SCHEMA = {
7
+ "service_id" => Polars::String,
8
+ "date" => Polars::String,
9
+ "exception_type" => Polars::Enum.new(EnumValues::EXCEPTION_TYPE.map(&:first))
10
+ }.freeze
11
+
12
+ REQUIRED_FIELDS = %w[service_id date exception_type].freeze
13
+
14
+ ENUM_VALUE_MAP = {
15
+ "exception_type" => :EXCEPTION_TYPE
16
+ }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GtfsDf
4
+ module Schema
5
+ module EnumValues
6
+ # trips.txt
7
+ # direction_id: Indicates the direction of travel for a trip.
8
+ DIRECTION_ID = [
9
+ ["0", "Outbound travel"],
10
+ ["1", "Inbound travel"]
11
+ ]
12
+
13
+ # wheelchair_accessible: Indicates wheelchair accessibility.
14
+ WHEELCHAIR_ACCESSIBLE = [
15
+ ["0", "No accessibility information"],
16
+ ["1", "Vehicle can accommodate at least one rider in a wheelchair"],
17
+ ["2", "No riders in wheelchairs can be accommodated"]
18
+ ]
19
+
20
+ # bikes_allowed: Indicates whether bikes are allowed.
21
+ BIKES_ALLOWED = [
22
+ ["0", "No bike information"],
23
+ ["1", "Vehicle can accommodate at least one bicycle"],
24
+ ["2", "No bicycles allowed"]
25
+ ]
26
+
27
+ # cars_allowed: Indicates whether cars are allowed.
28
+ CARS_ALLOWED = [
29
+ ["0", "No car information"],
30
+ ["1", "Vehicle can accommodate at least one car"],
31
+ ["2", "No cars allowed"]
32
+ ]
33
+
34
+ # stop_times.txt
35
+ # pickup_type/drop_off_type: Pickup/drop off method.
36
+ PICKUP_TYPE = [
37
+ ["0", "Regularly scheduled pickup"],
38
+ ["1", "No pickup available"],
39
+ ["2", "Must phone agency to arrange pickup"],
40
+ ["3", "Must coordinate with driver to arrange pickup"]
41
+ ]
42
+ DROP_OFF_TYPE = [
43
+ ["0", "Regularly scheduled drop off"],
44
+ ["1", "No drop off available"],
45
+ ["2", "Must phone agency to arrange drop off"],
46
+ ["3", "Must coordinate with driver to arrange drop off"]
47
+ ]
48
+
49
+ # continuous_pickup/continuous_drop_off: Continuous stopping behavior.
50
+ CONTINUOUS_PICKUP = [
51
+ ["0", "Continuous stopping pickup"],
52
+ ["1", "No continuous stopping pickup"],
53
+ ["2", "Must phone agency to arrange continuous stopping pickup"],
54
+ ["3", "Must coordinate with driver to arrange continuous stopping pickup"]
55
+ ]
56
+ CONTINUOUS_DROP_OFF = [
57
+ ["0", "Continuous stopping drop off"],
58
+ ["1", "No continuous stopping drop off"],
59
+ ["2", "Must phone agency to arrange continuous stopping drop off"],
60
+ ["3", "Must coordinate with driver to arrange continuous stopping drop off"]
61
+ ]
62
+
63
+ # timepoint: Indicates if times are exact or approximate.
64
+ TIMEPOINT = [
65
+ ["0", "Times are approximate"],
66
+ ["1", "Times are exact"]
67
+ ]
68
+
69
+ # calendar.txt
70
+ # Service days: 1 = service available, 0 = not available
71
+ SERVICE_DAY = [
72
+ ["0", "Service not available"],
73
+ ["1", "Service available"]
74
+ ]
75
+
76
+ # calendar_dates.txt
77
+ # exception_type: Indicates whether service is added or removed for a date.
78
+ EXCEPTION_TYPE = [
79
+ ["1", "Service added for the date"],
80
+ ["2", "Service removed for the date"]
81
+ ]
82
+
83
+ # stops.txt
84
+ # location_type: Type of location
85
+ LOCATION_TYPE = [
86
+ ["0", "Stop or platform"],
87
+ ["1", "Station"],
88
+ ["2", "Entrance/Exit"],
89
+ ["3", "Generic Node"],
90
+ ["4", "Boarding Area"]
91
+ ]
92
+
93
+ # wheelchair_boarding: Indicates wheelchair boarding possibility
94
+ WHEELCHAIR_BOARDING = [
95
+ ["0", "No accessibility information"],
96
+ ["1", "Some vehicles can be boarded by a rider in a wheelchair"],
97
+ ["2", "Wheelchair boarding not possible"]
98
+ ]
99
+
100
+ # stop_access: How the stop is accessed
101
+ STOP_ACCESS = [
102
+ ["0", "Cannot be directly accessed from street network"],
103
+ ["1", "Direct access from street network"]
104
+ ]
105
+
106
+ # routes.txt
107
+ # route_type: Type of transportation
108
+ ROUTE_TYPE = [
109
+ ["0", "Tram, Streetcar, Light rail"],
110
+ ["1", "Subway, Metro"],
111
+ ["2", "Rail"],
112
+ ["3", "Bus"],
113
+ ["4", "Ferry"],
114
+ ["5", "Cable tram"],
115
+ ["6", "Aerial lift, suspended cable car"],
116
+ ["7", "Funicular"],
117
+ ["11", "Trolleybus"],
118
+ ["12", "Monorail"]
119
+ ]
120
+
121
+ # cemv_support: Contactless EMV support
122
+ CEMV_SUPPORT = [
123
+ ["0", "No cEMV information"],
124
+ ["1", "Riders may use cEMVs as fare media"],
125
+ ["2", "cEMVs are not supported"]
126
+ ]
127
+
128
+ # pathways.txt
129
+ # pathway_mode: Type of pathway
130
+ PATHWAY_MODE = [
131
+ ["1", "Walkway"],
132
+ ["2", "Stairs"],
133
+ ["3", "Moving sidewalk/travelator"],
134
+ ["4", "Escalator"],
135
+ ["5", "Elevator"],
136
+ ["6", "Fare gate"],
137
+ ["7", "Exit gate"]
138
+ ]
139
+
140
+ # is_bidirectional: Directionality of pathway
141
+ IS_BIDIRECTIONAL = [
142
+ %w[0 Unidirectional],
143
+ %w[1 Bidirectional]
144
+ ]
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GtfsDf
4
+ module Schema
5
+ class FareAttributes < BaseGtfsTable
6
+ SCHEMA = {
7
+ "fare_id" => Polars::String,
8
+ "price" => Polars::Float64,
9
+ "currency_type" => Polars::String,
10
+ "payment_method" => Polars::Int64,
11
+ "transfers" => Polars::Int64,
12
+ "agency_id" => Polars::String,
13
+ "transfer_duration" => Polars::Int64
14
+ }.freeze
15
+
16
+ REQUIRED_FIELDS = %w[
17
+ fare_id
18
+ price
19
+ currency_type
20
+ payment_method
21
+ transfers
22
+ ].freeze
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GtfsDf
4
+ module Schema
5
+ class FareLegJoinRules < BaseGtfsTable
6
+ SCHEMA = {
7
+ "fare_leg_join_rule_id" => Polars::String,
8
+ "from_leg_group_id" => Polars::String,
9
+ "to_leg_group_id" => Polars::String,
10
+ "network_id" => Polars::String
11
+ }.freeze
12
+
13
+ REQUIRED_FIELDS = %w[
14
+ fare_leg_join_rule_id
15
+ from_leg_group_id
16
+ to_leg_group_id
17
+ ].freeze
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GtfsDf
4
+ module Schema
5
+ class FareLegRules < BaseGtfsTable
6
+ SCHEMA = {
7
+ "fare_leg_rule_id" => Polars::String,
8
+ "fare_product_id" => Polars::String,
9
+ "from_area_id" => Polars::String,
10
+ "to_area_id" => Polars::String,
11
+ "leg_group_id" => Polars::String,
12
+ "network_id" => Polars::String
13
+ }.freeze
14
+
15
+ REQUIRED_FIELDS = %w[
16
+ fare_leg_rule_id
17
+ fare_product_id
18
+ ].freeze
19
+ end
20
+ end
21
+ end