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.
Files changed (49) hide show
  1. r5py/__init__.py +27 -0
  2. r5py/__main__.py +3 -0
  3. r5py/r5/__init__.py +39 -0
  4. r5py/r5/access_leg.py +12 -0
  5. r5py/r5/base_travel_time_matrix.py +255 -0
  6. r5py/r5/detailed_itineraries.py +226 -0
  7. r5py/r5/direct_leg.py +38 -0
  8. r5py/r5/egress_leg.py +12 -0
  9. r5py/r5/elevation_cost_function.py +50 -0
  10. r5py/r5/elevation_model.py +89 -0
  11. r5py/r5/file_storage.py +82 -0
  12. r5py/r5/isochrones.py +345 -0
  13. r5py/r5/regional_task.py +600 -0
  14. r5py/r5/scenario.py +36 -0
  15. r5py/r5/street_layer.py +90 -0
  16. r5py/r5/street_segment.py +39 -0
  17. r5py/r5/transfer_leg.py +12 -0
  18. r5py/r5/transit_layer.py +87 -0
  19. r5py/r5/transit_leg.py +12 -0
  20. r5py/r5/transport_mode.py +148 -0
  21. r5py/r5/transport_network.py +299 -0
  22. r5py/r5/travel_time_matrix.py +186 -0
  23. r5py/r5/trip.py +97 -0
  24. r5py/r5/trip_leg.py +204 -0
  25. r5py/r5/trip_planner.py +576 -0
  26. r5py/util/__init__.py +31 -0
  27. r5py/util/camel_to_snake_case.py +25 -0
  28. r5py/util/classpath.py +95 -0
  29. r5py/util/config.py +176 -0
  30. r5py/util/contains_gtfs_data.py +46 -0
  31. r5py/util/data_validation.py +28 -0
  32. r5py/util/environment.py +32 -0
  33. r5py/util/exceptions.py +43 -0
  34. r5py/util/file_digest.py +40 -0
  35. r5py/util/good_enough_equidistant_crs.py +73 -0
  36. r5py/util/jvm.py +138 -0
  37. r5py/util/memory_footprint.py +178 -0
  38. r5py/util/parse_int_date.py +24 -0
  39. r5py/util/sample_data_set.py +76 -0
  40. r5py/util/snake_to_camel_case.py +16 -0
  41. r5py/util/spatially_clustered_geodataframe.py +66 -0
  42. r5py/util/validating_requests_session.py +58 -0
  43. r5py/util/warnings.py +7 -0
  44. r5py/util/working_copy.py +42 -0
  45. r5py-1.1.0.dist-info/METADATA +176 -0
  46. r5py-1.1.0.dist-info/RECORD +49 -0
  47. r5py-1.1.0.dist-info/WHEEL +5 -0
  48. r5py-1.1.0.dist-info/licenses/LICENSE +3 -0
  49. r5py-1.1.0.dist-info/top_level.txt +1 -0
@@ -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