Quadtrees based on hashed datastructures rather than indexed ones
This commit is contained in:
parent
ff57f30d58
commit
d086833fd7
1 changed files with 105 additions and 84 deletions
203
quadtree.py
203
quadtree.py
|
|
@ -1,31 +1,23 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
# import enum
|
|
||||||
|
|
||||||
import geometry
|
import geometry
|
||||||
from geometry import x,y
|
from geometry import x,y
|
||||||
|
|
||||||
def next_id( existing_ids ):
|
# import enum
|
||||||
i = 0
|
|
||||||
while i in existing_ids:
|
|
||||||
i += 1
|
|
||||||
return i
|
|
||||||
|
|
||||||
|
|
||||||
class QuadTree(object):
|
class QuadTree(object):
|
||||||
|
|
||||||
def __init__( self, points = [] ):
|
def __init__( self, points = [] ):
|
||||||
|
"""Build a quadtree on the given set of points."""
|
||||||
# Data structures to handle the quadtree
|
|
||||||
|
|
||||||
# 0 is the root quadrant
|
|
||||||
self.quadrants = { 0: None }
|
|
||||||
self.widths = { 0: None }
|
|
||||||
self.occupants = { 0: None }
|
|
||||||
# Quadrants may have four children
|
|
||||||
self.children = { 0: [] }
|
|
||||||
|
|
||||||
# Initialize the root quadrant as the box around the points
|
# Initialize the root quadrant as the box around the points
|
||||||
self.init(points = points)
|
self.init( points = points )
|
||||||
|
|
||||||
|
# Data structures to handle the quadtree
|
||||||
|
self.residents = { self.root: None }
|
||||||
|
|
||||||
|
# Quadrants may have four children
|
||||||
|
self.children = { self.root: [] }
|
||||||
|
|
||||||
# Status of quadrants
|
# Status of quadrants
|
||||||
# class Status(enum.Enum):
|
# class Status(enum.Enum):
|
||||||
|
|
@ -42,121 +34,135 @@ class QuadTree(object):
|
||||||
|
|
||||||
|
|
||||||
def init( self, quadrant = None, box = None, points = None ):
|
def init( self, quadrant = None, box = None, points = None ):
|
||||||
|
"""Initialize the root quadrant with the given quadrant ((x,y),width), the given box or the given set of points."""
|
||||||
|
|
||||||
if len([k for k in (box,points,quadrant) if k]) > 1:
|
if len([k for k in (box,points,quadrant) if k]) > 1:
|
||||||
raise BaseException("ERROR: you should specify only one of the options")
|
raise BaseException("ERROR: you should specify only one of the options")
|
||||||
|
|
||||||
# Initialize the root quadrant as the box around the points
|
# Initialize the root quadrant as the given box
|
||||||
if box:
|
if box:
|
||||||
minp,maxp = box
|
minp,maxp = box
|
||||||
w = max( x(maxp)-x(minp), y(maxp)-y(minp) )
|
width = max( x(maxp)-x(minp), y(maxp)-y(minp) )
|
||||||
self.widths[0] = w
|
|
||||||
self.quadrants[0] = minp
|
# Initialize the root quadrant as the box around the points
|
||||||
elif points:
|
elif points:
|
||||||
minp,maxp = geometry.box( points )
|
minp,maxp = geometry.box( points )
|
||||||
w = max( x(maxp)-x(minp), y(maxp)-y(minp) )
|
width = max( x(maxp)-x(minp), y(maxp)-y(minp) )
|
||||||
self.widths[0] = w
|
|
||||||
self.quadrants[0] = minp
|
# Initialize the root quadrant as the given origin point and width
|
||||||
elif quadrant:
|
elif quadrant:
|
||||||
self.quadrants[0] = quadrant[0]
|
minp = quadrant[0]
|
||||||
self.widths[0] = quadrant[1]
|
width = quadrant[1]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise BaseException("ERROR: you should specify a box, a quadrant or points")
|
raise BaseException("ERROR: you should specify a box, a quadrant or points")
|
||||||
|
|
||||||
|
# There is always the root quadrant in the list of available ones.
|
||||||
|
self.root = (minp,width)
|
||||||
|
self.quadrants = [ self.root ]
|
||||||
|
|
||||||
def appendable( self, p, quad ):
|
|
||||||
|
def as_box( self, quadrant );
|
||||||
|
width = quadrant[1]
|
||||||
|
maxp = tuple(xy+width for xy in quadrant[0])
|
||||||
|
return (quadrant[0],maxp)
|
||||||
|
|
||||||
|
|
||||||
|
def status( self, point, quadrant ):
|
||||||
"""Return Status.Empty if the given point can be appended in the given quadrant."""
|
"""Return Status.Empty if the given point can be appended in the given quadrant."""
|
||||||
assert(p is not None)
|
|
||||||
assert(len(p) == 2)
|
assert(point is not None)
|
||||||
minp = self.quadrants[quad]
|
assert(len(point) == 2)
|
||||||
assert(minp is not None)
|
assert(quadrant is not None)
|
||||||
assert(len(minp) == 2)
|
assert(len(quadrant) == 2)
|
||||||
w = self.widths[quad]
|
|
||||||
maxp = tuple(i+w for i in minp)
|
box = self.as_box( quadrant )
|
||||||
box = (minp,maxp)
|
|
||||||
|
|
||||||
# if the point lies inside the given quadrant
|
# if the point lies inside the given quadrant
|
||||||
if geometry.in_box( p, box):
|
if geometry.in_box( point, box):
|
||||||
if self.occupants[quad]:
|
if self.residents[quadrant]:
|
||||||
# external: a quadrant that already contains a point
|
# external: a quadrant that already contains a point
|
||||||
assert( not self.children[quad] )
|
assert( not self.children[quadrant] )
|
||||||
# print("is external leaf")
|
# print("is external leaf")
|
||||||
return self.Status.Leaf
|
return self.Status.Leaf
|
||||||
elif self.children[quad]:
|
elif self.children[quadrant]:
|
||||||
# internal: a quadrant that contains other quadrants
|
# internal: a quadrant that contains other quadrants
|
||||||
# print("is internal node")
|
|
||||||
return self.Status.Node
|
return self.Status.Node
|
||||||
else:
|
else:
|
||||||
# empty: there is not point yet in this quadrant
|
# empty: there is not point yet in this quadrant
|
||||||
# print("is empty")
|
|
||||||
return self.Status.Empty
|
return self.Status.Empty
|
||||||
else:
|
else:
|
||||||
# point is out of the quadrant
|
# point is out of the quadrant
|
||||||
# print("is out")
|
|
||||||
return self.Status.Out
|
return self.Status.Out
|
||||||
|
|
||||||
|
|
||||||
def split(self, quadrant ):
|
def split(self, quadrant ):
|
||||||
"""Split an existing quadrant in four children quadrants.
|
"""Split an existing quadrant in four children quadrants.
|
||||||
|
|
||||||
Spread existing occupants to the children."""
|
Spread existing residents to the children."""
|
||||||
|
|
||||||
# We cannot split a quadrant if it already have sub-quadrants
|
# We cannot split a quadrant if it already have sub-quadrants
|
||||||
if quadrant != 0:
|
if quadrant != self.root:
|
||||||
assert( not self.children[quadrant] )
|
assert( not self.children[quadrant] )
|
||||||
|
|
||||||
qx, qy = self.quadrants[quadrant]
|
qx, qy = quadrant[0]
|
||||||
w = self.widths[quadrant] / 2
|
w = quadrant[1] / 2
|
||||||
|
|
||||||
# For each four children quadrant's origins
|
# For each four children quadrant's origins
|
||||||
|
self.children[quadrant] = []
|
||||||
for orig in ((qx,qy), (qx,qy+w), (qx+w,qy+w), (qx+w,qy)):
|
for orig in ((qx,qy), (qx,qy+w), (qx+w,qy+w), (qx+w,qy)):
|
||||||
|
q = (orig,w)
|
||||||
# Create a child quadrant of half its width
|
# Create a child quadrant of half its width
|
||||||
id = next_id( self.quadrants )
|
self.quadrants.append(q)
|
||||||
self.widths[id] = w
|
self.residents[q] = None
|
||||||
self.quadrants[id] = orig
|
|
||||||
self.occupants[id] = None
|
|
||||||
|
|
||||||
# add a new child to the current parent
|
# Add a new child to the current parent.
|
||||||
self.children[quadrant].append(id)
|
self.children[quadrant].append(q)
|
||||||
self.children[id] = []
|
# The new quadrant has no child.
|
||||||
|
self.children[q] = []
|
||||||
# Move the occupant to the related children node
|
|
||||||
p = self.occupants[quadrant]
|
|
||||||
if p is not None:
|
|
||||||
# Find the suitable children quadrant
|
|
||||||
for child in self.children[quadrant]:
|
|
||||||
if self.appendable(p,child) == self.Status.Empty:
|
|
||||||
self.occupants[child] = p
|
|
||||||
break
|
|
||||||
# Forget we had occupant here
|
|
||||||
# Do not pop the key, because we have tests on it elsewhere
|
|
||||||
self.occupants[quadrant] = None
|
|
||||||
|
|
||||||
assert( len(self.children[quadrant]) == 4 )
|
assert( len(self.children[quadrant]) == 4 )
|
||||||
|
|
||||||
|
# Move the resident to the related children node
|
||||||
|
p = self.residents[quadrant]
|
||||||
|
if p is not None:
|
||||||
|
# Find the suitable children quadrant
|
||||||
|
for child in self.children[quadrant]:
|
||||||
|
if self.status(p,child) == self.Status.Empty:
|
||||||
|
self.residents[child] = p
|
||||||
|
break
|
||||||
|
# Forget we had resident here
|
||||||
|
# Do not pop the key, because we have tests on it elsewhere
|
||||||
|
self.residents[quadrant] = None
|
||||||
|
|
||||||
def append( self, point, quadrant = 0 ):
|
|
||||||
|
|
||||||
|
def append( self, point, quadrant = None ):
|
||||||
"""Try to inset the given point in the existing quadtree, under the given quadrant.
|
"""Try to inset the given point in the existing quadtree, under the given quadrant.
|
||||||
|
|
||||||
The default quadrant is the root one (0).
|
The default quadrant is the root one.
|
||||||
Returns True if the point was appended, False if it is impossible to append it."""
|
Returns True if the point was appended, False if it is impossible to append it."""
|
||||||
|
|
||||||
|
# Default to the root quadrant
|
||||||
|
if not quadrant:
|
||||||
|
quadrant = self.root
|
||||||
assert(quadrant in self.quadrants)
|
assert(quadrant in self.quadrants)
|
||||||
# The point should not be out the root quadrant
|
|
||||||
assert( self.appendable(point,0) != self.Status.Out )
|
# The point should not be out of the root quadrant
|
||||||
|
assert( self.status(point,self.root) != self.Status.Out )
|
||||||
|
|
||||||
for q in self.walk(quadrant):
|
for q in self.walk(quadrant):
|
||||||
status = self.appendable( point, q )
|
status = self.status( point, q )
|
||||||
if status == self.Status.Leaf:
|
if status == self.Status.Leaf:
|
||||||
# Create sub-quadrants
|
# Create sub-quadrants
|
||||||
self.split(q)
|
self.split(q)
|
||||||
# Try to attach the point in children quadrants, recursively
|
# Try to attach the point in children quadrants, recursively
|
||||||
for child in self.children[q]:
|
for child in self.children[q]:
|
||||||
# print("consider children %i" % child)
|
|
||||||
if self.append( point, child ):
|
if self.append( point, child ):
|
||||||
return True
|
return True
|
||||||
elif status == self.Status.Empty:
|
elif status == self.Status.Empty:
|
||||||
# add the point as an occupant of the quadrant q
|
# add the point as an resident of the quadrant q
|
||||||
self.occupants[q] = point
|
self.residents[q] = point
|
||||||
# print("%s appended at %i" % (point,q))
|
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -165,10 +171,15 @@ class QuadTree(object):
|
||||||
"""append all the given points in the quadtree."""
|
"""append all the given points in the quadtree."""
|
||||||
for p in points:
|
for p in points:
|
||||||
self.append(p)
|
self.append(p)
|
||||||
# assert( len(points) == len(self) )
|
assert( len(points) == len(self) )
|
||||||
|
|
||||||
|
|
||||||
def iterative_walk(self, at_quad = 0 ):
|
def iterative_walk(self, at_quad = None ):
|
||||||
|
|
||||||
|
# Default to the root quadrant
|
||||||
|
if not at_quad:
|
||||||
|
at_quad = self.root
|
||||||
|
|
||||||
# First, consider the root quadrant
|
# First, consider the root quadrant
|
||||||
yield at_quad
|
yield at_quad
|
||||||
|
|
||||||
|
|
@ -180,32 +191,41 @@ class QuadTree(object):
|
||||||
quads.extend( self.children[child] )
|
quads.extend( self.children[child] )
|
||||||
|
|
||||||
|
|
||||||
def recursive_walk(self, at_quad = 0 ):
|
def recursive_walk(self, at_quad = None ):
|
||||||
|
|
||||||
|
# Default to the root quadrant
|
||||||
|
if not at_quad:
|
||||||
|
at_quad = self.root
|
||||||
|
|
||||||
yield at_quad
|
yield at_quad
|
||||||
for child in self.children[at_quad]:
|
for child in self.children[at_quad]:
|
||||||
for q in self.recursive_walk(child):
|
for q in self.recursive_walk(child):
|
||||||
yield q
|
yield q
|
||||||
|
|
||||||
|
|
||||||
def repr(self,quad=0,depth=0):
|
def repr(self, quad=None, depth=0):
|
||||||
|
|
||||||
|
# Default to the root quadrant
|
||||||
|
if not quad:
|
||||||
|
quad = self.root
|
||||||
|
|
||||||
head = " "*depth
|
head = " "*depth
|
||||||
r = head+"{"
|
r = head+"{"
|
||||||
quadrep = '"origin" : %s, "width" : %f' % (self.quadrants[quad],self.widths[quad])
|
quadrep = '"origin" : %s, "width" : %f' % quad
|
||||||
if self.occupants[quad]: # external
|
if self.residents[quad]: # external
|
||||||
r += ' "id" : %i, "occupant" : %s, \t%s },\n' % (quad,self.occupants[quad],quadrep)
|
r += ' "resident" : %s, \t%s },\n' % (self.residents[quad],quadrep)
|
||||||
elif self.children[quad]: # internal
|
elif self.children[quad]: # internal
|
||||||
r += ' "id" : %i, "children_ids" : %s, \t%s, "children" : [\n' % (quad,self.children[quad],quadrep)
|
r += ' "children_ids" : %s, \t%s, "children" : [\n' % (self.children[quad],quadrep)
|
||||||
for child in self.children[quad]:
|
for child in self.children[quad]:
|
||||||
r += self.repr(child, depth+1)
|
r += self.repr(child, depth+1)
|
||||||
r+="%s]},\n" % head
|
r+="%s]},\n" % head
|
||||||
else: # empty
|
else: # empty
|
||||||
r += ' "id" : %i, "occupant" : (), \t\t\t%s},\n' % (quad,quadrep)
|
r += ' "resident" : (), \t\t\t%s},\n' % (quadrep)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def points( self ):
|
def points( self ):
|
||||||
# return [self.occupants[q] for q in self.occupants if self.occupants[q] is not None]
|
return [p for p in self.residents.values() if p is not None]
|
||||||
return [p for p in self.occupants.values() if p is not None]
|
|
||||||
|
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
|
|
@ -242,12 +262,12 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
random.seed(seed)
|
random.seed(seed)
|
||||||
|
|
||||||
n=200
|
n=20
|
||||||
points = [ ( round(random.uniform(-n,n),2),round(random.uniform(-n,n),2) ) for i in range(n) ]
|
points = [ ( round(random.uniform(-n,n),2),round(random.uniform(-n,n),2) ) for i in range(n) ]
|
||||||
|
|
||||||
quad = QuadTree( points )
|
quad = QuadTree( points )
|
||||||
print(quad)
|
print(quad)
|
||||||
sys.stderr.write( "%i attached points / %i appended points\n" % (len(quad), len(points)) )
|
sys.stderr.write( "%i points in the quadtree / %i points\n" % (len(quad), len(points)) )
|
||||||
|
|
||||||
|
|
||||||
fig = plot.figure()
|
fig = plot.figure()
|
||||||
|
|
@ -255,11 +275,12 @@ if __name__ == "__main__":
|
||||||
ax = fig.add_subplot(111)
|
ax = fig.add_subplot(111)
|
||||||
ax.set_aspect('equal')
|
ax.set_aspect('equal')
|
||||||
uberplot.scatter_points( ax, points, facecolor="red", edgecolor="red")
|
uberplot.scatter_points( ax, points, facecolor="red", edgecolor="red")
|
||||||
uberplot.scatter_points( ax, quad.points(), facecolor="green", edgecolor="None")
|
# uberplot.scatter_points( ax, quad.points(), facecolor="green", edgecolor="None")
|
||||||
|
uberplot.scatter_points( ax, list(quad), facecolor="green", edgecolor="None")
|
||||||
|
|
||||||
for q in quad.quadrants:
|
for q in quad.quadrants:
|
||||||
qx, qy = quad.quadrants[q]
|
qx, qy = q[0]
|
||||||
w = quad.widths[q]
|
w = q[1]
|
||||||
box = [(qx,qy), (qx,qy+w), (qx+w,qy+w), (qx+w,qy)]
|
box = [(qx,qy), (qx,qy+w), (qx+w,qy+w), (qx+w,qy)]
|
||||||
edges = list( utils.tour(box) )
|
edges = list( utils.tour(box) )
|
||||||
uberplot.plot_segments( ax, edges, edgecolor = "blue", alpha = 0.1, linewidth = 2 )
|
uberplot.plot_segments( ax, edges, edgecolor = "blue", alpha = 0.1, linewidth = 2 )
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue