ObjectNat 1.1.0__py3-none-any.whl → 1.2.1__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 ObjectNat might be problematic. Click here for more details.
- objectnat/__init__.py +9 -13
- objectnat/_api.py +14 -13
- objectnat/_config.py +47 -47
- objectnat/_version.py +1 -1
- objectnat/methods/coverage_zones/__init__.py +3 -3
- objectnat/methods/coverage_zones/graph_coverage.py +98 -108
- objectnat/methods/coverage_zones/radius_voronoi_coverage.py +37 -45
- objectnat/methods/coverage_zones/stepped_coverage.py +126 -142
- objectnat/methods/isochrones/__init__.py +1 -1
- objectnat/methods/isochrones/isochrone_utils.py +167 -167
- objectnat/methods/isochrones/isochrones.py +262 -299
- objectnat/methods/noise/__init__.py +3 -3
- objectnat/methods/noise/noise_init_data.py +10 -10
- objectnat/methods/noise/noise_reduce.py +155 -155
- objectnat/methods/noise/{noise_sim.py → noise_simulation.py} +452 -448
- objectnat/methods/noise/noise_simulation_simplified.py +209 -0
- objectnat/methods/point_clustering/__init__.py +1 -1
- objectnat/methods/point_clustering/cluster_points_in_polygons.py +115 -116
- objectnat/methods/provision/__init__.py +1 -1
- objectnat/methods/provision/provision.py +117 -110
- objectnat/methods/provision/provision_exceptions.py +59 -59
- objectnat/methods/provision/provision_model.py +337 -337
- objectnat/methods/utils/__init__.py +1 -0
- objectnat/methods/utils/geom_utils.py +173 -130
- objectnat/methods/utils/graph_utils.py +306 -206
- objectnat/methods/utils/math_utils.py +32 -32
- objectnat/methods/visibility/__init__.py +6 -6
- objectnat/methods/visibility/visibility_analysis.py +470 -511
- {objectnat-1.1.0.dist-info → objectnat-1.2.1.dist-info}/LICENSE.txt +28 -28
- objectnat-1.2.1.dist-info/METADATA +115 -0
- objectnat-1.2.1.dist-info/RECORD +33 -0
- objectnat/methods/noise/noise_exceptions.py +0 -14
- objectnat-1.1.0.dist-info/METADATA +0 -148
- objectnat-1.1.0.dist-info/RECORD +0 -33
- {objectnat-1.1.0.dist-info → objectnat-1.2.1.dist-info}/WHEEL +0 -0
|
@@ -1,110 +1,117 @@
|
|
|
1
|
-
from typing import Tuple
|
|
2
|
-
|
|
3
|
-
import geopandas as gpd
|
|
4
|
-
import numpy as np
|
|
5
|
-
import pandas as pd
|
|
6
|
-
|
|
7
|
-
from objectnat import config
|
|
8
|
-
|
|
9
|
-
from .provision_model import Provision
|
|
10
|
-
|
|
11
|
-
logger = config.logger
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def get_service_provision(
|
|
15
|
-
buildings: gpd.GeoDataFrame,
|
|
16
|
-
adjacency_matrix: pd.DataFrame,
|
|
17
|
-
services: gpd.GeoDataFrame,
|
|
18
|
-
threshold: int,
|
|
19
|
-
buildings_demand_column: str = "demand",
|
|
20
|
-
services_capacity_column: str = "capacity",
|
|
21
|
-
) -> Tuple[gpd.GeoDataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame]:
|
|
22
|
-
"""Calculate load from buildings with demands on the given services using the distances matrix between them.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
services (gpd.GeoDataFrame):
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
1
|
+
from typing import Tuple
|
|
2
|
+
|
|
3
|
+
import geopandas as gpd
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from objectnat import config
|
|
8
|
+
|
|
9
|
+
from .provision_model import Provision
|
|
10
|
+
|
|
11
|
+
logger = config.logger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_service_provision(
|
|
15
|
+
buildings: gpd.GeoDataFrame,
|
|
16
|
+
adjacency_matrix: pd.DataFrame,
|
|
17
|
+
services: gpd.GeoDataFrame,
|
|
18
|
+
threshold: int,
|
|
19
|
+
buildings_demand_column: str = "demand",
|
|
20
|
+
services_capacity_column: str = "capacity",
|
|
21
|
+
) -> Tuple[gpd.GeoDataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame]:
|
|
22
|
+
"""Calculate load from buildings with demands on the given services using the distances matrix between them.
|
|
23
|
+
|
|
24
|
+
Parameters:
|
|
25
|
+
services (gpd.GeoDataFrame):
|
|
26
|
+
GeoDataFrame of services
|
|
27
|
+
adjacency_matrix (pd.DataFrame):
|
|
28
|
+
DataFrame representing the adjacency matrix
|
|
29
|
+
buildings (gpd.GeoDataFrame):
|
|
30
|
+
GeoDataFrame of demanded buildings
|
|
31
|
+
threshold (int):
|
|
32
|
+
Threshold value
|
|
33
|
+
buildings_demand_column (str):
|
|
34
|
+
column name of buildings demands
|
|
35
|
+
services_capacity_column (str):
|
|
36
|
+
column name of services capacity
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
(Tuple[gpd.GeoDataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame]): Tuple of GeoDataFrames representing provision
|
|
40
|
+
buildings, provision services, and provision links
|
|
41
|
+
"""
|
|
42
|
+
buildings = buildings.copy()
|
|
43
|
+
services = services.copy()
|
|
44
|
+
adjacency_matrix = adjacency_matrix.copy()
|
|
45
|
+
buildings["demand"] = buildings[buildings_demand_column]
|
|
46
|
+
services["capacity"] = services[services_capacity_column]
|
|
47
|
+
|
|
48
|
+
provision_buildings, provision_services, provision_links = Provision(
|
|
49
|
+
services=services,
|
|
50
|
+
demanded_buildings=buildings,
|
|
51
|
+
adjacency_matrix=adjacency_matrix,
|
|
52
|
+
threshold=threshold,
|
|
53
|
+
).run()
|
|
54
|
+
return provision_buildings, provision_services, provision_links
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def clip_provision(
|
|
58
|
+
buildings: gpd.GeoDataFrame, services: gpd.GeoDataFrame, links: gpd.GeoDataFrame, selection_zone: gpd.GeoDataFrame
|
|
59
|
+
) -> Tuple[gpd.GeoDataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame]:
|
|
60
|
+
|
|
61
|
+
assert selection_zone.crs == buildings.crs == services.crs == links.crs, (
|
|
62
|
+
f"CRS mismatch: buildings_crs:{buildings.crs}, "
|
|
63
|
+
f"links_crs:{links.crs} , "
|
|
64
|
+
f"services_crs:{services.crs}, "
|
|
65
|
+
f"selection_zone_crs:{selection_zone.crs}"
|
|
66
|
+
)
|
|
67
|
+
buildings = buildings.copy()
|
|
68
|
+
links = links.copy()
|
|
69
|
+
services = services.copy()
|
|
70
|
+
|
|
71
|
+
s = buildings.intersects(selection_zone.union_all())
|
|
72
|
+
buildings = buildings.loc[s[s].index]
|
|
73
|
+
links = links[links["building_index"].isin(buildings.index.tolist())]
|
|
74
|
+
services_to_keep = set(links["service_index"].tolist())
|
|
75
|
+
services.drop(list(set(services.index.tolist()) - services_to_keep), inplace=True)
|
|
76
|
+
return buildings, services, links
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def recalculate_links(
|
|
80
|
+
buildings: gpd.GeoDataFrame, services: gpd.GeoDataFrame, links: gpd.GeoDataFrame, new_max_dist: float
|
|
81
|
+
) -> tuple[gpd.GeoDataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame]:
|
|
82
|
+
buildings = buildings.copy()
|
|
83
|
+
services = services.copy()
|
|
84
|
+
links = links.copy()
|
|
85
|
+
|
|
86
|
+
links_to_recalculate = links[links["distance"] > new_max_dist]
|
|
87
|
+
if len(links_to_recalculate) == 0:
|
|
88
|
+
logger.warning("To clip distance exceeds max links distance, returning full provision")
|
|
89
|
+
return buildings, services, links
|
|
90
|
+
|
|
91
|
+
links_to_keep = links[links["distance"] <= new_max_dist]
|
|
92
|
+
free_demand = links_to_recalculate.groupby("building_index").agg({"demand": list, "distance": list})
|
|
93
|
+
free_demand["distance"] = free_demand.apply(
|
|
94
|
+
lambda x: sum((x1 * x2) for x1, x2 in zip(x.demand, x.distance)), axis=1
|
|
95
|
+
)
|
|
96
|
+
free_demand["demand"] = free_demand["demand"].apply(sum)
|
|
97
|
+
free_demand = free_demand.reindex(buildings.index, fill_value=0)
|
|
98
|
+
new_sum_time = (buildings["supplied_demands_within"] + buildings["supplied_demands_without"]) * buildings[
|
|
99
|
+
"avg_dist"
|
|
100
|
+
] - free_demand["distance"]
|
|
101
|
+
|
|
102
|
+
buildings["demand_left"] = buildings["demand_left"] + free_demand["demand"]
|
|
103
|
+
buildings["supplied_demands_without"] = buildings["supplied_demands_without"] - free_demand["demand"]
|
|
104
|
+
buildings["avg_dist"] = new_sum_time / (
|
|
105
|
+
buildings["supplied_demands_without"] + buildings["supplied_demands_within"]
|
|
106
|
+
)
|
|
107
|
+
buildings["avg_dist"] = buildings.apply(
|
|
108
|
+
lambda x: np.nan if (x["demand"] == x["demand_left"]) else round(x["avg_dist"], 2), axis=1
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
free_capacity = links_to_recalculate.groupby("service_index").agg({"demand": "sum"})
|
|
112
|
+
free_capacity = free_capacity.reindex(services.index, fill_value=0)
|
|
113
|
+
services["capacity_left"] = services["capacity_left"] + free_capacity["demand"]
|
|
114
|
+
services["carried_capacity_without"] = services["carried_capacity_without"] - free_capacity["demand"]
|
|
115
|
+
services["service_load"] = services["service_load"] - free_capacity["demand"]
|
|
116
|
+
|
|
117
|
+
return buildings, services, links_to_keep
|
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
class CapacityKeyError(KeyError):
|
|
2
|
-
def __init__(self, *args):
|
|
3
|
-
if args:
|
|
4
|
-
self.message = args[0]
|
|
5
|
-
else:
|
|
6
|
-
self.message = None
|
|
7
|
-
|
|
8
|
-
def __str__(self):
|
|
9
|
-
if self.message:
|
|
10
|
-
return f"CapacityKeyError, {self.message} "
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
"Column 'capacity' was not found in provided 'services' GeoDataFrame. This attribute "
|
|
14
|
-
"corresponds to the total capacity for each service."
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class CapacityValueError(ValueError):
|
|
19
|
-
def __init__(self, *args):
|
|
20
|
-
if args:
|
|
21
|
-
self.message = args[0]
|
|
22
|
-
else:
|
|
23
|
-
self.message = None
|
|
24
|
-
|
|
25
|
-
def __str__(self):
|
|
26
|
-
if self.message:
|
|
27
|
-
return f"CapacityValueError, {self.message} "
|
|
28
|
-
|
|
29
|
-
return "Column 'capacity' in 'services' GeoDataFrame has no valid value."
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class DemandKeyError(KeyError):
|
|
33
|
-
def __init__(self, *args):
|
|
34
|
-
if args:
|
|
35
|
-
self.message = args[0]
|
|
36
|
-
else:
|
|
37
|
-
self.message = None
|
|
38
|
-
|
|
39
|
-
def __str__(self):
|
|
40
|
-
if self.message:
|
|
41
|
-
return f"DemandKeyError, {self.message} "
|
|
42
|
-
|
|
43
|
-
return (
|
|
44
|
-
"The column 'demand' was not found in the provided 'demanded_buildings' GeoDataFrame. "
|
|
45
|
-
"This attribute corresponds to the number of demands for the selected service in each building."
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class DemandValueError(ValueError):
|
|
50
|
-
def __init__(self, *args):
|
|
51
|
-
if args:
|
|
52
|
-
self.message = args[0]
|
|
53
|
-
else:
|
|
54
|
-
self.message = None
|
|
55
|
-
|
|
56
|
-
def __str__(self):
|
|
57
|
-
if self.message:
|
|
58
|
-
return f"DemandValueError, {self.message} "
|
|
59
|
-
return "Column 'demand' in 'demanded_buildings' GeoDataFrame has no valid value."
|
|
1
|
+
class CapacityKeyError(KeyError):
|
|
2
|
+
def __init__(self, *args):
|
|
3
|
+
if args:
|
|
4
|
+
self.message = args[0]
|
|
5
|
+
else:
|
|
6
|
+
self.message = None
|
|
7
|
+
|
|
8
|
+
def __str__(self):
|
|
9
|
+
if self.message:
|
|
10
|
+
return f"CapacityKeyError, {self.message} "
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
"Column 'capacity' was not found in provided 'services' GeoDataFrame. This attribute "
|
|
14
|
+
"corresponds to the total capacity for each service."
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CapacityValueError(ValueError):
|
|
19
|
+
def __init__(self, *args):
|
|
20
|
+
if args:
|
|
21
|
+
self.message = args[0]
|
|
22
|
+
else:
|
|
23
|
+
self.message = None
|
|
24
|
+
|
|
25
|
+
def __str__(self):
|
|
26
|
+
if self.message:
|
|
27
|
+
return f"CapacityValueError, {self.message} "
|
|
28
|
+
|
|
29
|
+
return "Column 'capacity' in 'services' GeoDataFrame has no valid value."
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class DemandKeyError(KeyError):
|
|
33
|
+
def __init__(self, *args):
|
|
34
|
+
if args:
|
|
35
|
+
self.message = args[0]
|
|
36
|
+
else:
|
|
37
|
+
self.message = None
|
|
38
|
+
|
|
39
|
+
def __str__(self):
|
|
40
|
+
if self.message:
|
|
41
|
+
return f"DemandKeyError, {self.message} "
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
"The column 'demand' was not found in the provided 'demanded_buildings' GeoDataFrame. "
|
|
45
|
+
"This attribute corresponds to the number of demands for the selected service in each building."
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class DemandValueError(ValueError):
|
|
50
|
+
def __init__(self, *args):
|
|
51
|
+
if args:
|
|
52
|
+
self.message = args[0]
|
|
53
|
+
else:
|
|
54
|
+
self.message = None
|
|
55
|
+
|
|
56
|
+
def __str__(self):
|
|
57
|
+
if self.message:
|
|
58
|
+
return f"DemandValueError, {self.message} "
|
|
59
|
+
return "Column 'demand' in 'demanded_buildings' GeoDataFrame has no valid value."
|