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
|
@@ -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]
|