r5py 1.1.0__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.
- r5py/__init__.py +27 -0
- r5py/__main__.py +3 -0
- r5py/r5/__init__.py +39 -0
- r5py/r5/access_leg.py +12 -0
- r5py/r5/base_travel_time_matrix.py +255 -0
- r5py/r5/detailed_itineraries.py +226 -0
- r5py/r5/direct_leg.py +38 -0
- r5py/r5/egress_leg.py +12 -0
- r5py/r5/elevation_cost_function.py +50 -0
- r5py/r5/elevation_model.py +89 -0
- r5py/r5/file_storage.py +82 -0
- r5py/r5/isochrones.py +345 -0
- r5py/r5/regional_task.py +600 -0
- r5py/r5/scenario.py +36 -0
- r5py/r5/street_layer.py +90 -0
- r5py/r5/street_segment.py +39 -0
- r5py/r5/transfer_leg.py +12 -0
- r5py/r5/transit_layer.py +87 -0
- r5py/r5/transit_leg.py +12 -0
- r5py/r5/transport_mode.py +148 -0
- r5py/r5/transport_network.py +299 -0
- r5py/r5/travel_time_matrix.py +186 -0
- r5py/r5/trip.py +97 -0
- r5py/r5/trip_leg.py +204 -0
- r5py/r5/trip_planner.py +576 -0
- r5py/util/__init__.py +31 -0
- r5py/util/camel_to_snake_case.py +25 -0
- r5py/util/classpath.py +95 -0
- r5py/util/config.py +176 -0
- r5py/util/contains_gtfs_data.py +46 -0
- r5py/util/data_validation.py +28 -0
- r5py/util/environment.py +32 -0
- r5py/util/exceptions.py +43 -0
- r5py/util/file_digest.py +40 -0
- r5py/util/good_enough_equidistant_crs.py +73 -0
- r5py/util/jvm.py +138 -0
- r5py/util/memory_footprint.py +178 -0
- r5py/util/parse_int_date.py +24 -0
- r5py/util/sample_data_set.py +76 -0
- r5py/util/snake_to_camel_case.py +16 -0
- r5py/util/spatially_clustered_geodataframe.py +66 -0
- r5py/util/validating_requests_session.py +58 -0
- r5py/util/warnings.py +7 -0
- r5py/util/working_copy.py +42 -0
- r5py-1.1.0.dist-info/METADATA +176 -0
- r5py-1.1.0.dist-info/RECORD +49 -0
- r5py-1.1.0.dist-info/WHEEL +5 -0
- r5py-1.1.0.dist-info/licenses/LICENSE +3 -0
- r5py-1.1.0.dist-info/top_level.txt +1 -0
r5py/r5/regional_task.py
ADDED
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""Wraps a com.conveyal.r5.analyst.cluster.RegionalTask."""
|
|
4
|
+
|
|
5
|
+
import collections.abc
|
|
6
|
+
import datetime
|
|
7
|
+
import warnings
|
|
8
|
+
|
|
9
|
+
import jpype
|
|
10
|
+
|
|
11
|
+
from .scenario import Scenario
|
|
12
|
+
from .transport_mode import TransportMode
|
|
13
|
+
from ..util import start_jvm
|
|
14
|
+
|
|
15
|
+
import java.io
|
|
16
|
+
import java.time
|
|
17
|
+
import com.conveyal.r5
|
|
18
|
+
|
|
19
|
+
__all__ = ["RegionalTask"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
start_jvm()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RegionalTask:
|
|
26
|
+
"""Create a RegionalTask, a computing request for R5."""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
transport_network,
|
|
31
|
+
origin=None,
|
|
32
|
+
destinations=None,
|
|
33
|
+
departure=None, # default: datetime.datetime.now(),
|
|
34
|
+
departure_time_window=datetime.timedelta(minutes=10), # noqa: B008
|
|
35
|
+
percentiles=[50], # noqa: B006
|
|
36
|
+
transport_modes=[TransportMode.TRANSIT], # noqa: B006
|
|
37
|
+
access_modes=[TransportMode.WALK], # noqa: B006
|
|
38
|
+
egress_modes=None, # default: access_modes
|
|
39
|
+
max_time=datetime.timedelta(hours=2), # noqa: B008
|
|
40
|
+
max_time_walking=None,
|
|
41
|
+
max_time_cycling=None,
|
|
42
|
+
max_time_driving=None,
|
|
43
|
+
speed_walking=3.6,
|
|
44
|
+
speed_cycling=12.0,
|
|
45
|
+
max_public_transport_rides=8,
|
|
46
|
+
max_bicycle_traffic_stress=3,
|
|
47
|
+
):
|
|
48
|
+
"""
|
|
49
|
+
Create a RegionalTask, a computing request for R5.
|
|
50
|
+
|
|
51
|
+
A RegionalTask wraps a `com.conveyal.r5.analyst.cluster.RegionalTask`,
|
|
52
|
+
which is used to specify the details of a requested computation.
|
|
53
|
+
RegionalTasks underlie virtually all major computations carried out,
|
|
54
|
+
such as, e.g., `TravelTimeMatrix` or `AccessibilityEstimator`.
|
|
55
|
+
|
|
56
|
+
In **r5py**, there is usually no need to explicitely create a
|
|
57
|
+
`RegionalTask`. Rather, the constructors to the computation classes
|
|
58
|
+
(`TravelTimeMatrix`, `AccessibilityEstimator`, ...) accept the
|
|
59
|
+
arguments, and pass them through to an internally handled
|
|
60
|
+
`RegionalTask`.
|
|
61
|
+
|
|
62
|
+
Arguments
|
|
63
|
+
---------
|
|
64
|
+
transport_network : r5py.TransportNetwork
|
|
65
|
+
The street + public transport network to route on
|
|
66
|
+
origin : shapely.geometry.Point
|
|
67
|
+
Point to route from
|
|
68
|
+
destinations : geopandas.GeoDataFrame
|
|
69
|
+
Points to route to, has to have at least an ``id`` column and a
|
|
70
|
+
geometry
|
|
71
|
+
departure : datetime.datetime
|
|
72
|
+
Find public transport connections leaving every minute within
|
|
73
|
+
``departure_time_window`` after ``departure``. Default: current date
|
|
74
|
+
and time
|
|
75
|
+
departure_time_window : datetime.timedelta
|
|
76
|
+
(see ``departure``) Default: 10 minutes
|
|
77
|
+
percentiles : list[int]
|
|
78
|
+
Return the travel time for these percentiles of all computed trips,
|
|
79
|
+
by travel time. By default, return the median travel time. Default:
|
|
80
|
+
[50]
|
|
81
|
+
transport_modes : list[r5py.TransportMode] or list[str]
|
|
82
|
+
The mode of transport to use for routing. Can be a r5py mode
|
|
83
|
+
enumerable, or a string representation (e.g. "TRANSIT") Default:
|
|
84
|
+
[r5py.TransportMode.TRANSIT] (all public transport)
|
|
85
|
+
access_modes : list[r5py.TransportMode] or list[str]
|
|
86
|
+
Mode of transport to public transport stops. Can be a r5py mode
|
|
87
|
+
object, or a string representation (e.g. "WALK") Default:
|
|
88
|
+
[r5py.TransportMode.WALK]
|
|
89
|
+
egress_modes : list[r5py.TransportMode]
|
|
90
|
+
Mode of transport from public transport stops. Default: access_modes
|
|
91
|
+
max_time : datetime.timedelta
|
|
92
|
+
Maximum trip duration. If any of ``max_time_walking``,
|
|
93
|
+
``max_time_cycling``, or ``max_time_driving`` are set, ``max_time``
|
|
94
|
+
is increased to the maximum value of those, if it is lower.
|
|
95
|
+
Default: 2 hours
|
|
96
|
+
max_time_walking : datetime.timedelta
|
|
97
|
+
Maximum time spent walking, potentially including access and egress
|
|
98
|
+
Default: max_time
|
|
99
|
+
max_time_cycling : datetime.timedelta
|
|
100
|
+
Maximum time spent cycling, potentially including access and egress
|
|
101
|
+
Default: max_time
|
|
102
|
+
max_time_driving : datetime.timedelta
|
|
103
|
+
Maximum time spent driving Default: max_time
|
|
104
|
+
speed_walking : float
|
|
105
|
+
Mean walking speed for routing, km/h. Default: 3.6 km/h
|
|
106
|
+
speed_cycling : float
|
|
107
|
+
Mean cycling speed for routing, km/h. Default: 12.0 km/h
|
|
108
|
+
max_public_transport_rides : int
|
|
109
|
+
Use at most ``max_public_transport_rides`` consecutive public
|
|
110
|
+
transport connections. Default: 8
|
|
111
|
+
max_bicycle_traffic_stress : int
|
|
112
|
+
Maximum stress level for cyclist routing, ranges from 1-4 see
|
|
113
|
+
https://docs.conveyal.com/learn-more/traffic-stress Default: 3
|
|
114
|
+
"""
|
|
115
|
+
self._regional_task = com.conveyal.r5.analyst.cluster.RegionalTask()
|
|
116
|
+
self.scenario = Scenario()
|
|
117
|
+
|
|
118
|
+
self.transport_network = transport_network
|
|
119
|
+
|
|
120
|
+
self.origin = origin
|
|
121
|
+
self.destinations = destinations
|
|
122
|
+
|
|
123
|
+
self.access_modes = access_modes
|
|
124
|
+
self.egress_modes = egress_modes if egress_modes is not None else access_modes
|
|
125
|
+
# last, because extra logic that depends on the others having been set
|
|
126
|
+
self.transport_modes = transport_modes
|
|
127
|
+
|
|
128
|
+
if departure is None:
|
|
129
|
+
departure = datetime.datetime.now()
|
|
130
|
+
self.departure = departure # transport modes should be set before this line
|
|
131
|
+
self.departure_time_window = departure_time_window
|
|
132
|
+
self.percentiles = percentiles
|
|
133
|
+
|
|
134
|
+
self.max_time = max_time
|
|
135
|
+
self.max_time_cycling = (
|
|
136
|
+
max_time_cycling if max_time_cycling is not None else max_time
|
|
137
|
+
)
|
|
138
|
+
self.max_time_driving = (
|
|
139
|
+
max_time_driving if max_time_driving is not None else max_time
|
|
140
|
+
)
|
|
141
|
+
self.max_time_walking = (
|
|
142
|
+
max_time_walking if max_time_walking is not None else max_time
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
self.speed_cycling = speed_cycling
|
|
146
|
+
self.speed_walking = speed_walking
|
|
147
|
+
|
|
148
|
+
self.max_public_transport_rides = max_public_transport_rides
|
|
149
|
+
self.max_bicycle_traffic_stress = max_bicycle_traffic_stress
|
|
150
|
+
|
|
151
|
+
# always record travel times
|
|
152
|
+
self._regional_task.recordTimes = True
|
|
153
|
+
|
|
154
|
+
# a few settings we don’t expose (yet?)
|
|
155
|
+
self._regional_task.makeTauiSite = False
|
|
156
|
+
self._regional_task.oneToOne = False
|
|
157
|
+
self._regional_task.monteCarloDraws = 60
|
|
158
|
+
self._regional_task.recordAccessibility = False
|
|
159
|
+
|
|
160
|
+
def __copy__(self):
|
|
161
|
+
"""Override `copy.copy()` to properly handle Java classes."""
|
|
162
|
+
# remember all __dict__ except `_regional_task`, which is a Java `Clonable`
|
|
163
|
+
_dict = {k: v for k, v in self.__dict__.items() if k != "_regional_task"}
|
|
164
|
+
# add a clone of the Java object
|
|
165
|
+
_dict["_regional_task"] = self._regional_task.clone()
|
|
166
|
+
|
|
167
|
+
# create a new instance, copy over the __dict__ created above
|
|
168
|
+
clone = super().__new__(self.__class__)
|
|
169
|
+
clone.__dict__ = _dict
|
|
170
|
+
|
|
171
|
+
return clone
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def access_modes(self):
|
|
175
|
+
"""Route with these modes to reach public transport (r5py.TransportMode)."""
|
|
176
|
+
return self._access_modes
|
|
177
|
+
|
|
178
|
+
@access_modes.setter
|
|
179
|
+
def access_modes(self, access_modes):
|
|
180
|
+
# eliminate duplicates, cast to TransportMode (converts str values)
|
|
181
|
+
access_modes = set(TransportMode(mode) for mode in set(access_modes))
|
|
182
|
+
self._access_modes = access_modes
|
|
183
|
+
self._regional_task.accessModes = RegionalTask._enum_set(
|
|
184
|
+
access_modes, com.conveyal.r5.api.util.LegMode
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def departure(self):
|
|
189
|
+
"""
|
|
190
|
+
Find public transport connections.
|
|
191
|
+
|
|
192
|
+
Find public transport connections leaving within
|
|
193
|
+
``departure_time_window`` after ``departure`` (datetime.datetime).
|
|
194
|
+
"""
|
|
195
|
+
return self._departure
|
|
196
|
+
|
|
197
|
+
@departure.setter
|
|
198
|
+
def departure(self, departure):
|
|
199
|
+
# fmt: off
|
|
200
|
+
if (
|
|
201
|
+
[mode for mode in self.transport_modes if mode.is_transit_mode]
|
|
202
|
+
and not self.transport_network.transit_layer.covers(departure)
|
|
203
|
+
):
|
|
204
|
+
# fmt: on
|
|
205
|
+
warnings.warn(
|
|
206
|
+
(
|
|
207
|
+
"The currently loaded GTFS data sets do not define "
|
|
208
|
+
f"any services on {departure.date()}."
|
|
209
|
+
),
|
|
210
|
+
RuntimeWarning,
|
|
211
|
+
stacklevel=1,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
self._departure = departure
|
|
215
|
+
self._regional_task.date = java.time.LocalDate.of(
|
|
216
|
+
departure.year, departure.month, departure.day
|
|
217
|
+
)
|
|
218
|
+
# seconds from midnight
|
|
219
|
+
self._regional_task.fromTime = int(
|
|
220
|
+
datetime.timedelta(
|
|
221
|
+
hours=departure.hour, minutes=departure.minute
|
|
222
|
+
).total_seconds()
|
|
223
|
+
)
|
|
224
|
+
try:
|
|
225
|
+
self._regional_task.toTime = int(
|
|
226
|
+
self._regional_task.fromTime
|
|
227
|
+
+ self.departure_time_window.total_seconds()
|
|
228
|
+
)
|
|
229
|
+
except AttributeError: # departure_time_window has not been set yet
|
|
230
|
+
pass
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def departure_time_window(self):
|
|
234
|
+
"""Find public transport connections.
|
|
235
|
+
|
|
236
|
+
Find public transport connections leaving within
|
|
237
|
+
``departure_time_window`` after ``departure`` (datetime.timedelta).
|
|
238
|
+
|
|
239
|
+
**Note:** The value of ``departure_time_window`` should be set with some
|
|
240
|
+
caution. Specifically, setting values near or below the typical headways
|
|
241
|
+
in the studied transit network may lead to routing problems. See `this
|
|
242
|
+
GitHub discussion <https://github.com/r5py/r5py/issues/292>`_ for
|
|
243
|
+
details.
|
|
244
|
+
"""
|
|
245
|
+
return self._departure_time_window
|
|
246
|
+
|
|
247
|
+
@departure_time_window.setter
|
|
248
|
+
def departure_time_window(self, departure_time_window: datetime.timedelta):
|
|
249
|
+
if departure_time_window.total_seconds() < 300:
|
|
250
|
+
warnings.warn(
|
|
251
|
+
"The provided departure time window is below 5 minutes. "
|
|
252
|
+
"This may cause adverse effects with routing.",
|
|
253
|
+
RuntimeWarning,
|
|
254
|
+
stacklevel=1,
|
|
255
|
+
)
|
|
256
|
+
self._departure_time_window = departure_time_window
|
|
257
|
+
self._regional_task.toTime = int(
|
|
258
|
+
self._regional_task.fromTime + departure_time_window.total_seconds()
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
@property
|
|
262
|
+
def destinations(self):
|
|
263
|
+
"""
|
|
264
|
+
Points to route to.
|
|
265
|
+
|
|
266
|
+
A ``geopandas.GeoDataFrame`` with a point geometry, and at least
|
|
267
|
+
an ``id`` column (which R5 mangles to ``str``).
|
|
268
|
+
"""
|
|
269
|
+
return self._destinations
|
|
270
|
+
|
|
271
|
+
@destinations.setter
|
|
272
|
+
def destinations(self, destinations):
|
|
273
|
+
if destinations is not None:
|
|
274
|
+
self._destinations = destinations
|
|
275
|
+
|
|
276
|
+
# wrap destinations in a few layers of streams (yeah, Java)
|
|
277
|
+
output_stream = java.io.ByteArrayOutputStream()
|
|
278
|
+
data_output_stream = java.io.DataOutputStream(output_stream)
|
|
279
|
+
|
|
280
|
+
# first: number of destinations
|
|
281
|
+
data_output_stream.writeInt(len(destinations))
|
|
282
|
+
|
|
283
|
+
# then, data columns, one by one, then still ‘opportunties’
|
|
284
|
+
for id_ in destinations.id.astype(str):
|
|
285
|
+
data_output_stream.writeUTF(id_)
|
|
286
|
+
for lat in destinations.geometry.y:
|
|
287
|
+
data_output_stream.writeDouble(lat)
|
|
288
|
+
for lon in destinations.geometry.x:
|
|
289
|
+
data_output_stream.writeDouble(lon)
|
|
290
|
+
for _ in range(len(destinations)):
|
|
291
|
+
data_output_stream.writeDouble(0) # ‘opportunities’
|
|
292
|
+
|
|
293
|
+
# convert to input stream, then into a point set
|
|
294
|
+
destinations_point_set = com.conveyal.r5.analyst.FreeFormPointSet(
|
|
295
|
+
java.io.ByteArrayInputStream(output_stream.toByteArray())
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
self._regional_task.destinationPointSets = [destinations_point_set]
|
|
299
|
+
|
|
300
|
+
# TODO: figure out whether we could cut this a bit shorter. We
|
|
301
|
+
# should be able to construct the ByteArray fed to
|
|
302
|
+
# java.io.ByteArrayInputStream as a Python `bytes` without the
|
|
303
|
+
# detour via two Java OutputStreams. (but not sure how to
|
|
304
|
+
# distinguish between the writeUTF/writeDouble/etc)
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def egress_modes(self):
|
|
308
|
+
"""Route with these modes from public transport (r5py.TransportMode)."""
|
|
309
|
+
return self._egress_modes
|
|
310
|
+
|
|
311
|
+
@egress_modes.setter
|
|
312
|
+
def egress_modes(self, egress_modes):
|
|
313
|
+
# eliminate duplicates, cast to TransportMode (converts str values)
|
|
314
|
+
egress_modes = set(TransportMode(mode) for mode in set(egress_modes))
|
|
315
|
+
self._egress_modes = egress_modes
|
|
316
|
+
self._regional_task.egressModes = RegionalTask._enum_set(
|
|
317
|
+
egress_modes, com.conveyal.r5.api.util.LegMode
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
@property
|
|
321
|
+
def max_bicycle_traffic_stress(self):
|
|
322
|
+
"""
|
|
323
|
+
Find routes with this maximum stress level for cyclists.
|
|
324
|
+
|
|
325
|
+
Int, in the range 1-4, see https://docs.conveyal.com/learn-more/traffic-stress
|
|
326
|
+
"""
|
|
327
|
+
return self._max_bicycle_traffic_stress
|
|
328
|
+
|
|
329
|
+
@max_bicycle_traffic_stress.setter
|
|
330
|
+
def max_bicycle_traffic_stress(self, max_bicycle_traffic_stress):
|
|
331
|
+
self._max_bicycle_traffic_stress = max_bicycle_traffic_stress
|
|
332
|
+
self._regional_task.bikeTrafficStress = max_bicycle_traffic_stress
|
|
333
|
+
|
|
334
|
+
@property
|
|
335
|
+
def max_public_transport_rides(self):
|
|
336
|
+
"""Include at most this many consecutive public transport rides (int)."""
|
|
337
|
+
return self._max_public_transport_rides
|
|
338
|
+
|
|
339
|
+
@max_public_transport_rides.setter
|
|
340
|
+
def max_public_transport_rides(self, max_public_transport_rides):
|
|
341
|
+
self._max_public_transport_rides = max_public_transport_rides
|
|
342
|
+
self._regional_task.maxRides = max_public_transport_rides
|
|
343
|
+
|
|
344
|
+
@property
|
|
345
|
+
def max_time(self):
|
|
346
|
+
"""Restrict trip duration (datetime.timedelta)."""
|
|
347
|
+
return self._max_time
|
|
348
|
+
|
|
349
|
+
@max_time.setter
|
|
350
|
+
def max_time(self, max_time):
|
|
351
|
+
self._max_time = max_time
|
|
352
|
+
|
|
353
|
+
try:
|
|
354
|
+
max_time_cycling = self.max_time_cycling
|
|
355
|
+
if max_time_cycling > max_time:
|
|
356
|
+
self.max_time_cycling = max_time
|
|
357
|
+
except AttributeError:
|
|
358
|
+
pass
|
|
359
|
+
try:
|
|
360
|
+
max_time_driving = self.max_time_driving
|
|
361
|
+
if max_time_driving > max_time:
|
|
362
|
+
self.max_time_driving = max_time
|
|
363
|
+
except AttributeError:
|
|
364
|
+
pass
|
|
365
|
+
try:
|
|
366
|
+
max_time_walking = self.max_time_walking
|
|
367
|
+
if max_time_walking > max_time:
|
|
368
|
+
self.max_time_walking = max_time
|
|
369
|
+
except AttributeError:
|
|
370
|
+
pass
|
|
371
|
+
|
|
372
|
+
max_time_sec = int(max_time.total_seconds() / 60)
|
|
373
|
+
self._regional_task.streetTime = max_time_sec
|
|
374
|
+
self._regional_task.maxTripDurationMinutes = max_time_sec
|
|
375
|
+
|
|
376
|
+
@property
|
|
377
|
+
def max_time_cycling(self):
|
|
378
|
+
"""
|
|
379
|
+
Restrict routes to at most this duration of cycling (datetime.timedelta).
|
|
380
|
+
|
|
381
|
+
Depending on the transport modes specified, this includes times
|
|
382
|
+
on the main leg of the trip, as well as during access and egress.
|
|
383
|
+
"""
|
|
384
|
+
return self._max_time_cycling
|
|
385
|
+
|
|
386
|
+
@max_time_cycling.setter
|
|
387
|
+
def max_time_cycling(self, max_time_cycling):
|
|
388
|
+
self._max_time_cycling = max_time_cycling
|
|
389
|
+
self._regional_task.maxBikeTime = int(max_time_cycling.total_seconds() / 60)
|
|
390
|
+
|
|
391
|
+
if self.max_time < max_time_cycling:
|
|
392
|
+
self.max_time = max_time_cycling
|
|
393
|
+
|
|
394
|
+
@property
|
|
395
|
+
def max_time_driving(self):
|
|
396
|
+
"""Restrict routes to at most this duration of driving (datetime.timedelta)."""
|
|
397
|
+
return self._max_time_driving
|
|
398
|
+
|
|
399
|
+
@max_time_driving.setter
|
|
400
|
+
def max_time_driving(self, max_time_driving):
|
|
401
|
+
self._max_time_driving = max_time_driving
|
|
402
|
+
self._regional_task.maxCarTime = int(max_time_driving.total_seconds() / 60)
|
|
403
|
+
|
|
404
|
+
if self.max_time < max_time_driving:
|
|
405
|
+
self.max_time = max_time_driving
|
|
406
|
+
|
|
407
|
+
@property
|
|
408
|
+
def max_time_walking(self):
|
|
409
|
+
"""
|
|
410
|
+
Restrict routes to at most this duration of walking (datetime.timedelta).
|
|
411
|
+
|
|
412
|
+
Depending on the transport modes specified, this includes times
|
|
413
|
+
on the main leg of the trip, as well as during access and egress.
|
|
414
|
+
"""
|
|
415
|
+
return self._max_time_walking
|
|
416
|
+
|
|
417
|
+
@max_time_walking.setter
|
|
418
|
+
def max_time_walking(self, max_time_walking):
|
|
419
|
+
self._max_time_walking = max_time_walking
|
|
420
|
+
self._regional_task.maxWalkTime = int(max_time_walking.total_seconds() / 60)
|
|
421
|
+
|
|
422
|
+
if self.max_time < max_time_walking:
|
|
423
|
+
self.max_time = max_time_walking
|
|
424
|
+
|
|
425
|
+
@property
|
|
426
|
+
def percentiles(self):
|
|
427
|
+
"""
|
|
428
|
+
Return the travel time for these percentiles.
|
|
429
|
+
|
|
430
|
+
By default, return the median travel time.
|
|
431
|
+
(collections.abc.Sequence[int])
|
|
432
|
+
"""
|
|
433
|
+
return self._percentiles
|
|
434
|
+
|
|
435
|
+
@percentiles.setter
|
|
436
|
+
def percentiles(self, percentiles):
|
|
437
|
+
try:
|
|
438
|
+
assert isinstance(percentiles, collections.abc.Sequence)
|
|
439
|
+
assert len(percentiles) <= 5 # R5 does not allow more than five percentiles
|
|
440
|
+
# (compare https://github.com/r5py/r5py/issues/139 )
|
|
441
|
+
except AssertionError as exception:
|
|
442
|
+
raise ValueError(
|
|
443
|
+
"Maximum number of percentiles allowed is 5"
|
|
444
|
+
) from exception
|
|
445
|
+
self._percentiles = percentiles
|
|
446
|
+
self._regional_task.percentiles = percentiles
|
|
447
|
+
|
|
448
|
+
# TODO: implement a proper balancing mechanism between the different per-mode
|
|
449
|
+
# maximum times, i.e., a sanity check that the different more specific max_times
|
|
450
|
+
# don’t exceed max_time, for instance, but probably also more complex interrelations
|
|
451
|
+
# (this needs some sitting down with pen and paper and a large cup of tea)
|
|
452
|
+
|
|
453
|
+
@property
|
|
454
|
+
def origin(self):
|
|
455
|
+
"""Set the origin for the routing operation (shapely.geometry.Point)."""
|
|
456
|
+
return self._origin
|
|
457
|
+
|
|
458
|
+
@origin.setter
|
|
459
|
+
def origin(self, origin):
|
|
460
|
+
"""
|
|
461
|
+
Set origin geometry.
|
|
462
|
+
|
|
463
|
+
Arguments:
|
|
464
|
+
----------
|
|
465
|
+
origin : shapely.geometry.Point
|
|
466
|
+
Point to route from
|
|
467
|
+
"""
|
|
468
|
+
if origin is not None:
|
|
469
|
+
self._origin = origin
|
|
470
|
+
self._regional_task.fromLat = origin.y
|
|
471
|
+
self._regional_task.fromLon = origin.x
|
|
472
|
+
|
|
473
|
+
@property
|
|
474
|
+
def scenario(self):
|
|
475
|
+
"""Expose the ``RegionalTask``’s ``Scenario`` to Python."""
|
|
476
|
+
return self._regional_task.scenario
|
|
477
|
+
|
|
478
|
+
@scenario.setter
|
|
479
|
+
def scenario(self, scenario):
|
|
480
|
+
self._regional_task.scenario = scenario
|
|
481
|
+
self._regional_task.scenarioId = scenario.id
|
|
482
|
+
|
|
483
|
+
@property
|
|
484
|
+
def speed_cycling(self):
|
|
485
|
+
"""Use this speed for routing for cyclists (km/h, float)."""
|
|
486
|
+
return self._speed_cycling
|
|
487
|
+
|
|
488
|
+
@speed_cycling.setter
|
|
489
|
+
def speed_cycling(self, speed_cycling):
|
|
490
|
+
self._speed_cycling = speed_cycling
|
|
491
|
+
self._regional_task.bikeSpeed = speed_cycling / 3600 * 1000 # km/h -> m/s
|
|
492
|
+
|
|
493
|
+
@property
|
|
494
|
+
def speed_walking(self):
|
|
495
|
+
"""Use this speed for routing pedestrian movement (km/h, float)."""
|
|
496
|
+
return self._speed_walking
|
|
497
|
+
|
|
498
|
+
@speed_walking.setter
|
|
499
|
+
def speed_walking(self, speed_walking):
|
|
500
|
+
self._speed_walking = speed_walking
|
|
501
|
+
self._regional_task.walkSpeed = speed_walking / 3600 * 1000 # km/h -> m/s
|
|
502
|
+
|
|
503
|
+
@property
|
|
504
|
+
def transport_modes(self):
|
|
505
|
+
"""
|
|
506
|
+
Get/set the transport modes used to route the main leg of trips.
|
|
507
|
+
|
|
508
|
+
(list[r5py.TransportMode])
|
|
509
|
+
"""
|
|
510
|
+
return self._transport_modes
|
|
511
|
+
|
|
512
|
+
@transport_modes.setter
|
|
513
|
+
def transport_modes(self, transport_modes):
|
|
514
|
+
# eliminate duplicates, cast to TransportMode (converts str values)
|
|
515
|
+
transport_modes = set(TransportMode(mode) for mode in set(transport_modes))
|
|
516
|
+
self._transport_modes = transport_modes
|
|
517
|
+
|
|
518
|
+
# split them up into direct and transit modes,
|
|
519
|
+
transit_modes = [mode for mode in transport_modes if mode.is_transit_mode]
|
|
520
|
+
direct_modes = [mode for mode in transport_modes if mode.is_street_mode]
|
|
521
|
+
|
|
522
|
+
# the different modes underlie certain rules
|
|
523
|
+
# e.g., some direct modes require certain access modes
|
|
524
|
+
# see https://github.com/ipeaGIT/r5r/blob/
|
|
525
|
+
# 2e8b9acfd81834f185d95ce53dc5c34beb1315f2/r-package/R/utils.R#L86
|
|
526
|
+
if transit_modes: # public transport:
|
|
527
|
+
egress_modes = self.egress_modes
|
|
528
|
+
if TransportMode.TRANSIT in transport_modes:
|
|
529
|
+
transit_modes = [mode for mode in TransportMode if mode.is_transit_mode]
|
|
530
|
+
if not direct_modes:
|
|
531
|
+
# only public transport modes passed in,
|
|
532
|
+
# let people walk to and from the stops
|
|
533
|
+
access_modes = direct_modes = [TransportMode.WALK]
|
|
534
|
+
else:
|
|
535
|
+
# otherwise, include the supplied direct modes into access_modes
|
|
536
|
+
access_modes = set(list(self.access_modes) + direct_modes)
|
|
537
|
+
|
|
538
|
+
else: # no public transport
|
|
539
|
+
egress_modes = [] # ignore egress (why?)
|
|
540
|
+
|
|
541
|
+
# this is weird (the following is the logic implemented in
|
|
542
|
+
# r5r) I reckon this is trying to keep the fastest mode only,
|
|
543
|
+
# and assumes that car is always faster that bike is always
|
|
544
|
+
# faster than walking
|
|
545
|
+
# if TransportMode.CAR in transport_modes:
|
|
546
|
+
# access_modes = direct_modes = [TransportMode.CAR]
|
|
547
|
+
# elif TransportMode.BICYCLE in transport_modes:
|
|
548
|
+
# access_modes = direct_modes = [TransportMode.BICYCLE]
|
|
549
|
+
# elif TransportMode.WALK in transport_modes:
|
|
550
|
+
# access_modes = direct_modes = [TransportMode.WALK]
|
|
551
|
+
|
|
552
|
+
# let’s do that differently
|
|
553
|
+
# (even if potentially more expensive, computationally)
|
|
554
|
+
access_modes = direct_modes
|
|
555
|
+
|
|
556
|
+
self.access_modes = access_modes
|
|
557
|
+
self.egress_modes = egress_modes
|
|
558
|
+
|
|
559
|
+
self._regional_task.transitModes = RegionalTask._enum_set(
|
|
560
|
+
transit_modes, com.conveyal.r5.api.util.TransitModes
|
|
561
|
+
)
|
|
562
|
+
self._regional_task.directModes = RegionalTask._enum_set(
|
|
563
|
+
direct_modes, com.conveyal.r5.api.util.LegMode
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
# pre-compute closest road segments/public transport stops to
|
|
567
|
+
# destination points (for fully-interconnected travel time matrices this
|
|
568
|
+
# also covers all origin points, but potentially this needs to be
|
|
569
|
+
# extended to run also for origins) //TODO
|
|
570
|
+
|
|
571
|
+
if self._regional_task.destinationPointSets is not None:
|
|
572
|
+
for mode in direct_modes:
|
|
573
|
+
for destination_point_set in self._regional_task.destinationPointSets:
|
|
574
|
+
self.transport_network.linkage_cache.getLinkage(
|
|
575
|
+
destination_point_set,
|
|
576
|
+
self.transport_network.street_layer,
|
|
577
|
+
mode,
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
@staticmethod
|
|
581
|
+
def _enum_set(values, java_class):
|
|
582
|
+
# helper function to construct a Java EnumSet out of a list of enum.Enum
|
|
583
|
+
enum_set = java.util.EnumSet.noneOf(java_class)
|
|
584
|
+
for mode in values:
|
|
585
|
+
enum_set.add(java_class.valueOf(mode.value))
|
|
586
|
+
return enum_set
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
@jpype._jcustomizer.JConversion(
|
|
590
|
+
"com.conveyal.r5.analyst.cluster.AnalysisWorkerTask", exact=RegionalTask
|
|
591
|
+
)
|
|
592
|
+
@jpype._jcustomizer.JConversion(
|
|
593
|
+
"com.conveyal.r5.profile.ProfileRequest", exact=RegionalTask
|
|
594
|
+
)
|
|
595
|
+
@jpype._jcustomizer.JConversion(
|
|
596
|
+
"com.conveyal.r5.analyst.cluster.RegionalTask", exact=RegionalTask
|
|
597
|
+
)
|
|
598
|
+
def _cast_RegionalTask(java_class, object_):
|
|
599
|
+
return object_._regional_task.clone()
|
|
600
|
+
# cloned, so we can reuse the Python instance (e.g., with next origin)
|
r5py/r5/scenario.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""Wraps a com.conveyal.r5.analyst.scenario.Scenario."""
|
|
4
|
+
|
|
5
|
+
import jpype
|
|
6
|
+
|
|
7
|
+
from ..util import start_jvm
|
|
8
|
+
|
|
9
|
+
import com.conveyal.r5
|
|
10
|
+
|
|
11
|
+
__all__ = ["Scenario"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
start_jvm()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Scenario:
|
|
18
|
+
"""Wrap a com.conveyal.r5.analyst.scenario.Scenario."""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
"""Initialise a most simple Scenario."""
|
|
22
|
+
scenario = com.conveyal.r5.analyst.scenario.Scenario()
|
|
23
|
+
scenario.id = "id"
|
|
24
|
+
self._scenario = scenario
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def id(self):
|
|
28
|
+
"""Retrieve the Java Scenario’s instance’s ID."""
|
|
29
|
+
return self._scenario.id
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@jpype._jcustomizer.JConversion(
|
|
33
|
+
"com.conveyal.r5.analyst.scenario.Scenario", exact=Scenario
|
|
34
|
+
)
|
|
35
|
+
def _cast_Scenario(java_class, object_):
|
|
36
|
+
return object_._scenario
|