r5py 0.1.1.dev2__py3-none-any.whl → 1.0.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.
Potentially problematic release.
This version of r5py might be problematic. Click here for more details.
- r5py/__init__.py +8 -1
- r5py/__main__.py +1 -14
- r5py/r5/__init__.py +20 -2
- r5py/r5/{base_travel_time_matrix_computer.py → base_travel_time_matrix.py} +28 -8
- r5py/r5/{detailed_itineraries_computer.py → detailed_itineraries.py} +82 -20
- r5py/r5/direct_leg.py +1 -3
- r5py/r5/isochrones.py +351 -0
- r5py/r5/regional_task.py +12 -9
- r5py/r5/street_layer.py +8 -3
- r5py/r5/street_segment.py +41 -0
- r5py/r5/transfer_leg.py +2 -6
- r5py/r5/transit_layer.py +6 -0
- r5py/r5/transit_leg.py +1 -5
- r5py/r5/transport_mode.py +5 -3
- r5py/r5/transport_network.py +60 -138
- r5py/r5/travel_time_matrix.py +209 -0
- r5py/r5/trip.py +13 -8
- r5py/r5/trip_leg.py +76 -15
- r5py/r5/trip_planner.py +109 -54
- r5py/util/__init__.py +8 -0
- r5py/util/classpath.py +9 -5
- r5py/util/config.py +32 -7
- r5py/util/environment.py +34 -0
- r5py/util/file_digest.py +42 -0
- r5py/util/good_enough_equidistant_crs.py +8 -4
- r5py/util/memory_footprint.py +3 -5
- r5py/util/sample_data_set.py +17 -6
- r5py/util/spatially_clustered_geodataframe.py +78 -0
- r5py/util/validating_requests_session.py +2 -2
- r5py/util/working_copy.py +44 -0
- {r5py-0.1.1.dev2.dist-info → r5py-1.0.0.dist-info}/METADATA +34 -33
- r5py-1.0.0.dist-info/RECORD +47 -0
- {r5py-0.1.1.dev2.dist-info → r5py-1.0.0.dist-info}/WHEEL +1 -1
- r5py/r5/travel_time_matrix_computer.py +0 -134
- r5py/sampledata/_keep/__init__.py +0 -3
- r5py-0.1.1.dev2.dist-info/RECORD +0 -42
- {r5py-0.1.1.dev2.dist-info → r5py-1.0.0.dist-info}/LICENSE +0 -0
- {r5py-0.1.1.dev2.dist-info → r5py-1.0.0.dist-info}/top_level.txt +0 -0
r5py/util/config.py
CHANGED
|
@@ -2,23 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
"""Handle configuration options and command line options."""
|
|
4
4
|
|
|
5
|
+
import datetime
|
|
5
6
|
import functools
|
|
7
|
+
import importlib.resources
|
|
6
8
|
import os
|
|
7
9
|
import pathlib
|
|
8
10
|
import sys
|
|
9
11
|
import tempfile
|
|
10
12
|
|
|
11
13
|
import configargparse
|
|
12
|
-
import importlib_resources
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
__all__ = ["Config"]
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
PACKAGE = __package__.split(".")[0]
|
|
19
|
-
CONFIG_FILE_TEMPLATE =
|
|
20
|
+
CONFIG_FILE_TEMPLATE = importlib.resources.files(f"{PACKAGE}.util").joinpath(
|
|
20
21
|
f"{PACKAGE}.yml.template"
|
|
21
22
|
)
|
|
23
|
+
CACHE_MAX_AGE = datetime.timedelta(weeks=2)
|
|
22
24
|
|
|
23
25
|
if "HOME" not in os.environ: # e.g., testing environment or container
|
|
24
26
|
os.environ["HOME"] = "."
|
|
@@ -31,7 +33,6 @@ class Config:
|
|
|
31
33
|
|
|
32
34
|
def __init__(self):
|
|
33
35
|
"""Load configuration from config files or command line arguments."""
|
|
34
|
-
|
|
35
36
|
self.argparser.add(
|
|
36
37
|
"-v",
|
|
37
38
|
"--verbose",
|
|
@@ -40,6 +41,7 @@ class Config:
|
|
|
40
41
|
)
|
|
41
42
|
|
|
42
43
|
def __new__(cls):
|
|
44
|
+
"""Load configuration from config files or command line arguments."""
|
|
43
45
|
if cls._instance is None:
|
|
44
46
|
cls._instance = super(Config, cls).__new__(cls)
|
|
45
47
|
return cls._instance
|
|
@@ -55,6 +57,7 @@ class Config:
|
|
|
55
57
|
|
|
56
58
|
@property
|
|
57
59
|
def argparser(self):
|
|
60
|
+
"""Return a singleton instance of a `configargparse.ArgumentParser`."""
|
|
58
61
|
try:
|
|
59
62
|
argparser = configargparse.get_argument_parser(
|
|
60
63
|
prog=PACKAGE,
|
|
@@ -67,25 +70,41 @@ class Config:
|
|
|
67
70
|
|
|
68
71
|
@functools.cached_property
|
|
69
72
|
def CACHE_DIR(self):
|
|
73
|
+
"""Save persistent cache files into this directory."""
|
|
70
74
|
cache_dir = (
|
|
71
75
|
pathlib.Path(
|
|
72
76
|
os.environ.get("LOCALAPPDATA")
|
|
73
77
|
or os.environ.get("XDG_CACHE_HOME")
|
|
74
|
-
or (pathlib.Path(os.environ
|
|
78
|
+
or (pathlib.Path(os.environ.get("HOME")) / ".cache")
|
|
75
79
|
)
|
|
76
80
|
/ PACKAGE
|
|
77
81
|
)
|
|
78
82
|
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
83
|
+
|
|
84
|
+
# clean old files to keep cache dir from growing too much
|
|
85
|
+
cache_treshold = (datetime.datetime.now() - CACHE_MAX_AGE).timestamp()
|
|
86
|
+
for cached_file in cache_dir.glob("**/*"):
|
|
87
|
+
try:
|
|
88
|
+
*_, atime, mtime, _ = cached_file.stat()
|
|
89
|
+
assert max(atime, mtime) > cache_treshold
|
|
90
|
+
except (
|
|
91
|
+
AssertionError, # expired
|
|
92
|
+
FileNotFoundError, # broken symlink
|
|
93
|
+
PermissionError,
|
|
94
|
+
):
|
|
95
|
+
cached_file.unlink()
|
|
96
|
+
|
|
79
97
|
return cache_dir
|
|
80
98
|
|
|
81
99
|
@functools.cached_property
|
|
82
100
|
def CONFIG_FILES(self):
|
|
101
|
+
"""List locations of potential configuration files."""
|
|
83
102
|
config_files = [
|
|
84
103
|
pathlib.Path(f"/etc/{PACKAGE}.yml"),
|
|
85
104
|
pathlib.Path(
|
|
86
105
|
os.environ.get("APPDATA")
|
|
87
106
|
or os.environ.get("XDG_CONFIG_HOME")
|
|
88
|
-
or (pathlib.Path(os.environ
|
|
107
|
+
or (pathlib.Path(os.environ.get("HOME")) / ".config")
|
|
89
108
|
)
|
|
90
109
|
/ f"{PACKAGE}.yml",
|
|
91
110
|
]
|
|
@@ -113,7 +132,7 @@ class Config:
|
|
|
113
132
|
try:
|
|
114
133
|
destination_path.parent.mkdir(parents=True, exist_ok=True)
|
|
115
134
|
|
|
116
|
-
with
|
|
135
|
+
with importlib.resources.as_file(CONFIG_FILE_TEMPLATE) as template:
|
|
117
136
|
destination_path.write_text(template.read_text())
|
|
118
137
|
|
|
119
138
|
except (
|
|
@@ -130,8 +149,14 @@ class Config:
|
|
|
130
149
|
|
|
131
150
|
@functools.cached_property
|
|
132
151
|
def TEMP_DIR(self):
|
|
152
|
+
"""
|
|
153
|
+
Save temporary files to this directory.
|
|
154
|
+
|
|
155
|
+
read-only property,
|
|
156
|
+
use command-line option `--temporary-directory` to change.
|
|
157
|
+
"""
|
|
133
158
|
parent_dir = self.arguments.temporary_directory
|
|
134
|
-
temp_dir = pathlib.Path(tempfile.mkdtemp(prefix=self.PACKAGE
|
|
159
|
+
temp_dir = pathlib.Path(tempfile.mkdtemp(prefix=self.PACKAGE, dir=parent_dir))
|
|
135
160
|
return temp_dir
|
|
136
161
|
|
|
137
162
|
PACKAGE = PACKAGE
|
r5py/util/environment.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
"""Normalise some environment variables that might not always get set."""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import pathlib
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# if a readthedocs runner uses a conda environment, it fails to
|
|
12
|
+
# properly initialise the JAVA_HOME and PROJ_LIB environment variables
|
|
13
|
+
#
|
|
14
|
+
# this might happen on other installation, so let’s keep this as general
|
|
15
|
+
# as possible.
|
|
16
|
+
#
|
|
17
|
+
# As readthedocs also does not export CONDA_PREFIX, we first reconstruct
|
|
18
|
+
# it from CONDA_ENVS_PATH and CONDA_DEFAULT_ENV
|
|
19
|
+
if (
|
|
20
|
+
"CONDA_PREFIX" not in os.environ
|
|
21
|
+
and "CONDA_DEFAULT_ENV" in os.environ
|
|
22
|
+
and "CONDA_ENVS_PATH" in os.environ
|
|
23
|
+
):
|
|
24
|
+
os.environ["CONDA_PREFIX"] = str(
|
|
25
|
+
pathlib.Path(os.environ["CONDA_ENVS_PATH"]) / os.environ["CONDA_DEFAULT_ENV"]
|
|
26
|
+
)
|
|
27
|
+
if "JAVA_HOME" not in os.environ and "CONDA_PREFIX" in os.environ:
|
|
28
|
+
os.environ["JAVA_HOME"] = str(
|
|
29
|
+
pathlib.Path(os.environ["CONDA_PREFIX"]) / "lib" / "jvm"
|
|
30
|
+
)
|
|
31
|
+
if "PROJ_LIB" not in os.environ and "CONDA_PREFIX" in os.environ:
|
|
32
|
+
os.environ["PROJ_LIB"] = str(
|
|
33
|
+
pathlib.Path(os.environ["CONDA_PREFIX"]) / "share" / "proj"
|
|
34
|
+
)
|
r5py/util/file_digest.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""Create a hash sum of a file."""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import hashlib
|
|
7
|
+
import pathlib
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
__all__ = ["FileDigest"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
BUFFER_SIZE = 64 * 1024
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FileDigest(str):
|
|
17
|
+
"""Create a hash sum of a file."""
|
|
18
|
+
|
|
19
|
+
def __new__(cls, input_file, digest="blake2s"):
|
|
20
|
+
"""
|
|
21
|
+
Create a hash sum of a file.
|
|
22
|
+
|
|
23
|
+
Arguments
|
|
24
|
+
---------
|
|
25
|
+
input_file : pathlib.Path | str
|
|
26
|
+
for which file to compute a hash digest
|
|
27
|
+
digest : str | func
|
|
28
|
+
name of hash algorithm (s.
|
|
29
|
+
https://docs.python.org/3/library/hashlib.html) or function that
|
|
30
|
+
returns a hash sum
|
|
31
|
+
"""
|
|
32
|
+
input_file = pathlib.Path(input_file)
|
|
33
|
+
try:
|
|
34
|
+
with input_file.open("rb") as f:
|
|
35
|
+
hashdigest = hashlib.file_digest(f, digest)
|
|
36
|
+
except AttributeError: # Python<=3.10
|
|
37
|
+
hashdigest = hashlib.new(digest)
|
|
38
|
+
with input_file.open("rb") as f:
|
|
39
|
+
while data := f.read(BUFFER_SIZE):
|
|
40
|
+
hashdigest.update(data)
|
|
41
|
+
|
|
42
|
+
return hashdigest.hexdigest()
|
|
@@ -10,6 +10,11 @@ import shapely
|
|
|
10
10
|
from .exceptions import UnexpectedCrsError
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
FALLBACK_CRS = 3857
|
|
14
|
+
DATUM_NAME = "WGS 84"
|
|
15
|
+
VERY_SMALL_BUFFER_SIZE = 0.001
|
|
16
|
+
|
|
17
|
+
|
|
13
18
|
class GoodEnoughEquidistantCrs(pyproj.CRS):
|
|
14
19
|
"""
|
|
15
20
|
Find the most appropriate UTM reference system for the current extent.
|
|
@@ -31,16 +36,15 @@ class GoodEnoughEquidistantCrs(pyproj.CRS):
|
|
|
31
36
|
The geographical extent for which to find an equidistant reference
|
|
32
37
|
system, in `EPSG:4326`
|
|
33
38
|
"""
|
|
34
|
-
|
|
35
39
|
if GoodEnoughEquidistantCrs._is_plausible_in_epsg4326(extent):
|
|
36
40
|
# default CRS in case we do not find any better match
|
|
37
|
-
crs = pyproj.CRS.from_epsg(
|
|
41
|
+
crs = pyproj.CRS.from_epsg(FALLBACK_CRS)
|
|
38
42
|
|
|
39
43
|
# buffer extent (so everything is a polygon)
|
|
40
|
-
extent = extent.buffer(
|
|
44
|
+
extent = extent.buffer(VERY_SMALL_BUFFER_SIZE)
|
|
41
45
|
|
|
42
46
|
crsinfo = pyproj.database.query_utm_crs_info(
|
|
43
|
-
datum_name=
|
|
47
|
+
datum_name=DATUM_NAME,
|
|
44
48
|
area_of_interest=pyproj.aoi.AreaOfInterest(*extent.bounds),
|
|
45
49
|
)
|
|
46
50
|
for candidate_crs in crsinfo:
|
r5py/util/memory_footprint.py
CHANGED
|
@@ -62,9 +62,9 @@ def _share_of_ram(share=0.8, leave_at_least=(2 * 1024**3)):
|
|
|
62
62
|
|
|
63
63
|
def _parse_value_and_unit(value_and_unit, max_unit_length=1):
|
|
64
64
|
"""
|
|
65
|
-
Extract value and unit from a string
|
|
66
|
-
(non-numeric) unit suffix.
|
|
65
|
+
Extract value and unit from a string.
|
|
67
66
|
|
|
67
|
+
The string is allowed to contain a (non-numeric) unit suffix.
|
|
68
68
|
For instance, input values of `'1M'` or `3.732G` would yield return
|
|
69
69
|
values `(1, 'M')` or `(3.732, 'G')`, respectively.
|
|
70
70
|
|
|
@@ -112,11 +112,10 @@ def _interpret_power_of_two_units(value, unit):
|
|
|
112
112
|
int:
|
|
113
113
|
interpreted value in bytes
|
|
114
114
|
"""
|
|
115
|
-
|
|
116
|
-
SUFFIXES = " KMGTPEZY"
|
|
117
115
|
# the position of each suffix in this string is the unit’s exponent
|
|
118
116
|
# over 1024.
|
|
119
117
|
# Compare https://en.wikipedia.org/wiki/ISO%2FIEC_80000#Part_13:_Information_science_and_technology
|
|
118
|
+
SUFFIXES = " KMGTPEZY"
|
|
120
119
|
|
|
121
120
|
if unit is None:
|
|
122
121
|
unit = " "
|
|
@@ -152,7 +151,6 @@ def _get_max_memory(max_memory):
|
|
|
152
151
|
int
|
|
153
152
|
Maximum amount of memory allocated for R5 in bytes.
|
|
154
153
|
"""
|
|
155
|
-
|
|
156
154
|
try:
|
|
157
155
|
value, unit = _parse_value_and_unit(max_memory)
|
|
158
156
|
except TypeError:
|
r5py/util/sample_data_set.py
CHANGED
|
@@ -20,11 +20,15 @@ class SampleDataSet(pathlib.Path):
|
|
|
20
20
|
|
|
21
21
|
# decide which kind of pathlib.Path we are (Windows, Unix, ...)
|
|
22
22
|
# cf. https://stackoverflow.com/a/66613346/463864
|
|
23
|
-
|
|
23
|
+
try:
|
|
24
|
+
_flavour = type(pathlib.Path())._flavour
|
|
25
|
+
except AttributeError: # Python>=3.13
|
|
26
|
+
pass
|
|
24
27
|
|
|
25
28
|
_CACHE_DIR = pathlib.Path(config.CACHE_DIR) / "sampledata"
|
|
26
29
|
|
|
27
30
|
def __new__(cls, remote_url, sha256_checksum):
|
|
31
|
+
"""Define a data set that is downloaded and cached on demand."""
|
|
28
32
|
# pathlib.Path does everything in __new__, rather than __init__
|
|
29
33
|
cached_path = cls._CACHE_DIR / pathlib.Path(remote_url).name
|
|
30
34
|
return super().__new__(cls, cached_path)
|
|
@@ -40,10 +44,16 @@ class SampleDataSet(pathlib.Path):
|
|
|
40
44
|
sha256_checksum : str
|
|
41
45
|
checksum for this data set, using an SHA256 algorithm
|
|
42
46
|
"""
|
|
43
|
-
|
|
47
|
+
cached_path = self._CACHE_DIR / pathlib.Path(remote_url).name
|
|
48
|
+
|
|
49
|
+
try: # Python>=3.12
|
|
50
|
+
super().__init__(cached_path)
|
|
51
|
+
except TypeError:
|
|
52
|
+
super().__init__()
|
|
53
|
+
|
|
44
54
|
self.remote_url = remote_url
|
|
45
55
|
self.checksum = sha256_checksum
|
|
46
|
-
self.cached_path =
|
|
56
|
+
self.cached_path = cached_path
|
|
47
57
|
self._download_remote_file()
|
|
48
58
|
|
|
49
59
|
def _download_remote_file(self):
|
|
@@ -60,7 +70,8 @@ class SampleDataSet(pathlib.Path):
|
|
|
60
70
|
RuntimeWarning,
|
|
61
71
|
)
|
|
62
72
|
self.cached_path.parent.mkdir(exist_ok=True)
|
|
63
|
-
with
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
with (
|
|
74
|
+
ValidatingRequestsSession() as session,
|
|
75
|
+
session.get(self.remote_url, self.checksum) as response,
|
|
76
|
+
):
|
|
66
77
|
self.cached_path.write_bytes(response.content)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
"""Assign a cluster label column to a point-geometry GeoDataFrame."""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
import warnings
|
|
8
|
+
|
|
9
|
+
import geopandas
|
|
10
|
+
import numpy
|
|
11
|
+
import shapely
|
|
12
|
+
import sklearn.cluster
|
|
13
|
+
|
|
14
|
+
from .good_enough_equidistant_crs import GoodEnoughEquidistantCrs
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = ["SpatiallyClusteredGeoDataFrame"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SpatiallyClusteredGeoDataFrame(geopandas.GeoDataFrame):
|
|
21
|
+
"""Assign a cluster label column to a point-geometry GeoDataFrame."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, data, *args, eps=200.0, min_cluster_size=3, **kwargs):
|
|
24
|
+
"""
|
|
25
|
+
Assign a cluster label column to a point-geometry GeoDataFrame.
|
|
26
|
+
|
|
27
|
+
Arguments:
|
|
28
|
+
----------
|
|
29
|
+
data : geopandas.GeoDataFrame
|
|
30
|
+
input data set
|
|
31
|
+
eps : int | float
|
|
32
|
+
EPS parameter to a DBSCAN cluster algorithm, the maximum
|
|
33
|
+
intra-cluster distance between two points
|
|
34
|
+
*args, **kwargs: passed to geopandas.GeoDataFrame.__init__()
|
|
35
|
+
"""
|
|
36
|
+
geopandas.GeoDataFrame.__init__(self, *args, **kwargs)
|
|
37
|
+
|
|
38
|
+
EQUIDISTANT_CRS = GoodEnoughEquidistantCrs(
|
|
39
|
+
shapely.box(*data.to_crs("EPSG:4326").geometry.total_bounds)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# loosely based on:
|
|
43
|
+
# https://github.com/geopandas/scipy2018-geospatial-data/blob/master/08-clustering.ipynb
|
|
44
|
+
|
|
45
|
+
coordinates = numpy.vstack(
|
|
46
|
+
data.to_crs(EQUIDISTANT_CRS)["geometry"]
|
|
47
|
+
.apply(lambda geometry: numpy.hstack(geometry.xy))
|
|
48
|
+
.values
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
with warnings.catch_warnings():
|
|
52
|
+
warnings.filterwarnings(
|
|
53
|
+
"ignore",
|
|
54
|
+
"Could not find the number of physical cores",
|
|
55
|
+
category=UserWarning,
|
|
56
|
+
)
|
|
57
|
+
data["cluster"] = (
|
|
58
|
+
sklearn.cluster.DBSCAN(
|
|
59
|
+
eps=eps,
|
|
60
|
+
min_samples=min_cluster_size,
|
|
61
|
+
n_jobs=-1,
|
|
62
|
+
)
|
|
63
|
+
.fit(coordinates)
|
|
64
|
+
.labels_
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
with warnings.catch_warnings():
|
|
68
|
+
warnings.filterwarnings(
|
|
69
|
+
"ignore",
|
|
70
|
+
message=(
|
|
71
|
+
"You are adding a column named 'geometry' to a GeoDataFrame "
|
|
72
|
+
"constructed without an active geometry column"
|
|
73
|
+
),
|
|
74
|
+
category=FutureWarning,
|
|
75
|
+
)
|
|
76
|
+
for column in data.columns:
|
|
77
|
+
self[column] = data[column]
|
|
78
|
+
self.set_geometry("geometry")
|
|
@@ -30,12 +30,12 @@ class ValidatingRequestsSession(requests.Session):
|
|
|
30
30
|
self._algorithm = checksum_algorithm
|
|
31
31
|
|
|
32
32
|
def get(self, url, checksum, **kwargs):
|
|
33
|
-
"""
|
|
33
|
+
"""Send a GET request, tests checksum."""
|
|
34
34
|
kwargs.setdefault("allow_redirects", True)
|
|
35
35
|
return self.request("GET", url, checksum, **kwargs)
|
|
36
36
|
|
|
37
37
|
def post(self, url, checksum, **kwargs):
|
|
38
|
-
"""
|
|
38
|
+
"""Send a POST request, tests checksum."""
|
|
39
39
|
return self.request("POST", url, checksum, **kwargs)
|
|
40
40
|
|
|
41
41
|
# delete, put, head don’t return data,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""Create a copy or link of an input file in a cache directory."""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import filelock
|
|
7
|
+
import pathlib
|
|
8
|
+
import shutil
|
|
9
|
+
|
|
10
|
+
from .config import Config
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
__all__ = ["WorkingCopy"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class WorkingCopy(pathlib.Path):
|
|
17
|
+
"""Create a copy or link of an input file in a cache directory."""
|
|
18
|
+
|
|
19
|
+
def __new__(cls, path):
|
|
20
|
+
"""
|
|
21
|
+
Create a copy or link of an input file in a cache directory.
|
|
22
|
+
|
|
23
|
+
This exists because R5 creates temporary files in the directory of input
|
|
24
|
+
files. This can not only be annoying clutter, but also create problems
|
|
25
|
+
of concurrency, performance, etc., for instance, when the data comes
|
|
26
|
+
from a shared network drive or a read-only file system.
|
|
27
|
+
|
|
28
|
+
Arguments
|
|
29
|
+
---------
|
|
30
|
+
path : str or pathlib.Path
|
|
31
|
+
The file to create a copy or link of in a cache directory
|
|
32
|
+
"""
|
|
33
|
+
# try to first create a symbolic link, if that fails (e.g., on Windows),
|
|
34
|
+
# copy the file to a cache directory
|
|
35
|
+
path = pathlib.Path(path).absolute()
|
|
36
|
+
destination = pathlib.Path(Config().CACHE_DIR / path.name).absolute()
|
|
37
|
+
|
|
38
|
+
with filelock.FileLock(destination.parent / f"{destination.name}.lock"):
|
|
39
|
+
if not destination.exists():
|
|
40
|
+
try:
|
|
41
|
+
destination.symlink_to(path)
|
|
42
|
+
except OSError:
|
|
43
|
+
shutil.copyfile(f"{path}", f"{destination}")
|
|
44
|
+
return destination
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: r5py
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: Python wrapper for the R5 routing analysis engine
|
|
5
|
-
Author: Willem Klumpenhouwer, Marcus Sairava, Rafael Pereira, Henrikki Tenkanen
|
|
6
|
-
Author-email: Christoph Fink <christoph.fink@helsinki.fi>
|
|
5
|
+
Author: Christoph Fink, Willem Klumpenhouwer, Marcus Sairava, Rafael Pereira, Henrikki Tenkanen
|
|
7
6
|
License: GPL-3.0-or-later or MIT
|
|
8
7
|
Project-URL: Documentation, https://r5py.readthedocs.org/
|
|
9
8
|
Project-URL: Repository, https://github.com/r5py/r5py.git
|
|
@@ -19,43 +18,45 @@ Description-Content-Type: text/markdown
|
|
|
19
18
|
License-File: LICENSE
|
|
20
19
|
Requires-Dist: ConfigArgParse
|
|
21
20
|
Requires-Dist: filelock
|
|
22
|
-
Requires-Dist:
|
|
21
|
+
Requires-Dist: geohexgrid
|
|
23
22
|
Requires-Dist: geopandas
|
|
24
|
-
Requires-Dist: importlib-resources
|
|
25
23
|
Requires-Dist: joblib
|
|
26
24
|
Requires-Dist: jpype1
|
|
27
25
|
Requires-Dist: numpy
|
|
28
|
-
Requires-Dist: pandas
|
|
26
|
+
Requires-Dist: pandas>=2.1.0
|
|
29
27
|
Requires-Dist: psutil
|
|
30
28
|
Requires-Dist: pyproj
|
|
31
29
|
Requires-Dist: requests
|
|
32
|
-
Requires-Dist:
|
|
30
|
+
Requires-Dist: scikit-learn
|
|
31
|
+
Requires-Dist: shapely>=2.0
|
|
32
|
+
Requires-Dist: simplification
|
|
33
33
|
Provides-Extra: docs
|
|
34
|
-
Requires-Dist: contextily
|
|
35
|
-
Requires-Dist: folium
|
|
36
|
-
Requires-Dist: GitPython
|
|
37
|
-
Requires-Dist: h3
|
|
38
|
-
Requires-Dist:
|
|
39
|
-
Requires-Dist: mapclassify
|
|
40
|
-
Requires-Dist: matplotlib
|
|
41
|
-
Requires-Dist: myst-nb
|
|
42
|
-
Requires-Dist: nbsphinx
|
|
43
|
-
Requires-Dist: pybtex-apa7-style
|
|
44
|
-
Requires-Dist: r5py.sampledata.helsinki
|
|
45
|
-
Requires-Dist: r5py.sampledata.
|
|
46
|
-
Requires-Dist: shapely
|
|
47
|
-
Requires-Dist: sphinx
|
|
48
|
-
Requires-Dist: sphinx-book-theme
|
|
49
|
-
Requires-Dist: sphinx-design
|
|
50
|
-
Requires-Dist: sphinxcontrib-bibtex
|
|
51
|
-
Requires-Dist: sphinxcontrib-images
|
|
34
|
+
Requires-Dist: contextily; extra == "docs"
|
|
35
|
+
Requires-Dist: folium; extra == "docs"
|
|
36
|
+
Requires-Dist: GitPython; extra == "docs"
|
|
37
|
+
Requires-Dist: h3>=4.0.0b2; extra == "docs"
|
|
38
|
+
Requires-Dist: jupyterlab_myst; extra == "docs"
|
|
39
|
+
Requires-Dist: mapclassify; extra == "docs"
|
|
40
|
+
Requires-Dist: matplotlib; extra == "docs"
|
|
41
|
+
Requires-Dist: myst-nb; extra == "docs"
|
|
42
|
+
Requires-Dist: nbsphinx; extra == "docs"
|
|
43
|
+
Requires-Dist: pybtex-apa7-style; extra == "docs"
|
|
44
|
+
Requires-Dist: r5py.sampledata.helsinki>=0.1.1; extra == "docs"
|
|
45
|
+
Requires-Dist: r5py.sampledata.sao_paulo>=0.1.1; extra == "docs"
|
|
46
|
+
Requires-Dist: shapely; extra == "docs"
|
|
47
|
+
Requires-Dist: sphinx; extra == "docs"
|
|
48
|
+
Requires-Dist: sphinx-book-theme; extra == "docs"
|
|
49
|
+
Requires-Dist: sphinx-design; extra == "docs"
|
|
50
|
+
Requires-Dist: sphinxcontrib-bibtex; extra == "docs"
|
|
51
|
+
Requires-Dist: sphinxcontrib-images; extra == "docs"
|
|
52
52
|
Provides-Extra: tests
|
|
53
|
-
Requires-Dist:
|
|
54
|
-
Requires-Dist: pytest
|
|
55
|
-
Requires-Dist: pytest-cov
|
|
56
|
-
Requires-Dist: pytest-lazy-
|
|
57
|
-
Requires-Dist: r5py.sampledata.helsinki
|
|
58
|
-
Requires-Dist: r5py.sampledata.
|
|
53
|
+
Requires-Dist: pyarrow; extra == "tests"
|
|
54
|
+
Requires-Dist: pytest; extra == "tests"
|
|
55
|
+
Requires-Dist: pytest-cov; extra == "tests"
|
|
56
|
+
Requires-Dist: pytest-lazy-fixtures; extra == "tests"
|
|
57
|
+
Requires-Dist: r5py.sampledata.helsinki>=0.1.1; extra == "tests"
|
|
58
|
+
Requires-Dist: r5py.sampledata.sao_paulo>=0.1.1; extra == "tests"
|
|
59
|
+
Requires-Dist: typing-extensions; extra == "tests"
|
|
59
60
|
|
|
60
61
|
<img class="r5py_logo" align="right" src="https://github.com/r5py/r5py/raw/main/docs/_static/images/r5py_blue.svg" alt="r5py logo" style="width:180px; max-width:30vW;">
|
|
61
62
|
|
|
@@ -155,7 +156,7 @@ your project better.
|
|
|
155
156
|
<!-- (2) other links -->
|
|
156
157
|
[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
|
|
157
158
|
[conveyal]: https://www.conveyal.com/
|
|
158
|
-
[env-file]: https://github.com/r5py/r5py/blob/main/ci/
|
|
159
|
+
[env-file]: https://github.com/r5py/r5py/blob/main/ci/r5py.yaml
|
|
159
160
|
[geopandas]: https://geopandas.org/
|
|
160
161
|
[r5-github]: https://github.com/conveyal/r5/
|
|
161
162
|
[r5r-github]: https://github.com/ipeaGIT/r5r/
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
r5py/__init__.py,sha256=j6T-4NRTg_w9P_RloJOBLuc1L2ZnERHJzsqbeM5M-cc,546
|
|
2
|
+
r5py/__main__.py,sha256=Wvn0ChD7E-dCSZ8b8k_HhHG0KMOk0qMNFkijGuSH3-0,81
|
|
3
|
+
r5py/r5/__init__.py,sha256=6IQpvStxKeNxflizfRWh05USpdM18KBSB3UZ8Z_cGY4,1124
|
|
4
|
+
r5py/r5/access_leg.py,sha256=W3GfPEpqmWD1c4xipd6UcVIaBC-yb6srGCZV30E2dPY,293
|
|
5
|
+
r5py/r5/base_travel_time_matrix.py,sha256=Vl82Wkk2iANNy6L3r937yXNnQ9lmMOErGT_-fQnb1Ms,6978
|
|
6
|
+
r5py/r5/breakdown_stat.py,sha256=ZQkWA0hXlcRH3KVgtxPSNHP0FUDri8MWqdFk8EUdDMU,533
|
|
7
|
+
r5py/r5/detailed_itineraries.py,sha256=Oo8JnF5jM2FsYFR1ma9r4y3evOmU7itDYs5M4vbqrZo,11245
|
|
8
|
+
r5py/r5/direct_leg.py,sha256=T7wX8puhOVIssCpflXthYs-G9OA8pasFbdz9p8k8teg,1054
|
|
9
|
+
r5py/r5/egress_leg.py,sha256=9rsCIcwlZUzoZE6q4imNY3VWpjJepO1IJvheVrlPi90,297
|
|
10
|
+
r5py/r5/isochrones.py,sha256=NNpV3Df4NeLdDksGERkb2Eos33ziMBGEeaWyCKPt5P8,12974
|
|
11
|
+
r5py/r5/regional_task.py,sha256=wTNx2NT3-GCEvDyz0e-_YYkVWtpE66dg2IlXTA1gI-4,23234
|
|
12
|
+
r5py/r5/scenario.py,sha256=nUNAlN3cO7E_b4sMpNqdL0FD7WQaQ49iIvh-k8l4YRM,763
|
|
13
|
+
r5py/r5/street_layer.py,sha256=2AWhIE0-aTNGQenX6bF1xv5bmhR_LV0CgqM4BKgVYfk,2329
|
|
14
|
+
r5py/r5/street_segment.py,sha256=0O0QV8Eyfss-xHJShKGSQV1IusZfTrrxzu_AWl3KACo,1109
|
|
15
|
+
r5py/r5/transfer_leg.py,sha256=_IpzQJAyW4hDPO5V4k-ZjIPd3uyxhHPa4U6_b8UbKt4,311
|
|
16
|
+
r5py/r5/transit_layer.py,sha256=vVo_o10yDCzpujOQ99xdzmznwVjAbANjdDflQy2QOpI,3223
|
|
17
|
+
r5py/r5/transit_leg.py,sha256=R0Qc9YLMEXYu51NIdo7Q0bdmpYIJf5irEDXWrW6pZWE,221
|
|
18
|
+
r5py/r5/transport_mode.py,sha256=zHSqXb0R4oyjTp069CzO69IgoCKt0nmOAwsSy272rGo,3675
|
|
19
|
+
r5py/r5/transport_network.py,sha256=wy7jsIqcladee6FdUiOu0kJgMhGYymtJpfyjX9RRnNU,7925
|
|
20
|
+
r5py/r5/travel_time_matrix.py,sha256=Z_ErylB8mMD_eO2BogV3K_OFdYFVCcmIPmcMe7GGRiU,8003
|
|
21
|
+
r5py/r5/trip.py,sha256=AqhlhgYaGRL5jVzV08BhsqgWxe8f4wAb5HMP8HIGwc8,2944
|
|
22
|
+
r5py/r5/trip_leg.py,sha256=9E4vZpBEJCXIVqAXWJvnPloC-upEASKhFnjiuen8i8A,6495
|
|
23
|
+
r5py/r5/trip_planner.py,sha256=QM3kSx-EC1VWtmReMtwxzG0CdbtlRi3-PpLxOAoHGrA,23754
|
|
24
|
+
r5py/util/__init__.py,sha256=3iqzebRt7RE1TMzzuGNzyXca0SBcBx1rHLs8eW3ijo4,940
|
|
25
|
+
r5py/util/camel_to_snake_case.py,sha256=zj5F3PNBvsuS6vqN4USeeo8NI-3hnscGhwun0G95AK0,673
|
|
26
|
+
r5py/util/classpath.py,sha256=b16xL94pDxTpc0vrf68R1nvZHnHqZXGcFJaN36eW3wc,2773
|
|
27
|
+
r5py/util/config.py,sha256=5jz42iUaftgBfJ2HNnktZw5oXIPE2ytl3Nxt2RjjDoM,5267
|
|
28
|
+
r5py/util/contains_gtfs_data.py,sha256=ooX4hfVDKK0aqX1MI46jSFZ7dZ6riyXaORrgF6PUFrk,1211
|
|
29
|
+
r5py/util/data_validation.py,sha256=H5Mcp2nS4vu5RKym20mPnGpl-8d0SDchzDRJBrrL6WE,1039
|
|
30
|
+
r5py/util/environment.py,sha256=cbSM8TKTuhbXsTIIB06pMtydBOiqLkitF2Lj2asVTho,1082
|
|
31
|
+
r5py/util/exceptions.py,sha256=r65XUg_AJ_bTw8ARNj7A2-GbFZlSTrOAjDynx1pSD2Y,1049
|
|
32
|
+
r5py/util/file_digest.py,sha256=95UbaxbTZLa54j1CupsKria028xZ8f6ueZsTupnjlYE,1061
|
|
33
|
+
r5py/util/good_enough_equidistant_crs.py,sha256=7FX3Ly3qegSV_YRA4OFk49LC29xUyTte1Gc5qOEi_9E,2458
|
|
34
|
+
r5py/util/jvm.py,sha256=NCwoYLDznXydcIRAZl2kzUQA6D6NCvzjVG74pm6ioR0,5027
|
|
35
|
+
r5py/util/memory_footprint.py,sha256=p8efCUs4UXRg6P1GrRxVs71m7SpEw2mASoz6PVTRvgQ,4672
|
|
36
|
+
r5py/util/parse_int_date.py,sha256=JmnV8TwdUdUp3kSp2e73ZSxCbRyqv2FmQzNt0I_MsM0,667
|
|
37
|
+
r5py/util/sample_data_set.py,sha256=aqUCx6drWD-WbCauewO4EzgOGnFr35mAZt-YHlqb92k,2463
|
|
38
|
+
r5py/util/snake_to_camel_case.py,sha256=uJ5hTCVDUEmIxTyy4LGFTbpGC_rtnjDZVQ2vmVRTQ4k,485
|
|
39
|
+
r5py/util/spatially_clustered_geodataframe.py,sha256=FxG8V3SSeK-PuCep565p1b3TNcl8oCkY764tk1L0sKM,2410
|
|
40
|
+
r5py/util/validating_requests_session.py,sha256=sH5FgpS9eGax5DG2qA2GrGuiwgTJgh8tKsZ9OiXKmvk,1807
|
|
41
|
+
r5py/util/warnings.py,sha256=CvxKWKlNO_p3riB4SkNqbU5AGPsaY_3-OzqaBObE3B8,139
|
|
42
|
+
r5py/util/working_copy.py,sha256=sbLbRCi39LtC-0tXxvh2y7ZN2D15chbhleCZXzHAFSc,1432
|
|
43
|
+
r5py-1.0.0.dist-info/LICENSE,sha256=VAnuGDX1TPylSN9G2xLa-urDpj_SQwn-qqs068dx4tk,51
|
|
44
|
+
r5py-1.0.0.dist-info/METADATA,sha256=yWDmg1mjGHQHj4SveWnYfuI4E3TWvo43Gug4L1hwFno,9965
|
|
45
|
+
r5py-1.0.0.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
|
46
|
+
r5py-1.0.0.dist-info/top_level.txt,sha256=fOH1R85dkNDOI7jkg-lIsl5CQIO4fE5X868K9dTqs9U,5
|
|
47
|
+
r5py-1.0.0.dist-info/RECORD,,
|