r5py 0.1.2__py3-none-any.whl → 1.0.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.
Potentially problematic release.
This version of r5py might be problematic. Click here for more details.
- r5py/__init__.py +7 -1
- r5py/r5/__init__.py +6 -2
- r5py/r5/{base_travel_time_matrix_computer.py → base_travel_time_matrix.py} +25 -4
- r5py/r5/{detailed_itineraries_computer.py → detailed_itineraries.py} +76 -10
- r5py/r5/isochrones.py +351 -0
- r5py/r5/regional_task.py +2 -2
- r5py/r5/street_layer.py +7 -3
- r5py/r5/street_segment.py +41 -0
- r5py/r5/transit_layer.py +4 -0
- r5py/r5/transport_network.py +59 -146
- r5py/r5/{travel_time_matrix_computer.py → travel_time_matrix.py} +96 -10
- r5py/r5/trip.py +5 -4
- r5py/r5/trip_leg.py +32 -10
- r5py/r5/trip_planner.py +28 -15
- r5py/util/__init__.py +6 -0
- r5py/util/classpath.py +2 -2
- r5py/util/config.py +21 -5
- r5py/util/file_digest.py +42 -0
- r5py/util/good_enough_equidistant_crs.py +8 -3
- r5py/util/sample_data_set.py +4 -1
- r5py/util/spatially_clustered_geodataframe.py +78 -0
- r5py/util/working_copy.py +44 -0
- {r5py-0.1.2.dist-info → r5py-1.0.0.dist-info}/METADATA +34 -34
- r5py-1.0.0.dist-info/RECORD +47 -0
- {r5py-0.1.2.dist-info → r5py-1.0.0.dist-info}/WHEEL +1 -1
- r5py/sampledata/_keep/__init__.py +0 -3
- r5py-0.1.2.dist-info/RECORD +0 -43
- {r5py-0.1.2.dist-info → r5py-1.0.0.dist-info}/LICENSE +0 -0
- {r5py-0.1.2.dist-info → r5py-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""A less complex representation of com.conveyal.r5.api.util.StreetSegment."""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import datetime
|
|
7
|
+
|
|
8
|
+
import shapely
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = ["StreetSegment"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class StreetSegment:
|
|
15
|
+
"""A less complex representation of com.conveyal.r5.api.util.StreetSegment."""
|
|
16
|
+
|
|
17
|
+
distance = 0
|
|
18
|
+
duration = datetime.timedelta()
|
|
19
|
+
geometry = shapely.LineString()
|
|
20
|
+
|
|
21
|
+
def __init__(self, street_path):
|
|
22
|
+
"""
|
|
23
|
+
Initialise a less complex representation of com.conveyal.r5.api.util.StreetSegment.
|
|
24
|
+
|
|
25
|
+
Arguments
|
|
26
|
+
---------
|
|
27
|
+
street_path : com.conveyal.r5.profile.StreetPath
|
|
28
|
+
StreetPath, obtained, e.g., from StreetRouter state
|
|
29
|
+
"""
|
|
30
|
+
self.distance = street_path.getDistance()
|
|
31
|
+
self.duration = street_path.getDuration()
|
|
32
|
+
self.geometry = shapely.line_merge(
|
|
33
|
+
shapely.MultiLineString(
|
|
34
|
+
[
|
|
35
|
+
shapely.from_wkt(
|
|
36
|
+
str(street_path.getEdge(edge).getGeometry().toText())
|
|
37
|
+
)
|
|
38
|
+
for edge in street_path.getEdges()
|
|
39
|
+
]
|
|
40
|
+
)
|
|
41
|
+
)
|
r5py/r5/transit_layer.py
CHANGED
|
@@ -98,6 +98,10 @@ class TransitLayer:
|
|
|
98
98
|
"""Return a list of GTFS trip patterns."""
|
|
99
99
|
return list(self._transit_layer.tripPatterns)
|
|
100
100
|
|
|
101
|
+
def get_stop_id_from_index(self, stop_index):
|
|
102
|
+
"""Get the GTFS stop id for the `stop_index`-th stop of this transit layer."""
|
|
103
|
+
return self._transit_layer.stopIdForIndex[stop_index]
|
|
104
|
+
|
|
101
105
|
|
|
102
106
|
@jpype._jcustomizer.JConversion(
|
|
103
107
|
"com.conveyal.r5.transit.TransitLayer", exact=TransitLayer
|
r5py/r5/transport_network.py
CHANGED
|
@@ -5,24 +5,22 @@
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
import functools
|
|
8
|
+
import hashlib
|
|
8
9
|
import pathlib
|
|
9
|
-
import random
|
|
10
|
-
import shutil
|
|
11
|
-
import time
|
|
12
10
|
import warnings
|
|
13
11
|
|
|
14
|
-
import filelock
|
|
15
12
|
import jpype
|
|
16
13
|
import jpype.types
|
|
17
14
|
|
|
18
15
|
from .street_layer import StreetLayer
|
|
19
16
|
from .transit_layer import TransitLayer
|
|
20
17
|
from .transport_mode import TransportMode
|
|
21
|
-
from ..util import Config, contains_gtfs_data, start_jvm
|
|
18
|
+
from ..util import Config, contains_gtfs_data, FileDigest, start_jvm, WorkingCopy
|
|
22
19
|
|
|
23
20
|
import com.conveyal.gtfs
|
|
24
21
|
import com.conveyal.osmlib
|
|
25
22
|
import com.conveyal.r5
|
|
23
|
+
import java.io
|
|
26
24
|
|
|
27
25
|
|
|
28
26
|
__all__ = ["TransportNetwork"]
|
|
@@ -48,114 +46,63 @@ class TransportNetwork:
|
|
|
48
46
|
gtfs : str | pathlib.Path | list[str] | list[pathlib.Path]
|
|
49
47
|
path(s) to public transport schedule information in GTFS format
|
|
50
48
|
"""
|
|
51
|
-
osm_pbf =
|
|
49
|
+
osm_pbf = WorkingCopy(osm_pbf)
|
|
52
50
|
if isinstance(gtfs, (str, pathlib.Path)):
|
|
53
51
|
gtfs = [gtfs]
|
|
54
|
-
gtfs = [
|
|
52
|
+
gtfs = [WorkingCopy(path) for path in gtfs]
|
|
55
53
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
osm_file.readFromFile(f"{osm_pbf}")
|
|
63
|
-
|
|
64
|
-
self.osm_file = osm_file # keep the mapdb open, close in destructor
|
|
65
|
-
|
|
66
|
-
transport_network.streetLayer = com.conveyal.r5.streets.StreetLayer()
|
|
67
|
-
transport_network.streetLayer.loadFromOsm(osm_file)
|
|
68
|
-
transport_network.streetLayer.parentNetwork = transport_network
|
|
69
|
-
transport_network.streetLayer.indexStreets()
|
|
70
|
-
|
|
71
|
-
transport_network.transitLayer = com.conveyal.r5.transit.TransitLayer()
|
|
72
|
-
for gtfs_file in gtfs:
|
|
73
|
-
gtfs_feed = com.conveyal.gtfs.GTFSFeed.readOnlyTempFileFromGtfs(gtfs_file)
|
|
74
|
-
transport_network.transitLayer.loadFromGtfs(gtfs_feed)
|
|
75
|
-
gtfs_feed.close()
|
|
76
|
-
transport_network.transitLayer.parentNetwork = transport_network
|
|
77
|
-
|
|
78
|
-
transport_network.streetLayer.associateStops(transport_network.transitLayer)
|
|
79
|
-
transport_network.streetLayer.buildEdgeLists()
|
|
80
|
-
|
|
81
|
-
transport_network.transitLayer.rebuildTransientIndexes()
|
|
54
|
+
# a hash representing all input files
|
|
55
|
+
digest = hashlib.sha256(
|
|
56
|
+
"".join([FileDigest(osm_pbf)] + [FileDigest(path) for path in gtfs]).encode(
|
|
57
|
+
"utf-8"
|
|
58
|
+
)
|
|
59
|
+
).hexdigest()
|
|
82
60
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
61
|
+
try:
|
|
62
|
+
transport_network = self._load_pickled_transport_network(
|
|
63
|
+
Config().CACHE_DIR / f"{digest}.transport_network"
|
|
64
|
+
)
|
|
65
|
+
except FileNotFoundError:
|
|
66
|
+
transport_network = com.conveyal.r5.transit.TransportNetwork()
|
|
67
|
+
transport_network.scenarioId = PACKAGE
|
|
68
|
+
|
|
69
|
+
osm_mapdb = Config().CACHE_DIR / f"{digest}.mapdb"
|
|
70
|
+
osm_file = com.conveyal.osmlib.OSM(f"{osm_mapdb}")
|
|
71
|
+
osm_file.intersectionDetection = True
|
|
72
|
+
osm_file.readFromFile(f"{osm_pbf}")
|
|
73
|
+
|
|
74
|
+
transport_network.streetLayer = com.conveyal.r5.streets.StreetLayer()
|
|
75
|
+
transport_network.streetLayer.parentNetwork = transport_network
|
|
76
|
+
transport_network.streetLayer.loadFromOsm(osm_file)
|
|
77
|
+
transport_network.streetLayer.indexStreets()
|
|
78
|
+
|
|
79
|
+
transport_network.transitLayer = com.conveyal.r5.transit.TransitLayer()
|
|
80
|
+
transport_network.transitLayer.parentNetwork = transport_network
|
|
81
|
+
for gtfs_file in gtfs:
|
|
82
|
+
gtfs_feed = com.conveyal.gtfs.GTFSFeed.readOnlyTempFileFromGtfs(
|
|
83
|
+
f"{gtfs_file}"
|
|
84
|
+
)
|
|
85
|
+
transport_network.transitLayer.loadFromGtfs(gtfs_feed)
|
|
86
|
+
gtfs_feed.close()
|
|
86
87
|
|
|
87
|
-
|
|
88
|
+
transport_network.streetLayer.associateStops(transport_network.transitLayer)
|
|
89
|
+
transport_network.streetLayer.buildEdgeLists()
|
|
88
90
|
|
|
89
|
-
|
|
91
|
+
transport_network.transitLayer.rebuildTransientIndexes()
|
|
90
92
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
transfer_finder = com.conveyal.r5.transit.TransferFinder(transport_network)
|
|
94
|
+
transfer_finder.findTransfers()
|
|
95
|
+
transfer_finder.findParkRideTransfer()
|
|
94
96
|
|
|
95
|
-
|
|
96
|
-
# delete Java objects, and
|
|
97
|
-
# trigger Java garbage collection
|
|
98
|
-
try:
|
|
99
|
-
self.osm_file.close()
|
|
100
|
-
except jpype.JVMNotRunning:
|
|
101
|
-
# JVM was stopped already, file should be closed
|
|
102
|
-
pass
|
|
103
|
-
try:
|
|
104
|
-
del self.street_layer
|
|
105
|
-
except AttributeError: # might not have been accessed a single time
|
|
106
|
-
pass
|
|
107
|
-
try:
|
|
108
|
-
del self.transit_layer
|
|
109
|
-
except AttributeError:
|
|
110
|
-
pass
|
|
111
|
-
try:
|
|
112
|
-
del self._transport_network
|
|
113
|
-
except AttributeError:
|
|
114
|
-
pass
|
|
97
|
+
transport_network.transitLayer.buildDistanceTables(None)
|
|
115
98
|
|
|
116
|
-
|
|
117
|
-
try:
|
|
118
|
-
jpype.java.lang.System.gc()
|
|
119
|
-
except jpype.JVMNotRunning:
|
|
120
|
-
pass
|
|
99
|
+
osm_file.close() # not needed after here?
|
|
121
100
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
temporary_files = [child for child in self._cache_directory.iterdir()]
|
|
125
|
-
except FileNotFoundError: # deleted in the meantime/race condition
|
|
126
|
-
temporary_files = []
|
|
127
|
-
|
|
128
|
-
for _ in range(MAX_TRIES):
|
|
129
|
-
for temporary_file in temporary_files:
|
|
130
|
-
try:
|
|
131
|
-
temporary_file.unlink()
|
|
132
|
-
temporary_files.remove(temporary_file)
|
|
133
|
-
except (FileNotFoundError, IOError, OSError):
|
|
134
|
-
print(
|
|
135
|
-
f"could not delete {temporary_file}, keeping in {temporary_files}"
|
|
136
|
-
)
|
|
137
|
-
pass
|
|
138
|
-
|
|
139
|
-
if not temporary_files: # empty
|
|
140
|
-
break
|
|
141
|
-
|
|
142
|
-
# there are still files open, let’s wait a moment and try again
|
|
143
|
-
time.sleep(0.1)
|
|
144
|
-
else:
|
|
145
|
-
remaining_files = ", ".join(
|
|
146
|
-
[f"{temporary_file}" for temporary_file in temporary_files]
|
|
147
|
-
)
|
|
148
|
-
warnings.warn(
|
|
149
|
-
f"Failed to clean cache directory ‘{self._cache_directory}’. "
|
|
150
|
-
f"Remaining file(s): {remaining_files}",
|
|
151
|
-
RuntimeWarning,
|
|
101
|
+
self._save_pickled_transport_network(
|
|
102
|
+
transport_network, Config().CACHE_DIR / f"{digest}.transport_network"
|
|
152
103
|
)
|
|
153
104
|
|
|
154
|
-
|
|
155
|
-
try:
|
|
156
|
-
self._cache_directory.rmdir()
|
|
157
|
-
except OSError: # not empty
|
|
158
|
-
pass # the JVM destructor is going to take care of this
|
|
105
|
+
self._transport_network = transport_network
|
|
159
106
|
|
|
160
107
|
@classmethod
|
|
161
108
|
def from_directory(cls, path):
|
|
@@ -219,56 +166,22 @@ class TransportNetwork:
|
|
|
219
166
|
# then find the smaller extent of the two (or the larger one?)
|
|
220
167
|
return self.street_layer.extent
|
|
221
168
|
|
|
222
|
-
@functools.cached_property
|
|
223
|
-
def _cache_directory(self):
|
|
224
|
-
cache_dir = (
|
|
225
|
-
pathlib.Path(Config().TEMP_DIR)
|
|
226
|
-
/ f"{self.__class__.__name__:s}_{id(self):x}_{random.randrange(16**5):07x}"
|
|
227
|
-
)
|
|
228
|
-
cache_dir.mkdir(exist_ok=True)
|
|
229
|
-
return cache_dir
|
|
230
|
-
|
|
231
|
-
def _working_copy(self, input_file):
|
|
232
|
-
"""Create a copy or link of an input file in a cache directory.
|
|
233
|
-
|
|
234
|
-
This method exists because R5 creates temporary files in the
|
|
235
|
-
directory of input files. This can not only be annoying clutter,
|
|
236
|
-
but also create problems of concurrency, performance, etc., for
|
|
237
|
-
instance, when the data comes from a shared network drive or a
|
|
238
|
-
read-only file system.
|
|
239
|
-
|
|
240
|
-
Arguments
|
|
241
|
-
---------
|
|
242
|
-
input_file : str or pathlib.Path
|
|
243
|
-
The file to create a copy or link of in a cache directory
|
|
244
|
-
|
|
245
|
-
Returns
|
|
246
|
-
-------
|
|
247
|
-
pathlib.Path
|
|
248
|
-
The path to the copy or link created
|
|
249
|
-
"""
|
|
250
|
-
# try to first create a symbolic link, if that fails (e.g., on Windows),
|
|
251
|
-
# copy the file to a cache directory
|
|
252
|
-
input_file = pathlib.Path(input_file).absolute()
|
|
253
|
-
destination_file = pathlib.Path(
|
|
254
|
-
self._cache_directory / input_file.name
|
|
255
|
-
).absolute()
|
|
256
|
-
|
|
257
|
-
with filelock.FileLock(
|
|
258
|
-
destination_file.parent / f"{destination_file.name}.lock"
|
|
259
|
-
):
|
|
260
|
-
if not destination_file.exists():
|
|
261
|
-
try:
|
|
262
|
-
destination_file.symlink_to(input_file)
|
|
263
|
-
except OSError:
|
|
264
|
-
shutil.copyfile(str(input_file), str(destination_file))
|
|
265
|
-
return destination_file
|
|
266
|
-
|
|
267
169
|
@property
|
|
268
170
|
def linkage_cache(self):
|
|
269
171
|
"""Expose the `TransportNetwork`’s `linkageCache` to Python."""
|
|
270
172
|
return self._transport_network.linkageCache
|
|
271
173
|
|
|
174
|
+
def _load_pickled_transport_network(self, path):
|
|
175
|
+
try:
|
|
176
|
+
input_file = java.io.File(f"{path}")
|
|
177
|
+
return com.conveyal.r5.kryo.KryoNetworkSerializer.read(input_file)
|
|
178
|
+
except java.io.FileNotFoundException:
|
|
179
|
+
raise FileNotFoundError
|
|
180
|
+
|
|
181
|
+
def _save_pickled_transport_network(self, transport_network, path):
|
|
182
|
+
output_file = java.io.File(f"{path}")
|
|
183
|
+
com.conveyal.r5.kryo.KryoNetworkSerializer.write(transport_network, output_file)
|
|
184
|
+
|
|
272
185
|
def snap_to_network(
|
|
273
186
|
self,
|
|
274
187
|
points,
|
|
@@ -4,24 +4,83 @@
|
|
|
4
4
|
|
|
5
5
|
import copy
|
|
6
6
|
|
|
7
|
+
try:
|
|
8
|
+
from warnings import deprecated
|
|
9
|
+
except ImportError: # Python<=3.12
|
|
10
|
+
from typing_extensions import deprecated
|
|
11
|
+
|
|
7
12
|
import pandas
|
|
8
13
|
|
|
9
|
-
from .
|
|
14
|
+
from .base_travel_time_matrix import BaseTravelTimeMatrix
|
|
10
15
|
from ..util import start_jvm
|
|
11
16
|
|
|
12
17
|
import com.conveyal.r5
|
|
13
18
|
|
|
14
19
|
|
|
15
|
-
__all__ = ["TravelTimeMatrixComputer"]
|
|
20
|
+
__all__ = ["TravelTimeMatrix", "TravelTimeMatrixComputer"]
|
|
16
21
|
|
|
17
22
|
|
|
18
23
|
start_jvm()
|
|
19
24
|
|
|
20
25
|
|
|
21
|
-
class
|
|
26
|
+
class TravelTimeMatrix(BaseTravelTimeMatrix):
|
|
22
27
|
"""Compute travel times between many origins and destinations."""
|
|
23
28
|
|
|
24
|
-
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
|
+
del self.transport_network
|
|
82
|
+
|
|
83
|
+
def _compute(self):
|
|
25
84
|
"""
|
|
26
85
|
Compute travel times from all origins to all destinations.
|
|
27
86
|
|
|
@@ -76,17 +135,16 @@ class TravelTimeMatrixComputer(BaseTravelTimeMatrixComputer):
|
|
|
76
135
|
travel time.
|
|
77
136
|
"""
|
|
78
137
|
# First, create an empty DataFrame (this forces column types)
|
|
79
|
-
|
|
80
|
-
"from_id": pandas.Series(dtype=str),
|
|
81
|
-
"to_id": pandas.Series(dtype=str),
|
|
82
|
-
}
|
|
83
|
-
travel_time_columns.update(
|
|
138
|
+
od_matrix = pandas.DataFrame(
|
|
84
139
|
{
|
|
140
|
+
"from_id": pandas.Series(dtype=str),
|
|
141
|
+
"to_id": pandas.Series(dtype=str),
|
|
142
|
+
}
|
|
143
|
+
| {
|
|
85
144
|
f"travel_time_p{percentile:d}": pandas.Series(dtype=float)
|
|
86
145
|
for percentile in self.request.percentiles
|
|
87
146
|
}
|
|
88
147
|
)
|
|
89
|
-
od_matrix = pandas.DataFrame(travel_time_columns)
|
|
90
148
|
|
|
91
149
|
# first assign columns with correct length (`to_id`),
|
|
92
150
|
# only then fill `from_id` (it’s a scalar)
|
|
@@ -121,3 +179,31 @@ class TravelTimeMatrixComputer(BaseTravelTimeMatrixComputer):
|
|
|
121
179
|
od_matrix = self._parse_results(from_id, results)
|
|
122
180
|
|
|
123
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
|
@@ -54,8 +54,9 @@ class Trip:
|
|
|
54
54
|
Returns
|
|
55
55
|
=======
|
|
56
56
|
list : detailed information about this trip and its legs (segments):
|
|
57
|
-
|
|
58
|
-
|
|
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``
|
|
59
60
|
"""
|
|
60
61
|
return [[segment] + leg.as_table_row() for segment, leg in enumerate(self.legs)]
|
|
61
62
|
|
|
@@ -76,9 +77,9 @@ class Trip:
|
|
|
76
77
|
)
|
|
77
78
|
|
|
78
79
|
@property
|
|
79
|
-
def
|
|
80
|
+
def route_ids(self):
|
|
80
81
|
"""The public transport route(s) used on this trip."""
|
|
81
|
-
return [leg.
|
|
82
|
+
return [leg.route_id for leg in self.legs]
|
|
82
83
|
|
|
83
84
|
@property
|
|
84
85
|
def transport_modes(self):
|
r5py/r5/trip_leg.py
CHANGED
|
@@ -26,7 +26,11 @@ class TripLeg:
|
|
|
26
26
|
"distance",
|
|
27
27
|
"travel_time",
|
|
28
28
|
"wait_time",
|
|
29
|
-
"
|
|
29
|
+
"feed",
|
|
30
|
+
"agency_id",
|
|
31
|
+
"route_id",
|
|
32
|
+
"start_stop_id",
|
|
33
|
+
"end_stop_id",
|
|
30
34
|
"geometry",
|
|
31
35
|
]
|
|
32
36
|
|
|
@@ -37,28 +41,41 @@ class TripLeg:
|
|
|
37
41
|
distance=None,
|
|
38
42
|
travel_time=datetime.timedelta(seconds=0),
|
|
39
43
|
wait_time=datetime.timedelta(seconds=0),
|
|
40
|
-
|
|
44
|
+
feed=None,
|
|
45
|
+
agency_id=None,
|
|
46
|
+
route_id=None,
|
|
47
|
+
start_stop_id=None,
|
|
48
|
+
end_stop_id=None,
|
|
41
49
|
geometry=shapely.LineString(),
|
|
42
50
|
):
|
|
43
51
|
"""
|
|
44
52
|
Represent one leg of a trip.
|
|
45
53
|
|
|
46
|
-
This is a base class, use one the specific classes,
|
|
47
|
-
|
|
54
|
+
This is a base class, use one of the more specific classes, e.g.,
|
|
55
|
+
TransitLeg, or DirectLeg
|
|
48
56
|
|
|
49
57
|
Arguments
|
|
50
58
|
=========
|
|
51
59
|
transport_mode : r5py.TransportMode
|
|
52
60
|
mode of transport this trip leg was travelled
|
|
53
|
-
departure_time : datetime.datetime
|
|
61
|
+
departure_time : datetime.datetime
|
|
62
|
+
departure time of this trip leg
|
|
54
63
|
distance : float
|
|
55
64
|
distance covered by this trip leg, in metres
|
|
56
65
|
travel_time : datetime.timedelta
|
|
57
66
|
time spent travelling on this trip leg
|
|
58
67
|
wait_time : datetime.timedelta
|
|
59
68
|
time spent waiting for a connection on this trip leg
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
62
79
|
geometry : shapely.LineString
|
|
63
80
|
spatial representation of this trip leg
|
|
64
81
|
"""
|
|
@@ -67,7 +84,11 @@ class TripLeg:
|
|
|
67
84
|
self.distance = distance
|
|
68
85
|
self.travel_time = travel_time
|
|
69
86
|
self.wait_time = wait_time
|
|
70
|
-
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
|
|
71
92
|
self.geometry = geometry
|
|
72
93
|
|
|
73
94
|
def __add__(self, other):
|
|
@@ -177,7 +198,8 @@ class TripLeg:
|
|
|
177
198
|
Returns
|
|
178
199
|
=======
|
|
179
200
|
list : detailed information about this trip leg: ``transport_mode``,
|
|
180
|
-
|
|
181
|
-
|
|
201
|
+
``departure_time``, ``distance``, ``travel_time``, ``wait_time``,
|
|
202
|
+
``feed``, ``agency_id`` ``route_id``, ``start_stop_id``,
|
|
203
|
+
``end_stop_id``, ``geometry``
|
|
182
204
|
"""
|
|
183
205
|
return [getattr(self, column) for column in self.COLUMNS]
|
r5py/r5/trip_planner.py
CHANGED
|
@@ -17,6 +17,7 @@ import shapely
|
|
|
17
17
|
from .access_leg import AccessLeg
|
|
18
18
|
from .direct_leg import DirectLeg
|
|
19
19
|
from .egress_leg import EgressLeg
|
|
20
|
+
from .street_segment import StreetSegment
|
|
20
21
|
from .transfer_leg import TransferLeg
|
|
21
22
|
from .transit_leg import TransitLeg
|
|
22
23
|
from .transport_mode import TransportMode
|
|
@@ -64,7 +65,9 @@ class TripPlanner:
|
|
|
64
65
|
|
|
65
66
|
EQUIDISTANT_CRS = GoodEnoughEquidistantCrs(self.transport_network.extent)
|
|
66
67
|
self._crs_transformer_function = pyproj.Transformer.from_crs(
|
|
67
|
-
R5_CRS,
|
|
68
|
+
R5_CRS,
|
|
69
|
+
EQUIDISTANT_CRS,
|
|
70
|
+
always_xy=True,
|
|
68
71
|
).transform
|
|
69
72
|
|
|
70
73
|
@property
|
|
@@ -165,17 +168,13 @@ class TripPlanner:
|
|
|
165
168
|
return direct_paths
|
|
166
169
|
|
|
167
170
|
def _street_segment_from_router_state(self, router_state, transport_mode):
|
|
168
|
-
"""Retrieve a
|
|
171
|
+
"""Retrieve a StreetSegment for a route."""
|
|
169
172
|
street_path = com.conveyal.r5.profile.StreetPath(
|
|
170
173
|
router_state,
|
|
171
174
|
self.transport_network,
|
|
172
175
|
False,
|
|
173
176
|
)
|
|
174
|
-
street_segment =
|
|
175
|
-
street_path,
|
|
176
|
-
transport_mode,
|
|
177
|
-
self.transport_network.street_layer,
|
|
178
|
-
)
|
|
177
|
+
street_segment = StreetSegment(street_path)
|
|
179
178
|
return street_segment
|
|
180
179
|
|
|
181
180
|
@functools.cached_property
|
|
@@ -216,7 +215,6 @@ class TripPlanner:
|
|
|
216
215
|
distance=0.0,
|
|
217
216
|
travel_time=ZERO_SECONDS,
|
|
218
217
|
wait_time=ZERO_SECONDS,
|
|
219
|
-
route=None,
|
|
220
218
|
geometry=shapely.LineString(((lon, lat), (lon, lat))),
|
|
221
219
|
)
|
|
222
220
|
]
|
|
@@ -327,6 +325,17 @@ class TripPlanner:
|
|
|
327
325
|
|
|
328
326
|
else: # TransitLeg
|
|
329
327
|
pattern = transit_layer.trip_patterns[state.pattern]
|
|
328
|
+
|
|
329
|
+
# Use the indices to look up the stop ids, which 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
|
+
|
|
330
339
|
route = transit_layer.routes[pattern.routeIndex]
|
|
331
340
|
transport_mode = TransportMode(
|
|
332
341
|
com.conveyal.r5.transit.TransitLayer.getTransitModes(
|
|
@@ -379,13 +388,17 @@ class TripPlanner:
|
|
|
379
388
|
distance = None
|
|
380
389
|
|
|
381
390
|
leg = TransitLeg(
|
|
382
|
-
transport_mode,
|
|
383
|
-
departure_time,
|
|
384
|
-
distance,
|
|
385
|
-
travel_time,
|
|
386
|
-
wait_time,
|
|
387
|
-
str(
|
|
388
|
-
|
|
391
|
+
transport_mode=transport_mode,
|
|
392
|
+
departure_time=departure_time,
|
|
393
|
+
distance=distance,
|
|
394
|
+
travel_time=travel_time,
|
|
395
|
+
wait_time=wait_time,
|
|
396
|
+
feed=str(feed),
|
|
397
|
+
agency_id=str(route.agency_id),
|
|
398
|
+
route_id=str(route.route_id),
|
|
399
|
+
start_stop_id=str(start_stop_id),
|
|
400
|
+
end_stop_id=str(end_stop_id),
|
|
401
|
+
geometry=geometry,
|
|
389
402
|
)
|
|
390
403
|
|
|
391
404
|
# we traverse in reverse order:
|
r5py/util/__init__.py
CHANGED
|
@@ -8,18 +8,24 @@ from .camel_to_snake_case import camel_to_snake_case
|
|
|
8
8
|
from .config import Config
|
|
9
9
|
from .contains_gtfs_data import contains_gtfs_data
|
|
10
10
|
from .data_validation import check_od_data_set
|
|
11
|
+
from .file_digest import FileDigest
|
|
11
12
|
from .good_enough_equidistant_crs import GoodEnoughEquidistantCrs
|
|
12
13
|
from .jvm import start_jvm
|
|
13
14
|
from .parse_int_date import parse_int_date
|
|
14
15
|
from .snake_to_camel_case import snake_to_camel_case
|
|
16
|
+
from .spatially_clustered_geodataframe import SpatiallyClusteredGeoDataFrame
|
|
17
|
+
from .working_copy import WorkingCopy
|
|
15
18
|
|
|
16
19
|
__all__ = [
|
|
17
20
|
"camel_to_snake_case",
|
|
18
21
|
"check_od_data_set",
|
|
19
22
|
"Config",
|
|
20
23
|
"contains_gtfs_data",
|
|
24
|
+
"FileDigest",
|
|
21
25
|
"GoodEnoughEquidistantCrs",
|
|
22
26
|
"parse_int_date",
|
|
23
27
|
"snake_to_camel_case",
|
|
28
|
+
"SpatiallyClusteredGeoDataFrame",
|
|
24
29
|
"start_jvm",
|
|
30
|
+
"WorkingCopy",
|
|
25
31
|
]
|