r5py 0.1.1.dev2__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 +8 -1
- r5py/__main__.py +1 -14
- r5py/r5/__init__.py +20 -2
- r5py/r5/{base_travel_time_matrix_computer.py → base_travel_time_matrix.py} +28 -8
- r5py/r5/{detailed_itineraries_computer.py → detailed_itineraries.py} +82 -20
- r5py/r5/direct_leg.py +1 -3
- r5py/r5/isochrones.py +351 -0
- r5py/r5/regional_task.py +12 -9
- r5py/r5/street_layer.py +8 -3
- r5py/r5/street_segment.py +41 -0
- r5py/r5/transfer_leg.py +2 -6
- r5py/r5/transit_layer.py +6 -0
- r5py/r5/transit_leg.py +1 -5
- r5py/r5/transport_mode.py +5 -3
- r5py/r5/transport_network.py +60 -138
- r5py/r5/travel_time_matrix.py +209 -0
- r5py/r5/trip.py +13 -8
- r5py/r5/trip_leg.py +76 -15
- r5py/r5/trip_planner.py +109 -54
- r5py/util/__init__.py +8 -0
- r5py/util/classpath.py +9 -5
- r5py/util/config.py +32 -7
- r5py/util/environment.py +34 -0
- r5py/util/file_digest.py +42 -0
- r5py/util/good_enough_equidistant_crs.py +8 -4
- r5py/util/memory_footprint.py +3 -5
- r5py/util/sample_data_set.py +17 -6
- r5py/util/spatially_clustered_geodataframe.py +78 -0
- r5py/util/validating_requests_session.py +2 -2
- r5py/util/working_copy.py +44 -0
- {r5py-0.1.1.dev2.dist-info → r5py-1.0.0.dist-info}/METADATA +34 -33
- r5py-1.0.0.dist-info/RECORD +47 -0
- {r5py-0.1.1.dev2.dist-info → r5py-1.0.0.dist-info}/WHEEL +1 -1
- r5py/r5/travel_time_matrix_computer.py +0 -134
- r5py/sampledata/_keep/__init__.py +0 -3
- r5py-0.1.1.dev2.dist-info/RECORD +0 -42
- {r5py-0.1.1.dev2.dist-info → r5py-1.0.0.dist-info}/LICENSE +0 -0
- {r5py-0.1.1.dev2.dist-info → r5py-1.0.0.dist-info}/top_level.txt +0 -0
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,106 +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
|
|
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()
|
|
77
60
|
|
|
78
|
-
|
|
79
|
-
|
|
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()
|
|
80
87
|
|
|
81
|
-
|
|
88
|
+
transport_network.streetLayer.associateStops(transport_network.transitLayer)
|
|
89
|
+
transport_network.streetLayer.buildEdgeLists()
|
|
82
90
|
|
|
83
|
-
|
|
84
|
-
transfer_finder.findTransfers()
|
|
85
|
-
transfer_finder.findParkRideTransfer()
|
|
91
|
+
transport_network.transitLayer.rebuildTransientIndexes()
|
|
86
92
|
|
|
87
|
-
|
|
93
|
+
transfer_finder = com.conveyal.r5.transit.TransferFinder(transport_network)
|
|
94
|
+
transfer_finder.findTransfers()
|
|
95
|
+
transfer_finder.findParkRideTransfer()
|
|
88
96
|
|
|
89
|
-
|
|
97
|
+
transport_network.transitLayer.buildDistanceTables(None)
|
|
90
98
|
|
|
91
|
-
|
|
92
|
-
"""
|
|
93
|
-
Delete all temporary files upon destruction.
|
|
94
|
-
"""
|
|
95
|
-
MAX_TRIES = 10
|
|
99
|
+
osm_file.close() # not needed after here?
|
|
96
100
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
# trigger Java garbage collection
|
|
100
|
-
try:
|
|
101
|
-
self.osm_file.close()
|
|
102
|
-
except jpype.JVMNotRunning:
|
|
103
|
-
# JVM was stopped already, file should be closed
|
|
104
|
-
pass
|
|
105
|
-
try:
|
|
106
|
-
del self.street_layer
|
|
107
|
-
except AttributeError: # might not have been accessed a single time
|
|
108
|
-
pass
|
|
109
|
-
try:
|
|
110
|
-
del self.transit_layer
|
|
111
|
-
except AttributeError:
|
|
112
|
-
pass
|
|
113
|
-
del self._transport_network
|
|
114
|
-
|
|
115
|
-
time.sleep(0.5)
|
|
116
|
-
jpype.java.lang.System.gc()
|
|
117
|
-
|
|
118
|
-
# then, try to delete all files in cache directory
|
|
119
|
-
temporary_files = [child for child in self._cache_directory.iterdir()]
|
|
120
|
-
for _ in range(MAX_TRIES):
|
|
121
|
-
for temporary_file in temporary_files:
|
|
122
|
-
try:
|
|
123
|
-
temporary_file.unlink()
|
|
124
|
-
temporary_files.remove(temporary_file)
|
|
125
|
-
except (FileNotFoundError, IOError, OSError):
|
|
126
|
-
print(
|
|
127
|
-
f"could not delete {temporary_file}, keeping in {temporary_files}"
|
|
128
|
-
)
|
|
129
|
-
pass
|
|
130
|
-
|
|
131
|
-
if not temporary_files: # empty
|
|
132
|
-
break
|
|
133
|
-
|
|
134
|
-
# there are still files open, let’s wait a moment and try again
|
|
135
|
-
time.sleep(0.1)
|
|
136
|
-
else:
|
|
137
|
-
remaining_files = ", ".join(
|
|
138
|
-
[f"{temporary_file}" for temporary_file in temporary_files]
|
|
139
|
-
)
|
|
140
|
-
warnings.warn(
|
|
141
|
-
f"Failed to clean cache directory ‘{self._cache_directory}’. "
|
|
142
|
-
f"Remaining file(s): {remaining_files}",
|
|
143
|
-
RuntimeWarning,
|
|
101
|
+
self._save_pickled_transport_network(
|
|
102
|
+
transport_network, Config().CACHE_DIR / f"{digest}.transport_network"
|
|
144
103
|
)
|
|
145
104
|
|
|
146
|
-
|
|
147
|
-
try:
|
|
148
|
-
self._cache_directory.rmdir()
|
|
149
|
-
except OSError: # not empty
|
|
150
|
-
pass # the JVM destructor is going to take care of this
|
|
105
|
+
self._transport_network = transport_network
|
|
151
106
|
|
|
152
107
|
@classmethod
|
|
153
108
|
def from_directory(cls, path):
|
|
@@ -206,60 +161,27 @@ class TransportNetwork:
|
|
|
206
161
|
|
|
207
162
|
@property
|
|
208
163
|
def extent(self):
|
|
164
|
+
"""The geographic area covered, as a `shapely.box`."""
|
|
209
165
|
# TODO: figure out how to get the extent of the GTFS schedule,
|
|
210
166
|
# then find the smaller extent of the two (or the larger one?)
|
|
211
167
|
return self.street_layer.extent
|
|
212
168
|
|
|
213
|
-
@functools.cached_property
|
|
214
|
-
def _cache_directory(self):
|
|
215
|
-
cache_dir = (
|
|
216
|
-
pathlib.Path(Config().TEMP_DIR)
|
|
217
|
-
/ f"{self.__class__.__name__:s}_{id(self):x}_{random.randrange(16**5):07x}"
|
|
218
|
-
)
|
|
219
|
-
cache_dir.mkdir(exist_ok=True)
|
|
220
|
-
return cache_dir
|
|
221
|
-
|
|
222
|
-
def _working_copy(self, input_file):
|
|
223
|
-
"""Create a copy or link of an input file in a cache directory.
|
|
224
|
-
|
|
225
|
-
This method exists because R5 creates temporary files in the
|
|
226
|
-
directory of input files. This can not only be annoying clutter,
|
|
227
|
-
but also create problems of concurrency, performance, etc., for
|
|
228
|
-
instance, when the data comes from a shared network drive or a
|
|
229
|
-
read-only file system.
|
|
230
|
-
|
|
231
|
-
Arguments
|
|
232
|
-
---------
|
|
233
|
-
input_file : str or pathlib.Path
|
|
234
|
-
The file to create a copy or link of in a cache directory
|
|
235
|
-
|
|
236
|
-
Returns
|
|
237
|
-
-------
|
|
238
|
-
pathlib.Path
|
|
239
|
-
The path to the copy or link created
|
|
240
|
-
"""
|
|
241
|
-
# try to first create a symbolic link, if that fails (e.g., on Windows),
|
|
242
|
-
# copy the file to a cache directory
|
|
243
|
-
input_file = pathlib.Path(input_file).absolute()
|
|
244
|
-
destination_file = pathlib.Path(
|
|
245
|
-
self._cache_directory / input_file.name
|
|
246
|
-
).absolute()
|
|
247
|
-
|
|
248
|
-
with filelock.FileLock(
|
|
249
|
-
destination_file.parent / f"{destination_file.name}.lock"
|
|
250
|
-
):
|
|
251
|
-
if not destination_file.exists():
|
|
252
|
-
try:
|
|
253
|
-
destination_file.symlink_to(input_file)
|
|
254
|
-
except OSError:
|
|
255
|
-
shutil.copyfile(str(input_file), str(destination_file))
|
|
256
|
-
return destination_file
|
|
257
|
-
|
|
258
169
|
@property
|
|
259
170
|
def linkage_cache(self):
|
|
260
171
|
"""Expose the `TransportNetwork`’s `linkageCache` to Python."""
|
|
261
172
|
return self._transport_network.linkageCache
|
|
262
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
|
+
|
|
263
185
|
def snap_to_network(
|
|
264
186
|
self,
|
|
265
187
|
points,
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""Calculate travel times between many origins and destinations."""
|
|
4
|
+
|
|
5
|
+
import copy
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from warnings import deprecated
|
|
9
|
+
except ImportError: # Python<=3.12
|
|
10
|
+
from typing_extensions import deprecated
|
|
11
|
+
|
|
12
|
+
import pandas
|
|
13
|
+
|
|
14
|
+
from .base_travel_time_matrix import BaseTravelTimeMatrix
|
|
15
|
+
from ..util import start_jvm
|
|
16
|
+
|
|
17
|
+
import com.conveyal.r5
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = ["TravelTimeMatrix", "TravelTimeMatrixComputer"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
start_jvm()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TravelTimeMatrix(BaseTravelTimeMatrix):
|
|
27
|
+
"""Compute travel times between many origins and destinations."""
|
|
28
|
+
|
|
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):
|
|
84
|
+
"""
|
|
85
|
+
Compute travel times from all origins to all destinations.
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
pandas.DataFrame
|
|
90
|
+
A data frame containing the columns ``from_id``, ``to_id``, and
|
|
91
|
+
``travel_time``, where ``travel_time`` is the median calculated
|
|
92
|
+
travel time between ``from_id`` and ``to_id`` or ``numpy.nan``
|
|
93
|
+
if no connection with the given parameters was found.
|
|
94
|
+
If non-default ``percentiles`` were requested: one or more columns
|
|
95
|
+
``travel_time_p{:02d}`` representing the particular percentile of
|
|
96
|
+
travel time.
|
|
97
|
+
"""
|
|
98
|
+
self._prepare_origins_destinations()
|
|
99
|
+
self.request.destinations = self.destinations
|
|
100
|
+
|
|
101
|
+
od_matrix = pandas.concat(
|
|
102
|
+
[self._travel_times_per_origin(from_id) for from_id in self.origins.id],
|
|
103
|
+
ignore_index=True,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
od_matrix = od_matrix.to_crs(self._origins_crs)
|
|
108
|
+
except AttributeError: # (not a GeoDataFrame)
|
|
109
|
+
pass
|
|
110
|
+
return od_matrix
|
|
111
|
+
|
|
112
|
+
def _parse_results(self, from_id, results):
|
|
113
|
+
"""
|
|
114
|
+
Parse the results of an R5 TravelTimeMatrix.
|
|
115
|
+
|
|
116
|
+
Parse data as returned from `com.conveyal.r5.analyst.TravelTimeComputer.computeTravelTimes()`,
|
|
117
|
+
cast data to Python types, and return as a `pandas.Dataframe`. Because of the way r5py
|
|
118
|
+
and R5 interact, this parses the results of routing from one origin to many (all) destinations.
|
|
119
|
+
|
|
120
|
+
Arguments
|
|
121
|
+
---------
|
|
122
|
+
from_id : mixed
|
|
123
|
+
The value of the ID column of the origin record to report on.
|
|
124
|
+
results : `com.conveyal.r5.OneOriginResult` (Java object)
|
|
125
|
+
|
|
126
|
+
Returns
|
|
127
|
+
-------
|
|
128
|
+
pandas.DataFrame
|
|
129
|
+
A data frame containing the columns ``from_id``, ``to_id``, and
|
|
130
|
+
``travel_time``, where ``travel_time`` is the median calculated
|
|
131
|
+
travel time between ``from_id`` and ``to_id`` or ``numpy.nan``
|
|
132
|
+
if no connection with the given parameters was found.
|
|
133
|
+
If non-default ``percentiles`` were requested: one or more columns
|
|
134
|
+
``travel_time_p{:02d}`` representing the particular percentile of
|
|
135
|
+
travel time.
|
|
136
|
+
"""
|
|
137
|
+
# First, create an empty DataFrame (this forces column types)
|
|
138
|
+
od_matrix = pandas.DataFrame(
|
|
139
|
+
{
|
|
140
|
+
"from_id": pandas.Series(dtype=str),
|
|
141
|
+
"to_id": pandas.Series(dtype=str),
|
|
142
|
+
}
|
|
143
|
+
| {
|
|
144
|
+
f"travel_time_p{percentile:d}": pandas.Series(dtype=float)
|
|
145
|
+
for percentile in self.request.percentiles
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# first assign columns with correct length (`to_id`),
|
|
150
|
+
# only then fill `from_id` (it’s a scalar)
|
|
151
|
+
od_matrix["to_id"] = self.destinations.id
|
|
152
|
+
od_matrix["from_id"] = from_id
|
|
153
|
+
|
|
154
|
+
for p, percentile in enumerate(self.request.percentiles):
|
|
155
|
+
travel_times = results.travelTimes.getValues()[p]
|
|
156
|
+
od_matrix[f"travel_time_p{percentile:d}"] = travel_times
|
|
157
|
+
|
|
158
|
+
# rename percentile column if only median requested (the default)
|
|
159
|
+
if self.request.percentiles == [50]:
|
|
160
|
+
od_matrix = od_matrix.rename(columns={"travel_time_p50": "travel_time"})
|
|
161
|
+
|
|
162
|
+
# R5’s NULL value is MAX_INT32
|
|
163
|
+
od_matrix = self._fill_nulls(od_matrix)
|
|
164
|
+
|
|
165
|
+
# re-index (and don’t keep the old index as a new column)
|
|
166
|
+
od_matrix = od_matrix.reset_index(drop=True)
|
|
167
|
+
|
|
168
|
+
return od_matrix
|
|
169
|
+
|
|
170
|
+
def _travel_times_per_origin(self, from_id):
|
|
171
|
+
request = copy.copy(self.request)
|
|
172
|
+
request.origin = self.origins[self.origins.id == from_id].geometry.item()
|
|
173
|
+
|
|
174
|
+
travel_time_computer = com.conveyal.r5.analyst.TravelTimeComputer(
|
|
175
|
+
request, self.transport_network
|
|
176
|
+
)
|
|
177
|
+
results = travel_time_computer.computeTravelTimes()
|
|
178
|
+
|
|
179
|
+
od_matrix = self._parse_results(from_id, results)
|
|
180
|
+
|
|
181
|
+
return od_matrix
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@deprecated(
|
|
185
|
+
"Use `TravelTimeMatrix` instead, `TravelTimeMatrixComputer will be deprecated in a future release."
|
|
186
|
+
)
|
|
187
|
+
class TravelTimeMatrixComputer:
|
|
188
|
+
"""Compute travel times between many origins and destinations."""
|
|
189
|
+
|
|
190
|
+
def __init__(self, *args, **kwargs):
|
|
191
|
+
"""Compute travel times between many origins and destinations."""
|
|
192
|
+
self._ttm = TravelTimeMatrix(*args, **kwargs)
|
|
193
|
+
|
|
194
|
+
def compute_travel_times(self):
|
|
195
|
+
"""
|
|
196
|
+
Compute travel times from all origins to all destinations.
|
|
197
|
+
|
|
198
|
+
Returns
|
|
199
|
+
-------
|
|
200
|
+
pandas.DataFrame
|
|
201
|
+
A data frame containing the columns ``from_id``, ``to_id``, and
|
|
202
|
+
``travel_time``, where ``travel_time`` is the median calculated
|
|
203
|
+
travel time between ``from_id`` and ``to_id`` or ``numpy.nan``
|
|
204
|
+
if no connection with the given parameters was found.
|
|
205
|
+
If non-default ``percentiles`` were requested: one or more columns
|
|
206
|
+
``travel_time_p{:02d}`` representing the particular percentile of
|
|
207
|
+
travel time.
|
|
208
|
+
"""
|
|
209
|
+
return self._ttm
|
r5py/r5/trip.py
CHANGED
|
@@ -15,9 +15,7 @@ __all__ = ["Trip"]
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class Trip:
|
|
18
|
-
"""
|
|
19
|
-
Represent one trip, consisting of one or more `TripLeg`s.
|
|
20
|
-
"""
|
|
18
|
+
"""Represent one trip, consisting of one or more `r5py.r5.TripLeg`."""
|
|
21
19
|
|
|
22
20
|
COLUMNS = [
|
|
23
21
|
"segment",
|
|
@@ -25,7 +23,7 @@ class Trip:
|
|
|
25
23
|
|
|
26
24
|
def __init__(self, legs=[]):
|
|
27
25
|
"""
|
|
28
|
-
Represent one trip, consisting of one of more `TripLeg
|
|
26
|
+
Represent one trip, consisting of one of more `r5py.r5.TripLeg`.
|
|
29
27
|
|
|
30
28
|
Arguments
|
|
31
29
|
=========
|
|
@@ -34,7 +32,13 @@ class Trip:
|
|
|
34
32
|
"""
|
|
35
33
|
self.legs = legs
|
|
36
34
|
|
|
35
|
+
def __eq__(self, other):
|
|
36
|
+
"""Check whether `self` and `other` are equal."""
|
|
37
|
+
if isinstance(other, self.__class__):
|
|
38
|
+
return self.legs == other.legs
|
|
39
|
+
|
|
37
40
|
def __repr__(self):
|
|
41
|
+
"""Return a string representation of `self`."""
|
|
38
42
|
legs = ", ".join([str(leg) for leg in self.legs])
|
|
39
43
|
return (
|
|
40
44
|
f"<{self.__class__.__name__}: "
|
|
@@ -50,8 +54,9 @@ class Trip:
|
|
|
50
54
|
Returns
|
|
51
55
|
=======
|
|
52
56
|
list : detailed information about this trip and its legs (segments):
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
``segment``, ``transport_mode``, ``departure_time``, ``distance``,
|
|
58
|
+
``travel_time``, ``wait_time``, ``feed``, ``agency_id``, ``route_id``,
|
|
59
|
+
``start_stop_id``, ``end_stop_id``, ``geometry``
|
|
55
60
|
"""
|
|
56
61
|
return [[segment] + leg.as_table_row() for segment, leg in enumerate(self.legs)]
|
|
57
62
|
|
|
@@ -72,9 +77,9 @@ class Trip:
|
|
|
72
77
|
)
|
|
73
78
|
|
|
74
79
|
@property
|
|
75
|
-
def
|
|
80
|
+
def route_ids(self):
|
|
76
81
|
"""The public transport route(s) used on this trip."""
|
|
77
|
-
return [leg.
|
|
82
|
+
return [leg.route_id for leg in self.legs]
|
|
78
83
|
|
|
79
84
|
@property
|
|
80
85
|
def transport_modes(self):
|