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.
- checksums.yaml +7 -0
- data/.conform.yaml +25 -0
- data/.envrc +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +2 -0
- data/.solargraph.yml +26 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +105 -0
- data/Rakefile +10 -0
- data/devenv.lock +171 -0
- data/devenv.nix +13 -0
- data/devenv.yaml +8 -0
- data/examples/split-by-agency/.gitignore +1 -0
- data/examples/split-by-agency/Gemfile +5 -0
- data/examples/split-by-agency/Gemfile.lock +52 -0
- data/examples/split-by-agency/README.md +26 -0
- data/examples/split-by-agency/split_by_agency.rb +52 -0
- data/lib/gtfs_df/base_gtfs_table.rb +45 -0
- data/lib/gtfs_df/feed.rb +152 -0
- data/lib/gtfs_df/graph.rb +131 -0
- data/lib/gtfs_df/reader.rb +28 -0
- data/lib/gtfs_df/schema/agency.rb +26 -0
- data/lib/gtfs_df/schema/areas.rb +18 -0
- data/lib/gtfs_df/schema/attributions.rb +28 -0
- data/lib/gtfs_df/schema/booking_rules.rb +19 -0
- data/lib/gtfs_df/schema/calendar.rb +32 -0
- data/lib/gtfs_df/schema/calendar_dates.rb +19 -0
- data/lib/gtfs_df/schema/enum_values.rb +147 -0
- data/lib/gtfs_df/schema/fare_attributes.rb +25 -0
- data/lib/gtfs_df/schema/fare_leg_join_rules.rb +20 -0
- data/lib/gtfs_df/schema/fare_leg_rules.rb +21 -0
- data/lib/gtfs_df/schema/fare_media.rb +18 -0
- data/lib/gtfs_df/schema/fare_products.rb +24 -0
- data/lib/gtfs_df/schema/fare_rules.rb +19 -0
- data/lib/gtfs_df/schema/fare_transfer_rules.rb +23 -0
- data/lib/gtfs_df/schema/feed_info.rb +21 -0
- data/lib/gtfs_df/schema/frequencies.rb +22 -0
- data/lib/gtfs_df/schema/levels.rb +15 -0
- data/lib/gtfs_df/schema/location_group_stops.rb +17 -0
- data/lib/gtfs_df/schema/location_groups.rb +17 -0
- data/lib/gtfs_df/schema/networks.rb +17 -0
- data/lib/gtfs_df/schema/pathways.rb +29 -0
- data/lib/gtfs_df/schema/rider_categories.rb +18 -0
- data/lib/gtfs_df/schema/route_networks.rb +17 -0
- data/lib/gtfs_df/schema/routes.rb +33 -0
- data/lib/gtfs_df/schema/shapes.rb +24 -0
- data/lib/gtfs_df/schema/stop_areas.rb +19 -0
- data/lib/gtfs_df/schema/stop_attributes.rb +17 -0
- data/lib/gtfs_df/schema/stop_times.rb +38 -0
- data/lib/gtfs_df/schema/stops.rb +34 -0
- data/lib/gtfs_df/schema/transfers.rb +20 -0
- data/lib/gtfs_df/schema/translations.rb +24 -0
- data/lib/gtfs_df/schema/trips.rb +30 -0
- data/lib/gtfs_df/schema_validator.rb +89 -0
- data/lib/gtfs_df/utils.rb +52 -0
- data/lib/gtfs_df/version.rb +5 -0
- data/lib/gtfs_df/writer.rb +26 -0
- data/lib/gtfs_df.rb +49 -0
- data/sig/gtfs-df.rbs +4 -0
- metadata +148 -0
data/lib/gtfs_df/feed.rb
ADDED
|
@@ -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
|