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.

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