ZLayout EDA Library v1.0.0
Advanced Electronic Design Automation Layout Library with Bilingual Documentation
Loading...
Searching...
No Matches
spatial.py
Go to the documentation of this file.
1"""
2Spatial indexing structures for efficient geometric queries.
3"""
4
5from typing import List, Set, Optional, Union, Tuple, Any
6from .geometry import Point, Rectangle, Polygon
7
8
9class QuadTreeNode:
10 """A node in the quadtree structure."""
11
12 def __init__(self, boundary: Rectangle, capacity: int = 10, max_depth: int = 8):
13 self.boundary = boundary
14 self.capacity = capacity
15 self.max_depth = max_depth
16 self.objects: List[Tuple[Any, Rectangle]] = [] # (object, bounding_box) pairs
17 self.divided = False
18 self.children: List['QuadTreeNode'] = []
19
20 def subdivide(self) -> None:
21 """Divide this node into four quadrants."""
22 if self.divided:
23 return
24
25 x, y = self.boundary.x, self.boundary.y
26 w, h = self.boundary.width / 2, self.boundary.height / 2
27
28 # Create four child nodes
29 self.children = [
30 QuadTreeNode(Rectangle(x, y + h, w, h), self.capacity, self.max_depth - 1), # NW
31 QuadTreeNode(Rectangle(x + w, y + h, w, h), self.capacity, self.max_depth - 1), # NE
32 QuadTreeNode(Rectangle(x, y, w, h), self.capacity, self.max_depth - 1), # SW
33 QuadTreeNode(Rectangle(x + w, y, w, h), self.capacity, self.max_depth - 1), # SE
34 ]
35 self.divided = True
36
37 def insert(self, obj: Any, bbox: Rectangle) -> bool:
38 """Insert an object with its bounding box."""
39 # Check if object fits in this boundary
40 if not self._intersects_boundary(bbox):
41 return False
42
43 # If we have space and haven't subdivided, just add it
44 if len(self.objects) < self.capacity and not self.divided:
45 self.objects.append((obj, bbox))
46 return True
47
48 # If we can still subdivide, do so
49 if not self.divided and self.max_depth > 0:
50 self.subdivide()
51
52 # Try to insert into children
53 if self.divided:
54 for child in self.children:
55 if child.insert(obj, bbox):
56 return True
57 # If no child could contain it, store it at this level
58 self.objects.append((obj, bbox))
59 return True
60 else:
61 # No more subdivisions possible, store here
62 self.objects.append((obj, bbox))
63 return True
64
65 def query_range(self, range_bbox: Rectangle) -> List[Any]:
66 """Query all objects that intersect with the given range."""
67 result = []
68
69 # Check if this node intersects with query range
70 if not self.boundary.intersects(range_bbox):
71 return result
72
73 # Check objects in this node
74 for obj, bbox in self.objects:
75 if bbox.intersects(range_bbox):
76 result.append(obj)
77
78 # Recursively check children
79 if self.divided:
80 for child in self.children:
81 result.extend(child.query_range(range_bbox))
82
83 return result
84
85 def query_point(self, point: Point) -> List[Any]:
86 """Query all objects that contain the given point."""
87 result = []
88
89 # Check if point is in this boundary
90 if not self.boundary.contains_point(point):
91 return result
92
93 # Check objects in this node
94 for obj, bbox in self.objects:
95 if bbox.contains_point(point):
96 result.append(obj)
97
98 # Recursively check children
99 if self.divided:
100 for child in self.children:
101 result.extend(child.query_point(point))
102
103 return result
104
105 def _intersects_boundary(self, bbox: Rectangle) -> bool:
106 """Check if bounding box intersects with this node's boundary."""
107 return self.boundary.intersects(bbox)
108
109 def get_all_objects(self) -> List[Any]:
110 """Get all objects in this subtree."""
111 result = [obj for obj, _ in self.objects]
112
113 if self.divided:
114 for child in self.children:
115 result.extend(child.get_all_objects())
116
117 return result
118
119 def size(self) -> int:
120 """Get total number of objects in this subtree."""
121 count = len(self.objects)
122 if self.divided:
123 for child in self.children:
124 count += child.size()
125 return count
126
127
128class QuadTree:
129 """Quadtree spatial index for efficient range and intersection queries."""
130
131 def __init__(self, boundary: Rectangle, capacity: int = 10, max_depth: int = 8):
132 self.root = QuadTreeNode(boundary, capacity, max_depth)
134
135 def insert(self, obj: Any, bbox: Optional[Rectangle] = None) -> bool:
136 """Insert an object. If bbox is not provided, try to get it from the object."""
137 if bbox is None:
138 if hasattr(obj, 'bounding_box'):
139 bbox = obj.bounding_box()
140 elif hasattr(obj, 'boundary'):
141 bbox = obj.boundary
142 elif isinstance(obj, Rectangle):
143 bbox = obj
144 else:
145 raise ValueError("Cannot determine bounding box for object")
146
147 # bbox should now be guaranteed to be a Rectangle
148 if bbox is None:
149 raise ValueError("Could not determine bounding box")
150
151 success = self.root.insert(obj, bbox)
152 if success:
153 self.object_count += 1
154 return success
155
156 def query_range(self, range_bbox: Rectangle) -> List[Any]:
157 """Find all objects that intersect with the given range."""
158 return self.root.query_range(range_bbox)
159
160 def query_point(self, point: Point) -> List[Any]:
161 """Find all objects that contain the given point."""
162 return self.root.query_point(point)
163
164 def find_intersections(self) -> List[Tuple[Any, Any]]:
165 """Find all pairs of objects that potentially intersect."""
166 intersections = []
167 all_objects = [(obj, bbox) for obj, bbox in self._get_all_object_bbox_pairs()]
168
169 # Use spatial indexing to reduce comparison count
170 for i, (obj1, bbox1) in enumerate(all_objects):
171 # Query objects that intersect with obj1's bounding box
172 candidates = self.query_range(bbox1)
173
174 for obj2 in candidates:
175 if obj1 != obj2 and id(obj1) < id(obj2): # Avoid duplicates
176 intersections.append((obj1, obj2))
177
178 return intersections
179
180 def find_nearby_objects(self, obj: Any, distance: float) -> List[Any]:
181 """Find objects within a certain distance of the given object."""
182 if hasattr(obj, 'bounding_box'):
183 bbox = obj.bounding_box()
184 elif isinstance(obj, Rectangle):
185 bbox = obj
186 else:
187 raise ValueError("Cannot determine bounding box for object")
188
189 # Expand bounding box by distance
190 expanded_bbox = Rectangle(
191 bbox.x - distance,
192 bbox.y - distance,
193 bbox.width + 2 * distance,
194 bbox.height + 2 * distance
195 )
196
197 candidates = self.query_range(expanded_bbox)
198 return [candidate for candidate in candidates if candidate != obj]
199
200 def _get_all_object_bbox_pairs(self) -> List[Tuple[Any, Rectangle]]:
201 """Get all (object, bbox) pairs from the tree."""
202 return self._collect_object_bbox_pairs(self.root)
203
204 def _collect_object_bbox_pairs(self, node: QuadTreeNode) -> List[Tuple[Any, Rectangle]]:
205 """Recursively collect all object-bbox pairs."""
206 pairs = node.objects.copy()
207
208 if node.divided:
209 for child in node.children:
210 pairs.extend(self._collect_object_bbox_pairs(child))
211
212 return pairs
213
214 def size(self) -> int:
215 """Get total number of objects in the tree."""
216 return self.object_count
217
218 def clear(self) -> None:
219 """Remove all objects from the tree."""
220 boundary = self.root.boundary
221 capacity = self.root.capacity
222 max_depth = self.root.max_depth
223 self.root = QuadTreeNode(boundary, capacity, max_depth)
224 self.object_count = 0
225
226
228 """High-level spatial indexing interface."""
229
230 def __init__(self, world_bounds: Rectangle, capacity: int = 10, max_depth: int = 8):
231 self.quadtree = QuadTree(world_bounds, capacity, max_depth)
232 self.objects = {} # object_id -> object mapping
233 self._next_id = 0
234
235 def add_polygon(self, polygon: Polygon) -> int:
236 """Add a polygon to the spatial index."""
237 obj_id = self._next_id
238 self._next_id += 1
239
240 self.objects[obj_id] = polygon
241 self.quadtree.insert(polygon)
242 return obj_id
243
244 def add_rectangle(self, rectangle: Rectangle) -> int:
245 """Add a rectangle to the spatial index."""
246 obj_id = self._next_id
247 self._next_id += 1
248
249 self.objects[obj_id] = rectangle
250 self.quadtree.insert(rectangle)
251 return obj_id
252
253 def remove_object(self, obj_id: int) -> bool:
254 """Remove an object by ID. Note: QuadTree doesn't support efficient removal."""
255 if obj_id in self.objects:
256 del self.objects[obj_id]
257 # For efficient removal, we'd need to rebuild the tree
258 # For now, just mark as removed
259 return True
260 return False
261
262 def find_intersecting_edges(self) -> List[Tuple[int, int]]:
263 """Find all pairs of polygons with potentially intersecting edges."""
264 intersections = []
265 all_polygons = [(obj_id, obj) for obj_id, obj in self.objects.items()
266 if isinstance(obj, Polygon)]
267
268 for i, (id1, poly1) in enumerate(all_polygons):
269 bbox1 = poly1.bounding_box()
270 candidates = self.quadtree.query_range(bbox1)
271
272 for candidate in candidates:
273 if isinstance(candidate, Polygon):
274 # Find the ID of the candidate
275 id2 = None
276 for oid, obj in self.objects.items():
277 if obj is candidate:
278 id2 = oid
279 break
280
281 if id2 is not None and id1 < id2: # Avoid duplicates
282 intersections.append((id1, id2))
283
284 return intersections
285
286 def find_nearby_objects(self, obj_id: int, distance: float) -> List[int]:
287 """Find objects within distance of the given object."""
288 if obj_id not in self.objects:
289 return []
290
291 obj = self.objects[obj_id]
292 nearby = self.quadtree.find_nearby_objects(obj, distance)
293
294 # Convert objects back to IDs
295 nearby_ids = []
296 for nearby_obj in nearby:
297 for oid, stored_obj in self.objects.items():
298 if stored_obj is nearby_obj:
299 nearby_ids.append(oid)
300 break
301
302 return nearby_ids
303
304 def query_region(self, region: Rectangle) -> List[int]:
305 """Find all objects that intersect with the given region."""
306 objects_in_region = self.quadtree.query_range(region)
307
308 # Convert objects back to IDs
309 object_ids = []
310 for obj in objects_in_region:
311 for oid, stored_obj in self.objects.items():
312 if stored_obj is obj:
313 object_ids.append(oid)
314 break
315
316 return object_ids
Axis-aligned rectangle for bounding boxes and simple EDA components.
Definition rectangle.hpp:26
Quadtree spatial index for efficient range and intersection queries.
Definition quadtree.hpp:120
bool insert(const geometry::Rectangle &object)
List[Tuple[Any, Rectangle]] _get_all_object_bbox_pairs(self)
Definition spatial.py:200
List[Any] find_nearby_objects(self, Any obj, float distance)
Definition spatial.py:180
std::vector< geometry::Rectangle > query_range(const geometry::Rectangle &range) const
std::unique_ptr< QuadTreeNode< geometry::Rectangle > > root
Definition quadtree.hpp:126
List[Tuple[Any, Rectangle]] _collect_object_bbox_pairs(self, QuadTreeNode node)
Definition spatial.py:204
std::vector< std::pair< geometry::Rectangle, geometry::Rectangle > > find_intersections(std::function< bool(const geometry::Rectangle &, const geometry::Rectangle &)> collision_func) const
std::vector< geometry::Rectangle > query_point(const geometry::Point &point) const
__init__(self, Rectangle boundary, int capacity=10, int max_depth=8)
Definition spatial.py:131
A node in the quadtree structure.
Definition quadtree.hpp:28
std::vector< T > objects
Objects stored in this node.
Definition quadtree.hpp:34
std::unique_ptr< QuadTreeNode > children[4]
Child nodes (NW, NE, SW, SE)
Definition quadtree.hpp:35
std::vector< T > query_range(const geometry::Rectangle &range, const BoundingBoxFunc &get_bbox) const
Query objects in a rectangular range.
Definition quadtree.hpp:496
__init__(self, Rectangle boundary, int capacity=10, int max_depth=8)
Definition spatial.py:12
bool _intersects_boundary(self, Rectangle bbox)
Definition spatial.py:105
size_t size() const
Get total number of objects in this subtree.
Definition quadtree.hpp:579
bool divided
Whether this node has been subdivided.
Definition quadtree.hpp:36
size_t max_depth
Maximum subdivision depth.
Definition quadtree.hpp:38
std::vector< T > query_point(const geometry::Point &point, const BoundingBoxFunc &get_bbox) const
Query objects containing a specific point.
Definition quadtree.hpp:525
geometry::Rectangle boundary
Spatial boundary of this node.
Definition quadtree.hpp:33
std::vector< T > get_all_objects() const
Get all objects in this subtree.
Definition quadtree.hpp:601
size_t capacity
Maximum objects before subdivision.
Definition quadtree.hpp:37
bool insert(const T &object, const BoundingBoxFunc &get_bbox)
Insert object into this node or appropriate child.
Definition quadtree.hpp:461
List[int] find_nearby_objects(self, int obj_id, float distance)
Definition spatial.py:286
__init__(self, Rectangle world_bounds, int capacity=10, int max_depth=8)
Definition spatial.py:230
List[Tuple[int, int]] find_intersecting_edges(self)
Definition spatial.py:262
int add_rectangle(self, Rectangle rectangle)
Definition spatial.py:244
int add_polygon(self, Polygon polygon)
Definition spatial.py:235
List[int] query_region(self, Rectangle region)
Definition spatial.py:304
bool remove_object(self, int obj_id)
Definition spatial.py:253