source: anuga_core/source/anuga/utilities/quad.py @ 7707

Last change on this file since 7707 was 7707, checked in by hudson, 14 years ago

New quadtree implementation - unoptimised and no tree balancing. A couple of failing unit tests to fix.

File size: 7.5 KB
Line 
1"""quad.py - quad tree data structure for fast indexing of regions in the plane.
2
3This is a generic structure that can be used to store any geometry in a quadtree.
4
5
6"""
7
8from treenode import TreeNode
9import string, types, sys
10import anuga.utilities.log as log
11
12# Allow children to be slightly bigger than their parents to prevent straddling of a boundary
13SPLIT_BORDER_RATIO    = 0.55
14
15class AABB:
16    """Axially-aligned bounding box class.
17    """
18   
19    def __init__(self, xmin, xmax, ymin, ymax):
20        self.xmin = round(xmin,5)   
21        self.xmax = round(xmax,5)
22        self.ymin = round(ymin,5)   
23        self.ymax = round(ymax,5)
24
25    def __repr__(self):
26        return '(xmin:%f, xmax:%f, ymin:%f, ymax:%f)' \
27               % (round(self.xmin,1), round(self.xmax,1), round(self.ymin,1), round(self.ymax, 1)) 
28       
29    def size(self):
30        """return size as (w,h)"""
31        return self.xmax - self.xmin, self.ymax - self.ymin
32       
33    def split(self, border=SPLIT_BORDER_RATIO):
34        """Split along shorter axis.
35           return 2 subdivided AABBs.
36        """
37       
38        width, height = self.size()
39        assert width >= 0 and height >= 0
40       
41        if (width > height):
42            # split vertically
43            return AABB(self.xmin, self.xmin+width*border, self.ymin, self.ymax), \
44                   AABB(self.xmax-width*border, self.xmax, self.ymin, self.ymax)
45        else:
46            # split horizontally       
47            return AABB(self.xmin, self.xmax, self.ymin, self.ymin+height*border), \
48                   AABB(self.xmin, self.xmax, self.ymax-height*border, self.ymax)   
49   
50    def is_trivial_in(self, test):
51        if (test.xmin < self.xmin) or (test.xmax > self.xmax):
52            return False       
53        if (test.ymin < self.ymin) or (test.ymax > self.ymax):
54            return False       
55        return True
56 
57    def contains(self, x, y):
58        return (self.xmin <= x <= self.xmax) and (self.ymin <= y <= self.ymax)
59           
60class Cell(TreeNode):
61    """class Cell
62
63    One cell in the plane delimited by southern, northern,
64    western, eastern boundaries.
65
66    Public Methods:
67        insert(point)
68        search(x, y)
69        split()
70        store()
71        retrieve()
72        count()
73    """
74 
75    def __init__(self, extents,
76         name = 'cell'):
77 
78        # Initialise base classes
79        TreeNode.__init__(self, string.lower(name))
80   
81        self.extents = extents
82       
83        # The points in this cell     
84        self.leaves = []
85        self.children = None
86       
87   
88    def __repr__(self):
89        str = '%s: leaves: %d' \
90               % (self.name , len(self.leaves)) 
91        if self.children:
92            str += ', children: %d' % (len(self.children))
93        return str
94
95   
96
97    def clear(self):
98        self.Prune()   # TreeNode method
99
100
101    def clear_leaf_node(self):
102        """Clears storage in leaf node.
103    Called from Treenode.
104    Must exist.   
105    """
106        self.leaves = []
107   
108   
109    def clear_internal_node(self):
110        """Called from Treenode.   
111    Must exist.
112    """
113        self.leaves = []
114
115
116    def insert(self, new_leaf):
117        # process list items sequentially
118        if type(new_leaf)==type(list()):
119            ret_val = []
120            for leaf in new_leaf:
121                self._insert(leaf)
122        else:
123            self._insert(new_leaf)
124
125
126    def _insert(self, new_leaf):   
127        new_region, data = new_leaf
128       
129        # recurse down to any children until we get an intersection
130        if self.children:
131            for child in self.children:
132                if child.extents.is_trivial_in(new_region):
133                    child._insert(new_leaf)
134                    return
135        else:           
136            # try splitting this cell and see if we get a trivial in
137            subregion1, subregion2 = self.extents.split()
138            if subregion1.is_trivial_in(new_region):
139                self.children = [Cell(subregion1), Cell(subregion2)]   
140                self.children[0]._insert(new_leaf)
141                return
142            elif subregion2.is_trivial_in(new_region):
143                self.children = [Cell(subregion1), Cell(subregion2)]   
144                self.children[1]._insert(new_leaf)
145                return               
146   
147        # recursion ended without finding a fit, so attach it as a leaf
148        self.leaves.append(new_leaf)
149       
150     
151    def retrieve(self):
152        """Get all leaves from this tree. """
153       
154        leaves_found = list(self.leaves)
155       
156        if not self.children:
157            return leaves_found
158
159        for child in self.children:
160            leaves_found.extend(child.retrieve())
161           
162        return leaves_found
163
164    def count(self):
165        """Count all leaves from this tree. """
166       
167        leaves_found = len(self.leaves)
168       
169        if not self.children:
170            return leaves_found
171
172        for child in self.children:
173            leaves_found += child.count()
174           
175        return leaves_found       
176
177    def show(self, depth=0):
178        """Traverse tree below self
179        """
180        if depth == 0:
181            log.critical() 
182        print '%s%s' % ('  '*depth, self.name), self.extents,' [', self.leaves, ']'
183        if self.children:
184            log.critical()
185            for child in self.children:
186                child.show(depth+1)
187 
188
189    def search(self, x, y, get_vertices = False):
190        """return a list of possible intersections with geometry"""
191       
192        intersecting_regions = []
193       
194        # test all leaves to see if they intersect the point
195        for leaf in self.leaves:
196            aabb, data = leaf
197            if aabb.contains(x, y):
198                if get_vertices:
199                    intersecting_regions.append(leaf)
200                else:
201                    intersecting_regions.append(data)
202       
203        # recurse down into nodes that the point passes through
204        if self.children:
205            for child in self.children:   
206                if child.extents.contains(x, y):
207                    intersecting_regions.extend(child.search(x, y, get_vertices))
208             
209        return intersecting_regions
210       
211#from anuga.pmesh.mesh import Mesh
212   
213def build_quadtree(mesh, max_points_per_cell = 4):
214    """Build quad tree for mesh.
215
216    All vertices in mesh are stored in quadtree and a reference
217    to the root is returned.
218    """
219
220
221    #Make root cell
222    #print mesh.coordinates
223
224    xmin, xmax, ymin, ymax = mesh.get_extent(absolute=True)
225   
226    # Ensure boundary points are fully contained in region
227    # It is a property of the cell structure that
228    # points on xmax or ymax of any given cell
229    # belong to the neighbouring cell.
230    # Hence, the root cell needs to be expanded slightly
231    ymax += (ymax-ymin)/10
232    xmax += (xmax-xmin)/10
233
234    # To avoid round off error
235    ymin -= (ymax-ymin)/10
236    xmin -= (xmax-xmin)/10   
237
238    #print "xmin", xmin
239    #print "xmax", xmax
240    #print "ymin", ymin
241    #print "ymax", ymax
242   
243    root = Cell(AABB(xmin, xmax, ymin, ymax))
244   
245    N = len(mesh)
246
247    # Get x,y coordinates for all vertices for all triangles
248    V = mesh.get_vertex_coordinates(absolute=True)
249       
250    # Check each triangle
251    for i in range(N):
252        x0, y0 = V[3*i, :]
253        x1, y1 = V[3*i+1, :]
254        x2, y2 = V[3*i+2, :]
255
256        # insert a tuple with an AABB, and the triangle index as data
257        root._insert((AABB(min([x0, x1, x2]), max([x0, x1, x2]), \
258                         min([y0, y1, y2]), max([y0, y1, y2])), \
259                         i))
260
261    return root
Note: See TracBrowser for help on using the repository browser.