r5py 1.0.0.dev11__tar.gz → 1.0.1__tar.gz
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-1.0.0.dev11/src/r5py.egg-info → r5py-1.0.1}/PKG-INFO +11 -8
- {r5py-1.0.0.dev11 → r5py-1.0.1}/README.md +5 -5
- {r5py-1.0.0.dev11 → r5py-1.0.1}/pyproject.toml +6 -3
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/__init__.py +3 -1
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/r5/__init__.py +2 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/r5/detailed_itineraries.py +11 -2
- r5py-1.0.1/src/r5py/r5/isochrones.py +351 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/r5/street_layer.py +7 -3
- r5py-1.0.1/src/r5py/r5/street_segment.py +41 -0
- r5py-1.0.1/src/r5py/r5/transport_network.py +236 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/r5/travel_time_matrix.py +1 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/r5/trip_planner.py +6 -7
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/util/__init__.py +6 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/util/config.py +18 -2
- r5py-1.0.1/src/r5py/util/file_digest.py +42 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/util/good_enough_equidistant_crs.py +8 -3
- r5py-1.0.1/src/r5py/util/spatially_clustered_geodataframe.py +78 -0
- r5py-1.0.1/src/r5py/util/working_copy.py +44 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1/src/r5py.egg-info}/PKG-INFO +11 -8
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py.egg-info/SOURCES.txt +9 -1
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py.egg-info/requires.txt +6 -1
- r5py-1.0.1/tests/test_config.py +48 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_detailed_itineraries.py +138 -63
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_deterministic_behaviour.py +9 -8
- r5py-1.0.1/tests/test_file_digest.py +65 -0
- r5py-1.0.1/tests/test_isochrones.py +237 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_transport_network.py +0 -48
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_travel_time_matrix.py +2 -2
- r5py-1.0.1/tests/test_working_directory.py +39 -0
- r5py-1.0.0.dev11/src/r5py/r5/transport_network.py +0 -323
- r5py-1.0.0.dev11/tests/test_config.py +0 -27
- {r5py-1.0.0.dev11 → r5py-1.0.1}/LICENSE +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/setup.cfg +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/__main__.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/r5/access_leg.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/r5/base_travel_time_matrix.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/r5/breakdown_stat.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/r5/direct_leg.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/r5/egress_leg.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/r5/regional_task.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/r5/scenario.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/r5/transfer_leg.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/r5/transit_layer.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/r5/transit_leg.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/r5/transport_mode.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/r5/trip.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/r5/trip_leg.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/util/camel_to_snake_case.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/util/classpath.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/util/contains_gtfs_data.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/util/data_validation.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/util/environment.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/util/exceptions.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/util/jvm.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/util/memory_footprint.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/util/parse_int_date.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/util/sample_data_set.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/util/snake_to_camel_case.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/util/validating_requests_session.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py/util/warnings.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py.egg-info/dependency_links.txt +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/src/r5py.egg-info/top_level.txt +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_breakdownstats.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_camel_to_snake_case.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_classpath.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_contains_gtfs_data.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_data_validation.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_good_enough_equidistant_crs.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_java_casting.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_memory_footprint.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_parse_int_date.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_regional_task.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_sample_data_set.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_snake_to_camel_case.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_street_layer.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_transit_layer.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_transport_mode.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_trip.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_trip_leg.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_trip_planner.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_validating_request_session.py +0 -0
- {r5py-1.0.0.dev11 → r5py-1.0.1}/tests/test_verbose_warnings.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: r5py
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: Python wrapper for the R5 routing analysis engine
|
|
5
5
|
Author: Christoph Fink, Willem Klumpenhouwer, Marcus Sairava, Rafael Pereira, Henrikki Tenkanen
|
|
6
6
|
License: GPL-3.0-or-later or MIT
|
|
@@ -13,12 +13,12 @@ Classifier: Programming Language :: Python :: 3
|
|
|
13
13
|
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
14
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
16
|
-
Requires-Python: >=3.
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
Requires-Dist: ConfigArgParse
|
|
20
20
|
Requires-Dist: filelock
|
|
21
|
-
Requires-Dist:
|
|
21
|
+
Requires-Dist: geohexgrid
|
|
22
22
|
Requires-Dist: geopandas
|
|
23
23
|
Requires-Dist: joblib
|
|
24
24
|
Requires-Dist: jpype1
|
|
@@ -27,7 +27,10 @@ Requires-Dist: pandas>=2.1.0
|
|
|
27
27
|
Requires-Dist: psutil
|
|
28
28
|
Requires-Dist: pyproj
|
|
29
29
|
Requires-Dist: requests
|
|
30
|
+
Requires-Dist: scikit-learn
|
|
30
31
|
Requires-Dist: shapely>=2.0
|
|
32
|
+
Requires-Dist: simplification
|
|
33
|
+
Requires-Dist: typing_extensions; python_version < "3.13"
|
|
31
34
|
Provides-Extra: docs
|
|
32
35
|
Requires-Dist: contextily; extra == "docs"
|
|
33
36
|
Requires-Dist: folium; extra == "docs"
|
|
@@ -68,7 +71,7 @@ Requires-Dist: typing-extensions; extra == "tests"
|
|
|
68
71
|
[![downloads (pypi)][downloads-pypi-badge]][downloads-pypi-link]
|
|
69
72
|
[![downloads (conda-forge)][downloads-conda-forge-badge]][downloads-conda-forge-link]
|
|
70
73
|
<br />
|
|
71
|
-
[![
|
|
74
|
+
[![Unit tests][test-status-badge]][test-status-link]
|
|
72
75
|
[![Documentation Status][rtd-status-badge]][rtd-status-link]
|
|
73
76
|
[![Coverage][coverage-badge]][coverage-link]
|
|
74
77
|
<br />
|
|
@@ -135,9 +138,7 @@ your project better.
|
|
|
135
138
|
|
|
136
139
|
<!-- (1) badges -->
|
|
137
140
|
[binder-badge]: https://img.shields.io/badge/Try%20r5py%20with-binder-F5A252.svg?logo=data:image/png;base64,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
|
|
138
|
-
[binder-link]: https://
|
|
139
|
-
[build-status-badge]: https://github.com/r5py/r5py/actions/workflows/build-merged-pull-requests.yml/badge.svg
|
|
140
|
-
[build-status-link]: https://github.com/r5py/r5py/actions/workflows/build-merged-pull-requests.yml
|
|
141
|
+
[binder-link]: https://mybinder.org/v2/gh/r5py/r5py/stable?urlpath=tree/docs/user-guide/user-manual/quickstart.md
|
|
141
142
|
[coverage-badge]: https://codecov.io/gh/r5py/r5py/branch/main/graph/badge.svg?token=WG8RBMZBK6
|
|
142
143
|
[coverage-link]: https://codecov.io/gh/r5py/r5py
|
|
143
144
|
[doi-badge]: https://zenodo.org/badge/DOI/10.5281/zenodo.7060437.svg
|
|
@@ -150,11 +151,13 @@ your project better.
|
|
|
150
151
|
[rtd-status-link]: https://r5py.readthedocs.io/
|
|
151
152
|
[stable-version-badge]: https://img.shields.io/pypi/v/r5py?label=Stable
|
|
152
153
|
[stable-version-link]: https://github.com/r5py/r5py/releases
|
|
154
|
+
[test-status-badge]: https://github.com/r5py/r5py/actions/workflows/test.yml/badge.svg
|
|
155
|
+
[test-status-link]: https://github.com/r5py/r5py/actions/workflows/test.yml
|
|
153
156
|
|
|
154
157
|
<!-- (2) other links -->
|
|
155
158
|
[conda-create-env-from-yml]: https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#creating-an-environment-from-an-environment-yml-file
|
|
156
159
|
[conveyal]: https://www.conveyal.com/
|
|
157
|
-
[env-file]: https://github.com/r5py/r5py/blob/main/ci/
|
|
160
|
+
[env-file]: https://github.com/r5py/r5py/blob/main/ci/r5py.yaml
|
|
158
161
|
[geopandas]: https://geopandas.org/
|
|
159
162
|
[r5-github]: https://github.com/conveyal/r5/
|
|
160
163
|
[r5r-github]: https://github.com/ipeaGIT/r5r/
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
[![downloads (pypi)][downloads-pypi-badge]][downloads-pypi-link]
|
|
11
11
|
[![downloads (conda-forge)][downloads-conda-forge-badge]][downloads-conda-forge-link]
|
|
12
12
|
<br />
|
|
13
|
-
[![
|
|
13
|
+
[![Unit tests][test-status-badge]][test-status-link]
|
|
14
14
|
[![Documentation Status][rtd-status-badge]][rtd-status-link]
|
|
15
15
|
[![Coverage][coverage-badge]][coverage-link]
|
|
16
16
|
<br />
|
|
@@ -77,9 +77,7 @@ your project better.
|
|
|
77
77
|
|
|
78
78
|
<!-- (1) badges -->
|
|
79
79
|
[binder-badge]: https://img.shields.io/badge/Try%20r5py%20with-binder-F5A252.svg?logo=data:image/png;base64,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
|
|
80
|
-
[binder-link]: https://
|
|
81
|
-
[build-status-badge]: https://github.com/r5py/r5py/actions/workflows/build-merged-pull-requests.yml/badge.svg
|
|
82
|
-
[build-status-link]: https://github.com/r5py/r5py/actions/workflows/build-merged-pull-requests.yml
|
|
80
|
+
[binder-link]: https://mybinder.org/v2/gh/r5py/r5py/stable?urlpath=tree/docs/user-guide/user-manual/quickstart.md
|
|
83
81
|
[coverage-badge]: https://codecov.io/gh/r5py/r5py/branch/main/graph/badge.svg?token=WG8RBMZBK6
|
|
84
82
|
[coverage-link]: https://codecov.io/gh/r5py/r5py
|
|
85
83
|
[doi-badge]: https://zenodo.org/badge/DOI/10.5281/zenodo.7060437.svg
|
|
@@ -92,11 +90,13 @@ your project better.
|
|
|
92
90
|
[rtd-status-link]: https://r5py.readthedocs.io/
|
|
93
91
|
[stable-version-badge]: https://img.shields.io/pypi/v/r5py?label=Stable
|
|
94
92
|
[stable-version-link]: https://github.com/r5py/r5py/releases
|
|
93
|
+
[test-status-badge]: https://github.com/r5py/r5py/actions/workflows/test.yml/badge.svg
|
|
94
|
+
[test-status-link]: https://github.com/r5py/r5py/actions/workflows/test.yml
|
|
95
95
|
|
|
96
96
|
<!-- (2) other links -->
|
|
97
97
|
[conda-create-env-from-yml]: https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#creating-an-environment-from-an-environment-yml-file
|
|
98
98
|
[conveyal]: https://www.conveyal.com/
|
|
99
|
-
[env-file]: https://github.com/r5py/r5py/blob/main/ci/
|
|
99
|
+
[env-file]: https://github.com/r5py/r5py/blob/main/ci/r5py.yaml
|
|
100
100
|
[geopandas]: https://geopandas.org/
|
|
101
101
|
[r5-github]: https://github.com/conveyal/r5/
|
|
102
102
|
[r5r-github]: https://github.com/ipeaGIT/r5r/
|
|
@@ -18,7 +18,7 @@ authors = [
|
|
|
18
18
|
dependencies = [
|
|
19
19
|
"ConfigArgParse",
|
|
20
20
|
"filelock",
|
|
21
|
-
"
|
|
21
|
+
"geohexgrid",
|
|
22
22
|
"geopandas",
|
|
23
23
|
"joblib",
|
|
24
24
|
"jpype1",
|
|
@@ -27,9 +27,12 @@ dependencies = [
|
|
|
27
27
|
"psutil",
|
|
28
28
|
"pyproj",
|
|
29
29
|
"requests",
|
|
30
|
-
"
|
|
30
|
+
"scikit-learn",
|
|
31
|
+
"shapely>=2.0",
|
|
32
|
+
"simplification",
|
|
33
|
+
"typing_extensions; python_version < '3.13'"
|
|
31
34
|
]
|
|
32
|
-
requires-python = ">=3.
|
|
35
|
+
requires-python = ">=3.10"
|
|
33
36
|
|
|
34
37
|
classifiers = [
|
|
35
38
|
"Programming Language :: Python :: 3",
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
"""Python wrapper for the R5 routing analysis engine."""
|
|
4
4
|
|
|
5
|
-
__version__ = "1.0.
|
|
5
|
+
__version__ = "1.0.1"
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
from .r5 import (
|
|
9
9
|
DetailedItineraries,
|
|
10
10
|
DetailedItinerariesComputer,
|
|
11
|
+
Isochrones,
|
|
11
12
|
RegionalTask,
|
|
12
13
|
TransportMode,
|
|
13
14
|
TransportNetwork,
|
|
@@ -18,6 +19,7 @@ from .r5 import (
|
|
|
18
19
|
__all__ = [
|
|
19
20
|
"DetailedItineraries",
|
|
20
21
|
"DetailedItinerariesComputer",
|
|
22
|
+
"Isochrones",
|
|
21
23
|
"RegionalTask",
|
|
22
24
|
"TransportMode",
|
|
23
25
|
"TransportNetwork",
|
|
@@ -7,6 +7,7 @@ from .breakdown_stat import BreakdownStat
|
|
|
7
7
|
from .detailed_itineraries import DetailedItineraries, DetailedItinerariesComputer
|
|
8
8
|
from .direct_leg import DirectLeg
|
|
9
9
|
from .egress_leg import EgressLeg
|
|
10
|
+
from .isochrones import Isochrones
|
|
10
11
|
from .regional_task import RegionalTask
|
|
11
12
|
from .scenario import Scenario
|
|
12
13
|
from .street_layer import StreetLayer
|
|
@@ -25,6 +26,7 @@ __all__ = [
|
|
|
25
26
|
"DetailedItinerariesComputer",
|
|
26
27
|
"DirectLeg",
|
|
27
28
|
"EgressLeg",
|
|
29
|
+
"Isochrones",
|
|
28
30
|
"RegionalTask",
|
|
29
31
|
"Scenario",
|
|
30
32
|
"SpeedConfig",
|
|
@@ -84,7 +84,7 @@ class DetailedItineraries(BaseTravelTimeMatrix):
|
|
|
84
84
|
``access_modes``, ``egress_modes``, ``max_time``, ``max_time_walking``,
|
|
85
85
|
``max_time_cycling``, ``max_time_driving``, ``speed_cycling``, ``speed_walking``,
|
|
86
86
|
``max_public_transport_rides``, ``max_bicycle_traffic_stress``
|
|
87
|
-
|
|
87
|
+
Note that not all arguments might make sense in this context, and the
|
|
88
88
|
underlying R5 engine might ignore some of them.
|
|
89
89
|
"""
|
|
90
90
|
super().__init__(
|
|
@@ -122,11 +122,20 @@ class DetailedItineraries(BaseTravelTimeMatrix):
|
|
|
122
122
|
|
|
123
123
|
data = self._compute()
|
|
124
124
|
with warnings.catch_warnings():
|
|
125
|
-
warnings.
|
|
125
|
+
warnings.filterwarnings(
|
|
126
|
+
"ignore",
|
|
127
|
+
message=(
|
|
128
|
+
"You are adding a column named 'geometry' to a GeoDataFrame "
|
|
129
|
+
"constructed without an active geometry column"
|
|
130
|
+
),
|
|
131
|
+
category=FutureWarning,
|
|
132
|
+
)
|
|
126
133
|
for column in data.columns:
|
|
127
134
|
self[column] = data[column]
|
|
128
135
|
self.set_geometry("geometry")
|
|
129
136
|
|
|
137
|
+
del self.transport_network
|
|
138
|
+
|
|
130
139
|
def _compute(self):
|
|
131
140
|
"""
|
|
132
141
|
Compute travel times from all origins to all destinations.
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
"""Compute polygons of equal travel time from a destination."""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
import datetime
|
|
8
|
+
import warnings
|
|
9
|
+
|
|
10
|
+
import geohexgrid
|
|
11
|
+
import geopandas
|
|
12
|
+
import pandas
|
|
13
|
+
import pyproj
|
|
14
|
+
import shapely
|
|
15
|
+
import simplification.cutil
|
|
16
|
+
|
|
17
|
+
from .base_travel_time_matrix import BaseTravelTimeMatrix
|
|
18
|
+
from .transport_mode import TransportMode
|
|
19
|
+
from .travel_time_matrix import TravelTimeMatrix
|
|
20
|
+
from ..util import GoodEnoughEquidistantCrs, SpatiallyClusteredGeoDataFrame
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
__all__ = ["Isochrones"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
EMPTY_POINT = shapely.Point()
|
|
27
|
+
R5_CRS = "EPSG:4326"
|
|
28
|
+
|
|
29
|
+
CONCAVE_HULL_BUFFER_SIZE = 20.0 # metres
|
|
30
|
+
CONCAVE_HULL_RATIO = 0.3
|
|
31
|
+
|
|
32
|
+
VERY_SMALL_BUFFER_SIZE = 0.001 # turn points into polygons
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Isochrones(BaseTravelTimeMatrix):
|
|
36
|
+
"""Compute polygons of equal travel time from a destination."""
|
|
37
|
+
|
|
38
|
+
_r5py_attributes = BaseTravelTimeMatrix._r5py_attributes + [
|
|
39
|
+
"_isochrones",
|
|
40
|
+
"isochrones",
|
|
41
|
+
"point_grid_resolution",
|
|
42
|
+
"point_grid_sample_ratio",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
transport_network,
|
|
48
|
+
origins,
|
|
49
|
+
isochrones=pandas.timedelta_range(
|
|
50
|
+
start=datetime.timedelta(minutes=0),
|
|
51
|
+
end=datetime.timedelta(hours=1),
|
|
52
|
+
freq=datetime.timedelta(minutes=15),
|
|
53
|
+
),
|
|
54
|
+
point_grid_resolution=100,
|
|
55
|
+
point_grid_sample_ratio=1.0,
|
|
56
|
+
**kwargs,
|
|
57
|
+
):
|
|
58
|
+
"""
|
|
59
|
+
Compute polygons of equal travel time from one or more destinations.
|
|
60
|
+
|
|
61
|
+
``r5py.Isochrones`` are child classes of ``geopandas.GeoDataFrame`` and
|
|
62
|
+
support all of their methods and properties, see
|
|
63
|
+
https://geopandas.org/en/stable/docs.html
|
|
64
|
+
|
|
65
|
+
Arguments
|
|
66
|
+
---------
|
|
67
|
+
transport_network : r5py.TransportNetwork | tuple(str, list(str), dict)
|
|
68
|
+
The transport network to route on. This can either be a readily
|
|
69
|
+
initialised r5py.TransportNetwork or a tuple of the parameters
|
|
70
|
+
passed to ``TransportNetwork.__init__()``: the path to an OpenStreetMap
|
|
71
|
+
extract in PBF format, a list of zero of more paths to GTFS transport
|
|
72
|
+
schedule files, and a dict with ``build_config`` options.
|
|
73
|
+
origins : geopandas.GeoDataFrame | shapely.Point
|
|
74
|
+
Place(s) to find a route _from_
|
|
75
|
+
Must be/have a point geometry. If multiple origin points are passed,
|
|
76
|
+
isochrones will be computed as minimum travel time from any of them.
|
|
77
|
+
isochrones : pandas.TimedeltaIndex | collections.abc.Iterable[int]
|
|
78
|
+
For which interval to compute isochrone polygons. An iterable of
|
|
79
|
+
integers is interpreted as minutes.
|
|
80
|
+
point_grid_resolution : int
|
|
81
|
+
Distance in meters between points in the regular grid of points laid over the
|
|
82
|
+
transport network’s extent that is used to compute isochrones.
|
|
83
|
+
Increase this value for performance, decrease it for precision.
|
|
84
|
+
point_grid_sample_ratio : float
|
|
85
|
+
Share of points of the point grid that are used in computation,
|
|
86
|
+
ranging from 0.01 to 1.0.
|
|
87
|
+
Increase this value for performance, decrease it for precision.
|
|
88
|
+
**kwargs : mixed
|
|
89
|
+
Any arguments than can be passed to r5py.RegionalTask:
|
|
90
|
+
``departure``, ``departure_time_window``, ``percentiles``, ``transport_modes``,
|
|
91
|
+
``access_modes``, ``egress_modes``, ``max_time``, ``max_time_walking``,
|
|
92
|
+
``max_time_cycling``, ``max_time_driving``, ``speed_cycling``, ``speed_walking``,
|
|
93
|
+
``max_public_transport_rides``, ``max_bicycle_traffic_stress``
|
|
94
|
+
Note that not all arguments might make sense in this context, and the
|
|
95
|
+
underlying R5 engine might ignore some of them.
|
|
96
|
+
If percentiles are specified, the lowest one will be used for
|
|
97
|
+
isochrone computation.
|
|
98
|
+
"""
|
|
99
|
+
geopandas.GeoDataFrame.__init__(self)
|
|
100
|
+
BaseTravelTimeMatrix.__init__(
|
|
101
|
+
self,
|
|
102
|
+
transport_network,
|
|
103
|
+
**kwargs,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
self.EQUIDISTANT_CRS = GoodEnoughEquidistantCrs(self.transport_network.extent)
|
|
107
|
+
|
|
108
|
+
if isinstance(origins, shapely.Geometry):
|
|
109
|
+
origins = geopandas.GeoDataFrame(
|
|
110
|
+
{
|
|
111
|
+
"id": [
|
|
112
|
+
"origin",
|
|
113
|
+
],
|
|
114
|
+
"geometry": [
|
|
115
|
+
origins,
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
crs=R5_CRS,
|
|
119
|
+
)
|
|
120
|
+
self.origins = origins
|
|
121
|
+
self.isochrones = isochrones
|
|
122
|
+
|
|
123
|
+
self.point_grid_resolution = point_grid_resolution
|
|
124
|
+
self.point_grid_sample_ratio = max(0.01, min(1.0, point_grid_sample_ratio))
|
|
125
|
+
|
|
126
|
+
travel_times = TravelTimeMatrix(
|
|
127
|
+
transport_network,
|
|
128
|
+
origins=self.origins,
|
|
129
|
+
destinations=self.destinations,
|
|
130
|
+
max_time=self.isochrones.max(),
|
|
131
|
+
**kwargs,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
data = self._compute_isochrones_from_travel_times(travel_times)
|
|
135
|
+
|
|
136
|
+
with warnings.catch_warnings():
|
|
137
|
+
warnings.filterwarnings(
|
|
138
|
+
"ignore",
|
|
139
|
+
message=(
|
|
140
|
+
"You are adding a column named 'geometry' to a GeoDataFrame "
|
|
141
|
+
"constructed without an active geometry column"
|
|
142
|
+
),
|
|
143
|
+
category=FutureWarning,
|
|
144
|
+
)
|
|
145
|
+
for column in data.columns:
|
|
146
|
+
self[column] = data[column]
|
|
147
|
+
self.set_geometry("geometry")
|
|
148
|
+
|
|
149
|
+
del self.transport_network
|
|
150
|
+
|
|
151
|
+
def _compute_isochrones_from_travel_times(self, travel_times):
|
|
152
|
+
travel_times = travel_times.dropna().groupby("to_id").min().reset_index()
|
|
153
|
+
|
|
154
|
+
if self.request.percentiles == [50]:
|
|
155
|
+
travel_time_column = "travel_time"
|
|
156
|
+
else:
|
|
157
|
+
travel_time_column = f"travel_time_p{self.request.percentiles[0]:d}"
|
|
158
|
+
|
|
159
|
+
isochrones = {
|
|
160
|
+
"travel_time": [],
|
|
161
|
+
"geometry": [],
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
for isochrone in self.isochrones:
|
|
165
|
+
reached_nodes = (
|
|
166
|
+
self.destinations.set_index("id")
|
|
167
|
+
.join(
|
|
168
|
+
travel_times[
|
|
169
|
+
travel_times[travel_time_column]
|
|
170
|
+
<= (isochrone.total_seconds() / 60)
|
|
171
|
+
].set_index("to_id"),
|
|
172
|
+
how="inner",
|
|
173
|
+
)
|
|
174
|
+
.reset_index()
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# isochrone polygons might be disjoint (e.g., around metro stops)
|
|
178
|
+
if not reached_nodes.empty:
|
|
179
|
+
reached_nodes = SpatiallyClusteredGeoDataFrame(
|
|
180
|
+
reached_nodes, eps=(2.0 * self.point_grid_resolution)
|
|
181
|
+
).to_crs(self.EQUIDISTANT_CRS)
|
|
182
|
+
isochrone_polygons = pandas.concat(
|
|
183
|
+
[
|
|
184
|
+
(
|
|
185
|
+
reached_nodes[reached_nodes["cluster"] != -1]
|
|
186
|
+
.dissolve(by="cluster")
|
|
187
|
+
.concave_hull(ratio=CONCAVE_HULL_RATIO)
|
|
188
|
+
.buffer(VERY_SMALL_BUFFER_SIZE)
|
|
189
|
+
),
|
|
190
|
+
(
|
|
191
|
+
reached_nodes[reached_nodes["cluster"] == -1].buffer(
|
|
192
|
+
VERY_SMALL_BUFFER_SIZE
|
|
193
|
+
)
|
|
194
|
+
),
|
|
195
|
+
]
|
|
196
|
+
).union_all()
|
|
197
|
+
|
|
198
|
+
isochrones["travel_time"].append(isochrone)
|
|
199
|
+
isochrones["geometry"].append(isochrone_polygons)
|
|
200
|
+
|
|
201
|
+
isochrones = geopandas.GeoDataFrame(
|
|
202
|
+
isochrones, geometry="geometry", crs=self.EQUIDISTANT_CRS
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# clip smaller isochrones by larger isochrones
|
|
206
|
+
# (concave_hull’s ratio parameter depends on input shapes and does not
|
|
207
|
+
# produce the same results, e.g., around bridges or at the coast line)
|
|
208
|
+
for row in range(len(isochrones) - 2, 0, -1):
|
|
209
|
+
isochrones.loc[row, "geometry"] = shapely.intersection(
|
|
210
|
+
isochrones.loc[row, "geometry"], isochrones.loc[row + 1, "geometry"]
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
isochrones["geometry"] = (
|
|
214
|
+
isochrones["geometry"]
|
|
215
|
+
.buffer(CONCAVE_HULL_BUFFER_SIZE)
|
|
216
|
+
.boundary.apply(
|
|
217
|
+
lambda geometry: (
|
|
218
|
+
geometry
|
|
219
|
+
if isinstance(geometry, shapely.MultiLineString)
|
|
220
|
+
else shapely.MultiLineString([geometry])
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
.apply(
|
|
224
|
+
lambda multilinestring: (
|
|
225
|
+
shapely.MultiLineString(
|
|
226
|
+
[
|
|
227
|
+
simplification.cutil.simplify_coords_vwp(
|
|
228
|
+
linestring.coords,
|
|
229
|
+
self.point_grid_resolution * 5.0,
|
|
230
|
+
)
|
|
231
|
+
for linestring in multilinestring.geoms
|
|
232
|
+
]
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
)
|
|
236
|
+
.to_crs(R5_CRS)
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
return isochrones
|
|
240
|
+
|
|
241
|
+
@property
|
|
242
|
+
def destinations(self):
|
|
243
|
+
"""A regular grid of points covering the range of the chosen transport mode."""
|
|
244
|
+
try:
|
|
245
|
+
return self._destinations
|
|
246
|
+
except AttributeError:
|
|
247
|
+
destinations = self._regular_point_grid
|
|
248
|
+
destinations["geometry"] = self.transport_network.snap_to_network(
|
|
249
|
+
destinations["geometry"]
|
|
250
|
+
)
|
|
251
|
+
destinations = destinations[destinations["geometry"] != EMPTY_POINT]
|
|
252
|
+
destinations["geometry"] = destinations["geometry"].normalize()
|
|
253
|
+
destinations = destinations.drop_duplicates()
|
|
254
|
+
|
|
255
|
+
# with snapping, sometimes we end up with clumps of points
|
|
256
|
+
# below, we try to form clusters, from all clusters we retain
|
|
257
|
+
# one geometry, only
|
|
258
|
+
destinations = SpatiallyClusteredGeoDataFrame(
|
|
259
|
+
destinations, eps=(0.5 * self.point_grid_resolution)
|
|
260
|
+
)
|
|
261
|
+
destinations = pandas.concat(
|
|
262
|
+
[
|
|
263
|
+
(
|
|
264
|
+
destinations[destinations["cluster"] != -1]
|
|
265
|
+
.groupby("cluster")
|
|
266
|
+
.first()
|
|
267
|
+
.set_crs(R5_CRS)
|
|
268
|
+
),
|
|
269
|
+
destinations[destinations["cluster"] == -1],
|
|
270
|
+
]
|
|
271
|
+
)[["id", "geometry"]].copy()
|
|
272
|
+
|
|
273
|
+
if self.point_grid_sample_ratio < 1.0:
|
|
274
|
+
destinations = destinations.sample(frac=self.point_grid_sample_ratio)
|
|
275
|
+
|
|
276
|
+
self._destinations = destinations
|
|
277
|
+
|
|
278
|
+
return destinations
|
|
279
|
+
|
|
280
|
+
@destinations.setter
|
|
281
|
+
def destinations(self, destinations):
|
|
282
|
+
# https://bugs.python.org/issue14965
|
|
283
|
+
super(self.__class__, self.__class__).destinations.__set__(self, destinations)
|
|
284
|
+
|
|
285
|
+
@property
|
|
286
|
+
def isochrones(self):
|
|
287
|
+
"""
|
|
288
|
+
Compute isochrones for these travel times.
|
|
289
|
+
|
|
290
|
+
pandas.TimedeltaIndex | collections.abc.Iterable[int]
|
|
291
|
+
An iterable of integers is interpreted as minutes.
|
|
292
|
+
"""
|
|
293
|
+
try:
|
|
294
|
+
return self._isochrones
|
|
295
|
+
except AttributeError:
|
|
296
|
+
raise
|
|
297
|
+
|
|
298
|
+
@isochrones.setter
|
|
299
|
+
def isochrones(self, isochrones):
|
|
300
|
+
if not isinstance(isochrones, pandas.TimedeltaIndex):
|
|
301
|
+
isochrones = pandas.to_timedelta(isochrones, unit="minutes")
|
|
302
|
+
try:
|
|
303
|
+
# do not compute for 0 travel time
|
|
304
|
+
isochrones = isochrones.drop(datetime.timedelta(0))
|
|
305
|
+
except KeyError:
|
|
306
|
+
pass
|
|
307
|
+
self._isochrones = isochrones
|
|
308
|
+
|
|
309
|
+
@property
|
|
310
|
+
def _regular_point_grid(self):
|
|
311
|
+
extent = shapely.ops.transform(
|
|
312
|
+
pyproj.Transformer.from_crs(
|
|
313
|
+
R5_CRS,
|
|
314
|
+
self.EQUIDISTANT_CRS,
|
|
315
|
+
always_xy=True,
|
|
316
|
+
).transform,
|
|
317
|
+
self.transport_network.extent,
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
grid = geohexgrid.make_grid_from_bounds(
|
|
321
|
+
*extent.bounds,
|
|
322
|
+
self.point_grid_resolution,
|
|
323
|
+
crs=self.EQUIDISTANT_CRS,
|
|
324
|
+
)
|
|
325
|
+
grid["geometry"] = grid["geometry"].centroid
|
|
326
|
+
grid["id"] = grid.index
|
|
327
|
+
grid = grid[["id", "geometry"]].to_crs(R5_CRS)
|
|
328
|
+
|
|
329
|
+
# for walking and cycling, we can clip the extent to an area reachable
|
|
330
|
+
# by the (well-defined) travel speeds:
|
|
331
|
+
if set(self.request.transport_modes) <= set(
|
|
332
|
+
(TransportMode.WALK, TransportMode.BICYCLE)
|
|
333
|
+
):
|
|
334
|
+
if TransportMode.WALK in self.request.transport_modes:
|
|
335
|
+
speed = self.request.speed_walking
|
|
336
|
+
if TransportMode.BICYCLE in self.request.transport_modes:
|
|
337
|
+
speed = self.request.speed_cycling
|
|
338
|
+
|
|
339
|
+
speed = speed * (1000.0 / 3600.0) * 1.1 # km/h -> m/s, plus a bit of buffer
|
|
340
|
+
|
|
341
|
+
grid = grid.clip(
|
|
342
|
+
(
|
|
343
|
+
pandas.concat([self.origins] * 2) # workaround until
|
|
344
|
+
# https://github.com/pyproj4/pyproj/issues/1309 is fixed
|
|
345
|
+
.to_crs(self.EQUIDISTANT_CRS)
|
|
346
|
+
.buffer(speed * max(self.isochrones).total_seconds())
|
|
347
|
+
.to_crs(R5_CRS)
|
|
348
|
+
)
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
return grid.copy()
|
|
@@ -22,6 +22,9 @@ __all__ = ["StreetLayer"]
|
|
|
22
22
|
start_jvm()
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
EMPTY_POINT = shapely.Point()
|
|
26
|
+
|
|
27
|
+
|
|
25
28
|
class StreetLayer:
|
|
26
29
|
"""Wrap a com.conveyal.r5.streets.StreetLayer."""
|
|
27
30
|
|
|
@@ -72,13 +75,14 @@ class StreetLayer:
|
|
|
72
75
|
Closest location on the street network or `POINT EMPTY` if no
|
|
73
76
|
such location could be found within `radius`
|
|
74
77
|
"""
|
|
75
|
-
|
|
78
|
+
try:
|
|
79
|
+
split = self._street_layer.findSplit(point.y, point.x, radius, street_mode)
|
|
76
80
|
return shapely.Point(
|
|
77
81
|
split.fixedLon / com.conveyal.r5.streets.VertexStore.FIXED_FACTOR,
|
|
78
82
|
split.fixedLat / com.conveyal.r5.streets.VertexStore.FIXED_FACTOR,
|
|
79
83
|
)
|
|
80
|
-
|
|
81
|
-
return
|
|
84
|
+
except (AttributeError, TypeError):
|
|
85
|
+
return EMPTY_POINT
|
|
82
86
|
|
|
83
87
|
|
|
84
88
|
@jpype._jcustomizer.JConversion(
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""A less complex representation of com.conveyal.r5.api.util.StreetSegment."""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import datetime
|
|
7
|
+
|
|
8
|
+
import shapely
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = ["StreetSegment"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class StreetSegment:
|
|
15
|
+
"""A less complex representation of com.conveyal.r5.api.util.StreetSegment."""
|
|
16
|
+
|
|
17
|
+
distance = 0
|
|
18
|
+
duration = datetime.timedelta()
|
|
19
|
+
geometry = shapely.LineString()
|
|
20
|
+
|
|
21
|
+
def __init__(self, street_path):
|
|
22
|
+
"""
|
|
23
|
+
Initialise a less complex representation of com.conveyal.r5.api.util.StreetSegment.
|
|
24
|
+
|
|
25
|
+
Arguments
|
|
26
|
+
---------
|
|
27
|
+
street_path : com.conveyal.r5.profile.StreetPath
|
|
28
|
+
StreetPath, obtained, e.g., from StreetRouter state
|
|
29
|
+
"""
|
|
30
|
+
self.distance = street_path.getDistance()
|
|
31
|
+
self.duration = street_path.getDuration()
|
|
32
|
+
self.geometry = shapely.line_merge(
|
|
33
|
+
shapely.MultiLineString(
|
|
34
|
+
[
|
|
35
|
+
shapely.from_wkt(
|
|
36
|
+
str(street_path.getEdge(edge).getGeometry().toText())
|
|
37
|
+
)
|
|
38
|
+
for edge in street_path.getEdges()
|
|
39
|
+
]
|
|
40
|
+
)
|
|
41
|
+
)
|