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.
- gtfs_parser-0.2.2/README.md → gtfs_parser-0.3.0/PKG-INFO +92 -51
- gtfs_parser-0.2.2/PKG-INFO → gtfs_parser-0.3.0/README.md +29 -19
- gtfs_parser-0.3.0/gtfs_parser/__init__.py +4 -0
- {gtfs_parser-0.2.2 → gtfs_parser-0.3.0}/gtfs_parser/__main__.py +7 -8
- {gtfs_parser-0.2.2 → gtfs_parser-0.3.0}/gtfs_parser/aggregate.py +117 -87
- {gtfs_parser-0.2.2 → gtfs_parser-0.3.0}/gtfs_parser/gtfs.py +2 -1
- {gtfs_parser-0.2.2 → gtfs_parser-0.3.0}/gtfs_parser/parse.py +8 -5
- gtfs_parser-0.3.0/pyproject.toml +54 -0
- gtfs_parser-0.3.0/pyproject.toml.orig +41 -0
- gtfs_parser-0.2.2/gtfs_parser/__init__.py +0 -2
- gtfs_parser-0.2.2/pyproject.toml +0 -26
- {gtfs_parser-0.2.2 → gtfs_parser-0.3.0}/LICENSE +0 -0
|
@@ -1,51 +1,92 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
```
|
|
47
|
-
gtfs-parser
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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))
|
|
@@ -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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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(
|
|
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(
|
|
40
|
+
similar_results = Aggregator.__get_similar_stop_without_unifying(
|
|
41
|
+
self.gtfs.stops
|
|
42
|
+
)
|
|
38
43
|
else:
|
|
39
|
-
similar_results = Aggregator.__unify_similar_stops(
|
|
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[
|
|
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[
|
|
62
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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)][
|
|
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(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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 =
|
|
135
|
-
|
|
136
|
-
|
|
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[
|
|
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(
|
|
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 =
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
solo_similar_stops.rename(
|
|
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
|
|
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
|
|
198
|
-
leaf_pair = near_id_pair.query("stop_id != stop_id_r")
|
|
199
|
-
|
|
200
|
-
|
|
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[
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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[
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
365
|
-
]
|
|
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(
|
|
397
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
gtfs_parser-0.2.2/pyproject.toml
DELETED
|
@@ -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
|