ObjectNat 0.2.7__py3-none-any.whl → 1.0.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 (31) hide show
  1. objectnat/_api.py +5 -8
  2. objectnat/_config.py +0 -24
  3. objectnat/_version.py +1 -1
  4. objectnat/methods/coverage_zones/__init__.py +2 -0
  5. objectnat/methods/coverage_zones/graph_coverage.py +118 -0
  6. objectnat/methods/coverage_zones/radius_voronoi.py +45 -0
  7. objectnat/methods/isochrones/__init__.py +1 -0
  8. objectnat/methods/isochrones/isochrone_utils.py +130 -0
  9. objectnat/methods/isochrones/isochrones.py +325 -0
  10. objectnat/methods/noise/__init__.py +2 -2
  11. objectnat/methods/noise/noise_sim.py +14 -9
  12. objectnat/methods/point_clustering/__init__.py +1 -0
  13. objectnat/methods/{cluster_points_in_polygons.py → point_clustering/cluster_points_in_polygons.py} +22 -28
  14. objectnat/methods/provision/__init__.py +1 -0
  15. objectnat/methods/provision/provision.py +4 -4
  16. objectnat/methods/provision/provision_model.py +17 -18
  17. objectnat/methods/utils/geom_utils.py +54 -3
  18. objectnat/methods/utils/graph_utils.py +127 -0
  19. objectnat/methods/utils/math_utils.py +32 -0
  20. objectnat/methods/visibility/__init__.py +6 -0
  21. objectnat/methods/{visibility_analysis.py → visibility/visibility_analysis.py} +167 -208
  22. objectnat-1.0.1.dist-info/METADATA +142 -0
  23. objectnat-1.0.1.dist-info/RECORD +32 -0
  24. objectnat/methods/balanced_buildings.py +0 -69
  25. objectnat/methods/coverage_zones.py +0 -90
  26. objectnat/methods/isochrones.py +0 -143
  27. objectnat/methods/living_buildings_osm.py +0 -168
  28. objectnat-0.2.7.dist-info/METADATA +0 -118
  29. objectnat-0.2.7.dist-info/RECORD +0 -26
  30. {objectnat-0.2.7.dist-info → objectnat-1.0.1.dist-info}/LICENSE.txt +0 -0
  31. {objectnat-0.2.7.dist-info → objectnat-1.0.1.dist-info}/WHEEL +0 -0
@@ -1,7 +1,7 @@
1
1
  import math
2
2
 
3
3
  import geopandas as gpd
4
- from shapely import LineString, MultiLineString, MultiPolygon, Point, Polygon
4
+ from shapely import LineString, MultiPolygon, Point, Polygon
5
5
  from shapely.ops import polygonize, unary_union
6
6
 
7
7
  from objectnat import config
@@ -10,6 +10,9 @@ logger = config.logger
10
10
 
11
11
 
12
12
  def polygons_to_multilinestring(geom: Polygon | MultiPolygon):
13
+ # pylint: disable-next=redefined-outer-name,reimported,import-outside-toplevel
14
+ from shapely import LineString, MultiLineString, MultiPolygon
15
+
13
16
  def convert_polygon(polygon: Polygon):
14
17
  lines = []
15
18
  exterior = LineString(polygon.exterior.coords)
@@ -60,12 +63,12 @@ def gdf_to_circle_zones_from_point(
60
63
  """n_segments = 4*resolution,e.g. if resolution = 4 that means there will be 16 segments"""
61
64
  crs = gdf.crs
62
65
  buffer = point_from.buffer(zone_radius, resolution=resolution)
63
- gdf_unary = gdf.clip(buffer, keep_geom_type=True).unary_union
66
+ gdf_unary = gdf.clip(buffer, keep_geom_type=True).union_all()
64
67
  gdf_geometry = (
65
68
  gpd.GeoDataFrame(geometry=[gdf_unary], crs=crs)
66
69
  .explode(index_parts=True)
67
70
  .geometry.apply(polygons_to_multilinestring)
68
- .unary_union
71
+ .union_all()
69
72
  )
70
73
  zones_lines = [LineString([Point(coords1), Point(point_from)]) for coords1 in buffer.exterior.coords[:-1]]
71
74
  if explode_multigeom:
@@ -77,3 +80,51 @@ def gdf_to_circle_zones_from_point(
77
80
  return gpd.GeoDataFrame(geometry=list(polygonize(unary_union([gdf_geometry] + zones_lines))), crs=crs).clip(
78
81
  gdf_unary, keep_geom_type=True
79
82
  )
83
+
84
+
85
+ def remove_inner_geom(polygon: Polygon | MultiPolygon):
86
+ """function to get rid of inner polygons"""
87
+ if isinstance(polygon, Polygon):
88
+ return Polygon(polygon.exterior.coords)
89
+ if isinstance(polygon, MultiPolygon):
90
+ polys = []
91
+ for poly in polygon.geoms:
92
+ polys.append(Polygon(poly.exterior.coords))
93
+ return MultiPolygon(polys)
94
+ else:
95
+ return Polygon()
96
+
97
+
98
+ def combine_geometry(gdf: gpd.GeoDataFrame) -> gpd.GeoDataFrame:
99
+ """
100
+ Combine geometry of intersecting layers into a single GeoDataFrame.
101
+ Parameters
102
+ ----------
103
+ gdf: gpd.GeoDataFrame
104
+ A GeoPandas GeoDataFrame
105
+
106
+ Returns
107
+ -------
108
+ gpd.GeoDataFrame
109
+ The combined GeoDataFrame with aggregated in lists columns.
110
+
111
+ Examples
112
+ --------
113
+ >>> gdf = gpd.read_file('path_to_your_file.geojson')
114
+ >>> result = combine_geometry(gdf)
115
+ """
116
+
117
+ crs = gdf.crs
118
+
119
+ enclosures = gpd.GeoDataFrame(
120
+ geometry=list(polygonize(gdf["geometry"].apply(polygons_to_multilinestring).union_all())), crs=crs
121
+ )
122
+ enclosures_points = enclosures.copy()
123
+ enclosures_points.geometry = enclosures.representative_point()
124
+ joined = gpd.sjoin(enclosures_points, gdf, how="inner", predicate="within").reset_index()
125
+ cols = joined.columns.tolist()
126
+ cols.remove("geometry")
127
+ joined = joined.groupby("index").agg({column: list for column in cols})
128
+ joined["geometry"] = enclosures
129
+ joined = gpd.GeoDataFrame(joined, geometry="geometry", crs=crs)
130
+ return joined
@@ -0,0 +1,127 @@
1
+ import geopandas as gpd
2
+ import networkx as nx
3
+ import numpy as np
4
+ import pandas as pd
5
+ from loguru import logger
6
+ from scipy.spatial import KDTree
7
+ from shapely import LineString
8
+ from shapely.geometry.point import Point
9
+
10
+
11
+ def _edges_to_gdf(graph: nx.Graph, crs) -> gpd.GeoDataFrame:
12
+ """
13
+ Converts nx graph to gpd.GeoDataFrame as edges.
14
+ """
15
+ graph_df = pd.DataFrame(list(graph.edges(data=True)), columns=["u", "v", "data"])
16
+ edge_data_expanded = pd.json_normalize(graph_df["data"])
17
+ graph_df = pd.concat([graph_df.drop(columns=["data"]), edge_data_expanded], axis=1)
18
+ graph_df = gpd.GeoDataFrame(graph_df, geometry="geometry", crs=crs).set_index(["u", "v"])
19
+ graph_df["geometry"] = graph_df["geometry"].fillna(LineString())
20
+ return graph_df
21
+
22
+
23
+ def _nodes_to_gdf(graph: nx.Graph, crs: int) -> gpd.GeoDataFrame:
24
+ """
25
+ Converts nx graph to gpd.GeoDataFrame as nodes.
26
+ """
27
+
28
+ ind, data = zip(*graph.nodes(data=True))
29
+ node_geoms = (Point(d["x"], d["y"]) for d in data)
30
+ gdf_nodes = gpd.GeoDataFrame(data, index=ind, crs=crs, geometry=list(node_geoms))
31
+
32
+ return gdf_nodes
33
+
34
+
35
+ def _restore_edges_geom(nodes_gdf, edges_gdf) -> gpd.GeoDataFrame:
36
+ edges_wout_geom = edges_gdf[edges_gdf["geometry"].is_empty].reset_index()
37
+ edges_wout_geom["geometry"] = [
38
+ LineString((s, e))
39
+ for s, e in zip(
40
+ nodes_gdf.loc[edges_wout_geom["u"], "geometry"], nodes_gdf.loc[edges_wout_geom["v"], "geometry"]
41
+ )
42
+ ]
43
+ edges_wout_geom.set_index(["u", "v"], inplace=True)
44
+ edges_gdf.update(edges_wout_geom)
45
+ return edges_gdf
46
+
47
+
48
+ def graph_to_gdf(
49
+ graph: nx.MultiDiGraph | nx.Graph | nx.DiGraph, edges: bool = True, nodes: bool = True, restore_edge_geom=False
50
+ ) -> gpd.GeoDataFrame | tuple[gpd.GeoDataFrame, gpd.GeoDataFrame]:
51
+ """
52
+ Converts nx graph to gpd.GeoDataFrame as edges.
53
+
54
+ Parameters
55
+ ----------
56
+ graph : nx.MultiDiGraph
57
+ The graph to convert.
58
+ edges: bool, default to True
59
+ Keep edges in GoeDataFrame.
60
+ nodes: bool, default to True
61
+ Keep nodes in GoeDataFrame.
62
+ restore_edge_geom: bool, default to False
63
+ if True, will try to restore edge geometry from nodes.
64
+ Returns
65
+ -------
66
+ gpd.GeoDataFrame | tuple[gpd.GeoDataFrame, gpd.GeoDataFrame]
67
+ Graph representation in GeoDataFrame format, either nodes or nodes,or tuple of them nodes,edges.
68
+ """
69
+ try:
70
+ crs = graph.graph["crs"]
71
+ except KeyError as exc:
72
+ raise ValueError("Graph does not have crs attribute") from exc
73
+ if not edges and not nodes:
74
+ raise AttributeError("Neither edges or nodes were selected")
75
+ if nodes and not edges:
76
+ nodes_gdf = _nodes_to_gdf(graph, crs)
77
+ return nodes_gdf
78
+ if not nodes and edges:
79
+ edges_gdf = _edges_to_gdf(graph, crs)
80
+ if restore_edge_geom:
81
+ nodes_gdf = _nodes_to_gdf(graph, crs)
82
+ edges_gdf = _restore_edges_geom(nodes_gdf, edges_gdf)
83
+ return edges_gdf
84
+
85
+ nodes_gdf = _nodes_to_gdf(graph, crs)
86
+ edges_gdf = _edges_to_gdf(graph, crs)
87
+ if restore_edge_geom:
88
+ edges_gdf = _restore_edges_geom(nodes_gdf, edges_gdf)
89
+ return nodes_gdf, edges_gdf
90
+
91
+
92
+ def get_closest_nodes_from_gdf(gdf: gpd.GeoDataFrame, nx_graph: nx.Graph) -> tuple:
93
+ nodes_with_data = list(nx_graph.nodes(data=True))
94
+ try:
95
+ coordinates = np.array([(data["x"], data["y"]) for node, data in nodes_with_data])
96
+ except KeyError as e:
97
+ raise ValueError("Graph does not have coordinates attribute") from e
98
+ tree = KDTree(coordinates)
99
+ target_coord = [(p.x, p.y) for p in gdf.representative_point()]
100
+ distances, indices = tree.query(target_coord)
101
+ nearest_nodes = [nodes_with_data[idx][0] for idx in indices]
102
+ return distances, nearest_nodes
103
+
104
+
105
+ def remove_weakly_connected_nodes(graph: nx.DiGraph) -> nx.DiGraph:
106
+ graph = graph.copy()
107
+
108
+ weakly_connected_components = list(nx.weakly_connected_components(graph))
109
+ if len(weakly_connected_components) > 1:
110
+ logger.warning(
111
+ f"Found {len(weakly_connected_components)} disconnected subgraphs in the network. "
112
+ f"These are isolated groups of nodes with no connections between them. "
113
+ f"Size of components: {[len(c) for c in weakly_connected_components]}"
114
+ )
115
+
116
+ all_scc = sorted(nx.strongly_connected_components(graph), key=len)
117
+ nodes_to_del = set().union(*all_scc[:-1])
118
+
119
+ if nodes_to_del:
120
+ logger.warning(
121
+ f"Removing {len(nodes_to_del)} nodes that form {len(all_scc) - 1} trap components. "
122
+ f"These are groups where you can enter but can't exit (or vice versa). "
123
+ f"Keeping the largest strongly connected component ({len(all_scc[-1])} nodes)."
124
+ )
125
+ graph.remove_nodes_from(nodes_to_del)
126
+
127
+ return graph
@@ -0,0 +1,32 @@
1
+ import numpy as np
2
+
3
+
4
+ def min_max_normalization(data, new_min=0, new_max=1):
5
+ """
6
+ Min-max normalization for a given array of data.
7
+
8
+ Parameters
9
+ ----------
10
+ data: numpy.ndarray
11
+ Input data to be normalized.
12
+ new_min: float, optional
13
+ New minimum value for normalization. Defaults to 0.
14
+ new_max: float, optional
15
+ New maximum value for normalization. Defaults to 1.
16
+
17
+ Returns
18
+ -------
19
+ numpy.ndarray
20
+ Normalized data.
21
+
22
+ Examples
23
+ --------
24
+ >>> import numpy as np
25
+ >>> data = np.array([1, 2, 3, 4, 5])
26
+ >>> normalized_data = min_max_normalization(data, new_min=0, new_max=1)
27
+ """
28
+
29
+ min_value = np.min(data)
30
+ max_value = np.max(data)
31
+ normalized_data = (data - min_value) / (max_value - min_value) * (new_max - new_min) + new_min
32
+ return normalized_data
@@ -0,0 +1,6 @@
1
+ from .visibility_analysis import (
2
+ calculate_visibility_catchment_area,
3
+ get_visibilities_from_points,
4
+ get_visibility,
5
+ get_visibility_accurate,
6
+ )