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/trip_planner.py
ADDED
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
"""Find detailed routes between two points."""
|
|
5
|
+
|
|
6
|
+
import copy
|
|
7
|
+
import collections
|
|
8
|
+
import datetime
|
|
9
|
+
import functools
|
|
10
|
+
import warnings
|
|
11
|
+
|
|
12
|
+
import jpype
|
|
13
|
+
import pyproj
|
|
14
|
+
import shapely
|
|
15
|
+
|
|
16
|
+
from .access_leg import AccessLeg
|
|
17
|
+
from .direct_leg import DirectLeg
|
|
18
|
+
from .egress_leg import EgressLeg
|
|
19
|
+
from .street_segment import StreetSegment
|
|
20
|
+
from .transfer_leg import TransferLeg
|
|
21
|
+
from .transit_leg import TransitLeg
|
|
22
|
+
from .transport_mode import TransportMode
|
|
23
|
+
from .trip import Trip
|
|
24
|
+
from ..util import GoodEnoughEquidistantCrs, start_jvm
|
|
25
|
+
|
|
26
|
+
import com.conveyal.r5
|
|
27
|
+
import gnu.trove.map
|
|
28
|
+
import java.lang
|
|
29
|
+
|
|
30
|
+
__all__ = ["TripPlanner"]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
start_jvm()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
COORDINATE_CORRECTION_FACTOR = com.conveyal.r5.streets.VertexStore.FIXED_FACTOR
|
|
37
|
+
R5_CRS = "EPSG:4326"
|
|
38
|
+
|
|
39
|
+
ONE_MINUTE = datetime.timedelta(minutes=1)
|
|
40
|
+
ZERO_SECONDS = datetime.timedelta(seconds=0)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TripPlanner:
|
|
44
|
+
"""Find detailed routes between two points."""
|
|
45
|
+
|
|
46
|
+
MAX_ACCESS_TIME = datetime.timedelta(hours=1)
|
|
47
|
+
MAX_EGRESS_TIME = MAX_ACCESS_TIME
|
|
48
|
+
|
|
49
|
+
def __init__(self, transport_network, request):
|
|
50
|
+
"""
|
|
51
|
+
Find detailed routes between two points.
|
|
52
|
+
|
|
53
|
+
Arguments
|
|
54
|
+
=========
|
|
55
|
+
transport_network : r5py.r5.TransportNetwork
|
|
56
|
+
A transport network to route on
|
|
57
|
+
request : r5py.r5.regional_task
|
|
58
|
+
The parameters that should be used when finding a route
|
|
59
|
+
"""
|
|
60
|
+
self.transport_network = transport_network
|
|
61
|
+
self.request = request
|
|
62
|
+
self._transfer_paths = {}
|
|
63
|
+
|
|
64
|
+
EQUIDISTANT_CRS = GoodEnoughEquidistantCrs(self.transport_network.extent)
|
|
65
|
+
self._crs_transformer_function = pyproj.Transformer.from_crs(
|
|
66
|
+
R5_CRS,
|
|
67
|
+
EQUIDISTANT_CRS,
|
|
68
|
+
always_xy=True,
|
|
69
|
+
).transform
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def trips(self):
|
|
73
|
+
"""
|
|
74
|
+
Detailed routes between two points.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
=======
|
|
78
|
+
list[r5py.r5.Trip]
|
|
79
|
+
Detailed routes that meet the requested parameters
|
|
80
|
+
"""
|
|
81
|
+
trips = self.direct_paths + self.transit_paths
|
|
82
|
+
return trips
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def direct_paths(self):
|
|
86
|
+
"""
|
|
87
|
+
Detailed routes between two points using direct modes.
|
|
88
|
+
|
|
89
|
+
Returns
|
|
90
|
+
=======
|
|
91
|
+
list[r5py.r5.Trip]
|
|
92
|
+
Detailed routes that meet the requested parameters, using direct
|
|
93
|
+
modes (walking, cycling, driving).
|
|
94
|
+
"""
|
|
95
|
+
direct_paths = []
|
|
96
|
+
request = copy.copy(self.request)
|
|
97
|
+
|
|
98
|
+
direct_modes = [mode for mode in request.transport_modes if mode.is_street_mode]
|
|
99
|
+
|
|
100
|
+
for transport_mode in direct_modes:
|
|
101
|
+
# short-circuit identical from_id and to_id:
|
|
102
|
+
if (
|
|
103
|
+
request._regional_task.fromLat == request._regional_task.toLat
|
|
104
|
+
and request._regional_task.fromLon == request._regional_task.toLon
|
|
105
|
+
):
|
|
106
|
+
lat = request._regional_task.fromLat
|
|
107
|
+
lon = request._regional_task.fromLon
|
|
108
|
+
direct_paths.append(
|
|
109
|
+
Trip(
|
|
110
|
+
[
|
|
111
|
+
DirectLeg(
|
|
112
|
+
transport_mode,
|
|
113
|
+
collections.namedtuple(
|
|
114
|
+
"StreetSegment",
|
|
115
|
+
["distance", "duration", "geometry"],
|
|
116
|
+
)(0.0, 0.0, f"LINESTRING({lon} {lat}, {lon} {lat})"),
|
|
117
|
+
)
|
|
118
|
+
]
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
else:
|
|
122
|
+
street_router = com.conveyal.r5.streets.StreetRouter(
|
|
123
|
+
self.transport_network.street_layer
|
|
124
|
+
)
|
|
125
|
+
street_router.profileRequest = request
|
|
126
|
+
street_router.streetMode = transport_mode
|
|
127
|
+
|
|
128
|
+
street_router.setOrigin(
|
|
129
|
+
request._regional_task.fromLat,
|
|
130
|
+
request._regional_task.fromLon,
|
|
131
|
+
)
|
|
132
|
+
street_router.setDestination(
|
|
133
|
+
request._regional_task.toLat,
|
|
134
|
+
request._regional_task.toLon,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
street_router.route()
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
router_state = street_router.getState(
|
|
141
|
+
street_router.getDestinationSplit()
|
|
142
|
+
)
|
|
143
|
+
street_segment = self._street_segment_from_router_state(
|
|
144
|
+
router_state,
|
|
145
|
+
transport_mode,
|
|
146
|
+
)
|
|
147
|
+
direct_paths.append(
|
|
148
|
+
Trip(
|
|
149
|
+
[
|
|
150
|
+
DirectLeg(transport_mode, street_segment),
|
|
151
|
+
]
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
except (
|
|
155
|
+
java.lang.NullPointerException,
|
|
156
|
+
java.util.NoSuchElementException,
|
|
157
|
+
):
|
|
158
|
+
warnings.warn(
|
|
159
|
+
f"Could not find route between origin "
|
|
160
|
+
f"({self.request._regional_task.fromLon}, "
|
|
161
|
+
f"{self.request._regional_task.fromLat}) "
|
|
162
|
+
f"and destination ({self.request._regional_task.toLon}, "
|
|
163
|
+
f"{self.request._regional_task.toLat})",
|
|
164
|
+
RuntimeWarning,
|
|
165
|
+
stacklevel=1,
|
|
166
|
+
)
|
|
167
|
+
return direct_paths
|
|
168
|
+
|
|
169
|
+
def _street_segment_from_router_state(self, router_state, transport_mode):
|
|
170
|
+
"""Retrieve a StreetSegment for a route."""
|
|
171
|
+
street_path = com.conveyal.r5.profile.StreetPath(
|
|
172
|
+
router_state,
|
|
173
|
+
self.transport_network,
|
|
174
|
+
False,
|
|
175
|
+
)
|
|
176
|
+
street_segment = StreetSegment(street_path)
|
|
177
|
+
return street_segment
|
|
178
|
+
|
|
179
|
+
@functools.cached_property
|
|
180
|
+
def transit_paths(self):
|
|
181
|
+
"""
|
|
182
|
+
Detailed routes between two points on public transport.
|
|
183
|
+
|
|
184
|
+
Returns
|
|
185
|
+
=======
|
|
186
|
+
list[r5py.r5.Trip]
|
|
187
|
+
Detailed routes that meet the requested parameters, on public
|
|
188
|
+
transport.
|
|
189
|
+
"""
|
|
190
|
+
transit_paths = []
|
|
191
|
+
|
|
192
|
+
# if any transit mode requested:
|
|
193
|
+
if [mode for mode in self.request.transport_modes if mode.is_transit_mode]:
|
|
194
|
+
request = copy.copy(self.request)
|
|
195
|
+
|
|
196
|
+
midnight = self.request.departure.replace(
|
|
197
|
+
hour=0, minute=0, second=0, microsecond=0
|
|
198
|
+
)
|
|
199
|
+
suboptimal_minutes = max(self.request._regional_task.suboptimalMinutes, 0)
|
|
200
|
+
transit_layer = self.transport_network.transit_layer
|
|
201
|
+
|
|
202
|
+
if (
|
|
203
|
+
request._regional_task.fromLat == request._regional_task.toLat
|
|
204
|
+
and request._regional_task.fromLon == request._regional_task.toLon
|
|
205
|
+
):
|
|
206
|
+
lat = request._regional_task.fromLat
|
|
207
|
+
lon = request._regional_task.fromLon
|
|
208
|
+
transit_paths.append(
|
|
209
|
+
Trip(
|
|
210
|
+
[
|
|
211
|
+
TransitLeg(
|
|
212
|
+
transport_mode=TransportMode.TRANSIT,
|
|
213
|
+
departure_time=None,
|
|
214
|
+
distance=0.0,
|
|
215
|
+
travel_time=ZERO_SECONDS,
|
|
216
|
+
wait_time=ZERO_SECONDS,
|
|
217
|
+
geometry=shapely.LineString(((lon, lat), (lon, lat))),
|
|
218
|
+
)
|
|
219
|
+
]
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
else:
|
|
223
|
+
# McRapterSuboptimalPathProfileRouter needs this simple callback,
|
|
224
|
+
# this could, of course, be a lambda function, but this way it’s
|
|
225
|
+
# cleaner
|
|
226
|
+
def list_supplier_callback(departure_time):
|
|
227
|
+
return com.conveyal.r5.profile.SuboptimalDominatingList(
|
|
228
|
+
suboptimal_minutes
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
transit_router = (
|
|
232
|
+
com.conveyal.r5.profile.McRaptorSuboptimalPathProfileRouter(
|
|
233
|
+
self.transport_network,
|
|
234
|
+
request,
|
|
235
|
+
self._transit_access_times,
|
|
236
|
+
self._transit_egress_times,
|
|
237
|
+
list_supplier_callback,
|
|
238
|
+
None,
|
|
239
|
+
True,
|
|
240
|
+
)
|
|
241
|
+
)
|
|
242
|
+
transit_router.route()
|
|
243
|
+
|
|
244
|
+
# `finalStatesByDepartureTime` is a hashmap of lists of router
|
|
245
|
+
# states, indexed by departure times (in seconds since midnight)
|
|
246
|
+
final_states = {
|
|
247
|
+
(midnight + datetime.timedelta(seconds=departure_time)): state
|
|
248
|
+
for departure_time, states in zip(
|
|
249
|
+
transit_router.finalStatesByDepartureTime.keys(),
|
|
250
|
+
transit_router.finalStatesByDepartureTime.values(),
|
|
251
|
+
)
|
|
252
|
+
for state in list(states) # some departure times yield no results
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
# keep another cache layer of shortest access and egress legs
|
|
256
|
+
access_legs_by_stop = {}
|
|
257
|
+
egress_legs_by_stop = {}
|
|
258
|
+
|
|
259
|
+
for departure_time, state in final_states.items():
|
|
260
|
+
trip = Trip()
|
|
261
|
+
while state:
|
|
262
|
+
if state.stop == -1: # EgressLeg
|
|
263
|
+
try:
|
|
264
|
+
leg = egress_legs_by_stop[state.back.stop]
|
|
265
|
+
except KeyError:
|
|
266
|
+
leg = min(
|
|
267
|
+
[
|
|
268
|
+
self._transit_egress_paths[transport_mode][
|
|
269
|
+
state.back.stop
|
|
270
|
+
]
|
|
271
|
+
for transport_mode in self._transit_egress_paths
|
|
272
|
+
]
|
|
273
|
+
)
|
|
274
|
+
egress_legs_by_stop[state.back.stop] = leg
|
|
275
|
+
leg.wait_time = ZERO_SECONDS
|
|
276
|
+
leg.departure_time = (
|
|
277
|
+
midnight
|
|
278
|
+
+ datetime.timedelta(seconds=state.back.time)
|
|
279
|
+
+ ONE_MINUTE
|
|
280
|
+
)
|
|
281
|
+
leg.arrival_time = leg.departure_time + leg.travel_time
|
|
282
|
+
|
|
283
|
+
elif state.back is None: # AccessLeg
|
|
284
|
+
try:
|
|
285
|
+
leg = access_legs_by_stop[state.stop]
|
|
286
|
+
except KeyError:
|
|
287
|
+
leg = min(
|
|
288
|
+
[
|
|
289
|
+
self._transit_access_paths[transport_mode][
|
|
290
|
+
state.stop
|
|
291
|
+
]
|
|
292
|
+
for transport_mode in self._transit_access_paths
|
|
293
|
+
]
|
|
294
|
+
)
|
|
295
|
+
access_legs_by_stop[state.stop] = leg
|
|
296
|
+
leg.wait_time = ZERO_SECONDS
|
|
297
|
+
leg.arrival_time = midnight + datetime.timedelta(
|
|
298
|
+
seconds=state.time
|
|
299
|
+
)
|
|
300
|
+
leg.departure_time = leg.arrival_time - leg.travel_time
|
|
301
|
+
|
|
302
|
+
else:
|
|
303
|
+
if state.pattern == -1: # TransferLeg
|
|
304
|
+
departure_stop = state.back.stop
|
|
305
|
+
arrival_stop = state.stop
|
|
306
|
+
|
|
307
|
+
leg = self._transit_transfer_path(
|
|
308
|
+
departure_stop, arrival_stop
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
leg.departure_time = (
|
|
312
|
+
midnight
|
|
313
|
+
+ datetime.timedelta(seconds=state.back.time)
|
|
314
|
+
+ ONE_MINUTE
|
|
315
|
+
)
|
|
316
|
+
leg.arrival_time = leg.departure_time + leg.travel_time
|
|
317
|
+
leg.wait_time = (
|
|
318
|
+
datetime.timedelta(
|
|
319
|
+
seconds=(state.time - state.back.time)
|
|
320
|
+
)
|
|
321
|
+
- leg.travel_time
|
|
322
|
+
+ ONE_MINUTE # the slack added above
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
else: # TransitLeg
|
|
326
|
+
pattern = transit_layer.trip_patterns[state.pattern]
|
|
327
|
+
|
|
328
|
+
# Use the indices to look up the stop ids, which
|
|
329
|
+
# are scoped by the GTFS feed supplied
|
|
330
|
+
start_stop_id = transit_layer.get_stop_id_from_index(
|
|
331
|
+
state.back.stop
|
|
332
|
+
).split(":")[1]
|
|
333
|
+
end_stop = transit_layer.get_stop_id_from_index(
|
|
334
|
+
state.stop
|
|
335
|
+
)
|
|
336
|
+
end_stop_id = end_stop.split(":")[1]
|
|
337
|
+
feed = end_stop.split(":")[0]
|
|
338
|
+
|
|
339
|
+
route = transit_layer.routes[pattern.routeIndex]
|
|
340
|
+
transport_mode = TransportMode(
|
|
341
|
+
com.conveyal.r5.transit.TransitLayer.getTransitModes( # noqa: E501
|
|
342
|
+
route.route_type
|
|
343
|
+
).toString()
|
|
344
|
+
)
|
|
345
|
+
departure_time = midnight + datetime.timedelta(
|
|
346
|
+
seconds=state.boardTime
|
|
347
|
+
)
|
|
348
|
+
travel_time = datetime.timedelta(
|
|
349
|
+
seconds=(state.time - state.boardTime)
|
|
350
|
+
)
|
|
351
|
+
wait_time = datetime.timedelta(
|
|
352
|
+
seconds=(state.boardTime - state.back.time)
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
# ‘hops’ in R5 terminology are the LineStrings
|
|
356
|
+
# between each pair of consecutive stops of a route
|
|
357
|
+
hops = list(pattern.getHopGeometries(transit_layer))
|
|
358
|
+
|
|
359
|
+
# select only the ‘hops’ between our stops, and merge
|
|
360
|
+
# them into one LineString
|
|
361
|
+
hops = hops[
|
|
362
|
+
state.boardStopPosition : state.alightStopPosition # noqa: E203, E501
|
|
363
|
+
]
|
|
364
|
+
geometry = shapely.line_merge(
|
|
365
|
+
shapely.MultiLineString(
|
|
366
|
+
[
|
|
367
|
+
shapely.from_wkt(str(geometry.toText()))
|
|
368
|
+
for geometry in hops
|
|
369
|
+
]
|
|
370
|
+
)
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
# distance: based on the geometry, which might
|
|
374
|
+
# be inaccurate.
|
|
375
|
+
distance = shapely.ops.transform(
|
|
376
|
+
self._crs_transformer_function,
|
|
377
|
+
geometry,
|
|
378
|
+
).length
|
|
379
|
+
|
|
380
|
+
leg = TransitLeg(
|
|
381
|
+
transport_mode=transport_mode,
|
|
382
|
+
departure_time=departure_time,
|
|
383
|
+
distance=distance,
|
|
384
|
+
travel_time=travel_time,
|
|
385
|
+
wait_time=wait_time,
|
|
386
|
+
feed=str(feed),
|
|
387
|
+
agency_id=str(route.agency_id),
|
|
388
|
+
route_id=str(route.route_id),
|
|
389
|
+
start_stop_id=str(start_stop_id),
|
|
390
|
+
end_stop_id=str(end_stop_id),
|
|
391
|
+
geometry=geometry,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
# we traverse in reverse order:
|
|
395
|
+
# add leg to beginning of trip,
|
|
396
|
+
# then fetch previous state (=leg)
|
|
397
|
+
trip = leg + trip
|
|
398
|
+
state = state.back
|
|
399
|
+
|
|
400
|
+
# R5 sometimes reports the same path more than once, skip duplicates
|
|
401
|
+
if trip not in transit_paths:
|
|
402
|
+
transit_paths.append(trip)
|
|
403
|
+
|
|
404
|
+
return transit_paths
|
|
405
|
+
|
|
406
|
+
@functools.cached_property
|
|
407
|
+
def _transit_access_paths(self):
|
|
408
|
+
access_paths = {}
|
|
409
|
+
|
|
410
|
+
request = copy.copy(self.request)
|
|
411
|
+
request._regional_task.reverseSearch = False
|
|
412
|
+
|
|
413
|
+
street_router = com.conveyal.r5.streets.StreetRouter(
|
|
414
|
+
self.transport_network.street_layer
|
|
415
|
+
)
|
|
416
|
+
street_router.profileRequest = request
|
|
417
|
+
street_router.setOrigin(
|
|
418
|
+
self.request._regional_task.fromLat,
|
|
419
|
+
self.request._regional_task.fromLon,
|
|
420
|
+
)
|
|
421
|
+
street_router.transitStopSearch = True
|
|
422
|
+
street_router.timeLimitSeconds = round(self.MAX_ACCESS_TIME.total_seconds())
|
|
423
|
+
|
|
424
|
+
transit_layer = self.transport_network.transit_layer
|
|
425
|
+
|
|
426
|
+
for transport_mode in request.access_modes:
|
|
427
|
+
access_paths[transport_mode] = {}
|
|
428
|
+
|
|
429
|
+
street_router.streetMode = transport_mode
|
|
430
|
+
street_router.route()
|
|
431
|
+
reached_stops = street_router.getReachedStops()
|
|
432
|
+
|
|
433
|
+
for stop in reached_stops.keys():
|
|
434
|
+
router_state = street_router.getStateAtVertex(
|
|
435
|
+
transit_layer.get_street_vertex_for_stop(stop)
|
|
436
|
+
)
|
|
437
|
+
street_segment = self._street_segment_from_router_state(
|
|
438
|
+
router_state,
|
|
439
|
+
transport_mode,
|
|
440
|
+
)
|
|
441
|
+
access_paths[transport_mode][stop] = AccessLeg(
|
|
442
|
+
transport_mode, street_segment
|
|
443
|
+
)
|
|
444
|
+
return access_paths
|
|
445
|
+
|
|
446
|
+
@functools.cached_property
|
|
447
|
+
def _transit_access_times(self):
|
|
448
|
+
"""
|
|
449
|
+
Times to reached stops.
|
|
450
|
+
|
|
451
|
+
In the format required by McRaptorSuboptimalPathProfileRouter.
|
|
452
|
+
"""
|
|
453
|
+
access_times = jpype.JObject(
|
|
454
|
+
{
|
|
455
|
+
com.conveyal.r5.api.util.LegMode
|
|
456
|
+
@ mode: gnu.trove.map.hash.TIntIntHashMap(
|
|
457
|
+
[stop for stop in reached_stops.keys()],
|
|
458
|
+
[
|
|
459
|
+
round(transfer_leg.travel_time.total_seconds())
|
|
460
|
+
for transfer_leg in reached_stops.values()
|
|
461
|
+
],
|
|
462
|
+
)
|
|
463
|
+
for mode, reached_stops in self._transit_access_paths.items()
|
|
464
|
+
},
|
|
465
|
+
"java.util.Map<com.conveyal.r5.LegMode, gnu.trove.map.TIntIntMap>",
|
|
466
|
+
)
|
|
467
|
+
return access_times
|
|
468
|
+
|
|
469
|
+
@functools.cached_property
|
|
470
|
+
def _transit_egress_paths(self):
|
|
471
|
+
egress_paths = {}
|
|
472
|
+
|
|
473
|
+
request = copy.copy(self.request)
|
|
474
|
+
request._regional_task.reverseSearch = True
|
|
475
|
+
|
|
476
|
+
street_router = com.conveyal.r5.streets.StreetRouter(
|
|
477
|
+
self.transport_network.street_layer
|
|
478
|
+
)
|
|
479
|
+
street_router.profileRequest = request
|
|
480
|
+
street_router.setOrigin(
|
|
481
|
+
self.request._regional_task.toLat,
|
|
482
|
+
self.request._regional_task.toLon,
|
|
483
|
+
)
|
|
484
|
+
street_router.transitStopSearch = True
|
|
485
|
+
street_router.timeLimitSeconds = round(self.MAX_EGRESS_TIME.total_seconds())
|
|
486
|
+
|
|
487
|
+
transit_layer = self.transport_network.transit_layer
|
|
488
|
+
|
|
489
|
+
for transport_mode in request.egress_modes:
|
|
490
|
+
egress_paths[transport_mode] = {}
|
|
491
|
+
|
|
492
|
+
street_router.streetMode = transport_mode
|
|
493
|
+
|
|
494
|
+
street_router.route()
|
|
495
|
+
reached_stops = street_router.getReachedStops()
|
|
496
|
+
|
|
497
|
+
for stop in reached_stops.keys():
|
|
498
|
+
router_state = street_router.getStateAtVertex(
|
|
499
|
+
transit_layer.get_street_vertex_for_stop(stop)
|
|
500
|
+
)
|
|
501
|
+
street_segment = self._street_segment_from_router_state(
|
|
502
|
+
router_state,
|
|
503
|
+
transport_mode,
|
|
504
|
+
)
|
|
505
|
+
egress_paths[transport_mode][stop] = EgressLeg(
|
|
506
|
+
transport_mode, street_segment
|
|
507
|
+
)
|
|
508
|
+
return egress_paths
|
|
509
|
+
|
|
510
|
+
@functools.cached_property
|
|
511
|
+
def _transit_egress_times(self):
|
|
512
|
+
"""
|
|
513
|
+
Times to reached stops.
|
|
514
|
+
|
|
515
|
+
In the format required by McRaptorSuboptimalPathProfileRouter.
|
|
516
|
+
"""
|
|
517
|
+
egress_times = jpype.JObject(
|
|
518
|
+
{
|
|
519
|
+
com.conveyal.r5.api.util.LegMode
|
|
520
|
+
@ mode: gnu.trove.map.hash.TIntIntHashMap(
|
|
521
|
+
[stop for stop in reached_stops.keys()],
|
|
522
|
+
[
|
|
523
|
+
round(transfer_leg.travel_time.total_seconds())
|
|
524
|
+
for transfer_leg in reached_stops.values()
|
|
525
|
+
],
|
|
526
|
+
)
|
|
527
|
+
for mode, reached_stops in self._transit_egress_paths.items()
|
|
528
|
+
},
|
|
529
|
+
"java.util.Map<com.conveyal.r5.LegMode, gnu.trove.map.TIntIntMap>",
|
|
530
|
+
)
|
|
531
|
+
return egress_times
|
|
532
|
+
|
|
533
|
+
def _transit_transfer_path(self, from_stop, to_stop):
|
|
534
|
+
"""Find a transfer path between two transit stops."""
|
|
535
|
+
self._transfer_paths = {}
|
|
536
|
+
while True:
|
|
537
|
+
try:
|
|
538
|
+
transfer_path = self._transfer_paths[(from_stop, to_stop)]
|
|
539
|
+
except KeyError:
|
|
540
|
+
request = copy.copy(self.request)
|
|
541
|
+
|
|
542
|
+
street_router = com.conveyal.r5.streets.StreetRouter(
|
|
543
|
+
self.transport_network.street_layer
|
|
544
|
+
)
|
|
545
|
+
street_router.profileRequest = request
|
|
546
|
+
street_router.streetMode = TransportMode.WALK
|
|
547
|
+
|
|
548
|
+
get_coordinates_for_stop = (
|
|
549
|
+
self.transport_network.transit_layer._transit_layer.getCoordinateForStopFixed # noqa: E501
|
|
550
|
+
)
|
|
551
|
+
from_stop_coordinates = get_coordinates_for_stop(from_stop)
|
|
552
|
+
to_stop_coordinates = get_coordinates_for_stop(to_stop)
|
|
553
|
+
|
|
554
|
+
from_lat = from_stop_coordinates.getY() / COORDINATE_CORRECTION_FACTOR
|
|
555
|
+
from_lon = from_stop_coordinates.getX() / COORDINATE_CORRECTION_FACTOR
|
|
556
|
+
to_lat = to_stop_coordinates.getY() / COORDINATE_CORRECTION_FACTOR
|
|
557
|
+
to_lon = to_stop_coordinates.getX() / COORDINATE_CORRECTION_FACTOR
|
|
558
|
+
|
|
559
|
+
street_router.setOrigin(from_lat, from_lon)
|
|
560
|
+
street_router.setDestination(to_lat, to_lon)
|
|
561
|
+
|
|
562
|
+
street_router.route()
|
|
563
|
+
|
|
564
|
+
router_state = street_router.getState(
|
|
565
|
+
street_router.getDestinationSplit()
|
|
566
|
+
)
|
|
567
|
+
street_segment = self._street_segment_from_router_state(
|
|
568
|
+
router_state,
|
|
569
|
+
TransportMode.WALK,
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
transfer_path = self._transfer_paths[(from_stop, to_stop)] = (
|
|
573
|
+
TransferLeg(TransportMode.WALK, street_segment)
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
return transfer_path
|
r5py/util/__init__.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""Utility functions, e.g., starting a JVM, and accessing configuration."""
|
|
4
|
+
|
|
5
|
+
from . import environment # noqa: F401
|
|
6
|
+
|
|
7
|
+
from .camel_to_snake_case import camel_to_snake_case
|
|
8
|
+
from .config import Config
|
|
9
|
+
from .contains_gtfs_data import contains_gtfs_data
|
|
10
|
+
from .data_validation import check_od_data_set
|
|
11
|
+
from .file_digest import FileDigest
|
|
12
|
+
from .good_enough_equidistant_crs import GoodEnoughEquidistantCrs
|
|
13
|
+
from .jvm import start_jvm
|
|
14
|
+
from .parse_int_date import parse_int_date
|
|
15
|
+
from .snake_to_camel_case import snake_to_camel_case
|
|
16
|
+
from .spatially_clustered_geodataframe import SpatiallyClusteredGeoDataFrame
|
|
17
|
+
from .working_copy import WorkingCopy
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"camel_to_snake_case",
|
|
21
|
+
"check_od_data_set",
|
|
22
|
+
"Config",
|
|
23
|
+
"contains_gtfs_data",
|
|
24
|
+
"FileDigest",
|
|
25
|
+
"GoodEnoughEquidistantCrs",
|
|
26
|
+
"parse_int_date",
|
|
27
|
+
"snake_to_camel_case",
|
|
28
|
+
"SpatiallyClusteredGeoDataFrame",
|
|
29
|
+
"start_jvm",
|
|
30
|
+
"WorkingCopy",
|
|
31
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""Convert a camelCase/CamelCase formated string to a snake_case format."""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
__all__ = ["camel_to_snake_case"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# https://stackoverflow.com/a/1176023
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
CAMEL_CASE_TO_SNAKE_CASE_RE1 = re.compile("(.)([A-Z][a-z]+)")
|
|
14
|
+
CAMEL_CASE_TO_SNAKE_CASE_RE2 = re.compile("([a-z0-9])([A-Z])")
|
|
15
|
+
CAMEL_CASE_TO_SNAKE_CASE_SUBSTITUTE = r"\1_\2"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def camel_to_snake_case(camel_case):
|
|
19
|
+
"""Convert `camel_case` to snake_case spelling."""
|
|
20
|
+
return CAMEL_CASE_TO_SNAKE_CASE_RE2.sub(
|
|
21
|
+
CAMEL_CASE_TO_SNAKE_CASE_SUBSTITUTE,
|
|
22
|
+
CAMEL_CASE_TO_SNAKE_CASE_RE1.sub(
|
|
23
|
+
CAMEL_CASE_TO_SNAKE_CASE_SUBSTITUTE, camel_case
|
|
24
|
+
),
|
|
25
|
+
).lower()
|