r5py 0.1.1.dev2__py3-none-any.whl → 1.0.0.dev0__py3-none-any.whl
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.
Potentially problematic release.
This version of r5py might be problematic. Click here for more details.
- r5py/__init__.py +6 -1
- r5py/__main__.py +1 -14
- r5py/r5/__init__.py +18 -2
- r5py/r5/{base_travel_time_matrix_computer.py → base_travel_time_matrix.py} +28 -8
- r5py/r5/{detailed_itineraries_computer.py → detailed_itineraries.py} +72 -19
- r5py/r5/direct_leg.py +1 -3
- r5py/r5/regional_task.py +12 -9
- r5py/r5/street_layer.py +1 -0
- r5py/r5/transfer_leg.py +2 -6
- r5py/r5/transit_layer.py +6 -0
- r5py/r5/transit_leg.py +1 -5
- r5py/r5/transport_mode.py +5 -3
- r5py/r5/transport_network.py +16 -7
- r5py/r5/{travel_time_matrix_computer.py → travel_time_matrix.py} +94 -19
- r5py/r5/trip.py +13 -8
- r5py/r5/trip_leg.py +76 -15
- r5py/r5/trip_planner.py +103 -47
- r5py/util/__init__.py +2 -0
- r5py/util/classpath.py +9 -5
- r5py/util/config.py +11 -2
- r5py/util/environment.py +34 -0
- r5py/util/good_enough_equidistant_crs.py +0 -1
- r5py/util/memory_footprint.py +3 -5
- r5py/util/sample_data_set.py +13 -5
- r5py/util/validating_requests_session.py +2 -2
- {r5py-0.1.1.dev2.dist-info → r5py-1.0.0.dev0.dist-info}/METADATA +29 -27
- r5py-1.0.0.dev0.dist-info/RECORD +42 -0
- {r5py-0.1.1.dev2.dist-info → r5py-1.0.0.dev0.dist-info}/WHEEL +1 -1
- r5py/sampledata/_keep/__init__.py +0 -3
- r5py-0.1.1.dev2.dist-info/RECORD +0 -42
- {r5py-0.1.1.dev2.dist-info → r5py-1.0.0.dev0.dist-info}/LICENSE +0 -0
- {r5py-0.1.1.dev2.dist-info → r5py-1.0.0.dev0.dist-info}/top_level.txt +0 -0
|
@@ -4,25 +4,82 @@
|
|
|
4
4
|
|
|
5
5
|
import copy
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
try:
|
|
8
|
+
from warnings import deprecated
|
|
9
|
+
except ImportError: # Python<=3.12
|
|
10
|
+
from typing_extensions import deprecated
|
|
11
|
+
|
|
8
12
|
import pandas
|
|
9
13
|
|
|
10
|
-
from .
|
|
14
|
+
from .base_travel_time_matrix import BaseTravelTimeMatrix
|
|
11
15
|
from ..util import start_jvm
|
|
12
16
|
|
|
13
17
|
import com.conveyal.r5
|
|
14
18
|
|
|
15
19
|
|
|
16
|
-
__all__ = ["TravelTimeMatrixComputer"]
|
|
20
|
+
__all__ = ["TravelTimeMatrix", "TravelTimeMatrixComputer"]
|
|
17
21
|
|
|
18
22
|
|
|
19
23
|
start_jvm()
|
|
20
24
|
|
|
21
25
|
|
|
22
|
-
class
|
|
26
|
+
class TravelTimeMatrix(BaseTravelTimeMatrix):
|
|
23
27
|
"""Compute travel times between many origins and destinations."""
|
|
24
28
|
|
|
25
|
-
def
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
transport_network,
|
|
32
|
+
origins=None,
|
|
33
|
+
destinations=None,
|
|
34
|
+
snap_to_network=False,
|
|
35
|
+
**kwargs,
|
|
36
|
+
):
|
|
37
|
+
"""
|
|
38
|
+
Compute travel times between many origins and destinations.
|
|
39
|
+
|
|
40
|
+
``r5py.TravelTimeMatrix`` are child classes of ``pandas.DataFrame`` and
|
|
41
|
+
support all of their methods and properties,
|
|
42
|
+
see https://pandas.pydata.org/docs/
|
|
43
|
+
|
|
44
|
+
Arguments
|
|
45
|
+
---------
|
|
46
|
+
transport_network : r5py.TransportNetwork | tuple(str, list(str), dict)
|
|
47
|
+
The transport network to route on. This can either be a readily
|
|
48
|
+
initialised r5py.TransportNetwork or a tuple of the parameters
|
|
49
|
+
passed to ``TransportNetwork.__init__()``: the path to an OpenStreetMap
|
|
50
|
+
extract in PBF format, a list of zero of more paths to GTFS transport
|
|
51
|
+
schedule files, and a dict with ``build_config`` options.
|
|
52
|
+
origins : geopandas.GeoDataFrame
|
|
53
|
+
Places to find a route _from_
|
|
54
|
+
Has to have a point geometry, and at least an `id` column
|
|
55
|
+
destinations : geopandas.GeoDataFrame (optional)
|
|
56
|
+
Places to find a route _to_
|
|
57
|
+
Has to have a point geometry, and at least an `id` column
|
|
58
|
+
If omitted, use same data set as for origins
|
|
59
|
+
snap_to_network : bool or int, default False
|
|
60
|
+
Should origin an destination points be snapped to the street network
|
|
61
|
+
before routing? If `True`, the default search radius (defined in
|
|
62
|
+
`com.conveyal.r5.streets.StreetLayer.LINK_RADIUS_METERS`) is used,
|
|
63
|
+
if `int`, use `snap_to_network` meters as the search radius.
|
|
64
|
+
**kwargs : mixed
|
|
65
|
+
Any arguments than can be passed to r5py.RegionalTask:
|
|
66
|
+
``departure``, ``departure_time_window``, ``percentiles``, ``transport_modes``,
|
|
67
|
+
``access_modes``, ``egress_modes``, ``max_time``, ``max_time_walking``,
|
|
68
|
+
``max_time_cycling``, ``max_time_driving``, ``speed_cycling``, ``speed_walking``,
|
|
69
|
+
``max_public_transport_rides``, ``max_bicycle_traffic_stress``
|
|
70
|
+
"""
|
|
71
|
+
super().__init__(
|
|
72
|
+
transport_network,
|
|
73
|
+
origins,
|
|
74
|
+
destinations,
|
|
75
|
+
snap_to_network,
|
|
76
|
+
**kwargs,
|
|
77
|
+
)
|
|
78
|
+
data = self._compute()
|
|
79
|
+
for column in data.columns:
|
|
80
|
+
self[column] = data[column]
|
|
81
|
+
|
|
82
|
+
def _compute(self):
|
|
26
83
|
"""
|
|
27
84
|
Compute travel times from all origins to all destinations.
|
|
28
85
|
|
|
@@ -40,20 +97,10 @@ class TravelTimeMatrixComputer(BaseTravelTimeMatrixComputer):
|
|
|
40
97
|
self._prepare_origins_destinations()
|
|
41
98
|
self.request.destinations = self.destinations
|
|
42
99
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
verbose=(10 * self.verbose), # joblib has a funny verbosity scale
|
|
48
|
-
n_jobs=self.NUM_THREADS,
|
|
49
|
-
) as parallel:
|
|
50
|
-
od_matrix = pandas.concat(
|
|
51
|
-
parallel(
|
|
52
|
-
joblib.delayed(self._travel_times_per_origin)(from_id)
|
|
53
|
-
for from_id in self.origins.id
|
|
54
|
-
),
|
|
55
|
-
ignore_index=True,
|
|
56
|
-
)
|
|
100
|
+
od_matrix = pandas.concat(
|
|
101
|
+
[self._travel_times_per_origin(from_id) for from_id in self.origins.id],
|
|
102
|
+
ignore_index=True,
|
|
103
|
+
)
|
|
57
104
|
|
|
58
105
|
try:
|
|
59
106
|
od_matrix = od_matrix.to_crs(self._origins_crs)
|
|
@@ -132,3 +179,31 @@ class TravelTimeMatrixComputer(BaseTravelTimeMatrixComputer):
|
|
|
132
179
|
od_matrix = self._parse_results(from_id, results)
|
|
133
180
|
|
|
134
181
|
return od_matrix
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@deprecated(
|
|
185
|
+
"Use `TravelTimeMatrix` instead, `TravelTimeMatrixComputer will be deprecated in a future release."
|
|
186
|
+
)
|
|
187
|
+
class TravelTimeMatrixComputer:
|
|
188
|
+
"""Compute travel times between many origins and destinations."""
|
|
189
|
+
|
|
190
|
+
def __init__(self, *args, **kwargs):
|
|
191
|
+
"""Compute travel times between many origins and destinations."""
|
|
192
|
+
self._ttm = TravelTimeMatrix(*args, **kwargs)
|
|
193
|
+
|
|
194
|
+
def compute_travel_times(self):
|
|
195
|
+
"""
|
|
196
|
+
Compute travel times from all origins to all destinations.
|
|
197
|
+
|
|
198
|
+
Returns
|
|
199
|
+
-------
|
|
200
|
+
pandas.DataFrame
|
|
201
|
+
A data frame containing the columns ``from_id``, ``to_id``, and
|
|
202
|
+
``travel_time``, where ``travel_time`` is the median calculated
|
|
203
|
+
travel time between ``from_id`` and ``to_id`` or ``numpy.nan``
|
|
204
|
+
if no connection with the given parameters was found.
|
|
205
|
+
If non-default ``percentiles`` were requested: one or more columns
|
|
206
|
+
``travel_time_p{:02d}`` representing the particular percentile of
|
|
207
|
+
travel time.
|
|
208
|
+
"""
|
|
209
|
+
return self._ttm
|
r5py/r5/trip.py
CHANGED
|
@@ -15,9 +15,7 @@ __all__ = ["Trip"]
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class Trip:
|
|
18
|
-
"""
|
|
19
|
-
Represent one trip, consisting of one or more `TripLeg`s.
|
|
20
|
-
"""
|
|
18
|
+
"""Represent one trip, consisting of one or more `r5py.r5.TripLeg`."""
|
|
21
19
|
|
|
22
20
|
COLUMNS = [
|
|
23
21
|
"segment",
|
|
@@ -25,7 +23,7 @@ class Trip:
|
|
|
25
23
|
|
|
26
24
|
def __init__(self, legs=[]):
|
|
27
25
|
"""
|
|
28
|
-
Represent one trip, consisting of one of more `TripLeg
|
|
26
|
+
Represent one trip, consisting of one of more `r5py.r5.TripLeg`.
|
|
29
27
|
|
|
30
28
|
Arguments
|
|
31
29
|
=========
|
|
@@ -34,7 +32,13 @@ class Trip:
|
|
|
34
32
|
"""
|
|
35
33
|
self.legs = legs
|
|
36
34
|
|
|
35
|
+
def __eq__(self, other):
|
|
36
|
+
"""Check whether `self` and `other` are equal."""
|
|
37
|
+
if isinstance(other, self.__class__):
|
|
38
|
+
return self.legs == other.legs
|
|
39
|
+
|
|
37
40
|
def __repr__(self):
|
|
41
|
+
"""Return a string representation of `self`."""
|
|
38
42
|
legs = ", ".join([str(leg) for leg in self.legs])
|
|
39
43
|
return (
|
|
40
44
|
f"<{self.__class__.__name__}: "
|
|
@@ -50,8 +54,9 @@ class Trip:
|
|
|
50
54
|
Returns
|
|
51
55
|
=======
|
|
52
56
|
list : detailed information about this trip and its legs (segments):
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
``segment``, ``transport_mode``, ``departure_time``, ``distance``,
|
|
58
|
+
``travel_time``, ``wait_time``, ``feed``, ``agency_id``, ``route_id``,
|
|
59
|
+
``start_stop_id``, ``end_stop_id``, ``geometry``
|
|
55
60
|
"""
|
|
56
61
|
return [[segment] + leg.as_table_row() for segment, leg in enumerate(self.legs)]
|
|
57
62
|
|
|
@@ -72,9 +77,9 @@ class Trip:
|
|
|
72
77
|
)
|
|
73
78
|
|
|
74
79
|
@property
|
|
75
|
-
def
|
|
80
|
+
def route_ids(self):
|
|
76
81
|
"""The public transport route(s) used on this trip."""
|
|
77
|
-
return [leg.
|
|
82
|
+
return [leg.route_id for leg in self.legs]
|
|
78
83
|
|
|
79
84
|
@property
|
|
80
85
|
def transport_modes(self):
|
r5py/r5/trip_leg.py
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
"""Represent one leg of a trip."""
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
import datetime
|
|
8
|
+
import numpy
|
|
9
|
+
import shapely
|
|
10
|
+
|
|
11
|
+
|
|
7
12
|
__all__ = ["TripLeg"]
|
|
8
13
|
|
|
9
14
|
|
|
@@ -21,39 +26,56 @@ class TripLeg:
|
|
|
21
26
|
"distance",
|
|
22
27
|
"travel_time",
|
|
23
28
|
"wait_time",
|
|
24
|
-
"
|
|
29
|
+
"feed",
|
|
30
|
+
"agency_id",
|
|
31
|
+
"route_id",
|
|
32
|
+
"start_stop_id",
|
|
33
|
+
"end_stop_id",
|
|
25
34
|
"geometry",
|
|
26
35
|
]
|
|
27
36
|
|
|
28
37
|
def __init__(
|
|
29
38
|
self,
|
|
30
39
|
transport_mode=None,
|
|
31
|
-
departure_time=
|
|
40
|
+
departure_time=numpy.datetime64("NaT"),
|
|
32
41
|
distance=None,
|
|
33
|
-
travel_time=
|
|
34
|
-
wait_time=
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
travel_time=datetime.timedelta(seconds=0),
|
|
43
|
+
wait_time=datetime.timedelta(seconds=0),
|
|
44
|
+
feed=None,
|
|
45
|
+
agency_id=None,
|
|
46
|
+
route_id=None,
|
|
47
|
+
start_stop_id=None,
|
|
48
|
+
end_stop_id=None,
|
|
49
|
+
geometry=shapely.LineString(),
|
|
37
50
|
):
|
|
38
51
|
"""
|
|
39
52
|
Represent one leg of a trip.
|
|
40
53
|
|
|
41
|
-
This is a base class, use one the specific classes,
|
|
42
|
-
|
|
54
|
+
This is a base class, use one of the more specific classes, e.g.,
|
|
55
|
+
TransitLeg, or DirectLeg
|
|
43
56
|
|
|
44
57
|
Arguments
|
|
45
58
|
=========
|
|
46
59
|
transport_mode : r5py.TransportMode
|
|
47
60
|
mode of transport this trip leg was travelled
|
|
48
|
-
departure_time : datetime.datetime
|
|
61
|
+
departure_time : datetime.datetime
|
|
62
|
+
departure time of this trip leg
|
|
49
63
|
distance : float
|
|
50
64
|
distance covered by this trip leg, in metres
|
|
51
65
|
travel_time : datetime.timedelta
|
|
52
66
|
time spent travelling on this trip leg
|
|
53
67
|
wait_time : datetime.timedelta
|
|
54
68
|
time spent waiting for a connection on this trip leg
|
|
55
|
-
|
|
56
|
-
|
|
69
|
+
feed : str
|
|
70
|
+
the GTFS feed identifier used for this trip leg
|
|
71
|
+
agency_id : str
|
|
72
|
+
the GTFS id the agency used for this trip leg
|
|
73
|
+
route_id : str
|
|
74
|
+
the GTFS id of the public transport route used for this trip leg
|
|
75
|
+
start_stop_id : str
|
|
76
|
+
the GTFS stop_id of the boarding stop used for this trip leg
|
|
77
|
+
end_stop_id : str
|
|
78
|
+
the GTFS stop_id of the aligning stop used for this trip leg
|
|
57
79
|
geometry : shapely.LineString
|
|
58
80
|
spatial representation of this trip leg
|
|
59
81
|
"""
|
|
@@ -62,10 +84,15 @@ class TripLeg:
|
|
|
62
84
|
self.distance = distance
|
|
63
85
|
self.travel_time = travel_time
|
|
64
86
|
self.wait_time = wait_time
|
|
65
|
-
self.
|
|
87
|
+
self.feed = feed
|
|
88
|
+
self.agency_id = agency_id
|
|
89
|
+
self.route_id = route_id
|
|
90
|
+
self.start_stop_id = start_stop_id
|
|
91
|
+
self.end_stop_id = end_stop_id
|
|
66
92
|
self.geometry = geometry
|
|
67
93
|
|
|
68
94
|
def __add__(self, other):
|
|
95
|
+
"""Trip-chain `other` to `self`."""
|
|
69
96
|
from .trip import Trip
|
|
70
97
|
|
|
71
98
|
if isinstance(other, self.__class__):
|
|
@@ -80,6 +107,7 @@ class TripLeg:
|
|
|
80
107
|
)
|
|
81
108
|
|
|
82
109
|
def __radd__(self, other):
|
|
110
|
+
"""Trip-chain `self` to `other`."""
|
|
83
111
|
from .trip import Trip
|
|
84
112
|
|
|
85
113
|
if other == 0: # first iteration of sum()
|
|
@@ -90,31 +118,43 @@ class TripLeg:
|
|
|
90
118
|
else:
|
|
91
119
|
return self.__add__(other)
|
|
92
120
|
|
|
121
|
+
def __eq__(self, other):
|
|
122
|
+
"""Check if `other` is an equal `TripLeg`."""
|
|
123
|
+
if isinstance(other, self.__class__):
|
|
124
|
+
return False not in [
|
|
125
|
+
self._are_columns_equal(other, column) for column in self.COLUMNS
|
|
126
|
+
]
|
|
127
|
+
|
|
93
128
|
def __gt__(self, other):
|
|
129
|
+
"""Check if `other` has a longer travel time."""
|
|
94
130
|
if isinstance(other, TripLeg):
|
|
95
131
|
return (self.travel_time + self.wait_time) > (
|
|
96
132
|
other.travel_time + other.wait_time
|
|
97
133
|
)
|
|
98
134
|
|
|
99
135
|
def __ge__(self, other):
|
|
136
|
+
"""Check if `other` has a longer or equal travel time."""
|
|
100
137
|
if isinstance(other, TripLeg):
|
|
101
138
|
return (self.travel_time + self.wait_time) >= (
|
|
102
139
|
other.travel_time + other.wait_time
|
|
103
140
|
)
|
|
104
141
|
|
|
105
142
|
def __lt__(self, other):
|
|
143
|
+
"""Check if `other` has a shorter travel time."""
|
|
106
144
|
if isinstance(other, TripLeg):
|
|
107
145
|
return (self.travel_time + self.wait_time) < (
|
|
108
146
|
other.travel_time + other.wait_time
|
|
109
147
|
)
|
|
110
148
|
|
|
111
149
|
def __le__(self, other):
|
|
150
|
+
"""Check if `other` has a shorter or equal travel time."""
|
|
112
151
|
if isinstance(other, TripLeg):
|
|
113
152
|
return (self.travel_time + self.wait_time) <= (
|
|
114
153
|
other.travel_time + other.wait_time
|
|
115
154
|
)
|
|
116
155
|
|
|
117
156
|
def __repr__(self):
|
|
157
|
+
"""Return a string representation."""
|
|
118
158
|
try:
|
|
119
159
|
first_point = self.geometry.coords[0]
|
|
120
160
|
last_point = self.geometry.coords[-1]
|
|
@@ -127,10 +167,30 @@ class TripLeg:
|
|
|
127
167
|
f"{first_point} -> {last_point}"
|
|
128
168
|
">"
|
|
129
169
|
)
|
|
130
|
-
except AttributeError:
|
|
170
|
+
except (AttributeError, IndexError):
|
|
131
171
|
_repr = f"<{self.__class__.__name__}>"
|
|
132
172
|
return _repr
|
|
133
173
|
|
|
174
|
+
def _are_columns_equal(self, other, column):
|
|
175
|
+
"""
|
|
176
|
+
Check if a column equals the same column of a different `Trip`.
|
|
177
|
+
|
|
178
|
+
Compare if attribute `column` of self equals attribute `column` of
|
|
179
|
+
other. Also True if both values are None or NaN or NaT.
|
|
180
|
+
"""
|
|
181
|
+
self_column = getattr(self, column)
|
|
182
|
+
other_column = getattr(other, column)
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
self_column == other_column
|
|
186
|
+
or (self_column is None and other_column is None)
|
|
187
|
+
or (self_column == numpy.nan and other_column == numpy.nan)
|
|
188
|
+
or (
|
|
189
|
+
self_column == numpy.datetime64("NaT")
|
|
190
|
+
and other_column == numpy.datetime64("NaT")
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
|
|
134
194
|
def as_table_row(self):
|
|
135
195
|
"""
|
|
136
196
|
Return a table row (list) of this trip leg’s details.
|
|
@@ -138,7 +198,8 @@ class TripLeg:
|
|
|
138
198
|
Returns
|
|
139
199
|
=======
|
|
140
200
|
list : detailed information about this trip leg: ``transport_mode``,
|
|
141
|
-
|
|
142
|
-
|
|
201
|
+
``departure_time``, ``distance``, ``travel_time``, ``wait_time``,
|
|
202
|
+
``feed``, ``agency_id`` ``route_id``, ``start_stop_id``,
|
|
203
|
+
``end_stop_id``, ``geometry``
|
|
143
204
|
"""
|
|
144
205
|
return [getattr(self, column) for column in self.COLUMNS]
|
r5py/r5/trip_planner.py
CHANGED
|
@@ -43,9 +43,7 @@ ZERO_SECONDS = datetime.timedelta(seconds=0)
|
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
class TripPlanner:
|
|
46
|
-
"""
|
|
47
|
-
Find detailed routes between two points.
|
|
48
|
-
"""
|
|
46
|
+
"""Find detailed routes between two points."""
|
|
49
47
|
|
|
50
48
|
MAX_ACCESS_TIME = datetime.timedelta(hours=1)
|
|
51
49
|
MAX_EGRESS_TIME = MAX_ACCESS_TIME
|
|
@@ -72,7 +70,7 @@ class TripPlanner:
|
|
|
72
70
|
@property
|
|
73
71
|
def trips(self):
|
|
74
72
|
"""
|
|
75
|
-
|
|
73
|
+
Detailed routes between two points.
|
|
76
74
|
|
|
77
75
|
Returns
|
|
78
76
|
=======
|
|
@@ -84,6 +82,15 @@ class TripPlanner:
|
|
|
84
82
|
|
|
85
83
|
@property
|
|
86
84
|
def direct_paths(self):
|
|
85
|
+
"""
|
|
86
|
+
Detailed routes between two points using direct modes.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
=======
|
|
90
|
+
list[r5py.r5.Trip]
|
|
91
|
+
Detailed routes that meet the requested parameters, using direct
|
|
92
|
+
modes (walking, cycling, driving).
|
|
93
|
+
"""
|
|
87
94
|
direct_paths = []
|
|
88
95
|
request = copy.copy(self.request)
|
|
89
96
|
|
|
@@ -143,11 +150,16 @@ class TripPlanner:
|
|
|
143
150
|
]
|
|
144
151
|
)
|
|
145
152
|
)
|
|
146
|
-
except
|
|
153
|
+
except (
|
|
154
|
+
java.lang.NullPointerException,
|
|
155
|
+
java.util.NoSuchElementException,
|
|
156
|
+
):
|
|
147
157
|
warnings.warn(
|
|
148
158
|
f"Could not find route between origin "
|
|
149
|
-
f"({self.request.fromLon},
|
|
150
|
-
f"
|
|
159
|
+
f"({self.request._regional_task.fromLon}, "
|
|
160
|
+
f"{self.request._regional_task.fromLat}) "
|
|
161
|
+
f"and destination ({self.request._regional_task.toLon}, "
|
|
162
|
+
f"{self.request._regional_task.toLat})",
|
|
151
163
|
RuntimeWarning,
|
|
152
164
|
)
|
|
153
165
|
return direct_paths
|
|
@@ -168,6 +180,15 @@ class TripPlanner:
|
|
|
168
180
|
|
|
169
181
|
@functools.cached_property
|
|
170
182
|
def transit_paths(self):
|
|
183
|
+
"""
|
|
184
|
+
Detailed routes between two points on public transport.
|
|
185
|
+
|
|
186
|
+
Returns
|
|
187
|
+
=======
|
|
188
|
+
list[r5py.r5.Trip]
|
|
189
|
+
Detailed routes that meet the requested parameters, on public
|
|
190
|
+
transport.
|
|
191
|
+
"""
|
|
171
192
|
transit_paths = []
|
|
172
193
|
|
|
173
194
|
# if any transit mode requested:
|
|
@@ -195,7 +216,6 @@ class TripPlanner:
|
|
|
195
216
|
distance=0.0,
|
|
196
217
|
travel_time=ZERO_SECONDS,
|
|
197
218
|
wait_time=ZERO_SECONDS,
|
|
198
|
-
route=None,
|
|
199
219
|
geometry=shapely.LineString(((lon, lat), (lon, lat))),
|
|
200
220
|
)
|
|
201
221
|
]
|
|
@@ -214,8 +234,8 @@ class TripPlanner:
|
|
|
214
234
|
com.conveyal.r5.profile.McRaptorSuboptimalPathProfileRouter(
|
|
215
235
|
self.transport_network,
|
|
216
236
|
request,
|
|
217
|
-
self.
|
|
218
|
-
self.
|
|
237
|
+
self._transit_access_times,
|
|
238
|
+
self._transit_egress_times,
|
|
219
239
|
list_supplier_callback,
|
|
220
240
|
None,
|
|
221
241
|
True,
|
|
@@ -234,18 +254,26 @@ class TripPlanner:
|
|
|
234
254
|
for state in list(states) # some departure times yield no results
|
|
235
255
|
}
|
|
236
256
|
|
|
257
|
+
# keep another cache layer of shortest access and egress legs
|
|
258
|
+
access_legs_by_stop = {}
|
|
259
|
+
egress_legs_by_stop = {}
|
|
260
|
+
|
|
237
261
|
for departure_time, state in final_states.items():
|
|
238
262
|
trip = Trip()
|
|
239
263
|
while state:
|
|
240
264
|
if state.stop == -1: # EgressLeg
|
|
241
|
-
|
|
242
|
-
[
|
|
243
|
-
|
|
244
|
-
|
|
265
|
+
try:
|
|
266
|
+
leg = egress_legs_by_stop[state.back.stop]
|
|
267
|
+
except KeyError:
|
|
268
|
+
leg = min(
|
|
269
|
+
[
|
|
270
|
+
self._transit_egress_paths[transport_mode][
|
|
271
|
+
state.back.stop
|
|
272
|
+
]
|
|
273
|
+
for transport_mode in self._transit_egress_paths.keys()
|
|
245
274
|
]
|
|
246
|
-
|
|
247
|
-
]
|
|
248
|
-
)
|
|
275
|
+
)
|
|
276
|
+
egress_legs_by_stop[state.back.stop] = leg
|
|
249
277
|
leg.wait_time = ZERO_SECONDS
|
|
250
278
|
leg.departure_time = (
|
|
251
279
|
midnight
|
|
@@ -255,14 +283,18 @@ class TripPlanner:
|
|
|
255
283
|
leg.arrival_time = leg.departure_time + leg.travel_time
|
|
256
284
|
|
|
257
285
|
elif state.back is None: # AccessLeg
|
|
258
|
-
|
|
259
|
-
[
|
|
260
|
-
|
|
261
|
-
|
|
286
|
+
try:
|
|
287
|
+
leg = access_legs_by_stop[state.stop]
|
|
288
|
+
except KeyError:
|
|
289
|
+
leg = min(
|
|
290
|
+
[
|
|
291
|
+
self._transit_access_paths[transport_mode][
|
|
292
|
+
state.stop
|
|
293
|
+
]
|
|
294
|
+
for transport_mode in self._transit_access_paths.keys()
|
|
262
295
|
]
|
|
263
|
-
|
|
264
|
-
]
|
|
265
|
-
)
|
|
296
|
+
)
|
|
297
|
+
access_legs_by_stop[state.stop] = leg
|
|
266
298
|
leg.wait_time = ZERO_SECONDS
|
|
267
299
|
leg.arrival_time = midnight + datetime.timedelta(
|
|
268
300
|
seconds=state.time
|
|
@@ -274,7 +306,7 @@ class TripPlanner:
|
|
|
274
306
|
departure_stop = state.back.stop
|
|
275
307
|
arrival_stop = state.stop
|
|
276
308
|
|
|
277
|
-
leg = self.
|
|
309
|
+
leg = self._transit_transfer_path(
|
|
278
310
|
departure_stop, arrival_stop
|
|
279
311
|
)
|
|
280
312
|
|
|
@@ -294,6 +326,17 @@ class TripPlanner:
|
|
|
294
326
|
|
|
295
327
|
else: # TransitLeg
|
|
296
328
|
pattern = transit_layer.trip_patterns[state.pattern]
|
|
329
|
+
|
|
330
|
+
# Use the indices to look up the stop ids, which are scoped by the GTFS feed supplied
|
|
331
|
+
start_stop_id = transit_layer.get_stop_id_from_index(
|
|
332
|
+
state.back.stop
|
|
333
|
+
).split(":")[1]
|
|
334
|
+
end_stop = transit_layer.get_stop_id_from_index(
|
|
335
|
+
state.stop
|
|
336
|
+
)
|
|
337
|
+
end_stop_id = end_stop.split(":")[1]
|
|
338
|
+
feed = end_stop.split(":")[0]
|
|
339
|
+
|
|
297
340
|
route = transit_layer.routes[pattern.routeIndex]
|
|
298
341
|
transport_mode = TransportMode(
|
|
299
342
|
com.conveyal.r5.transit.TransitLayer.getTransitModes(
|
|
@@ -346,13 +389,17 @@ class TripPlanner:
|
|
|
346
389
|
distance = None
|
|
347
390
|
|
|
348
391
|
leg = TransitLeg(
|
|
349
|
-
transport_mode,
|
|
350
|
-
departure_time,
|
|
351
|
-
distance,
|
|
352
|
-
travel_time,
|
|
353
|
-
wait_time,
|
|
354
|
-
str(
|
|
355
|
-
|
|
392
|
+
transport_mode=transport_mode,
|
|
393
|
+
departure_time=departure_time,
|
|
394
|
+
distance=distance,
|
|
395
|
+
travel_time=travel_time,
|
|
396
|
+
wait_time=wait_time,
|
|
397
|
+
feed=str(feed),
|
|
398
|
+
agency_id=str(route.agency_id),
|
|
399
|
+
route_id=str(route.route_id),
|
|
400
|
+
start_stop_id=str(start_stop_id),
|
|
401
|
+
end_stop_id=str(end_stop_id),
|
|
402
|
+
geometry=geometry,
|
|
356
403
|
)
|
|
357
404
|
|
|
358
405
|
# we traverse in reverse order:
|
|
@@ -361,12 +408,14 @@ class TripPlanner:
|
|
|
361
408
|
trip = leg + trip
|
|
362
409
|
state = state.back
|
|
363
410
|
|
|
364
|
-
|
|
411
|
+
# R5 sometimes reports the same path more than once, skip duplicates
|
|
412
|
+
if trip not in transit_paths:
|
|
413
|
+
transit_paths.append(trip)
|
|
365
414
|
|
|
366
415
|
return transit_paths
|
|
367
416
|
|
|
368
417
|
@functools.cached_property
|
|
369
|
-
def
|
|
418
|
+
def _transit_access_paths(self):
|
|
370
419
|
access_paths = {}
|
|
371
420
|
|
|
372
421
|
request = copy.copy(self.request)
|
|
@@ -406,9 +455,12 @@ class TripPlanner:
|
|
|
406
455
|
return access_paths
|
|
407
456
|
|
|
408
457
|
@functools.cached_property
|
|
409
|
-
def
|
|
410
|
-
"""
|
|
411
|
-
|
|
458
|
+
def _transit_access_times(self):
|
|
459
|
+
"""
|
|
460
|
+
Times to reached stops.
|
|
461
|
+
|
|
462
|
+
In the format required by McRaptorSuboptimalPathProfileRouter.
|
|
463
|
+
"""
|
|
412
464
|
access_times = jpype.JObject(
|
|
413
465
|
{
|
|
414
466
|
com.conveyal.r5.api.util.LegMode
|
|
@@ -419,14 +471,14 @@ class TripPlanner:
|
|
|
419
471
|
for transfer_leg in reached_stops.values()
|
|
420
472
|
],
|
|
421
473
|
)
|
|
422
|
-
for mode, reached_stops in self.
|
|
474
|
+
for mode, reached_stops in self._transit_access_paths.items()
|
|
423
475
|
},
|
|
424
476
|
"java.util.Map<com.conveyal.r5.LegMode, gnu.trove.map.TIntIntMap>",
|
|
425
477
|
)
|
|
426
478
|
return access_times
|
|
427
479
|
|
|
428
480
|
@functools.cached_property
|
|
429
|
-
def
|
|
481
|
+
def _transit_egress_paths(self):
|
|
430
482
|
egress_paths = {}
|
|
431
483
|
|
|
432
484
|
request = copy.copy(self.request)
|
|
@@ -467,9 +519,12 @@ class TripPlanner:
|
|
|
467
519
|
return egress_paths
|
|
468
520
|
|
|
469
521
|
@functools.cached_property
|
|
470
|
-
def
|
|
471
|
-
"""
|
|
472
|
-
|
|
522
|
+
def _transit_egress_times(self):
|
|
523
|
+
"""
|
|
524
|
+
Times to reached stops.
|
|
525
|
+
|
|
526
|
+
In the format required by McRaptorSuboptimalPathProfileRouter.
|
|
527
|
+
"""
|
|
473
528
|
egress_times = jpype.JObject(
|
|
474
529
|
{
|
|
475
530
|
com.conveyal.r5.api.util.LegMode
|
|
@@ -480,13 +535,14 @@ class TripPlanner:
|
|
|
480
535
|
for transfer_leg in reached_stops.values()
|
|
481
536
|
],
|
|
482
537
|
)
|
|
483
|
-
for mode, reached_stops in self.
|
|
538
|
+
for mode, reached_stops in self._transit_egress_paths.items()
|
|
484
539
|
},
|
|
485
540
|
"java.util.Map<com.conveyal.r5.LegMode, gnu.trove.map.TIntIntMap>",
|
|
486
541
|
)
|
|
487
542
|
return egress_times
|
|
488
543
|
|
|
489
|
-
def
|
|
544
|
+
def _transit_transfer_path(self, from_stop, to_stop):
|
|
545
|
+
"""Find a transfer path between two transit stops."""
|
|
490
546
|
self._transfer_paths = {}
|
|
491
547
|
while True:
|
|
492
548
|
try:
|
|
@@ -524,8 +580,8 @@ class TripPlanner:
|
|
|
524
580
|
TransportMode.WALK,
|
|
525
581
|
)
|
|
526
582
|
|
|
527
|
-
transfer_path = self._transfer_paths[
|
|
528
|
-
(
|
|
529
|
-
|
|
583
|
+
transfer_path = self._transfer_paths[(from_stop, to_stop)] = (
|
|
584
|
+
TransferLeg(TransportMode.WALK, street_segment)
|
|
585
|
+
)
|
|
530
586
|
|
|
531
587
|
return transfer_path
|