2Advanced geometric analysis algorithms for layout optimization.
6from typing
import List, Tuple, Set, Dict, Optional, Union
7from .geometry
import Point, Rectangle, Polygon
8from .spatial
import QuadTree, SpatialIndex
12 """Result of edge intersection analysis."""
21 """Result of narrow distance analysis."""
31 """Result of sharp angle analysis."""
41 Find intersection point of two line segments.
42 Returns None if lines don't intersect.
47 denom = (p1.x - p2.x) * (p3.y - p4.y) - (p1.y - p2.y) * (p3.x - p4.x)
49 if abs(denom) < 1e-10:
52 t = ((p1.x - p3.x) * (p3.y - p4.y) - (p1.y - p3.y) * (p3.x - p4.x)) / denom
53 u = -((p1.x - p2.x) * (p1.y - p3.y) - (p1.y - p2.y) * (p1.x - p3.x)) / denom
56 if 0 <= t <= 1
and 0 <= u <= 1:
57 x = p1.x + t * (p2.x - p1.x)
58 y = p1.y + t * (p2.y - p1.y)
66 Fast check if two line segments intersect using orientation method.
67 This is more efficient than computing actual intersection point.
69 def orientation(p: Point, q: Point, r: Point) -> int:
70 """Find orientation of ordered triplet (p, q, r).
72 0 -> p, q and r are colinear
76 val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y)
79 return 1
if val > 0
else 2
81 def on_segment(p: Point, q: Point, r: Point) -> bool:
82 """Check if point q lies on segment pr."""
83 return (q.x <= max(p.x, r.x)
and q.x >= min(p.x, r.x)
and
84 q.y <= max(p.y, r.y)
and q.y >= min(p.y, r.y))
86 o1 = orientation(p1, p2, p3)
87 o2 = orientation(p1, p2, p4)
88 o3 = orientation(p3, p4, p1)
89 o4 = orientation(p3, p4, p2)
92 if o1 != o2
and o3 != o4:
96 if (o1 == 0
and on_segment(p1, p3, p2)
or
97 o2 == 0
and on_segment(p1, p4, p2)
or
98 o3 == 0
and on_segment(p3, p1, p4)
or
99 o4 == 0
and on_segment(p3, p2, p4)):
106 """Analyzer for polygon geometric properties and relationships."""
108 def __init__(self, spatial_index: Optional[SpatialIndex] =
None):
114 """Add a polygon for analysis."""
124 """Find all sharp angles in all polygons."""
127 polygons_to_analyze = []
130 if isinstance(obj, Polygon):
131 polygons_to_analyze.append((obj_id, obj))
133 for i, polygon
in enumerate(self.
polygons):
134 polygons_to_analyze.append((i, polygon))
139 for poly_id, polygon
in polygons_to_analyze:
140 sharp_vertices = polygon.get_sharp_angles(threshold_degrees)
143 for vertex_idx
in sharp_vertices:
144 n = len(polygon.vertices)
145 prev_vertex = polygon.vertices[(vertex_idx - 1) % n]
146 curr_vertex = polygon.vertices[vertex_idx]
147 next_vertex = polygon.vertices[(vertex_idx + 1) % n]
150 v1 =
Point(prev_vertex.x - curr_vertex.x, prev_vertex.y - curr_vertex.y)
151 v2 =
Point(next_vertex.x - curr_vertex.x, next_vertex.y - curr_vertex.y)
153 dot_product = v1.x * v2.x + v1.y * v2.y
154 mag1 = math.sqrt(v1.x**2 + v1.y**2)
155 mag2 = math.sqrt(v2.x**2 + v2.y**2)
157 if mag1 > 1e-10
and mag2 > 1e-10:
158 cos_angle = dot_product / (mag1 * mag2)
159 cos_angle = max(-1.0, min(1.0, cos_angle))
160 angle_degrees = math.degrees(math.acos(cos_angle))
162 result.sharp_angles.append((poly_id, vertex_idx, angle_degrees))
163 result.sharpest_angle = min(result.sharpest_angle, angle_degrees)
164 angle_sum += angle_degrees
168 result.average_angle = angle_sum / total_angles
173 """Find regions where polygon edges are too close together."""
176 polygons_to_analyze = []
179 if isinstance(obj, Polygon):
180 polygons_to_analyze.append((obj_id, obj))
182 for i, polygon
in enumerate(self.
polygons):
183 polygons_to_analyze.append((i, polygon))
188 for i, (id1, poly1)
in enumerate(polygons_to_analyze):
189 for j, (id2, poly2)
in enumerate(polygons_to_analyze[i+1:], i+1):
191 for edge1
in poly1.edges:
192 for edge2
in poly2.edges:
194 distances.append(dist)
196 if dist < threshold_distance:
199 edge1[0], edge1[1], edge2[0], edge2[1]
201 result.narrow_regions.append((closest_points[0], closest_points[1], dist))
204 for poly_id, polygon
in polygons_to_analyze:
205 edges = polygon.edges
206 for i, edge1
in enumerate(edges):
207 for j, edge2
in enumerate(edges[i+2:], i+2):
208 if j == len(edges) - 1
and i == 0:
212 distances.append(dist)
214 if dist < threshold_distance:
216 edge1[0], edge1[1], edge2[0], edge2[1]
218 result.narrow_regions.append((closest_points[0], closest_points[1], dist))
221 result.min_distance = min(distances)
222 result.max_distance = max(distances)
223 result.average_distance = sum(distances) / len(distances)
228 """Find all edge intersections using spatial indexing for efficiency."""
233 intersecting_pairs = self.
spatial_index.find_intersecting_edges()
235 for id1, id2
in intersecting_pairs:
239 if isinstance(poly1, Polygon)
and isinstance(poly2, Polygon):
242 result.intersecting_pairs.append((id1, id2))
243 result.intersection_points.extend(intersections)
244 result.total_intersections += len(intersections)
247 for i, poly1
in enumerate(self.
polygons):
248 for j, poly2
in enumerate(self.
polygons[i+1:], i+1):
251 result.intersecting_pairs.append((i, j))
252 result.intersection_points.extend(intersections)
253 result.total_intersections += len(intersections)
258 """Find all intersection points between edges of two polygons."""
261 for edge1
in poly1.edges:
262 for edge2
in poly2.edges:
265 intersections.append(intersection)
270 """Calculate minimum distance between two line segments."""
273 p1.distance_to_line(p3, p4),
274 p2.distance_to_line(p3, p4),
275 p3.distance_to_line(p1, p2),
276 p4.distance_to_line(p1, p2)
281 p1.distance_to(p3), p1.distance_to(p4),
282 p2.distance_to(p3), p2.distance_to(p4)
285 return min(distances)
288 """Find the closest points on two line segments."""
290 min_dist = float(
'inf')
291 closest_pair = (p1, p3)
294 (p1, p3), (p1, p4), (p2, p3), (p2, p4)
297 for pt1, pt2
in candidates:
298 dist = pt1.distance_to(pt2)
301 closest_pair = (pt1, pt2)
307 """High-level geometry processing and optimization."""
314 """Add a geometric component to the processor."""
315 if isinstance(geometry, Rectangle):
317 elif isinstance(geometry, Polygon):
318 return self.
analyzer.add_polygon(geometry)
320 raise ValueError(
"Unsupported geometry type")
323 narrow_distance_threshold: float = 1.0) -> Dict:
324 """Perform comprehensive layout analysis."""
328 sharp_angles = self.
analyzer.find_sharp_angles(sharp_angle_threshold)
329 results[
'sharp_angles'] = {
330 'count': len(sharp_angles.sharp_angles),
331 'sharpest': sharp_angles.sharpest_angle,
332 'average': sharp_angles.average_angle,
333 'details': sharp_angles.sharp_angles
337 narrow_distances = self.
analyzer.find_narrow_distances(narrow_distance_threshold)
338 results[
'narrow_distances'] = {
339 'count': len(narrow_distances.narrow_regions),
340 'minimum': narrow_distances.min_distance,
341 'maximum': narrow_distances.max_distance,
342 'average': narrow_distances.average_distance,
343 'details': narrow_distances.narrow_regions
347 intersections = self.
analyzer.find_edge_intersections()
348 results[
'intersections'] = {
349 'polygon_pairs': len(intersections.intersecting_pairs),
350 'total_points': intersections.total_intersections,
351 'pairs': intersections.intersecting_pairs,
352 'points': intersections.intersection_points
358 """Suggest layout optimizations based on analysis."""
362 if analysis[
'sharp_angles'][
'count'] > 0:
363 suggestions.append(f
"Found {analysis['sharp_angles']['count']} sharp angles. "
364 f
"Consider rounding corners or adjusting geometry.")
366 if analysis[
'narrow_distances'][
'count'] > 0:
367 suggestions.append(f
"Found {analysis['narrow_distances']['count']} narrow regions. "
368 f
"Minimum distance: {analysis['narrow_distances']['minimum']:.3f}")
370 if analysis[
'intersections'][
'polygon_pairs'] > 0:
371 suggestions.append(f
"Found {analysis['intersections']['polygon_pairs']} intersecting polygon pairs. "
372 f
"Total intersection points: {analysis['intersections']['total_points']}")
375 'analysis': analysis,
376 'suggestions': suggestions,
381 """Calculate a score representing layout quality (0-100, higher is better)."""
385 sharp_penalty = min(analysis[
'sharp_angles'][
'count'] * 5, 30)
386 score -= sharp_penalty
389 narrow_penalty = min(analysis[
'narrow_distances'][
'count'] * 10, 40)
390 score -= narrow_penalty
393 intersection_penalty = min(analysis[
'intersections'][
'polygon_pairs'] * 20, 50)
394 score -= intersection_penalty
396 return max(0.0, score)
float _calculate_optimization_score(self, Dict analysis)
int add_component(self, Union[Rectangle, Polygon] geometry)
Dict optimize_layout(self)
__init__(self, Rectangle world_bounds)
Dict analyze_layout(self, float sharp_angle_threshold=30.0, float narrow_distance_threshold=1.0)
SharpAngleResult find_sharp_angles(self, float threshold_degrees=30.0)
float _edge_to_edge_distance(self, Point p1, Point p2, Point p3, Point p4)
__init__(self, Optional[SpatialIndex] spatial_index=None)
List[Point] _find_polygon_edge_intersections(self, Polygon poly1, Polygon poly2)
int add_polygon(self, Polygon polygon)
NarrowDistanceResult find_narrow_distances(self, float threshold_distance=1.0)
Tuple[Point, Point] _closest_points_on_edges(self, Point p1, Point p2, Point p3, Point p4)
EdgeIntersectionResult find_edge_intersections(self)
2D point with high-precision coordinates and utility methods
bool segments_intersect(Point p1, Point p2, Point p3, Point p4)
Optional[Point] line_intersection(Point p1, Point p2, Point p3, Point p4)