gtfs-parser 0.2.2__tar.gz → 0.3.0__tar.gz

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.
@@ -1,51 +1,92 @@
1
- # gtfs_parser
2
-
3
- ## LICENSE
4
-
5
- MIT License
6
-
7
- ## Installation
8
-
9
- ```sh
10
- pip install gtfs-parser
11
- ```
12
-
13
- ## API
14
-
15
- TODO
16
-
17
- ## CLI
18
-
19
- ```
20
- usage: gtfs-parser [-h] [--parse_ignoreshapes] [--parse_ignorenoroute]
21
- [--aggregate_yyyymmdd AGGREGATE_YYYYMMDD]
22
- [--aggregate_nounifystops]
23
- [--aggregate_delimiter AGGREGATE_DELIMITER]
24
- [--aggregate_begintime AGGREGATE_BEGINTIME]
25
- [--aggregate_endtime AGGREGATE_ENDTIME]
26
- mode src dst
27
-
28
- positional arguments:
29
- mode
30
- src
31
- dst
32
-
33
- optional arguments:
34
- -h, --help show this help message and exit
35
- --parse_ignoreshapes
36
- --parse_ignorenoroute
37
- --aggregate_yyyymmdd AGGREGATE_YYYYMMDD
38
- --aggregate_nounifystops
39
- --aggregate_delimiter AGGREGATE_DELIMITER
40
- --aggregate_begintime AGGREGATE_BEGINTIME
41
- --aggregate_endtime AGGREGATE_ENDTIME
42
- ```
43
-
44
- ### Example
45
-
46
- ```sh
47
- gtfs-parser parse gtfs.zip output
48
- gtfs-parser parse gtfs_dir output --parse_ignoreshapes
49
- gtfs-parser aggregate gtfs.zip output
50
- gtfs-parser aggregate gtfs_dir output --aggregate_nounifystops
51
- ```
1
+ Metadata-Version: 2.4
2
+ Name: gtfs_parser
3
+ Version: 0.3.0
4
+ Summary: parse and aggregate GTFS
5
+ Author: MIERUNE Inc., Kanahiro IGUCHI
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Dist: pandas>=1.3.3
9
+ Requires-Python: >=3.9
10
+ Project-URL: Homepage, https://github.com/MIERUNE
11
+ Project-URL: Repository, https://github.com/MIERUNE/gtfs-parser
12
+ Description-Content-Type: text/markdown
13
+
14
+ # gtfs_parser
15
+
16
+ ## LICENSE
17
+
18
+ MIT License
19
+
20
+ ## Installation
21
+
22
+ ```sh
23
+ pip install gtfs-parser
24
+ ```
25
+
26
+ ## API
27
+
28
+ ```python
29
+ import gtfs_parser
30
+
31
+ # construct GTFS object
32
+ gtfs = gtfs_parser.GTFSFactory(zip_path)
33
+
34
+ # parse as GeoJSON
35
+ stops = gtfs_parser.parse.read_stops(gtfs)
36
+ routes = gtfs_parser.parse.read_routes(gtfs)
37
+
38
+ # aggregate frequency
39
+ aggregator = gtfs_parser.aggregate.Aggregator(gtfs, yyyymmdd=yyyymmdd)
40
+ interpolated_stops = aggregator.read_interpolated_stops()
41
+ route_freq = aggregator.read_route_frequency()
42
+ ```
43
+
44
+ ## CLI
45
+
46
+ ```
47
+ usage: gtfs-parser [-h] [--parse_ignoreshapes] [--parse_ignorenoroute]
48
+ [--aggregate_yyyymmdd AGGREGATE_YYYYMMDD]
49
+ [--aggregate_nounifystops]
50
+ [--aggregate_delimiter AGGREGATE_DELIMITER]
51
+ [--aggregate_begintime AGGREGATE_BEGINTIME]
52
+ [--aggregate_endtime AGGREGATE_ENDTIME]
53
+ mode src dst
54
+
55
+ positional arguments:
56
+ mode
57
+ src
58
+ dst
59
+
60
+ optional arguments:
61
+ -h, --help show this help message and exit
62
+ --parse_ignoreshapes
63
+ --parse_ignorenoroute
64
+ --aggregate_yyyymmdd AGGREGATE_YYYYMMDD
65
+ --aggregate_nounifystops
66
+ --aggregate_delimiter AGGREGATE_DELIMITER
67
+ --aggregate_begintime AGGREGATE_BEGINTIME
68
+ --aggregate_endtime AGGREGATE_ENDTIME
69
+ ```
70
+
71
+ ### Example
72
+
73
+ ```sh
74
+ gtfs-parser parse gtfs.zip output
75
+ gtfs-parser parse gtfs_dir output --parse_ignoreshapes
76
+ gtfs-parser aggregate gtfs.zip output
77
+ gtfs-parser aggregate gtfs_dir output --aggregate_nounifystops
78
+ ```
79
+
80
+ ## Development
81
+
82
+ ```sh
83
+ uv sync
84
+ uv run pytest
85
+ uv run ruff check
86
+ uv run ruff format
87
+ ```
88
+
89
+ ## Authors
90
+
91
+ - Kanahiro Iguchi ([@Kanahiro](https://github.com/Kanahiro)) - original author
92
+ - Kohei Ota ([@takohei](https://github.com/takohei))
@@ -1,21 +1,3 @@
1
- Metadata-Version: 2.1
2
- Name: gtfs_parser
3
- Version: 0.2.2
4
- Summary: parse and aggregate GTFS
5
- Home-page: https://github.com/MIERUNE
6
- License: MIT
7
- Author: MIERUNE Inc.
8
- Requires-Python: >=3.9.0
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.9
12
- Classifier: Programming Language :: Python :: 3.10
13
- Classifier: Programming Language :: Python :: 3.11
14
- Classifier: Programming Language :: Python :: 3.12
15
- Requires-Dist: pandas (>=1.3.3)
16
- Project-URL: Repository, https://github.com/MIERUNE/gtfs-parser
17
- Description-Content-Type: text/markdown
18
-
19
1
  # gtfs_parser
20
2
 
21
3
  ## LICENSE
@@ -30,7 +12,21 @@ pip install gtfs-parser
30
12
 
31
13
  ## API
32
14
 
33
- TODO
15
+ ```python
16
+ import gtfs_parser
17
+
18
+ # construct GTFS object
19
+ gtfs = gtfs_parser.GTFSFactory(zip_path)
20
+
21
+ # parse as GeoJSON
22
+ stops = gtfs_parser.parse.read_stops(gtfs)
23
+ routes = gtfs_parser.parse.read_routes(gtfs)
24
+
25
+ # aggregate frequency
26
+ aggregator = gtfs_parser.aggregate.Aggregator(gtfs, yyyymmdd=yyyymmdd)
27
+ interpolated_stops = aggregator.read_interpolated_stops()
28
+ route_freq = aggregator.read_route_frequency()
29
+ ```
34
30
 
35
31
  ## CLI
36
32
 
@@ -67,3 +63,17 @@ gtfs-parser parse gtfs_dir output --parse_ignoreshapes
67
63
  gtfs-parser aggregate gtfs.zip output
68
64
  gtfs-parser aggregate gtfs_dir output --aggregate_nounifystops
69
65
  ```
66
+
67
+ ## Development
68
+
69
+ ```sh
70
+ uv sync
71
+ uv run pytest
72
+ uv run ruff check
73
+ uv run ruff format
74
+ ```
75
+
76
+ ## Authors
77
+
78
+ - Kanahiro Iguchi ([@Kanahiro](https://github.com/Kanahiro)) - original author
79
+ - Kohei Ota ([@takohei](https://github.com/takohei))
@@ -0,0 +1,4 @@
1
+ from . import aggregate, parse
2
+ from .gtfs import GTFS, GTFSFactory
3
+
4
+ __all__ = ["GTFS", "GTFSFactory", "aggregate", "parse"]
@@ -1,10 +1,10 @@
1
+ import argparse
1
2
  import json
2
3
  import os
3
- import argparse
4
4
 
5
+ from .aggregate import Aggregator
5
6
  from .gtfs import GTFSFactory
6
7
  from .parse import read_routes, read_stops
7
- from .aggregate import Aggregator
8
8
 
9
9
 
10
10
  def load_args():
@@ -24,12 +24,11 @@ def load_args():
24
24
 
25
25
 
26
26
  def validate_args(args):
27
- if args.aggregate_yyyymmdd:
28
- if len(args.aggregate_yyyymmdd) != 8:
29
- raise RuntimeError(
30
- f"yyyymmdd must be 8 characters string, for example 20210401, \
31
- your is {args.aggregate_yyyymmdd} ({len(args.aggregate_yyyymmdd)} characters)"
32
- )
27
+ if args.aggregate_yyyymmdd and len(args.aggregate_yyyymmdd) != 8:
28
+ raise RuntimeError(
29
+ f"yyyymmdd must be 8 characters string, for example 20210401, \
30
+ your is {args.aggregate_yyyymmdd} ({len(args.aggregate_yyyymmdd)} characters)"
31
+ )
33
32
 
34
33
  if args.aggregate_begintime:
35
34
  if len(args.aggregate_begintime) != 6:
@@ -19,6 +19,7 @@ class Aggregator:
19
19
  Returns:
20
20
  [type]: [description]
21
21
  """
22
+
22
23
  def __init__(
23
24
  self,
24
25
  gtfs: GTFS,
@@ -31,12 +32,18 @@ class Aggregator:
31
32
  ):
32
33
  self.gtfs = gtfs
33
34
 
34
- self.stop_times = Aggregator.__filter_stop_times(self.gtfs, yyyymmdd, begin_time, end_time)
35
+ self.stop_times = Aggregator.__filter_stop_times(
36
+ self.gtfs, yyyymmdd, begin_time, end_time
37
+ )
35
38
 
36
39
  if no_unify_stops:
37
- similar_results = Aggregator.__get_similar_stop_without_unifying(self.gtfs.stops)
40
+ similar_results = Aggregator.__get_similar_stop_without_unifying(
41
+ self.gtfs.stops
42
+ )
38
43
  else:
39
- similar_results = Aggregator.__unify_similar_stops(self.gtfs.stops, delimiter, max_distance_degree)
44
+ similar_results = Aggregator.__unify_similar_stops(
45
+ self.gtfs.stops, delimiter, max_distance_degree
46
+ )
40
47
  self.similar_stops, self.stop_relations = similar_results
41
48
 
42
49
  @staticmethod
@@ -54,12 +61,15 @@ class Aggregator:
54
61
  # departure_time is nullable and expressed in "hh:mm:ss" or "h:mm:ss" format.
55
62
  # Hour can be mor than 24.
56
63
  # Therefore, drop null records and convert times to integers.
57
- filtered_stop_times = filtered_stop_times[~filtered_stop_times["departure_time"].isnull()]
64
+ filtered_stop_times = filtered_stop_times[
65
+ ~filtered_stop_times["departure_time"].isnull()
66
+ ]
58
67
  int_dep_times = filtered_stop_times.departure_time.str.replace(
59
68
  ":", ""
60
69
  ).astype(int)
61
- filtered_stop_times = filtered_stop_times[(int_dep_times >= int(begin_time))
62
- & (int_dep_times < int(end_time))]
70
+ filtered_stop_times = filtered_stop_times[
71
+ (int_dep_times >= int(begin_time)) & (int_dep_times < int(end_time))
72
+ ]
63
73
  return filtered_stop_times
64
74
 
65
75
  @staticmethod
@@ -68,15 +78,16 @@ class Aggregator:
68
78
  stops = stops[stops["location_type"] == 0]
69
79
  similar_stops_centroid = stops[["stop_lon", "stop_lat"]].values.tolist()
70
80
 
71
- similar_stops = pd.DataFrame({
72
- "similar_stop_id": stops["stop_id"],
73
- "similar_stop_name": stops["stop_name"],
74
- "similar_stops_centroid": similar_stops_centroid,
75
- })
76
- similar_relations = pd.concat([
77
- stops["stop_id"],
78
- similar_stops["similar_stop_id"]
79
- ], axis=1)
81
+ similar_stops = pd.DataFrame(
82
+ {
83
+ "similar_stop_id": stops["stop_id"],
84
+ "similar_stop_name": stops["stop_name"],
85
+ "similar_stops_centroid": similar_stops_centroid,
86
+ }
87
+ )
88
+ similar_relations = pd.concat(
89
+ [stops["stop_id"], similar_stops["similar_stop_id"]], axis=1
90
+ )
80
91
  return similar_stops, similar_relations
81
92
 
82
93
  @staticmethod
@@ -96,10 +107,14 @@ class Aggregator:
96
107
  solo_stops = stops
97
108
 
98
109
  # unify solo stops
99
- solo_similar_stops, solo_id_pair = Aggregator.__unify_solo_stops(solo_stops, delimiter, max_distance_degree)
110
+ solo_similar_stops, solo_id_pair = Aggregator.__unify_solo_stops(
111
+ solo_stops, delimiter, max_distance_degree
112
+ )
100
113
 
101
114
  # concat similar stops
102
- return pd.concat([child_similar_stop, solo_similar_stops]), pd.concat([child_id_pair, solo_id_pair])
115
+ return pd.concat([child_similar_stop, solo_similar_stops]), pd.concat(
116
+ [child_id_pair, solo_id_pair]
117
+ )
103
118
 
104
119
  @staticmethod
105
120
  def __unify_child_stops(stops):
@@ -108,21 +123,28 @@ class Aggregator:
108
123
  & (stops["location_type"] == 0)
109
124
  ][["stop_id", "parent_station"]]
110
125
 
111
- child_id_pair.rename(columns={"parent_station": "similar_stop_id"}, inplace=True)
126
+ child_id_pair.rename(
127
+ columns={"parent_station": "similar_stop_id"}, inplace=True
128
+ )
112
129
 
113
130
  similar_ids = child_id_pair["similar_stop_id"].unique()
114
131
 
115
- similar_stops = stops[stops["stop_id"].isin(similar_ids)][["stop_id", "stop_name", "stop_lon", "stop_lat"]]
132
+ similar_stops = stops[stops["stop_id"].isin(similar_ids)][
133
+ ["stop_id", "stop_name", "stop_lon", "stop_lat"]
134
+ ]
116
135
 
117
136
  similar_stops["similar_stops_centroid"] = similar_stops[
118
137
  ["stop_lon", "stop_lat"]
119
138
  ].values.tolist()
120
139
  similar_stops.drop(columns=["stop_lon", "stop_lat"], inplace=True)
121
140
 
122
- similar_stops.rename(columns={
123
- "stop_id": "similar_stop_id",
124
- "stop_name": "similar_stop_name",
125
- }, inplace=True)
141
+ similar_stops.rename(
142
+ columns={
143
+ "stop_id": "similar_stop_id",
144
+ "stop_name": "similar_stop_name",
145
+ },
146
+ inplace=True,
147
+ )
126
148
 
127
149
  return similar_stops, child_id_pair
128
150
 
@@ -131,19 +153,28 @@ class Aggregator:
131
153
  delimited_id_pair = []
132
154
  if delimiter:
133
155
  # unify by delimiter
134
- stop_id_delimited = solo_stops["stop_id"].str.split(delimiter).str[0].rename("similar_stop_id")
135
- delimited_id_pair = pd.concat([solo_stops["stop_id"], stop_id_delimited], axis=1)[
136
- solo_stops["stop_id"] != stop_id_delimited
137
- ]
156
+ stop_id_delimited = (
157
+ solo_stops["stop_id"]
158
+ .str.split(delimiter)
159
+ .str[0]
160
+ .rename("similar_stop_id")
161
+ )
162
+ delimited_id_pair = pd.concat(
163
+ [solo_stops["stop_id"], stop_id_delimited], axis=1
164
+ )[solo_stops["stop_id"] != stop_id_delimited]
138
165
  if len(delimited_id_pair) == len(solo_stops):
139
166
  solo_id_pair = delimited_id_pair
140
167
  else:
141
168
  # unify by distance
142
169
  if len(delimited_id_pair) > 0:
143
- undelimited_stops = solo_stops[~solo_stops["stop_id"].isin(delimited_id_pair["stop_id"])]
170
+ undelimited_stops = solo_stops[
171
+ ~solo_stops["stop_id"].isin(delimited_id_pair["stop_id"])
172
+ ]
144
173
  else:
145
174
  undelimited_stops = solo_stops
146
- near_id_pair = Aggregator.__calc_near_id_pair(undelimited_stops, max_distance_degree)
175
+ near_id_pair = Aggregator.__calc_near_id_pair(
176
+ undelimited_stops, max_distance_degree
177
+ )
147
178
 
148
179
  if len(delimited_id_pair) == 0:
149
180
  solo_id_pair = near_id_pair
@@ -155,12 +186,14 @@ class Aggregator:
155
186
 
156
187
  # calc similar stop attributes
157
188
  solo_stops_with_similar = pd.merge(solo_stops, solo_id_pair, on="stop_id")
158
- solo_similar_stops = solo_stops_with_similar.groupby("similar_stop_id").agg({
159
- 'stop_name': 'min',
160
- 'stop_lon': 'mean',
161
- 'stop_lat': 'mean'
162
- }).reset_index()
163
- solo_similar_stops.rename(columns={"stop_name": "similar_stop_name"}, inplace=True)
189
+ solo_similar_stops = (
190
+ solo_stops_with_similar.groupby("similar_stop_id")
191
+ .agg({"stop_name": "min", "stop_lon": "mean", "stop_lat": "mean"})
192
+ .reset_index()
193
+ )
194
+ solo_similar_stops.rename(
195
+ columns={"stop_name": "similar_stop_name"}, inplace=True
196
+ )
164
197
  solo_similar_stops["similar_stops_centroid"] = solo_similar_stops[
165
198
  ["stop_lon", "stop_lat"]
166
199
  ].values.tolist()
@@ -170,15 +203,12 @@ class Aggregator:
170
203
  @staticmethod
171
204
  def __calc_near_id_pair(solo_stops, max_distance_degree):
172
205
  stop_matrix = pd.merge(
173
- solo_stops,
174
- solo_stops,
175
- on="stop_name",
176
- suffixes=("", "_r")
206
+ solo_stops, solo_stops, on="stop_name", suffixes=("", "_r")
177
207
  )
178
208
  near_matrix = stop_matrix[
179
209
  (stop_matrix["stop_lon"] - stop_matrix["stop_lon_r"]) ** 2
180
210
  + (stop_matrix["stop_lat"] - stop_matrix["stop_lat_r"]) ** 2
181
- <= max_distance_degree ** 2
211
+ <= max_distance_degree**2
182
212
  ]
183
213
 
184
214
  near_matrix = near_matrix[["stop_id", "stop_id_r"]]
@@ -192,16 +222,22 @@ class Aggregator:
192
222
  Join near groups of stops.
193
223
  Trace root stops up to 5 times and modify root id.
194
224
  """
225
+
195
226
  @staticmethod
196
227
  def __join_near_group(near_id_pair):
197
- for i in range(5):
198
- leaf_pair = near_id_pair.query("stop_id != stop_id_r")\
199
- .rename(columns={"stop_id": "stop_id_r", "stop_id_r": "stop_id_r2"})
200
- sub_pair = pd.merge(near_id_pair, leaf_pair, on="stop_id_r").drop(columns=["stop_id_r"])
228
+ for _ in range(5):
229
+ leaf_pair = near_id_pair.query("stop_id != stop_id_r").rename(
230
+ columns={"stop_id": "stop_id_r", "stop_id_r": "stop_id_r2"}
231
+ )
232
+ sub_pair = pd.merge(near_id_pair, leaf_pair, on="stop_id_r").drop(
233
+ columns=["stop_id_r"]
234
+ )
201
235
  if len(sub_pair) == 0:
202
236
  break
203
237
  mod_id_trio = pd.merge(near_id_pair, sub_pair, on="stop_id", how="left")
204
- mod_id_trio.loc[~mod_id_trio['stop_id_r2'].isna(), 'stop_id_r'] = mod_id_trio['stop_id_r2']
238
+ mod_id_trio.loc[~mod_id_trio["stop_id_r2"].isna(), "stop_id_r"] = (
239
+ mod_id_trio["stop_id_r2"]
240
+ )
205
241
  near_id_pair = mod_id_trio.drop(columns=["stop_id_r2"])
206
242
 
207
243
  return near_id_pair
@@ -209,14 +245,14 @@ class Aggregator:
209
245
  def read_interpolated_stops(self):
210
246
  stop_pass_count = self.stop_times.groupby("stop_id").size().rename("count")
211
247
  stop_pass_count = pd.merge(
212
- self.stop_relations,
213
- stop_pass_count,
214
- on="stop_id",
215
- how="left"
248
+ self.stop_relations, stop_pass_count, on="stop_id", how="left"
249
+ )
250
+ similar_pass_count = (
251
+ stop_pass_count.groupby("similar_stop_id")[["count"]].sum().astype(int)
252
+ )
253
+ similar_stop_summary = self.similar_stops.merge(
254
+ similar_pass_count, on="similar_stop_id"
216
255
  )
217
- similar_pass_count = stop_pass_count.groupby("similar_stop_id").sum("count").astype(int)
218
- similar_stop_summary = self.similar_stops.merge(similar_pass_count,
219
- on="similar_stop_id")
220
256
 
221
257
  stop_dicts = similar_stop_summary.to_dict(orient="records")
222
258
 
@@ -253,29 +289,29 @@ class Aggregator:
253
289
  stop_times_df = pd.merge(
254
290
  self.stop_times[["trip_id", "stop_sequence", "stop_id"]],
255
291
  self.stop_relations,
256
- on="stop_id"
292
+ on="stop_id",
257
293
  )
258
294
  # append agency_id
259
295
  trip_agency_df = pd.merge(
260
296
  self.gtfs.trips[["trip_id", "route_id"]],
261
297
  self.gtfs.routes[["route_id", "agency_id"]],
262
- on="route_id"
263
- )
264
- stop_times_df = pd.merge(
265
- stop_times_df,
266
- trip_agency_df,
267
- on="trip_id"
298
+ on="route_id",
268
299
  )
300
+ stop_times_df = pd.merge(stop_times_df, trip_agency_df, on="trip_id")
269
301
  stop_times_df = stop_times_df.sort_values(["trip_id", "stop_sequence"])
270
302
 
271
303
  # generate path by joining next stop_times
272
304
  stop_times_df["next_stop_id"] = stop_times_df["similar_stop_id"].shift(-1)
273
305
  stop_times_df["next_trip_id"] = stop_times_df["trip_id"].shift(-1)
274
- stop_times_df = stop_times_df[stop_times_df["trip_id"] == stop_times_df["next_trip_id"]]
306
+ stop_times_df = stop_times_df[
307
+ stop_times_df["trip_id"] == stop_times_df["next_trip_id"]
308
+ ]
275
309
  path_df = stop_times_df.rename(columns={"similar_stop_id": "prev_stop_id"})
276
310
 
277
311
  # count frequency
278
- path_freq_sr = path_df.groupby(["agency_id", "prev_stop_id", "next_stop_id"]).size()
312
+ path_freq_sr = path_df.groupby(
313
+ ["agency_id", "prev_stop_id", "next_stop_id"]
314
+ ).size()
279
315
  path_freq_sr.name = "frequency"
280
316
  path_freq_df = path_freq_sr.reset_index()
281
317
 
@@ -285,18 +321,19 @@ class Aggregator:
285
321
  path_freq_df,
286
322
  self.similar_stops,
287
323
  left_on=f"{order}_stop_id",
288
- right_on="similar_stop_id"
324
+ right_on="similar_stop_id",
325
+ )
326
+ path_freq_df.rename(
327
+ columns={
328
+ "similar_stop_name": f"{order}_stop_name",
329
+ "similar_stops_centroid": f"{order}_similar_stops_centroid",
330
+ },
331
+ inplace=True,
289
332
  )
290
- path_freq_df.rename(columns={
291
- "similar_stop_name": f"{order}_stop_name",
292
- "similar_stops_centroid": f"{order}_similar_stops_centroid"
293
- }, inplace=True)
294
333
  path_freq_df.drop(columns="similar_stop_id", inplace=True)
295
334
 
296
335
  path_freq_df = pd.merge(
297
- path_freq_df,
298
- self.gtfs.agency[["agency_id", "agency_name"]],
299
- on="agency_id"
336
+ path_freq_df, self.gtfs.agency[["agency_id", "agency_name"]], on="agency_id"
300
337
  )
301
338
  # convert to features
302
339
  path_freq_dict = path_freq_df.to_dict(orient="records")
@@ -322,7 +359,7 @@ class Aggregator:
322
359
  }
323
360
  for path in path_freq_dict
324
361
  ]
325
-
362
+
326
363
  @staticmethod
327
364
  def __get_trips_on_a_date(gtfs, yyyymmdd: str):
328
365
  """
@@ -346,12 +383,8 @@ class Aggregator:
346
383
  # generate an empty series if calendar.txt is missing because it is not required.
347
384
  service_ids_on = pd.Series(name="service_id", dtype=str)
348
385
  else:
349
- calendar = gtfs.calendar.astype(
350
- {"start_date": int, "end_date": int}
351
- )
352
- calendar = calendar[
353
- calendar[day_of_week] == "1"
354
- ]
386
+ calendar = gtfs.calendar.astype({"start_date": int, "end_date": int})
387
+ calendar = calendar[calendar[day_of_week] == "1"]
355
388
  calendar = calendar.query(
356
389
  f"start_date <= {int(yyyymmdd)} and {int(yyyymmdd)} <= end_date",
357
390
  engine="python",
@@ -360,9 +393,9 @@ class Aggregator:
360
393
 
361
394
  # filter services by dates
362
395
  if gtfs.calendar_dates is not None:
363
- filtered = gtfs.calendar_dates[
364
- gtfs.calendar_dates["date"] == yyyymmdd
365
- ][["service_id", "exception_type"]]
396
+ filtered = gtfs.calendar_dates[gtfs.calendar_dates["date"] == yyyymmdd][
397
+ ["service_id", "exception_type"]
398
+ ]
366
399
  to_be_removed_service_ids = filtered[filtered["exception_type"] == "2"][
367
400
  "service_id"
368
401
  ]
@@ -376,23 +409,20 @@ class Aggregator:
376
409
  service_ids_on = pd.concat([service_ids_on, to_be_appended_services_ids])
377
410
 
378
411
  # filter trips
379
- trips_in_services = gtfs.trips[
380
- gtfs.trips["service_id"].isin(service_ids_on)
381
- ]
412
+ trips_in_services = gtfs.trips[gtfs.trips["service_id"].isin(service_ids_on)]
382
413
 
383
414
  return trips_in_services["trip_id"]
384
415
 
385
416
  def read_stop_relations(self) -> list:
386
417
  stop_relation_df = pd.merge(
387
- self.stop_relations,
388
- self.gtfs.stops[["stop_id", "stop_name"]],
389
- on="stop_id"
418
+ self.stop_relations, self.gtfs.stops[["stop_id", "stop_name"]], on="stop_id"
390
419
  )
391
420
  stop_relation_df = pd.merge(
392
421
  stop_relation_df,
393
422
  self.similar_stops,
394
423
  on="similar_stop_id",
395
424
  )
396
- stop_relation_df = stop_relation_df.reindex(columns=["stop_id", "stop_name",
397
- "similar_stop_id", "similar_stop_name"])
425
+ stop_relation_df = stop_relation_df.reindex(
426
+ columns=["stop_id", "stop_name", "similar_stop_id", "similar_stop_name"]
427
+ )
398
428
  return stop_relation_df.to_dict(orient="records")
@@ -1,7 +1,7 @@
1
1
  import glob
2
+ import io
2
3
  import os
3
4
  import zipfile
4
- import io
5
5
  from dataclasses import dataclass, fields
6
6
  from typing import Optional
7
7
 
@@ -33,6 +33,7 @@ class GTFS:
33
33
  reference: https://gtfs.org/schedule/reference/
34
34
  reference for Japan: https://www.mlit.go.jp/common/001283244.pdf
35
35
  """
36
+
36
37
  agency: pd.DataFrame
37
38
  routes: pd.DataFrame
38
39
  stop_times: pd.DataFrame
@@ -171,11 +171,11 @@ def __route_lines_to_features(route_lines, routes):
171
171
  .rename("multiline")
172
172
  )
173
173
  # join route_id and route_name
174
- multiline_df = pd.merge(
175
- multilines,
176
- routes[["route_id", "route_long_name", "route_short_name"]],
177
- on="route_id",
178
- )
174
+ route_columns = ["route_id", "route_long_name", "route_short_name"]
175
+ if "route_color" in routes.columns:
176
+ route_columns.append("route_color")
177
+
178
+ multiline_df = pd.merge(multilines, routes[route_columns], on="route_id")
179
179
  multiline_df["route_name"] = multiline_df["route_long_name"].fillna(
180
180
  ""
181
181
  ) + multiline_df["route_short_name"].fillna("")
@@ -195,6 +195,9 @@ def __route_multiline_df_to_features(multiline_df):
195
195
  "properties": {
196
196
  "route_id": row["route_id"],
197
197
  "route_name": row["route_name"],
198
+ "route_color": None
199
+ if pd.isna(row.get("route_color"))
200
+ else row.get("route_color"),
198
201
  },
199
202
  }
200
203
  for row in dicts
@@ -0,0 +1,54 @@
1
+ [project]
2
+ name = "gtfs_parser"
3
+ version = "0.3.0"
4
+ description = "parse and aggregate GTFS"
5
+ license = "MIT"
6
+ license-files = ["LICENSE"]
7
+ readme = "README.md"
8
+ requires-python = ">=3.9"
9
+ dependencies = ["pandas>=1.3.3"]
10
+
11
+ [[project.authors]]
12
+ name = "MIERUNE Inc."
13
+
14
+ [[project.authors]]
15
+ name = "Kanahiro IGUCHI"
16
+
17
+ [project.urls]
18
+ Homepage = "https://github.com/MIERUNE"
19
+ Repository = "https://github.com/MIERUNE/gtfs-parser"
20
+
21
+ [project.scripts]
22
+ gtfs-parser = "gtfs_parser.__main__:main"
23
+
24
+ [dependency-groups]
25
+ dev = [
26
+ "pytest>=8.4.2",
27
+ "pytest-cov>=7.0.0",
28
+ "ruff>=0.16.8",
29
+ ]
30
+
31
+ [build-system]
32
+ requires = ["uv_build>=0.12.19,<0.13"]
33
+ build-backend = "uv_build"
34
+
35
+ [tool.uv]
36
+ exclude-newer = "7 days"
37
+
38
+ [tool.uv.build-backend]
39
+ module-root = ""
40
+
41
+ [tool.ruff]
42
+ target-version = "py39"
43
+
44
+ [tool.ruff.lint]
45
+ select = [
46
+ "E",
47
+ "F",
48
+ "W",
49
+ "I",
50
+ "B",
51
+ "UP",
52
+ "SIM",
53
+ ]
54
+ ignore = ["E501"]
@@ -0,0 +1,41 @@
1
+ [project]
2
+ name = "gtfs_parser"
3
+ version = "0.3.0"
4
+ description = "parse and aggregate GTFS"
5
+ authors = [{ name = "MIERUNE Inc." }, { name = "Kanahiro IGUCHI" }]
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ readme = "README.md"
9
+ requires-python = ">=3.9"
10
+ dependencies = ["pandas>=1.3.3"]
11
+
12
+ [project.urls]
13
+ Homepage = "https://github.com/MIERUNE"
14
+ Repository = "https://github.com/MIERUNE/gtfs-parser"
15
+
16
+ [project.scripts]
17
+ gtfs-parser = "gtfs_parser.__main__:main"
18
+
19
+ [dependency-groups]
20
+ dev = [
21
+ "pytest>=8.4.2",
22
+ "pytest-cov>=7.0.0",
23
+ "ruff>=0.16.8",
24
+ ]
25
+
26
+ [build-system]
27
+ requires = ["uv_build>=0.12.19,<0.13"]
28
+ build-backend = "uv_build"
29
+
30
+ [tool.uv]
31
+ exclude-newer = "7 days"
32
+
33
+ [tool.uv.build-backend]
34
+ module-root = ""
35
+
36
+ [tool.ruff]
37
+ target-version = "py39"
38
+
39
+ [tool.ruff.lint]
40
+ select = ["E", "F", "W", "I", "B", "UP", "SIM"]
41
+ ignore = ["E501"] # line length is handled by the formatter
@@ -1,2 +0,0 @@
1
- from .gtfs import GTFSFactory, GTFS
2
- from . import aggregate, parse
@@ -1,26 +0,0 @@
1
- [tool.poetry]
2
- name = "gtfs_parser"
3
- version = "0.2.2"
4
- description = "parse and aggregate GTFS"
5
- authors = ["MIERUNE Inc.", "Kanahiro IGUCHI"]
6
- license = "MIT"
7
- homepage = "https://github.com/MIERUNE"
8
- repository = "https://github.com/MIERUNE/gtfs-parser"
9
- readme = "README.md"
10
- packages = [{include = "gtfs_parser"}]
11
-
12
- [tool.poetry.dependencies]
13
- python = ">=3.9.0"
14
- pandas = ">=1.3.3"
15
-
16
-
17
- [tool.poetry.group.dev.dependencies]
18
- pytest = "^8.2.0"
19
- pytest-cov = "^5.0.0"
20
-
21
- [build-system]
22
- requires = ["poetry-core"]
23
- build-backend = "poetry.core.masonry.api"
24
-
25
- [tool.poetry.scripts]
26
- gtfs-parser = "gtfs_parser.__main__:main"
File without changes