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
@@ -0,0 +1 @@
1
+ from .graph_utils import gdf_to_graph, graph_to_gdf
@@ -1,130 +1,173 @@
1
- import math
2
-
3
- import geopandas as gpd
4
- from shapely import LineString, MultiPolygon, Point, Polygon
5
- from shapely.ops import polygonize, unary_union
6
-
7
- from objectnat import config
8
-
9
- logger = config.logger
10
-
11
-
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
-
16
- def convert_polygon(polygon: Polygon):
17
- lines = []
18
- exterior = LineString(polygon.exterior.coords)
19
- lines.append(exterior)
20
- interior = [LineString(p.coords) for p in polygon.interiors]
21
- lines = lines + interior
22
- return lines
23
-
24
- def convert_multipolygon(polygon: MultiPolygon):
25
- return MultiLineString(sum([convert_polygon(p) for p in polygon.geoms], []))
26
-
27
- if geom.geom_type == "Polygon":
28
- return MultiLineString(convert_polygon(geom))
29
- return convert_multipolygon(geom)
30
-
31
-
32
- def explode_linestring(geometry: LineString) -> list[LineString]:
33
- """A function to return all segments of a linestring as a list of linestrings"""
34
- coords_ext = geometry.coords # Create a list of all line node coordinates
35
- result = [LineString(part) for part in zip(coords_ext, coords_ext[1:])]
36
- return result
37
-
38
-
39
- def point_side_of_line(line: LineString, point: Point) -> int:
40
- """A positive indicates the left-hand side a negative indicates the right-hand side"""
41
- x1, y1 = line.coords[0]
42
- x2, y2 = line.coords[-1]
43
- x, y = point.coords[0]
44
- cross_product = (x2 - x1) * (y - y1) - (y2 - y1) * (x - x1)
45
- if cross_product > 0:
46
- return 1
47
- return -1
48
-
49
-
50
- def get_point_from_a_thorough_b(a: Point, b: Point, dist):
51
- """
52
- Func to get Point from point a thorough point b on dist
53
- """
54
- direction = math.atan2(b.y - a.y, b.x - a.x)
55
- c_x = a.x + dist * math.cos(direction)
56
- c_y = a.y + dist * math.sin(direction)
57
- return Point(c_x, c_y)
58
-
59
-
60
- def gdf_to_circle_zones_from_point(
61
- gdf: gpd.GeoDataFrame, point_from: Point, zone_radius, resolution=4, explode_multigeom=True
62
- ) -> gpd.GeoDataFrame:
63
- """n_segments = 4*resolution,e.g. if resolution = 4 that means there will be 16 segments"""
64
- crs = gdf.crs
65
- buffer = point_from.buffer(zone_radius, resolution=resolution)
66
- gdf_unary = gdf.clip(buffer, keep_geom_type=True).union_all()
67
- gdf_geometry = (
68
- gpd.GeoDataFrame(geometry=[gdf_unary], crs=crs)
69
- .explode(index_parts=True)
70
- .geometry.apply(polygons_to_multilinestring)
71
- .union_all()
72
- )
73
- zones_lines = [LineString([Point(coords1), Point(point_from)]) for coords1 in buffer.exterior.coords[:-1]]
74
- if explode_multigeom:
75
- return (
76
- gpd.GeoDataFrame(geometry=list(polygonize(unary_union([gdf_geometry] + zones_lines))), crs=crs)
77
- .clip(gdf_unary, keep_geom_type=True)
78
- .explode(index_parts=False)
79
- )
80
- return gpd.GeoDataFrame(geometry=list(polygonize(unary_union([gdf_geometry] + zones_lines))), crs=crs).clip(
81
- gdf_unary, keep_geom_type=True
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
1
+ import math
2
+
3
+ import geopandas as gpd
4
+ from shapely import LineString, MultiPolygon, Point, Polygon
5
+ from shapely.ops import polygonize, unary_union
6
+
7
+ from objectnat import config
8
+
9
+ logger = config.logger
10
+
11
+
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
+
16
+ def convert_polygon(polygon: Polygon):
17
+ lines = []
18
+ exterior = LineString(polygon.exterior)
19
+ lines.append(exterior)
20
+ interior = [LineString(p) for p in polygon.interiors]
21
+ lines = lines + interior
22
+ return lines
23
+
24
+ def convert_multipolygon(polygon: MultiPolygon):
25
+ return MultiLineString(sum([convert_polygon(p) for p in polygon.geoms], []))
26
+
27
+ if geom.geom_type == "Polygon":
28
+ return MultiLineString(convert_polygon(geom))
29
+ return convert_multipolygon(geom)
30
+
31
+
32
+ def explode_linestring(geometry: LineString) -> list[LineString]:
33
+ """A function to return all segments of a linestring as a list of linestrings"""
34
+ coords_ext = geometry.coords # Create a list of all line node coordinates
35
+ result = [LineString(part) for part in zip(coords_ext, coords_ext[1:])]
36
+ return result
37
+
38
+
39
+ def point_side_of_line(line: LineString, point: Point) -> int:
40
+ """A positive indicates the left-hand side a negative indicates the right-hand side"""
41
+ x1, y1 = line.coords[0]
42
+ x2, y2 = line.coords[-1]
43
+ x, y = point.coords[0]
44
+ cross_product = (x2 - x1) * (y - y1) - (y2 - y1) * (x - x1)
45
+ if cross_product > 0:
46
+ return 1
47
+ return -1
48
+
49
+
50
+ def get_point_from_a_thorough_b(a: Point, b: Point, dist):
51
+ """
52
+ Func to get Point from point a thorough point b on dist
53
+ """
54
+ direction = math.atan2(b.y - a.y, b.x - a.x)
55
+ c_x = a.x + dist * math.cos(direction)
56
+ c_y = a.y + dist * math.sin(direction)
57
+ return Point(c_x, c_y)
58
+
59
+
60
+ def gdf_to_circle_zones_from_point(
61
+ gdf: gpd.GeoDataFrame, point_from: Point, zone_radius, resolution=4, explode_multigeom=True
62
+ ) -> gpd.GeoDataFrame:
63
+ """n_segments = 4*resolution,e.g. if resolution = 4 that means there will be 16 segments"""
64
+ crs = gdf.crs
65
+ buffer = point_from.buffer(zone_radius, resolution=resolution)
66
+ gdf_unary = gdf.clip(buffer, keep_geom_type=True).union_all()
67
+ gdf_geometry = (
68
+ gpd.GeoDataFrame(geometry=[gdf_unary], crs=crs)
69
+ .explode(index_parts=True)
70
+ .geometry.apply(polygons_to_multilinestring)
71
+ .union_all()
72
+ )
73
+ zones_lines = [LineString([Point(coords1), Point(point_from)]) for coords1 in buffer.exterior.coords[:-1]]
74
+ if explode_multigeom:
75
+ return (
76
+ gpd.GeoDataFrame(geometry=list(polygonize(unary_union([gdf_geometry] + zones_lines))), crs=crs)
77
+ .clip(gdf_unary, keep_geom_type=True)
78
+ .explode(index_parts=False)
79
+ )
80
+ return gpd.GeoDataFrame(geometry=list(polygonize(unary_union([gdf_geometry] + zones_lines))), crs=crs).clip(
81
+ gdf_unary, keep_geom_type=True
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
131
+
132
+
133
+ def distribute_points_on_linestrings(lines: gpd.GeoDataFrame, radius, lloyd_relax_n=2) -> gpd.GeoDataFrame:
134
+ lines = lines.copy()
135
+ lines = lines.explode(ignore_index=True)
136
+ lines = lines[lines.geom_type == "LineString"]
137
+ original_crs = lines.crs
138
+ lines = lines.to_crs(crs=lines.estimate_utm_crs())
139
+ lines = lines.reset_index(drop=True)
140
+ lines = lines[["geometry"]]
141
+ radius = radius * 1.1
142
+ segmentized = lines.geometry.apply(lambda x: x.simplify(radius).segmentize(radius))
143
+ points = [Point(pt) for line in segmentized for pt in line.coords]
144
+
145
+ points = gpd.GeoDataFrame(geometry=points, crs=lines.crs)
146
+ lines["lines"] = lines.geometry
147
+ geom_concave = lines.buffer(5, resolution=1).union_all()
148
+
149
+ for i in range(lloyd_relax_n):
150
+ points.geometry = points.voronoi_polygons().clip(geom_concave).centroid
151
+ points = points.sjoin_nearest(lines, how="left")
152
+ points = points[~points.index.duplicated(keep="first")]
153
+ points["geometry"] = points["lines"].interpolate(points["lines"].project(points.geometry))
154
+ points.drop(columns=["lines", "index_right"], inplace=True)
155
+
156
+ return points.dropna().to_crs(original_crs)
157
+
158
+
159
+ def distribute_points_on_polygons(
160
+ polygons: gpd.GeoDataFrame, radius, only_exterior=True, lloyd_relax_n=2
161
+ ) -> gpd.GeoDataFrame:
162
+ polygons = polygons.copy()
163
+ polygons = polygons.explode(ignore_index=True)
164
+ polygons = polygons[polygons.geom_type == "Polygon"]
165
+
166
+ if only_exterior:
167
+ polygons.geometry = polygons.geometry.apply(lambda x: LineString(x.exterior))
168
+ else:
169
+ polygons = gpd.GeoDataFrame(
170
+ geometry=list(polygons.geometry.apply(polygons_to_multilinestring)), crs=polygons.crs
171
+ )
172
+
173
+ return distribute_points_on_linestrings(polygons, radius, lloyd_relax_n=lloyd_relax_n)