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
r5py/r5/street_layer.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
"""Wraps a com.conveyal.r5.streets.StreetLayer."""
|
|
5
|
+
|
|
6
|
+
import functools
|
|
7
|
+
|
|
8
|
+
import jpype
|
|
9
|
+
import jpype.types
|
|
10
|
+
import shapely
|
|
11
|
+
|
|
12
|
+
from .transport_mode import TransportMode
|
|
13
|
+
from ..util import start_jvm
|
|
14
|
+
|
|
15
|
+
import com.conveyal.r5
|
|
16
|
+
|
|
17
|
+
__all__ = ["StreetLayer"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
start_jvm()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
EMPTY_POINT = shapely.Point()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class StreetLayer:
|
|
27
|
+
"""Wrap a com.conveyal.r5.streets.StreetLayer."""
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_r5_street_layer(cls, street_layer):
|
|
31
|
+
"""
|
|
32
|
+
Create a StreetLayer from a com.conveyal.r5.streets.StreetLayer.
|
|
33
|
+
|
|
34
|
+
Arguments
|
|
35
|
+
---------
|
|
36
|
+
street_layer : com.conveyal.r5.streets.StreetLayer
|
|
37
|
+
"""
|
|
38
|
+
instance = cls()
|
|
39
|
+
instance._street_layer = street_layer
|
|
40
|
+
return instance
|
|
41
|
+
|
|
42
|
+
@functools.cached_property
|
|
43
|
+
def extent(self):
|
|
44
|
+
"""The geographic area covered, as a `shapely.box`."""
|
|
45
|
+
envelope = self._street_layer.envelope
|
|
46
|
+
return shapely.box(
|
|
47
|
+
envelope.getMinX(),
|
|
48
|
+
envelope.getMinY(),
|
|
49
|
+
envelope.getMaxX(),
|
|
50
|
+
envelope.getMaxY(),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def find_split(
|
|
54
|
+
self,
|
|
55
|
+
point,
|
|
56
|
+
radius=com.conveyal.r5.streets.StreetLayer.LINK_RADIUS_METERS,
|
|
57
|
+
street_mode=TransportMode.WALK,
|
|
58
|
+
):
|
|
59
|
+
"""
|
|
60
|
+
Find a location on an existing street near `point`.
|
|
61
|
+
|
|
62
|
+
Arguments
|
|
63
|
+
---------
|
|
64
|
+
point : shapely.Point
|
|
65
|
+
Find a location close to this point
|
|
66
|
+
radius : float
|
|
67
|
+
Search radius around `point`
|
|
68
|
+
street_mode : travel mode that the snapped-to street should allow
|
|
69
|
+
|
|
70
|
+
Returns
|
|
71
|
+
-------
|
|
72
|
+
shapely.Point
|
|
73
|
+
Closest location on the street network or `POINT EMPTY` if no
|
|
74
|
+
such location could be found within `radius`
|
|
75
|
+
"""
|
|
76
|
+
try:
|
|
77
|
+
split = self._street_layer.findSplit(point.y, point.x, radius, street_mode)
|
|
78
|
+
return shapely.Point(
|
|
79
|
+
split.fixedLon / com.conveyal.r5.streets.VertexStore.FIXED_FACTOR,
|
|
80
|
+
split.fixedLat / com.conveyal.r5.streets.VertexStore.FIXED_FACTOR,
|
|
81
|
+
)
|
|
82
|
+
except (AttributeError, TypeError):
|
|
83
|
+
return EMPTY_POINT
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@jpype._jcustomizer.JConversion(
|
|
87
|
+
"com.conveyal.r5.streets.StreetLayer", exact=StreetLayer
|
|
88
|
+
)
|
|
89
|
+
def _cast_StreetLayer(java_class, object_):
|
|
90
|
+
return object_._street_layer
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""A less complex representation of com.conveyal.r5.api.util.StreetSegment."""
|
|
4
|
+
|
|
5
|
+
import datetime
|
|
6
|
+
|
|
7
|
+
import shapely
|
|
8
|
+
|
|
9
|
+
__all__ = ["StreetSegment"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class StreetSegment:
|
|
13
|
+
"""A less complex representation of com.conveyal.r5.api.util.StreetSegment."""
|
|
14
|
+
|
|
15
|
+
distance = 0
|
|
16
|
+
duration = datetime.timedelta()
|
|
17
|
+
geometry = shapely.LineString()
|
|
18
|
+
|
|
19
|
+
def __init__(self, street_path):
|
|
20
|
+
"""
|
|
21
|
+
Initialise a less complex StreetSegment.
|
|
22
|
+
|
|
23
|
+
Arguments
|
|
24
|
+
---------
|
|
25
|
+
street_path : com.conveyal.r5.profile.StreetPath
|
|
26
|
+
StreetPath, obtained, e.g., from StreetRouter state
|
|
27
|
+
"""
|
|
28
|
+
self.distance = street_path.getDistance()
|
|
29
|
+
self.duration = street_path.getDuration()
|
|
30
|
+
self.geometry = shapely.line_merge(
|
|
31
|
+
shapely.MultiLineString(
|
|
32
|
+
[
|
|
33
|
+
shapely.from_wkt(
|
|
34
|
+
str(street_path.getEdge(edge).getGeometry().toText())
|
|
35
|
+
)
|
|
36
|
+
for edge in street_path.getEdges()
|
|
37
|
+
]
|
|
38
|
+
)
|
|
39
|
+
)
|
r5py/r5/transfer_leg.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
"""Represent one leg of a trip: transfers between public transport vehicles."""
|
|
5
|
+
|
|
6
|
+
from .direct_leg import DirectLeg
|
|
7
|
+
|
|
8
|
+
__all__ = ["TransferLeg"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TransferLeg(DirectLeg):
|
|
12
|
+
"""Represent one leg of a trip: transfers between public transport vehicles."""
|
r5py/r5/transit_layer.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
"""Wraps a com.conveyal.r5.transit.TransitLayer."""
|
|
5
|
+
|
|
6
|
+
import functools
|
|
7
|
+
|
|
8
|
+
import jpype
|
|
9
|
+
import jpype.types
|
|
10
|
+
|
|
11
|
+
import java.time
|
|
12
|
+
|
|
13
|
+
__all__ = ["TransitLayer"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TransitLayer:
|
|
17
|
+
"""Wrap a com.conveyal.r5.transit.TransitLayer."""
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def from_r5_transit_layer(cls, transit_layer):
|
|
21
|
+
"""
|
|
22
|
+
Create a TransitLayer from a com.conveyal.r5.transit.TransitLayer.
|
|
23
|
+
|
|
24
|
+
Arguments
|
|
25
|
+
---------
|
|
26
|
+
transit_layer : com.conveyal.r5.transit.TransitLayer
|
|
27
|
+
"""
|
|
28
|
+
instance = cls()
|
|
29
|
+
instance._transit_layer = transit_layer
|
|
30
|
+
return instance
|
|
31
|
+
|
|
32
|
+
def covers(self, date):
|
|
33
|
+
"""
|
|
34
|
+
Check whether `date` is covered by GTFS data sets.
|
|
35
|
+
|
|
36
|
+
Arguments:
|
|
37
|
+
----------
|
|
38
|
+
date : datetime.date
|
|
39
|
+
date for which to check whether a GTFS service exists.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
--------
|
|
43
|
+
bool
|
|
44
|
+
Whether or not any services exist on `date`.
|
|
45
|
+
"""
|
|
46
|
+
date = java.time.LocalDate.of(date.year, date.month, date.day)
|
|
47
|
+
return True in set(
|
|
48
|
+
[service.activeOn(date) for service in self._transit_layer.services]
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def get_street_vertex_for_stop(self, stop):
|
|
52
|
+
"""
|
|
53
|
+
Get the street layer’s vertex corresponding to `stop`.
|
|
54
|
+
|
|
55
|
+
Arguments
|
|
56
|
+
---------
|
|
57
|
+
stop : int
|
|
58
|
+
ID of the public transport stop for which to find a vertex
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
int
|
|
63
|
+
ID of the vertex corresponding to the public transport stop
|
|
64
|
+
"""
|
|
65
|
+
street_vertex = self._transit_layer.streetVertexForStop.get(stop)
|
|
66
|
+
return street_vertex
|
|
67
|
+
|
|
68
|
+
@functools.cached_property
|
|
69
|
+
def routes(self):
|
|
70
|
+
"""Return a list of GTFS routes."""
|
|
71
|
+
return list(self._transit_layer.routes)
|
|
72
|
+
|
|
73
|
+
@functools.cached_property
|
|
74
|
+
def trip_patterns(self):
|
|
75
|
+
"""Return a list of GTFS trip patterns."""
|
|
76
|
+
return list(self._transit_layer.tripPatterns)
|
|
77
|
+
|
|
78
|
+
def get_stop_id_from_index(self, stop_index):
|
|
79
|
+
"""Get the GTFS stop id for the `stop_index`-th stop of this transit layer."""
|
|
80
|
+
return self._transit_layer.stopIdForIndex[stop_index]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@jpype._jcustomizer.JConversion(
|
|
84
|
+
"com.conveyal.r5.transit.TransitLayer", exact=TransitLayer
|
|
85
|
+
)
|
|
86
|
+
def _cast_TransitLayer(java_class, object_):
|
|
87
|
+
return object_._transit_layer
|
r5py/r5/transit_leg.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
"""The transport modes supported by R5 (Leg, Street, Transit, combined)."""
|
|
5
|
+
|
|
6
|
+
import enum
|
|
7
|
+
|
|
8
|
+
import jpype
|
|
9
|
+
|
|
10
|
+
from ..util import start_jvm
|
|
11
|
+
|
|
12
|
+
import com.conveyal.r5
|
|
13
|
+
|
|
14
|
+
__all__ = ["TransportMode"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
start_jvm()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
TRANSIT_MODES = [
|
|
21
|
+
"AIR",
|
|
22
|
+
"BUS",
|
|
23
|
+
"CABLE_CAR",
|
|
24
|
+
"FERRY",
|
|
25
|
+
"FUNICULAR",
|
|
26
|
+
"GONDOLA",
|
|
27
|
+
"RAIL",
|
|
28
|
+
"SUBWAY",
|
|
29
|
+
"TRAM",
|
|
30
|
+
"TRANSIT",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
STREET_MODES = [
|
|
34
|
+
"BICYCLE",
|
|
35
|
+
"CAR",
|
|
36
|
+
"WALK",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
LEG_MODES = STREET_MODES + [
|
|
40
|
+
"BICYCLE_RENT",
|
|
41
|
+
"CAR_PARK",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TransportMode(enum.Enum):
|
|
46
|
+
"""
|
|
47
|
+
Transport modes.
|
|
48
|
+
|
|
49
|
+
TransportMode.AIR, TransportMode.TRAM, TransportMode.SUBWAY,
|
|
50
|
+
TransportMode.RAIL, TransportMode.BUS, TransportMode.FERRY,
|
|
51
|
+
TransportMode.CABLE_CAR, TransportMode.GONDOLA, TransportMode.FUNICULAR,
|
|
52
|
+
TransportMode.TRANSIT (translate into R5’s TransitMode)
|
|
53
|
+
|
|
54
|
+
TransportMode.WALK, TransportMode.BICYCLE, TransportMode.CAR (translate into
|
|
55
|
+
R5’s StreetMode or LegMode)
|
|
56
|
+
|
|
57
|
+
TransportMode.BICYCLE_RENT, TransportMode.CAR_PARK (translate into R5’s LegMode)
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def _missing_(cls, value):
|
|
62
|
+
value = str(value).upper()
|
|
63
|
+
for member in cls:
|
|
64
|
+
if value == member.value:
|
|
65
|
+
return member
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
def __add__(self, other):
|
|
69
|
+
"""Combine two transport modes."""
|
|
70
|
+
if isinstance(other, self.__class__):
|
|
71
|
+
return [self, other]
|
|
72
|
+
elif isinstance(other, list):
|
|
73
|
+
return [self] + other
|
|
74
|
+
else:
|
|
75
|
+
raise TypeError(
|
|
76
|
+
"unsupported operand type(s) for '+': "
|
|
77
|
+
f"'{type(other)}' and '{type(self)}'"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def __radd__(self, other):
|
|
81
|
+
"""Combine two transport modes."""
|
|
82
|
+
if other == 0: # first iteration of sum()
|
|
83
|
+
return self
|
|
84
|
+
elif isinstance(other, list):
|
|
85
|
+
return other + [self]
|
|
86
|
+
else:
|
|
87
|
+
return self.__add__(other)
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def is_leg_mode(self):
|
|
91
|
+
"""Can this TransportMode function as a LegMode?."""
|
|
92
|
+
return self.name in LEG_MODES
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def is_street_mode(self):
|
|
96
|
+
"""Can this TransportMode function as a StreetMode?."""
|
|
97
|
+
return self.name in STREET_MODES
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def is_transit_mode(self):
|
|
101
|
+
"""Can this TransportMode function as a TransitMode?."""
|
|
102
|
+
return self.name in TRANSIT_MODES
|
|
103
|
+
|
|
104
|
+
AIR = "AIR"
|
|
105
|
+
BUS = "BUS"
|
|
106
|
+
CABLE_CAR = "CABLE_CAR"
|
|
107
|
+
FERRY = "FERRY"
|
|
108
|
+
FUNICULAR = "FUNICULAR"
|
|
109
|
+
GONDOLA = "GONDOLA"
|
|
110
|
+
RAIL = "RAIL"
|
|
111
|
+
SUBWAY = "SUBWAY"
|
|
112
|
+
TRAM = "TRAM"
|
|
113
|
+
TRANSIT = "TRANSIT"
|
|
114
|
+
|
|
115
|
+
BICYCLE = "BICYCLE"
|
|
116
|
+
CAR = "CAR"
|
|
117
|
+
WALK = "WALK"
|
|
118
|
+
|
|
119
|
+
BICYCLE_RENT = "BICYCLE_RENT"
|
|
120
|
+
CAR_PARK = "CAR_PARK"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@jpype._jcustomizer.JConversion("com.conveyal.r5.api.util.LegMode", exact=TransportMode)
|
|
124
|
+
def _cast_LegMode(java_class, object_):
|
|
125
|
+
if object_.name in LEG_MODES:
|
|
126
|
+
return com.conveyal.r5.api.util.LegMode.valueOf(object_.name)
|
|
127
|
+
else:
|
|
128
|
+
raise ValueError(f"{object_.name} is not a valid R5 LegMode")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@jpype._jcustomizer.JConversion(
|
|
132
|
+
"com.conveyal.r5.profile.StreetMode", exact=TransportMode
|
|
133
|
+
)
|
|
134
|
+
def _cast_StreetMode(java_class, object_):
|
|
135
|
+
if object_.name in STREET_MODES:
|
|
136
|
+
return com.conveyal.r5.profile.StreetMode.valueOf(object_.name)
|
|
137
|
+
else:
|
|
138
|
+
raise ValueError(f"{object_.name} is not a valid R5 StreetMode")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@jpype._jcustomizer.JConversion(
|
|
142
|
+
"com.conveyal.r5.api.util.TransitModes", exact=TransportMode
|
|
143
|
+
)
|
|
144
|
+
def _cast_TransitMode(java_class, object_):
|
|
145
|
+
if object_.name in TRANSIT_MODES:
|
|
146
|
+
return com.conveyal.r5.api.util.TransitModes.valueOf(object_.name)
|
|
147
|
+
else:
|
|
148
|
+
raise ValueError(f"{object_.name} is not a valid R5 TransitModes")
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
"""Wraps a com.conveyal.r5.transit.TransportNetwork."""
|
|
5
|
+
|
|
6
|
+
import functools
|
|
7
|
+
import hashlib
|
|
8
|
+
import pathlib
|
|
9
|
+
import warnings
|
|
10
|
+
|
|
11
|
+
import jpype
|
|
12
|
+
import jpype.types
|
|
13
|
+
|
|
14
|
+
from .elevation_cost_function import ElevationCostFunction
|
|
15
|
+
from .elevation_model import ElevationModel
|
|
16
|
+
from .street_layer import StreetLayer
|
|
17
|
+
from .transit_layer import TransitLayer
|
|
18
|
+
from .transport_mode import TransportMode
|
|
19
|
+
from ..util import Config, contains_gtfs_data, FileDigest, start_jvm, WorkingCopy
|
|
20
|
+
from ..util.exceptions import GtfsFileError
|
|
21
|
+
|
|
22
|
+
import com.conveyal.analysis
|
|
23
|
+
import com.conveyal.gtfs
|
|
24
|
+
import com.conveyal.osmlib
|
|
25
|
+
import com.conveyal.r5
|
|
26
|
+
import java.io
|
|
27
|
+
|
|
28
|
+
__all__ = ["TransportNetwork"]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
PACKAGE = __package__.split(".", maxsplit=1)[0]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
start_jvm()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TransportNetwork:
|
|
38
|
+
"""Wrap a com.conveyal.r5.transit.TransportNetwork."""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
osm_pbf,
|
|
43
|
+
gtfs=[], # noqa: B006
|
|
44
|
+
elevation_model=None,
|
|
45
|
+
elevation_cost_function=ElevationCostFunction.TOBLER,
|
|
46
|
+
allow_errors=False,
|
|
47
|
+
):
|
|
48
|
+
"""
|
|
49
|
+
Load a transport network.
|
|
50
|
+
|
|
51
|
+
Arguments
|
|
52
|
+
---------
|
|
53
|
+
osm_pbf : str | pathlib.Path
|
|
54
|
+
file path of an OpenStreetMap extract in PBF format
|
|
55
|
+
gtfs : str | pathlib.Path | list[str] | list[pathlib.Path]
|
|
56
|
+
path(s) to public transport schedule information in GTFS format
|
|
57
|
+
elevation_model : str | pathlib.Path
|
|
58
|
+
file path to a digital elevation model in TIF format,
|
|
59
|
+
single-band, the value of which is the elevation in metres
|
|
60
|
+
elevation_cost_function : r5py.ElevationCostFunction
|
|
61
|
+
which algorithm to use to compute the added effort and travel time
|
|
62
|
+
of slopes
|
|
63
|
+
allow_errors : bool
|
|
64
|
+
try to proceed with loading the transport network even if input data
|
|
65
|
+
contain errors
|
|
66
|
+
"""
|
|
67
|
+
osm_pbf = WorkingCopy(osm_pbf)
|
|
68
|
+
if isinstance(gtfs, (str, pathlib.Path)):
|
|
69
|
+
gtfs = [gtfs]
|
|
70
|
+
gtfs = [WorkingCopy(path) for path in gtfs]
|
|
71
|
+
|
|
72
|
+
# a hash representing all input files
|
|
73
|
+
digest = hashlib.sha256(
|
|
74
|
+
"".join(
|
|
75
|
+
[FileDigest(osm_pbf)]
|
|
76
|
+
+ [FileDigest(path) for path in gtfs]
|
|
77
|
+
+ [FileDigest(elevation_model) if elevation_model is not None else ""]
|
|
78
|
+
).encode("utf-8")
|
|
79
|
+
).hexdigest()
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
transport_network = self._load_pickled_transport_network(
|
|
83
|
+
Config().CACHE_DIR / f"{digest}.transport_network"
|
|
84
|
+
)
|
|
85
|
+
except (FileNotFoundError, java.io.IOError, java.lang.RuntimeException):
|
|
86
|
+
transport_network = com.conveyal.r5.transit.TransportNetwork()
|
|
87
|
+
transport_network.scenarioId = PACKAGE
|
|
88
|
+
|
|
89
|
+
osm_mapdb = Config().CACHE_DIR / f"{digest}.mapdb"
|
|
90
|
+
osm_file = com.conveyal.osmlib.OSM(f"{osm_mapdb}")
|
|
91
|
+
osm_file.intersectionDetection = True
|
|
92
|
+
osm_file.readFromFile(f"{osm_pbf}")
|
|
93
|
+
|
|
94
|
+
transport_network.streetLayer = com.conveyal.r5.streets.StreetLayer()
|
|
95
|
+
transport_network.streetLayer.parentNetwork = transport_network
|
|
96
|
+
transport_network.streetLayer.loadFromOsm(osm_file)
|
|
97
|
+
transport_network.streetLayer.indexStreets()
|
|
98
|
+
|
|
99
|
+
transport_network.transitLayer = com.conveyal.r5.transit.TransitLayer()
|
|
100
|
+
transport_network.transitLayer.saveShapes = True
|
|
101
|
+
transport_network.transitLayer.parentNetwork = transport_network
|
|
102
|
+
# GtfsTransferLoader(transitLayer, OSM_ONLY)
|
|
103
|
+
gtfs_transfer_loader = com.conveyal.r5.transit.GtfsTransferLoader(
|
|
104
|
+
transport_network.transitLayer,
|
|
105
|
+
com.conveyal.r5.analyst.cluster.TransportNetworkConfig.TransferConfig.OSM_ONLY, # noqa: E501
|
|
106
|
+
)
|
|
107
|
+
for gtfs_file in gtfs:
|
|
108
|
+
gtfs_feed = com.conveyal.gtfs.GTFSFeed.writableTempFileFromGtfs(
|
|
109
|
+
f"{gtfs_file}"
|
|
110
|
+
)
|
|
111
|
+
if gtfs_feed.errors.size() > 0:
|
|
112
|
+
errors = [
|
|
113
|
+
f"{error.errorType}: {error.getMessageWithContext()}"
|
|
114
|
+
for error in gtfs_feed.errors
|
|
115
|
+
]
|
|
116
|
+
if allow_errors:
|
|
117
|
+
warnings.warn(
|
|
118
|
+
(
|
|
119
|
+
"R5 reported the following issues with "
|
|
120
|
+
f"GTFS file {gtfs_file.name}: \n"
|
|
121
|
+
+ ("\n- ".join(errors))
|
|
122
|
+
),
|
|
123
|
+
RuntimeWarning,
|
|
124
|
+
stacklevel=1,
|
|
125
|
+
)
|
|
126
|
+
else:
|
|
127
|
+
raise GtfsFileError(
|
|
128
|
+
(
|
|
129
|
+
f"Could not load GTFS file {gtfs_file.name}. \n"
|
|
130
|
+
+ ("\n- ".join(errors))
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
transport_network.transitLayer.loadFromGtfs(
|
|
135
|
+
gtfs_feed,
|
|
136
|
+
gtfs_transfer_loader,
|
|
137
|
+
)
|
|
138
|
+
gtfs_feed.close()
|
|
139
|
+
|
|
140
|
+
transport_network.streetLayer.associateStops(transport_network.transitLayer)
|
|
141
|
+
transport_network.streetLayer.buildEdgeLists()
|
|
142
|
+
|
|
143
|
+
transport_network.transitLayer.rebuildTransientIndexes()
|
|
144
|
+
|
|
145
|
+
transfer_finder = com.conveyal.r5.transit.TransferFinder(
|
|
146
|
+
transport_network,
|
|
147
|
+
gtfs_transfer_loader,
|
|
148
|
+
)
|
|
149
|
+
transfer_finder.findTransfers()
|
|
150
|
+
transfer_finder.findParkRideTransfer()
|
|
151
|
+
|
|
152
|
+
transport_network.transitLayer.buildDistanceTables(None)
|
|
153
|
+
|
|
154
|
+
if elevation_model is not None:
|
|
155
|
+
ElevationModel(
|
|
156
|
+
elevation_model,
|
|
157
|
+
elevation_cost_function,
|
|
158
|
+
).apply_to(transport_network)
|
|
159
|
+
|
|
160
|
+
osm_file.close() # not needed after here?
|
|
161
|
+
|
|
162
|
+
self._save_pickled_transport_network(
|
|
163
|
+
transport_network, Config().CACHE_DIR / f"{digest}.transport_network"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
self._transport_network = transport_network
|
|
167
|
+
|
|
168
|
+
@classmethod
|
|
169
|
+
def from_directory(cls, path):
|
|
170
|
+
"""
|
|
171
|
+
Find input data in `path`, load an `r5py.TransportNetwork`.
|
|
172
|
+
|
|
173
|
+
This mimicks r5r’s behaviour to accept a directory path
|
|
174
|
+
as the only input to `setup_r5()`.
|
|
175
|
+
|
|
176
|
+
If more than one OpenStreetMap extract (`.osm.pbf`) is
|
|
177
|
+
found in `path`, the (alphabetically) first one is used.
|
|
178
|
+
In case *no* OpenStreetMap extract is found, a `FileNotFound`
|
|
179
|
+
exception is raised. Any and all GTFS data files are used.
|
|
180
|
+
|
|
181
|
+
Arguments
|
|
182
|
+
---------
|
|
183
|
+
path : str
|
|
184
|
+
directory path in which to search for GTFS and .osm.pbf files
|
|
185
|
+
|
|
186
|
+
Returns
|
|
187
|
+
-------
|
|
188
|
+
TransportNetwork
|
|
189
|
+
A fully initialised r5py.TransportNetwork
|
|
190
|
+
"""
|
|
191
|
+
path = pathlib.Path(path)
|
|
192
|
+
try:
|
|
193
|
+
potential_osm_pbf_files = sorted(path.glob("*.osm.pbf"))
|
|
194
|
+
osm_pbf = potential_osm_pbf_files[0]
|
|
195
|
+
if len(potential_osm_pbf_files) > 1:
|
|
196
|
+
warnings.warn(
|
|
197
|
+
(
|
|
198
|
+
f"Found more than one OpenStreetMap extract file (`.osm.pbf`), "
|
|
199
|
+
f"using alphabetically first one ({osm_pbf.name})"
|
|
200
|
+
),
|
|
201
|
+
RuntimeWarning,
|
|
202
|
+
stacklevel=1,
|
|
203
|
+
)
|
|
204
|
+
except IndexError as exception:
|
|
205
|
+
raise FileNotFoundError(
|
|
206
|
+
"Could not find any OpenStreetMap extract file (`.osm.pbf`) "
|
|
207
|
+
f"in {path.absolute()}"
|
|
208
|
+
) from exception
|
|
209
|
+
gtfs = [
|
|
210
|
+
potential_gtfs_file
|
|
211
|
+
for potential_gtfs_file in path.glob("*.zip")
|
|
212
|
+
if contains_gtfs_data(potential_gtfs_file)
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
return cls(osm_pbf, gtfs)
|
|
216
|
+
|
|
217
|
+
def __enter__(self):
|
|
218
|
+
"""Provide a context."""
|
|
219
|
+
return self
|
|
220
|
+
|
|
221
|
+
def __exit__(self, exception_type, exception_value, traceback):
|
|
222
|
+
"""Exit context."""
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def extent(self):
|
|
227
|
+
"""The geographic area covered, as a `shapely.box`."""
|
|
228
|
+
# TODO: figure out how to get the extent of the GTFS schedule,
|
|
229
|
+
# then find the smaller extent of the two (or the larger one?)
|
|
230
|
+
return self.street_layer.extent
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def linkage_cache(self):
|
|
234
|
+
"""Expose the `TransportNetwork`’s `linkageCache` to Python."""
|
|
235
|
+
return self._transport_network.linkageCache
|
|
236
|
+
|
|
237
|
+
def _load_pickled_transport_network(self, path):
|
|
238
|
+
try:
|
|
239
|
+
input_file = java.io.File(f"{path}")
|
|
240
|
+
return com.conveyal.r5.kryo.KryoNetworkSerializer.read(input_file)
|
|
241
|
+
except java.io.FileNotFoundException as exception:
|
|
242
|
+
raise FileNotFoundError from exception
|
|
243
|
+
|
|
244
|
+
def _save_pickled_transport_network(self, transport_network, path):
|
|
245
|
+
output_file = java.io.File(f"{path}")
|
|
246
|
+
com.conveyal.r5.kryo.KryoNetworkSerializer.write(transport_network, output_file)
|
|
247
|
+
|
|
248
|
+
def snap_to_network(
|
|
249
|
+
self,
|
|
250
|
+
points,
|
|
251
|
+
radius=com.conveyal.r5.streets.StreetLayer.LINK_RADIUS_METERS,
|
|
252
|
+
street_mode=TransportMode.WALK,
|
|
253
|
+
):
|
|
254
|
+
"""
|
|
255
|
+
Snap `points` to valid locations on the network.
|
|
256
|
+
|
|
257
|
+
Arguments
|
|
258
|
+
---------
|
|
259
|
+
points : geopandas.GeoSeries
|
|
260
|
+
point geometries that will be snapped to the network
|
|
261
|
+
radius : float
|
|
262
|
+
Search radius around each `point`
|
|
263
|
+
street_mode : travel mode that the snapped-to street should allow
|
|
264
|
+
|
|
265
|
+
Returns
|
|
266
|
+
-------
|
|
267
|
+
geopandas.GeoSeries
|
|
268
|
+
point geometries that have been snapped to the network,
|
|
269
|
+
using the same index and order as the input `points`
|
|
270
|
+
"""
|
|
271
|
+
return points.apply(
|
|
272
|
+
functools.partial(
|
|
273
|
+
self.street_layer.find_split,
|
|
274
|
+
radius=radius,
|
|
275
|
+
street_mode=street_mode,
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
@functools.cached_property
|
|
280
|
+
def street_layer(self):
|
|
281
|
+
"""Expose the `TransportNetwork`’s `streetLayer` to Python."""
|
|
282
|
+
return StreetLayer.from_r5_street_layer(self._transport_network.streetLayer)
|
|
283
|
+
|
|
284
|
+
@property
|
|
285
|
+
def timezone(self):
|
|
286
|
+
"""Determine the timezone of the GTFS data."""
|
|
287
|
+
return self._transport_network.getTimeZone()
|
|
288
|
+
|
|
289
|
+
@functools.cached_property
|
|
290
|
+
def transit_layer(self):
|
|
291
|
+
"""Expose the `TransportNetwork`’s `transitLayer` to Python."""
|
|
292
|
+
return TransitLayer.from_r5_transit_layer(self._transport_network.transitLayer)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
@jpype._jcustomizer.JConversion(
|
|
296
|
+
"com.conveyal.r5.transit.TransportNetwork", exact=TransportNetwork
|
|
297
|
+
)
|
|
298
|
+
def _cast_TransportNetwork(java_class, object_):
|
|
299
|
+
return object_._transport_network
|