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,186 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """Calculate travel times between many origins and destinations."""
4
+
5
+ import copy
6
+
7
+ import pandas
8
+
9
+ from .base_travel_time_matrix import BaseTravelTimeMatrix
10
+ from ..util import start_jvm
11
+
12
+ import com.conveyal.r5
13
+
14
+ __all__ = ["TravelTimeMatrix"]
15
+
16
+
17
+ start_jvm()
18
+
19
+
20
+ class TravelTimeMatrix(BaseTravelTimeMatrix):
21
+ """Compute travel times between many origins and destinations."""
22
+
23
+ def __init__(
24
+ self,
25
+ transport_network,
26
+ origins=None,
27
+ destinations=None,
28
+ snap_to_network=False,
29
+ **kwargs,
30
+ ):
31
+ """
32
+ Compute travel times between many origins and destinations.
33
+
34
+ ``r5py.TravelTimeMatrix`` are child classes of ``pandas.DataFrame`` and
35
+ support all of their methods and properties,
36
+ see https://pandas.pydata.org/docs/
37
+
38
+ Arguments
39
+ ---------
40
+ transport_network : r5py.TransportNetwork | tuple(
41
+ pathlib.Paths | str, list(pathlib.Path | str))
42
+ The transport network to route on. This can either be a readily
43
+ initialised r5py.TransportNetwork or a tuple of the parameters
44
+ passed to ``TransportNetwork.__init__()``: the path to an
45
+ OpenStreetMap extract in PBF format, and a list of zero of more
46
+ paths to GTFS transport schedule files.
47
+ origins : geopandas.GeoDataFrame
48
+ Places to find a route _from_
49
+ Has to have a point geometry, and at least an `id` column
50
+ destinations : geopandas.GeoDataFrame (optional)
51
+ Places to find a route _to_
52
+ Has to have a point geometry, and at least an `id` column
53
+ If omitted, use same data set as for origins
54
+ snap_to_network : bool or int, default False
55
+ Should origin an destination points be snapped to the street network
56
+ before routing? If `True`, the default search radius (defined in
57
+ `com.conveyal.r5.streets.StreetLayer.LINK_RADIUS_METERS`) is used,
58
+ if `int`, use `snap_to_network` meters as the search radius.
59
+ **kwargs : mixed
60
+ Any arguments than can be passed to r5py.RegionalTask:
61
+ ``departure``, ``departure_time_window``, ``percentiles``,
62
+ ``transport_modes``, ``access_modes``, ``egress_modes``,
63
+ ``max_time``, ``max_time_walking``, ``max_time_cycling``,
64
+ ``max_time_driving``, ``speed_cycling``, ``speed_walking``,
65
+ ``max_public_transport_rides``, ``max_bicycle_traffic_stress``
66
+ """
67
+ super().__init__(
68
+ transport_network,
69
+ origins,
70
+ destinations,
71
+ snap_to_network,
72
+ **kwargs,
73
+ )
74
+ data = self._compute()
75
+ for column in data.columns:
76
+ self[column] = data[column]
77
+ del self.transport_network
78
+
79
+ def _compute(self):
80
+ """
81
+ Compute travel times from all origins to all destinations.
82
+
83
+ Returns
84
+ -------
85
+ pandas.DataFrame
86
+ A data frame containing the columns ``from_id``, ``to_id``, and
87
+ ``travel_time``, where ``travel_time`` is the median calculated
88
+ travel time between ``from_id`` and ``to_id`` or ``numpy.nan``
89
+ if no connection with the given parameters was found.
90
+ If non-default ``percentiles`` were requested: one or more columns
91
+ ``travel_time_p{:02d}`` representing the particular percentile of
92
+ travel time.
93
+ """
94
+ self._prepare_origins_destinations()
95
+ self.request.destinations = self.destinations
96
+
97
+ od_matrix = pandas.concat(
98
+ [self._travel_times_per_origin(from_id) for from_id in self.origins.id],
99
+ ignore_index=True,
100
+ )
101
+
102
+ try:
103
+ od_matrix = od_matrix.to_crs(self._origins_crs)
104
+ except AttributeError: # (not a GeoDataFrame)
105
+ pass
106
+ return od_matrix
107
+
108
+ def _parse_results(self, from_id, results):
109
+ """
110
+ Parse the results of an R5 TravelTimeMatrix.
111
+
112
+ Parse data as returned from
113
+ `com.conveyal.r5.analyst.TravelTimeComputer.computeTravelTimes()`, cast
114
+ data to Python types, and return as a `pandas.Dataframe`. Because of the
115
+ way r5py and R5 interact, this parses the results of routing from one
116
+ origin to many (all) destinations.
117
+
118
+ Arguments
119
+ ---------
120
+ from_id : mixed
121
+ The value of the ID column of the origin record to report on.
122
+ results : `com.conveyal.r5.OneOriginResult` (Java object)
123
+
124
+ Returns
125
+ -------
126
+ pandas.DataFrame
127
+ A data frame containing the columns ``from_id``, ``to_id``, and
128
+ ``travel_time``, where ``travel_time`` is the median calculated
129
+ travel time between ``from_id`` and ``to_id`` or ``numpy.nan``
130
+ if no connection with the given parameters was found.
131
+ If non-default ``percentiles`` were requested: one or more columns
132
+ ``travel_time_p{:02d}`` representing the particular percentile of
133
+ travel time.
134
+ """
135
+ # First, create an empty DataFrame (this forces column types)
136
+ od_matrix = pandas.DataFrame(
137
+ {
138
+ "from_id": pandas.Series(dtype=str),
139
+ "to_id": pandas.Series(dtype=str),
140
+ }
141
+ | {
142
+ f"travel_time_p{percentile:d}": pandas.Series(dtype=float)
143
+ for percentile in self.request.percentiles
144
+ }
145
+ )
146
+
147
+ # first assign columns with correct length (`to_id`),
148
+ # only then fill `from_id` (it’s a scalar)
149
+ od_matrix["to_id"] = self.destinations.id
150
+ od_matrix["from_id"] = from_id
151
+
152
+ for p, percentile in enumerate(self.request.percentiles):
153
+ travel_times = results.travelTimes.getValues()[p]
154
+ od_matrix[f"travel_time_p{percentile:d}"] = travel_times
155
+
156
+ # R5 always routes from/to next street segment
157
+ # (results in >0 travel time for from_id==to_id)
158
+ od_matrix.loc[
159
+ (od_matrix.from_id == od_matrix.to_id),
160
+ f"travel_time_p{percentile:d}",
161
+ ] = 0
162
+
163
+ # rename percentile column if only median requested (the default)
164
+ if self.request.percentiles == [50]:
165
+ od_matrix = od_matrix.rename(columns={"travel_time_p50": "travel_time"})
166
+
167
+ # R5’s NULL value is MAX_INT32
168
+ od_matrix = self._fill_nulls(od_matrix)
169
+
170
+ # re-index (and don’t keep the old index as a new column)
171
+ od_matrix = od_matrix.reset_index(drop=True)
172
+
173
+ return od_matrix
174
+
175
+ def _travel_times_per_origin(self, from_id):
176
+ request = copy.copy(self.request)
177
+ request.origin = self.origins[self.origins.id == from_id].geometry.item()
178
+
179
+ travel_time_computer = com.conveyal.r5.analyst.TravelTimeComputer(
180
+ request, self.transport_network
181
+ )
182
+ results = travel_time_computer.computeTravelTimes()
183
+
184
+ od_matrix = self._parse_results(from_id, results)
185
+
186
+ return od_matrix
r5py/r5/trip.py ADDED
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ """Represent one trip, consisting of one or more `TripLeg`s."""
5
+
6
+ import datetime
7
+
8
+ import shapely
9
+
10
+ from .trip_leg import TripLeg
11
+
12
+ __all__ = ["Trip"]
13
+
14
+
15
+ class Trip:
16
+ """Represent one trip, consisting of one or more `r5py.r5.TripLeg`."""
17
+
18
+ COLUMNS = [
19
+ "segment",
20
+ ] + TripLeg.COLUMNS
21
+
22
+ def __init__(self, legs=[]): # noqa: B006
23
+ """
24
+ Represent one trip, consisting of one of more `r5py.r5.TripLeg`.
25
+
26
+ Arguments
27
+ =========
28
+ legs : collections.abc.Iterable
29
+ optional list of trip legs with which to initialise this trip
30
+ """
31
+ self.legs = legs
32
+
33
+ def __eq__(self, other):
34
+ """Check whether `self` and `other` are equal."""
35
+ if isinstance(other, self.__class__):
36
+ return self.legs == other.legs
37
+
38
+ def __repr__(self):
39
+ """Return a string representation of `self`."""
40
+ legs = ", ".join([str(leg) for leg in self.legs])
41
+ return (
42
+ f"<{self.__class__.__name__}: "
43
+ f"{self.distance}m, "
44
+ f"{self.travel_time.total_seconds()}s, "
45
+ f"{legs}>"
46
+ )
47
+
48
+ def as_table(self):
49
+ """
50
+ Return a table (list of lists) of this trip’s legs.
51
+
52
+ Returns
53
+ =======
54
+ list : detailed information about this trip and its legs (segments):
55
+ ``segment``, ``transport_mode``, ``departure_time``, ``distance``,
56
+ ``travel_time``, ``wait_time``, ``feed``, ``agency_id``, ``route_id``,
57
+ ``start_stop_id``, ``end_stop_id``, ``geometry``
58
+ """
59
+ return [[segment] + leg.as_table_row() for segment, leg in enumerate(self.legs)]
60
+
61
+ @property
62
+ def distance(self):
63
+ """Overall distance of this trip in metres (float)."""
64
+ try:
65
+ distance = sum([leg.distance for leg in self.legs])
66
+ except TypeError: # distance of a leg can be None
67
+ distance = None
68
+ return distance
69
+
70
+ @property
71
+ def geometry(self):
72
+ """Joined geometries of all legs of this trip."""
73
+ return shapely.line_merge(
74
+ shapely.MultiLineString([leg.geometry for leg in self.legs])
75
+ )
76
+
77
+ @property
78
+ def route_ids(self):
79
+ """The public transport route(s) used on this trip."""
80
+ return [leg.route_id for leg in self.legs]
81
+
82
+ @property
83
+ def transport_modes(self):
84
+ """The transport mode(s) used on this trip."""
85
+ return [leg.transport_mode for leg in self.legs]
86
+
87
+ @property
88
+ def travel_time(self):
89
+ """Overall travel_time of this trip (datetime.timedelta)."""
90
+ return sum(
91
+ [leg.travel_time for leg in self.legs], datetime.timedelta(seconds=0)
92
+ )
93
+
94
+ @property
95
+ def wait_time(self):
96
+ """Overall wait_time of this trip (datetime.timedelta)."""
97
+ return sum([leg.wait_time for leg in self.legs], datetime.timedelta(seconds=0))
r5py/r5/trip_leg.py ADDED
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ """Represent one leg of a trip."""
5
+
6
+ import datetime
7
+ import numpy
8
+ import shapely
9
+
10
+ __all__ = ["TripLeg"]
11
+
12
+
13
+ class TripLeg:
14
+ """
15
+ Represent one leg of a trip.
16
+
17
+ This is a base class, use one the specific classes,
18
+ e.g., TransitLeg, or DirectLeg
19
+ """
20
+
21
+ COLUMNS = [
22
+ "transport_mode",
23
+ "departure_time",
24
+ "distance",
25
+ "travel_time",
26
+ "wait_time",
27
+ "feed",
28
+ "agency_id",
29
+ "route_id",
30
+ "start_stop_id",
31
+ "end_stop_id",
32
+ "geometry",
33
+ ]
34
+
35
+ def __init__(
36
+ self,
37
+ transport_mode=None,
38
+ departure_time=numpy.datetime64("NaT"), # noqa: B008
39
+ distance=None,
40
+ travel_time=datetime.timedelta(seconds=0), # noqa: B008
41
+ wait_time=datetime.timedelta(seconds=0), # noqa: B008
42
+ feed=None,
43
+ agency_id=None,
44
+ route_id=None,
45
+ start_stop_id=None,
46
+ end_stop_id=None,
47
+ geometry=shapely.LineString(), # noqa: B008
48
+ ):
49
+ """
50
+ Represent one leg of a trip.
51
+
52
+ This is a base class, use one of the more specific classes, e.g.,
53
+ TransitLeg, or DirectLeg
54
+
55
+ Arguments
56
+ =========
57
+ transport_mode : r5py.TransportMode
58
+ mode of transport this trip leg was travelled
59
+ departure_time : datetime.datetime
60
+ departure time of this trip leg
61
+ distance : float
62
+ distance covered by this trip leg, in metres
63
+ travel_time : datetime.timedelta
64
+ time spent travelling on this trip leg
65
+ wait_time : datetime.timedelta
66
+ time spent waiting for a connection on this trip leg
67
+ feed : str
68
+ the GTFS feed identifier used for this trip leg
69
+ agency_id : str
70
+ the GTFS id the agency used for this trip leg
71
+ route_id : str
72
+ the GTFS id of the public transport route used for this trip leg
73
+ start_stop_id : str
74
+ the GTFS stop_id of the boarding stop used for this trip leg
75
+ end_stop_id : str
76
+ the GTFS stop_id of the aligning stop used for this trip leg
77
+ geometry : shapely.LineString
78
+ spatial representation of this trip leg
79
+ """
80
+ self.transport_mode = transport_mode
81
+ self.departure_time = departure_time
82
+ self.distance = distance
83
+ self.travel_time = travel_time
84
+ self.wait_time = wait_time
85
+ self.feed = feed
86
+ self.agency_id = agency_id
87
+ self.route_id = route_id
88
+ self.start_stop_id = start_stop_id
89
+ self.end_stop_id = end_stop_id
90
+ self.geometry = geometry
91
+
92
+ def __add__(self, other):
93
+ """Trip-chain `other` to `self`."""
94
+ from .trip import Trip
95
+
96
+ if isinstance(other, self.__class__):
97
+ trip = Trip([self, other])
98
+ return trip
99
+ elif isinstance(other, Trip):
100
+ other.legs = [self] + other.legs
101
+ return other
102
+ else:
103
+ raise TypeError(
104
+ "unsupported operand type(s) for '+': "
105
+ f"'{type(other)}' and '{type(self)}'"
106
+ )
107
+
108
+ def __radd__(self, other):
109
+ """Trip-chain `self` to `other`."""
110
+ from .trip import Trip
111
+
112
+ if other == 0: # first iteration of sum()
113
+ return self
114
+ elif isinstance(other, Trip):
115
+ other.legs.append(self)
116
+ return other
117
+ else:
118
+ return self.__add__(other)
119
+
120
+ def __eq__(self, other):
121
+ """Check if `other` is an equal `TripLeg`."""
122
+ if isinstance(other, self.__class__):
123
+ return False not in [
124
+ self._are_columns_equal(other, column) for column in self.COLUMNS
125
+ ]
126
+
127
+ def __gt__(self, other):
128
+ """Check if `other` has a longer travel time."""
129
+ if isinstance(other, TripLeg):
130
+ return (self.travel_time + self.wait_time) > (
131
+ other.travel_time + other.wait_time
132
+ )
133
+
134
+ def __ge__(self, other):
135
+ """Check if `other` has a longer or equal travel time."""
136
+ if isinstance(other, TripLeg):
137
+ return (self.travel_time + self.wait_time) >= (
138
+ other.travel_time + other.wait_time
139
+ )
140
+
141
+ def __lt__(self, other):
142
+ """Check if `other` has a shorter travel time."""
143
+ if isinstance(other, TripLeg):
144
+ return (self.travel_time + self.wait_time) < (
145
+ other.travel_time + other.wait_time
146
+ )
147
+
148
+ def __le__(self, other):
149
+ """Check if `other` has a shorter or equal travel time."""
150
+ if isinstance(other, TripLeg):
151
+ return (self.travel_time + self.wait_time) <= (
152
+ other.travel_time + other.wait_time
153
+ )
154
+
155
+ def __repr__(self):
156
+ """Return a string representation."""
157
+ try:
158
+ first_point = self.geometry.coords[0]
159
+ last_point = self.geometry.coords[-1]
160
+ _repr = (
161
+ "<"
162
+ f"{self.__class__.__name__}: "
163
+ f"{self.transport_mode}, "
164
+ f"{self.distance}m, "
165
+ f"{self.travel_time.total_seconds():0.1f}s, "
166
+ f"{first_point} -> {last_point}"
167
+ ">"
168
+ )
169
+ except (AttributeError, IndexError):
170
+ _repr = f"<{self.__class__.__name__}>"
171
+ return _repr
172
+
173
+ def _are_columns_equal(self, other, column):
174
+ """
175
+ Check if a column equals the same column of a different `Trip`.
176
+
177
+ Compare if attribute `column` of self equals attribute `column` of
178
+ other. Also True if both values are None or NaN or NaT.
179
+ """
180
+ self_column = getattr(self, column)
181
+ other_column = getattr(other, column)
182
+
183
+ return (
184
+ self_column == other_column
185
+ or (self_column is None and other_column is None)
186
+ or (self_column == numpy.nan and other_column == numpy.nan)
187
+ or (
188
+ self_column == numpy.datetime64("NaT")
189
+ and other_column == numpy.datetime64("NaT")
190
+ )
191
+ )
192
+
193
+ def as_table_row(self):
194
+ """
195
+ Return a table row (list) of this trip leg’s details.
196
+
197
+ Returns
198
+ =======
199
+ list : detailed information about this trip leg: ``transport_mode``,
200
+ ``departure_time``, ``distance``, ``travel_time``, ``wait_time``,
201
+ ``feed``, ``agency_id`` ``route_id``, ``start_stop_id``,
202
+ ``end_stop_id``, ``geometry``
203
+ """
204
+ return [getattr(self, column) for column in self.COLUMNS]