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
r5py/__init__.py ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """Python wrapper for the R5 routing analysis engine."""
4
+
5
+ __version__ = "1.1.0"
6
+
7
+
8
+ from .r5 import (
9
+ DetailedItineraries,
10
+ ElevationCostFunction,
11
+ Isochrones,
12
+ RegionalTask,
13
+ TransportMode,
14
+ TransportNetwork,
15
+ TravelTimeMatrix,
16
+ )
17
+
18
+ __all__ = [
19
+ "DetailedItineraries",
20
+ "ElevationCostFunction",
21
+ "Isochrones",
22
+ "RegionalTask",
23
+ "TransportMode",
24
+ "TransportNetwork",
25
+ "TravelTimeMatrix",
26
+ "__version__",
27
+ ]
r5py/__main__.py ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """Python wrapper for the R5 routing analysis engine."""
r5py/r5/__init__.py ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """R5 classes."""
4
+
5
+ from .access_leg import AccessLeg
6
+ from .detailed_itineraries import DetailedItineraries
7
+ from .direct_leg import DirectLeg
8
+ from .egress_leg import EgressLeg
9
+ from .elevation_cost_function import ElevationCostFunction
10
+ from .isochrones import Isochrones
11
+ from .regional_task import RegionalTask
12
+ from .scenario import Scenario
13
+ from .street_layer import StreetLayer
14
+ from .transfer_leg import TransferLeg
15
+ from .transit_leg import TransitLeg
16
+ from .transport_mode import TransportMode
17
+ from .transport_network import TransportNetwork
18
+ from .travel_time_matrix import TravelTimeMatrix
19
+ from .trip import Trip
20
+ from .trip_planner import TripPlanner
21
+
22
+ __all__ = [
23
+ "AccessLeg",
24
+ "DetailedItineraries",
25
+ "DirectLeg",
26
+ "EgressLeg",
27
+ "ElevationCostFunction",
28
+ "Isochrones",
29
+ "RegionalTask",
30
+ "Scenario",
31
+ "StreetLayer",
32
+ "TransferLeg",
33
+ "TransitLeg",
34
+ "TransportMode",
35
+ "TransportNetwork",
36
+ "TravelTimeMatrix",
37
+ "Trip",
38
+ "TripPlanner",
39
+ ]
r5py/r5/access_leg.py ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ """Represent one leg of a trip, specifically access to a public transport stop."""
5
+
6
+ from .transfer_leg import TransferLeg
7
+
8
+ __all__ = ["AccessLeg"]
9
+
10
+
11
+ class AccessLeg(TransferLeg):
12
+ """Represent one leg of a trip, specifically access to a public transport stop."""
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """Calculate travel times between many origins and destinations."""
4
+
5
+ import collections.abc
6
+ import inspect
7
+ import math
8
+ import multiprocessing
9
+ import pathlib
10
+ import warnings
11
+
12
+ import geopandas
13
+ import numpy
14
+ import pandas
15
+ import shapely
16
+
17
+ from ..util import Config, check_od_data_set
18
+ from .regional_task import RegionalTask
19
+ from .transport_network import TransportNetwork
20
+
21
+ __all__ = ["BaseTravelTimeMatrix"]
22
+
23
+
24
+ # R5 fills cut-off (NULL) values with MAX_INT32
25
+ MAX_INT32 = (2**31) - 1
26
+
27
+ # how many (Python) threads to start
28
+ # (they still run many Java threads, so be careful what you wish for ;) )
29
+ # TODO: benchmark the optimal number of threads
30
+ NUM_THREADS = math.ceil(multiprocessing.cpu_count() * 0.5)
31
+
32
+
33
+ class BaseTravelTimeMatrix(geopandas.GeoDataFrame):
34
+ """Base class for travel time computers between many origins and destinations."""
35
+
36
+ MAX_INT32 = MAX_INT32
37
+
38
+ NUM_THREADS = NUM_THREADS
39
+
40
+ _r5py_attributes = [
41
+ "_destinations",
42
+ "_destinations_crs",
43
+ "_origins",
44
+ "_origins_crs",
45
+ "destinations",
46
+ "origins",
47
+ "request",
48
+ "snap_to_network",
49
+ "transport_network",
50
+ "verbose",
51
+ ]
52
+
53
+ _constructor = geopandas.GeoDataFrame
54
+
55
+ _constructor_sliced = pandas.Series
56
+
57
+ @classmethod
58
+ def _geodataframe_constructor_with_fallback(
59
+ cls,
60
+ *args,
61
+ **kwargs,
62
+ ): # pragma: no cover
63
+ """
64
+ A flexible constructor for r5py frames.
65
+
66
+ Checks whether or not arguments of the child class are used.
67
+ """
68
+ # `transport_network` is the only required argument across all r5py
69
+ # data classes, it can be passed either as an r5py.TransportNetwork
70
+ # or a tuple (as described in the signature of __init__(), below)
71
+ if (
72
+ "transport_network" in kwargs
73
+ or isinstance(args[0], TransportNetwork)
74
+ or (
75
+ isinstance(args[0], tuple)
76
+ and isinstance(args[0][0], (pathlib.Path, str))
77
+ and isinstance(args[0][1], collections.abc.Iterable)
78
+ and all(isinstance(arg, (pathlib.Path, str)) for arg in args[0][1])
79
+ )
80
+ ):
81
+ df = cls(*args, **kwargs)
82
+
83
+ else:
84
+ df = geopandas.GeoDataFrame(*args, **kwargs)
85
+ geometry_cols_mask = df.dtypes == "geometry"
86
+ if len(geometry_cols_mask) == 0 or geometry_cols_mask.sum() == 0:
87
+ df = pandas.DataFrame(df)
88
+
89
+ return df
90
+
91
+ def __init__(
92
+ self,
93
+ transport_network,
94
+ origins=None,
95
+ destinations=None,
96
+ snap_to_network=False,
97
+ **kwargs,
98
+ ):
99
+ """
100
+ Compute travel times between many origins and destinations.
101
+
102
+ Arguments
103
+ ---------
104
+ transport_network : r5py.TransportNetwork | tuple(
105
+ pathlib.Paths | str, list(pathlib.Path | str))
106
+ The transport network to route on. This can either be a readily
107
+ initialised r5py.TransportNetwork or a tuple of the parameters
108
+ passed to ``TransportNetwork.__init__()``: the path to an
109
+ OpenStreetMap extract in PBF format, and a list of zero of more
110
+ paths to GTFS transport schedule files.
111
+ origins : geopandas.GeoDataFrame
112
+ Places to find a route _from_
113
+ Has to have a point geometry, and at least an `id` column
114
+ destinations : geopandas.GeoDataFrame (optional)
115
+ Places to find a route _to_
116
+ Has to have a point geometry, and at least an `id` column
117
+ If omitted, use same data set as for origins
118
+ snap_to_network : bool or int, default False
119
+ Should origin an destination points be snapped to the street network
120
+ before routing? If `True`, the default search radius (defined in
121
+ `com.conveyal.r5.streets.StreetLayer.LINK_RADIUS_METERS`) is used,
122
+ if `int`, use `snap_to_network` meters as the search radius.
123
+ **kwargs : mixed
124
+ Any arguments than can be passed to r5py.RegionalTask:
125
+ ``departure``, ``departure_time_window``, ``percentiles``,
126
+ ``transport_modes``, ``access_modes``, ``egress_modes``,
127
+ ``max_time``, ``max_time_walking``, ``max_time_cycling``,
128
+ ``max_time_driving``, ``speed_cycling``, ``speed_walking``,
129
+ ``max_public_transport_rides``, ``max_bicycle_traffic_stress``
130
+ """
131
+
132
+ super_parameters = inspect.signature(geopandas.GeoDataFrame).parameters
133
+ super_kwargs = {
134
+ key: value for key, value in kwargs.items() if key in super_parameters
135
+ }
136
+ kwargs = {
137
+ key: value for key, value in kwargs.items() if key not in super_parameters
138
+ }
139
+ super().__init__(**super_kwargs)
140
+
141
+ if not isinstance(transport_network, TransportNetwork):
142
+ transport_network = TransportNetwork(*transport_network)
143
+ self.transport_network = transport_network
144
+
145
+ self.snap_to_network = snap_to_network
146
+
147
+ self.origins = origins
148
+ self.destinations = destinations
149
+
150
+ self.request = RegionalTask(
151
+ transport_network,
152
+ origin=None,
153
+ destinations=None,
154
+ **kwargs,
155
+ )
156
+
157
+ self.verbose = Config().arguments.verbose
158
+
159
+ def __repr__(self):
160
+ """Return a string representation of `self`."""
161
+ return f"<{self.__class__.__name__}>"
162
+
163
+ def __setattr__(self, attr, val):
164
+ """Catch our own attributes here so we don’t mess with (geo)pandas columns."""
165
+ if attr in self._r5py_attributes:
166
+ object.__setattr__(self, attr, val)
167
+ else:
168
+ super().__setattr__(attr, val)
169
+
170
+ @property
171
+ def destinations(self):
172
+ """The destinations of this travel time matrix (`geopandas.GeoDataFrame`)."""
173
+ return self._destinations
174
+
175
+ @destinations.setter
176
+ def destinations(self, destinations):
177
+ if destinations is not None:
178
+ check_od_data_set(destinations)
179
+ self._destinations_crs = destinations.crs
180
+ self._destinations = destinations.to_crs("EPSG:4326").copy()
181
+
182
+ def _fill_nulls(self, data_set):
183
+ """
184
+ Fill NULL values in a data set returned from R5.
185
+
186
+ R5 uses `MAX_INT32` as a marker for NULL values, this function
187
+ replaces those values in `data_set` with `numpy.nan`
188
+
189
+ Arguments
190
+ ---------
191
+ data_set : pandas.DataFrame
192
+ Data frame in which NULLs are represented by MAX_INT32
193
+
194
+ Returns
195
+ -------
196
+ pandas.DataFrame
197
+ Data frame in which all MAX_INT32 have been replaced by `numpy.nan`.
198
+ """
199
+ return data_set.map(lambda x: numpy.nan if x == MAX_INT32 else x)
200
+
201
+ def _prepare_origins_destinations(self):
202
+ """Make sure we received enough information."""
203
+ try:
204
+ self.origins
205
+ except AttributeError as exception:
206
+ raise ValueError("No routing origins defined") from exception
207
+
208
+ try:
209
+ self.destinations
210
+ assert self.destinations is not None
211
+ except (AssertionError, AttributeError):
212
+ self.destinations = self.origins.copy()
213
+ if self.verbose:
214
+ warnings.warn(
215
+ "No routing destinations defined, "
216
+ "using origins as destinations, too.",
217
+ RuntimeWarning,
218
+ stacklevel=1,
219
+ )
220
+
221
+ if self.snap_to_network:
222
+ for which_end in ("origins", "destinations"):
223
+ points = getattr(self, f"_{which_end}")
224
+ points.geometry = self.transport_network.snap_to_network(
225
+ points.geometry
226
+ )
227
+ if len(points[points.geometry == shapely.Point()]):
228
+ # if there are origins/destinations for which
229
+ # no snapped point could be found
230
+ points = points[points.geometry != shapely.Point()]
231
+ warnings.warn(
232
+ f"Some {which_end[:-1]} points could not be "
233
+ "snapped to the street network",
234
+ RuntimeWarning,
235
+ stacklevel=1,
236
+ )
237
+
238
+ if points.empty:
239
+ raise ValueError(
240
+ f"After snapping, no valid {which_end[:-1]} points remain"
241
+ )
242
+
243
+ setattr(self, f"_{which_end}", points.copy())
244
+
245
+ @property
246
+ def origins(self):
247
+ """The origins of this travel time matrix (`geopandas.GeoDataFrame`)."""
248
+ return self._origins
249
+
250
+ @origins.setter
251
+ def origins(self, origins):
252
+ if origins is not None:
253
+ check_od_data_set(origins)
254
+ self._origins_crs = origins.crs
255
+ self._origins = origins.to_crs("EPSG:4326").copy()
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ """Calculate detailed itineraries between many origins and destinations."""
5
+
6
+ import copy
7
+ import warnings
8
+
9
+ import geopandas
10
+ import joblib
11
+ import pandas
12
+
13
+ from .base_travel_time_matrix import BaseTravelTimeMatrix
14
+ from .trip import Trip
15
+ from .trip_planner import TripPlanner
16
+
17
+ __all__ = ["DetailedItineraries"]
18
+
19
+
20
+ class DetailedItineraries(BaseTravelTimeMatrix):
21
+ """Compute detailed itineraries between many origins and destinations."""
22
+
23
+ COLUMNS = ["from_id", "to_id", "option"] + Trip.COLUMNS
24
+
25
+ _r5py_attributes = BaseTravelTimeMatrix._r5py_attributes + [
26
+ "all_to_all",
27
+ "od_pairs",
28
+ ]
29
+
30
+ def __init__(
31
+ self,
32
+ transport_network,
33
+ origins=None,
34
+ destinations=None,
35
+ snap_to_network=False,
36
+ force_all_to_all=False,
37
+ **kwargs,
38
+ ):
39
+ """
40
+ Compute travel times between many origins and destinations.
41
+
42
+ ``r5py.DetailedItineraries`` are child classes of
43
+ ``geopandas.GeoDataFrame`` and support all of their methods and
44
+ properties, see https://geopandas.org/en/stable/docs.html
45
+
46
+ Arguments
47
+ ---------
48
+ transport_network : r5py.TransportNetwork | tuple(str, list(str), dict)
49
+ The transport network to route on. This can either be a readily
50
+ initialised r5py.TransportNetwork or a tuple of the parameters
51
+ passed to ``TransportNetwork.__init__()``: the path to an OpenStreetMap
52
+ extract in PBF format, a list of zero of more paths to GTFS transport
53
+ schedule files, and a dict with ``build_config`` options.
54
+ origins : geopandas.GeoDataFrame
55
+ Places to find a route _from_
56
+ Has to have a point geometry, and at least an `id` column
57
+ destinations : geopandas.GeoDataFrame (optional)
58
+ Places to find a route _to_
59
+ Has to have a point geometry, and at least an `id` column
60
+ If omitted, use same data set as for origins
61
+ snap_to_network : bool or int, default False
62
+ Should origin an destination points be snapped to the street network
63
+ before routing? If `True`, the default search radius (defined in
64
+ `com.conveyal.r5.streets.StreetLayer.LINK_RADIUS_METERS`) is used,
65
+ if `int`, use `snap_to_network` meters as the search radius.
66
+ force_all_to_all : bool, default False
67
+ If ``origins`` and ``destinations`` have the same length, by
68
+ default, ``DetailedItineraries`` finds routes between pairs
69
+ of origins and destinations, i.e., it routes from origin #1 to
70
+ destination #1, origin #2 to destination #2, ... .
71
+ Set ``force_all_to_all=True`` to route from each origin to all
72
+ destinations (this is the default, if ``origins`` and ``destinations``
73
+ have different lengths, or if ``destinations`` is omitted)
74
+ **kwargs : mixed
75
+ Any arguments than can be passed to r5py.RegionalTask:
76
+ ``departure``, ``departure_time_window``, ``percentiles``,
77
+ ``transport_modes``, ``access_modes``, ``egress_modes``,
78
+ ``max_time``, ``max_time_walking``, ``max_time_cycling``,
79
+ ``max_time_driving``, ``speed_cycling``, ``speed_walking``,
80
+ ``max_public_transport_rides``, ``max_bicycle_traffic_stress`` Note
81
+ that not all arguments might make sense in this context, and the
82
+ underlying R5 engine might ignore some of them.
83
+ """
84
+ super().__init__(
85
+ transport_network,
86
+ origins,
87
+ destinations,
88
+ snap_to_network,
89
+ **kwargs,
90
+ )
91
+
92
+ if destinations is None:
93
+ self.all_to_all = True
94
+ if self.verbose:
95
+ warnings.warn(
96
+ "No destinations specified, computing an all-to-all matrix",
97
+ RuntimeWarning,
98
+ stacklevel=1,
99
+ )
100
+
101
+ elif len(origins) != len(destinations):
102
+ self.all_to_all = True
103
+ if self.verbose:
104
+ warnings.warn(
105
+ "Origins and destinations are of different length, "
106
+ "computing an all-to-all matrix",
107
+ RuntimeWarning,
108
+ stacklevel=1,
109
+ )
110
+ elif origins.equals(destinations):
111
+ self.all_to_all = True
112
+ if self.verbose:
113
+ warnings.warn(
114
+ "Origins and destinations are identical, "
115
+ "computing an all-to-all matrix",
116
+ RuntimeWarning,
117
+ stacklevel=1,
118
+ )
119
+ else:
120
+ self.all_to_all = force_all_to_all
121
+
122
+ self.od_pairs = None
123
+
124
+ data = self._compute()
125
+ with warnings.catch_warnings():
126
+ warnings.filterwarnings(
127
+ "ignore",
128
+ message=(
129
+ "You are adding a column named 'geometry' to a GeoDataFrame "
130
+ "constructed without an active geometry column"
131
+ ),
132
+ category=FutureWarning,
133
+ )
134
+ for column in data.columns:
135
+ self[column] = data[column]
136
+ self.set_geometry("geometry")
137
+
138
+ del self.transport_network
139
+
140
+ def _compute(self):
141
+ """
142
+ Compute travel times from all origins to all destinations.
143
+
144
+ Returns
145
+ -------
146
+ geopandas.GeoDataFrame
147
+ The resulting detailed routes. For each origin/destination pair,
148
+ multiple route alternatives (‘options’) might be reported that each
149
+ consist of one or more segments. Each segment represents one row.
150
+ multiple route alternatives (‘options’) might be reported that each
151
+ consist of one or more segments. Each segment represents one row.
152
+
153
+ The data frame comprises of the following columns: `from_id`,
154
+ `to_id`, `option` (`int`), `segment` (`int`), `transport_mode`
155
+ (`r5py.TransportMode`), `departure_time` (`datetime.datetime`),
156
+ `distance` (`float`, metres), `travel_time` (`datetime.timedelta`),
157
+ `wait_time` (`datetime.timedelta`), `feed` (`str`, the feed name
158
+ used), `agency_id` (`str` the public transport agency identifier),
159
+ `route_id` (`str`, public transport route ID), `start_stop_id`
160
+ (`str`, the GTFS stop_id for boarding), `end_stop_id` (`str`, the
161
+ GTFS stop_id for alighting), `geometry` (`shapely.LineString`)
162
+ """
163
+ self._prepare_origins_destinations()
164
+
165
+ # loop over all origin/destination pairs, modify the request, and
166
+ # compute times, distance, and other details for each trip
167
+ with joblib.Parallel(
168
+ prefer="threads",
169
+ verbose=(10 * self.verbose), # joblib has a funny verbosity scale
170
+ n_jobs=self.NUM_THREADS,
171
+ ) as parallel:
172
+ matrices = parallel(
173
+ joblib.delayed(self._travel_details_per_od_pair)(from_id, to_id)
174
+ for _, (from_id, to_id) in self.od_pairs.iterrows()
175
+ )
176
+ od_matrix = pandas.concat(
177
+ [matrix.astype(matrices[0].dtypes) for matrix in matrices],
178
+ ignore_index=True,
179
+ )
180
+
181
+ od_matrix = geopandas.GeoDataFrame(od_matrix, crs=self._origins_crs)
182
+ return od_matrix
183
+
184
+ def _prepare_origins_destinations(self):
185
+ """Make sure we received enough information."""
186
+ super()._prepare_origins_destinations()
187
+
188
+ if self.all_to_all:
189
+ # manually create a list of all all-to-all permutations
190
+ self.od_pairs = self.origins[["id"]].join(
191
+ self.destinations[["id"]],
192
+ how="cross",
193
+ lsuffix="_origin",
194
+ rsuffix="_destination",
195
+ )
196
+ else:
197
+ # origins and destinations are same length, run one-to-one routing
198
+ self.od_pairs = pandas.DataFrame(
199
+ {
200
+ "id_origin": self.origins.id.values,
201
+ "id_destination": self.destinations.id.values,
202
+ }
203
+ )
204
+
205
+ def _travel_details_per_od_pair(self, from_id, to_id):
206
+ origin = self.origins[self.origins.id == from_id]
207
+ destination = self.destinations[self.destinations.id == to_id]
208
+
209
+ request = copy.copy(self.request)
210
+ request._regional_task.fromLat = origin.geometry.item().y
211
+ request._regional_task.fromLon = origin.geometry.item().x
212
+ request._regional_task.toLat = destination.geometry.item().y
213
+ request._regional_task.toLon = destination.geometry.item().x
214
+
215
+ trip_planner = TripPlanner(self.transport_network, request)
216
+ trips = trip_planner.trips
217
+
218
+ # fmt: off
219
+ trips = [
220
+ [from_id, to_id, option] + segment
221
+ for option, trip in enumerate(trips)
222
+ for segment in trip.as_table()
223
+ ]
224
+ # fmt: on
225
+
226
+ return pandas.DataFrame(trips, columns=self.COLUMNS)
r5py/r5/direct_leg.py ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ """Represent one leg of a direct mode (walk, cycle, car) trip."""
5
+
6
+ import datetime
7
+
8
+ import shapely
9
+
10
+ from .trip_leg import TripLeg
11
+
12
+ __all__ = ["DirectLeg"]
13
+
14
+
15
+ class DirectLeg(TripLeg):
16
+ """Represent one leg of a public transport trip."""
17
+
18
+ def __init__(self, transport_mode, street_segment):
19
+ """
20
+ Represent one leg of a public transport trip.
21
+
22
+ Arguments
23
+ =========
24
+ transport_mode : r5py.TransportMode
25
+ mode of transport this trip leg was travelled
26
+ street_segment : com.conveyal.r5.profile.StreetSegment
27
+ the leg’s data as output by R5’s `StreetRouter`
28
+ """
29
+ distance = street_segment.distance / 1000.0 # millimetres!
30
+ travel_time = datetime.timedelta(seconds=street_segment.duration)
31
+ geometry = shapely.from_wkt(str(street_segment.geometry))
32
+
33
+ super().__init__(
34
+ transport_mode=transport_mode,
35
+ distance=distance,
36
+ travel_time=travel_time,
37
+ geometry=geometry,
38
+ )
r5py/r5/egress_leg.py ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ """Represent one leg of a trip, specifically egress from a public transport stop."""
5
+
6
+ from .transfer_leg import TransferLeg
7
+
8
+ __all__ = ["EgressLeg"]
9
+
10
+
11
+ class EgressLeg(TransferLeg):
12
+ """Represent one leg of a trip, specifically egress from a public transport stop."""
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ """The elevation cost functions supported by R5 (Tobler, Minetti)."""
5
+
6
+ import enum
7
+
8
+ import jpype
9
+
10
+ from ..util import start_jvm
11
+
12
+ import com.conveyal.r5
13
+
14
+ __all__ = ["ElevationCostFunction"]
15
+
16
+
17
+ start_jvm()
18
+
19
+
20
+ class ElevationCostFunction(enum.Enum):
21
+ """
22
+ Elevation cost functions.
23
+
24
+ TOBLER: Waldo Tobler’s hiking function,
25
+ cf. https://en.wikipedia.org/wiki/Tobler%27s_hiking_function
26
+ MINETTI: Minetti et al.’s perceived effort/energy consumption,
27
+ cf. https://doi.org/10.1152/japplphysiol.01177.2001
28
+
29
+ """
30
+
31
+ @classmethod
32
+ def _missing_(cls, value):
33
+ value = str(value).upper()
34
+ for member in cls:
35
+ if value == member.value:
36
+ return member
37
+ return None
38
+
39
+ TOBLER = "TOBLER"
40
+ MINETTI = "MINETTI"
41
+
42
+
43
+ @jpype._jcustomizer.JConversion(
44
+ "com.conveyal.r5.analyst.scenario.RasterCost.CostFunction",
45
+ exact=ElevationCostFunction,
46
+ )
47
+ def _cast_LegMode(java_class, object_):
48
+ return com.conveyal.r5.analyst.scenario.RasterCost.CostFunction.valueOf(
49
+ object_.name
50
+ )