def range(self, event): """Initiate a range search using a selected rectangular region.""" p = (event.x, self.toCartesian(event.y)) if self.selectedRegion is None: self.selectedStart = Region(p[X], p[Y], p[X], p[Y]) self.selectedRegion = self.selectedStart.unionPoint(p) self.paint() # return (node,0,True) where status is True if draining entire tree rooted at node. If False, # then (rect,id,False). Draw these # as shaded red rectangle to identify whole sub-tree is selected. for triple in self.tree.range(self.selectedRegion): if triple[2]: r = triple[0].region self.canvas.create_rectangle(r.x_min, self.toTk(r.y_min), r.x_max, self.toTk(r.y_max), fill='Red', stipple='gray12') else: r = triple[0] self.canvas.create_rectangle(r.x_min, self.toTk(r.y_min), r.x_max, self.toTk(r.y_max), fill='Red')
def test_adding(self): print ("Starting generation."); for _ in range(25): print ("Added " + str(_)) self.quad.add((random.randint(25,225), random.randint(25,225))) print ("Done with generation."); # make sure not to overlap! q_ne = self.expand(Region(128, 128, 256, 256)) q_nw = self.expand(Region(0, 128, 128, 256)) q_sw = self.expand(Region(0, 0, 128, 128)) q_se = self.expand(Region(128, 0, 256, 128)) q_all = self.expand(Region(0,0, 256, 256)) # quick check to see if any were missing combined = q_ne + q_nw + q_sw + q_se for q in q_all: if q not in combined: print (q, " missing ") # check duplicates for i in range(len(combined)): for j in range(len(combined)): if j <= i: pass else: if combined[i] == combined[j]: print ("Duplicate:", combined[i]) self.assertEquals(len(q_all), len(combined))
def test_adding(self): for _ in range(5000): self.kd.add((random.randint(250,750), random.randint(250,750))) # make sure not to overlap! q_ne = self.expand(Region(500, 500, 1000, 1000)) q_nw = self.expand(Region(0, 500, 499, 1000)) q_sw = self.expand(Region(0, 0, 499, 499)) q_se = self.expand(Region(500, 0, 1000, 499)) q_all = self.expand(Region(0,0, 1000, 1000)) # quick check to see if any were missing combined = q_ne + q_nw + q_sw + q_se for q in q_all: if q not in combined: print (q, " missing ") self.assertEquals(len(q_all), len(combined)) # validate searches are true for p in combined: self.assertTrue(self.kd.find(p)) # validate can't add these points anymore for p in combined: self.assertFalse(self.kd.add(p))
def subdivide(self): """Add four children nodes to node and reassign existing circles.""" r = self.region self.children[NE] = QuadNode( Region(self.origin[X], self.origin[Y], r.x_max, r.y_max)) self.children[NW] = QuadNode( Region(r.x_min, self.origin[Y], self.origin[X], r.y_max)) self.children[SW] = QuadNode( Region(r.x_min, r.y_min, self.origin[X], self.origin[Y])) self.children[SE] = QuadNode( Region(self.origin[X], r.y_min, r.x_max, self.origin[Y])) # go through completely contained circles and try to push to lowest # children. If intersect 2 or more quadrants then we must keep. update = self.circles self.circles = [] for circle in update: quads = self.quadrants(circle) # If circle intersects multiple quadrants, must add to self, and mark # as MULTIPLE, otherwise only add to that individual quadrant if len(quads) == 1: self.children[quads[0]].add(circle) circle[MULTIPLE] = False else: self.circles.append(circle) circle[MULTIPLE] = True
def range(self, event): """Initiate a range search using a selected rectangular region.""" p = (event.x, self.toCartesian(event.y)) if self.selectedRegion is None: self.selectedStart = Region(p[X],p[Y], p[X],p[Y]) self.selectedRegion = self.selectedStart.unionPoint(p) self.paint() # return (node,status) where status is True if draining entire tree rooted at node. Draw these # all in Yellow using another inorder traversal for pair in self.tree.range(self.selectedRegion): if pair[1]: self.canvas.create_rectangle(pair[0].region.x_min, self.toTk(pair[0].region.y_min), pair[0].region.x_max, self.toTk(pair[0].region.y_max), fill='Red', stipple='gray12') else: p = pair[0][0] # ignore data and grab point self.canvas.create_rectangle(p[X] - RectangleSize, self.toTk(p[Y]) - RectangleSize, p[X] + RectangleSize, self.toTk(p[Y]) + RectangleSize, fill='Red') self.queryRect = self.canvas.create_rectangle(self.selectedRegion.x_min, self.toTk(self.selectedRegion.y_min), self.selectedRegion.x_max, self.toTk(self.selectedRegion.y_max), outline='Red', dash=(2, 4))
def test_expand(self): r = Region(10,20,50,60) r2 = Region(30,60,70,80) self.tree.add(r, 99) self.tree.add(r2, 101) self.assertEquals(Region(10,20,70,80), self.tree.root.region)
def subquadrant(self, quad): """Create QuadNode associated with sub-quadrant for parent region.""" r = self.region if quad == NE: return QuadNode(Region(self.origin[X], self.origin[Y], r.x_max, r.y_max)) elif quad == NW: return QuadNode(Region(r.x_min, self.origin[Y], self.origin[X], r.y_max)) elif quad == SW: return QuadNode(Region(r.x_min, r.y_min, self.origin[X], self.origin[Y])) elif quad == SE: return QuadNode(Region(self.origin[X], r.y_min, r.x_max, self.origin[Y]))
def subregion(self, quad): """Return region associated with given quadrant.""" r = self.region if quad is NE: return Region(self.origin[X], self.origin[Y], r.x_max, r.y_max) if quad is NW: return Region(r.x_min, self.origin[Y], self.origin[X], r.y_max) if quad is SW: return Region(r.x_min, r.y_min, self.origin[X], self.origin[Y]) if quad is SE: return Region(self.origin[X], r.y_min, r.x_max, self.origin[Y])
def test_tiling(self): # create evenly placed rectangles (s=16_ and validate queries for x in range(1,8): for y in range(1,8): self.tree.add(Region(x*64,y*64, x*64+32,y*64+32)) for x in range(1,5): for y in range(1,5): s = Region(x*64, y*64, x*64+255, y*64+127) # includes 2x4 selection or 8 match = self.expand(s) self.assertEquals(8, len(match))
def test_verticalSlices(self): # check case of vertical rectangles all in a row. This forces regular extensions. # Make sure bounding rectangles are computed properly. for x in range(1,50): self.tree.add(Region(x*6,10, x*6+3,100)) # sliding window always grabs eight of them since there is one rect every 6 pixels and query width is 47 for x in range(1, 40): s = Region(x*6, 2, x*6+47, 100) match = self.expand(s) self.assertEquals(8, len(match)) self.ensure_maxBounding(self.tree.root)
def test_split(self): r1 = Region(10,20,50,60) r2 = Region(30,60,70,80) r3 = Region(-30,60,70,80) r4 = Region(40,70,60,100) self.tree.add(r1, 99) self.tree.add(r2, 101) self.tree.add(r3, 103) self.tree.add(r4, 105) self.assertEquals(Region(-30,20,70,100), self.tree.root.region) r5 = Region(10,60,0,90) self.tree.add(r5, 107) self.assertEquals(Region(-30,20,70,100), self.tree.root.region) # finds all rectangles. r_ids = [] for triple in self.tree.range(Region(-100, -100, 200, 200)): if triple[2]: for d in triple[0].leafOrder(): r_ids.append(d[1]) else: r_ids.append(triple[1]) r_ids.sort() self.assertEquals ([99,101,103,105,107], r_ids)
def test_deleteOnlyOne(self): r = Region(10,20,50,60) self.tree.add(r, 99) self.assertTrue(self.tree.search(r)) self.assertEquals(Region(10,20,50,60), self.tree.root.region) # not in tree. self.assertFalse(self.tree.remove(Region(2,4,22,50))) self.assertTrue(self.tree.remove(r)) # Not present. self.assertTrue(self.tree.search(r) is None)
def test_deleteFive(self): r1 = Region(38,148 , 300,288) r2 = Region(164,384 , 432,428) r3 = Region(316,342 , 456,392) r4 = Region(12,242 , 172,484) r5 = Region(324,200 , 494,276) for r in [r1,r2,r3,r4,r5]: self.tree.add(r) for r in [r1,r2,r3,r4,r5]: self.assertTrue(self.tree.search(r)) self.tree.remove(r) self.assertFalse(self.tree.search(r)) self.assertTrue (self.tree.root == None)
def table(n): """Generate tables for book.""" items = [] for r in range(int(n**0.5)): for c in range(int(n**0.5)): items.append(Region(c * 10, r * 10, c * 10 + 7, r * 10 + 7)) conductTrial("non intersecting rectangles", items) items = [] for _ in range(n): items.append( Region(random.random(), random.random(), random.random(), random.random())) conductTrial("random rectangles in unit square", items)
def reset(self, event): """Reset to start state.""" self.tree = QuadTree( Region(0, 0, 512 // self.factor, 512 // self.factor)) self.canvas.delete(ALL) self.visit(self.tree.root) self.viz.clear()
def conductTrial(label, items): samples = 128 n = len(items) rt = RTree(m=2, M=4) for r in items: rt.add(r) box = rt.root.region width = box.x_max - box.x_min height = box.y_max - box.y_min # can compute density for any tree, samples at 128 random spots ct = 0 for _ in range(samples): x = box.x_min + random.random() * width y = box.y_min + random.random() * height for triple in rt.range(Region(x, y, x, y)): assert (triple[2] == False) ct += 1 print("Density", n, "{:.2f}%".format(100 * ct / (n * samples)), "intersect random point in ", str(box)) trials(n, items, BUILD, "Build") trials(n, items, SEARCH, "Search") trials(n, items, DELETE, "Delete")
def randomRectangle(): """Random rectangle in unit square whose width/height is no greater than 0.5""" xmin = 0.5 * random.random() ymin = 0.5 * random.random() xmax = xmin + 0.5 * random.random() ymax = ymin + 0.5 * random.random() return Region(xmin, ymin, xmax, ymax)
def zoom(self, key): """ Zoom in (+) and Zoom out (-) with key events. In rebuilding tree, some points may get lost when zooming in. """ factor = self.factor if key.char == '+': factor = min(factor * 2, 256) elif key.char == '-': factor = max(factor // 2, 1) if factor != self.factor: self.factor = factor self.canvas.delete(ALL) oldTree = self.tree self.tree = QuadTree(Region(0, 0, 512 // factor, 512 // factor)) self.master.title("Click to add/remove points: [0,0] - (" + str(512 // factor) + "," + str(512 // self.factor) + ")") for pt in oldTree: self.tree.add(pt) self.visit(self.tree.root) self.viz.plot(self.tree.root)
def updateLocations(self): """Move all circles, reconstruct QuadTree and repaint.""" if self.ship is None: self.init() # schedule next update if we are still playing if self.status == PLAYING: self.master.after(frameDelay, self.updateLocations) if self.tree.root is None: self.bullets = [] self.status = WON self.canvas.delete(BULLET) self.updateShip() return # check if any bullet has hit an asteroid. If so, remove and add two for idx in range(len(self.bullets) - 1, -1, -1): b = self.bullets[idx] for c in self.tree.collide(b): if b: del self.bullets[idx] b = None self.tree.remove(c) c[RADIUS] = c[RADIUS] // 2 if c[RADIUS] >= 8: # perturb to avoid unique coordinates c2 = [ c[X] + c[DY], c[Y], c[RADIUS], False, False, c[DY], c[DX] ] c3 = [ c[X] - c[DY], c[Y], c[RADIUS], False, False, -c[DY], c[DX] ] self.tree.add(c2) self.tree.add(c3) # Destroy tree each time and reinsert all circles with new locations nodes = self.tree.root.preorder() self.tree = QuadTree(Region(0, 0, 512, 512)) for n in nodes: for c in n.circles: # Move from one side to the other, top and bottom self.updateShape(c) self.tree.add(c) # Update our location and the bullets self.updateShip() self.updateBullets() # Final check to see if any asteroid intersects our ship for c in self.tree.collide(self.ship): self.status = DESTROYED # Redraw all asteroids self.canvas.delete(ASTEROID) self.visit(self.tree.root)
def setUp(self): self.qt = QuadTree(Region(0, 0, 1024, 1024)) self.qt.add((22, 40)) self.qt.add((13, 59)) self.qt.add((57, 37)) self.qt.add((43, 21)) self.qt.add((33, 11))
def setUp(self): self.qt = QuadTree(Region(0, 0, 1024, 1024)) self.qt.add([22, 40, 10, False, False]) self.qt.add([13, 59, 20, False, False]) self.qt.add([57, 37, 30, False, False]) self.qt.add([43, 21, 20, False, False]) self.qt.add([33, 11, 10, False, False])
def test_iterator(self): # rectangles have all even numbered coordinates for _ in range(500): self.tree.add(Region(2*random.randint(2,250), 2*random.randint(2,250), 2*random.randint(2,250), 2*random.randint(2,250))) count = 0 for _ in self.tree: count = count + 1 self.assertEquals(500, count)
def extendAdd(self, event): """End of range search.""" if self.newRegionStart: x = event.x y = self.toCartesian(event.y) self.newRegion = Region(x, y, x, y).unionPoint(self.newRegionStart) self.paint()
def test_basic(self): r = Region(10,20,50,60) self.tree.add(r, 99) for node,_,status in self.tree.range(r): self.assertTrue(status) for rect, ident in node.leafOrder(): self.assertEquals(99, ident) self.assertEquals(r, rect)
def test_delete(self): r = Region(10,20,50,60) r2 = Region(30,60,70,80) self.tree.add(r, 99) self.tree.add(r2, 101) self.assertTrue(self.tree.search(r)) self.assertTrue(self.tree.search(r2)) self.assertEquals(Region(10,20,70,80), self.tree.root.region) # not in tree. self.assertFalse(self.tree.remove(Region(2,4,22,50))) self.assertTrue(self.tree.remove(r2)) # one is in, other is out self.assertTrue(self.tree.search(r)) self.assertTrue(self.tree.search(r2) is None)
def pause(self, event): """Pause or Reset to start state (if already paused).""" if self.paused: self.tree = QuadTree(Region(0, 0, 512, 512)) self.canvas.delete(ALL) self.visit(self.tree.root) self.viz.clear() self.restart() else: self.paused = True self.master.title(QuadTreeFixedApp.pausedTitle)
def test_iteration_more(self): self.qt = QuadTree(Region(0, 0, 1024, 1024)) points = [] for _ in range(100): x = random.randint(0, 1021) y = random.randint(0, 1021) points.append((x, y)) self.qt.add((x, y)) for pt in self.qt: self.assertTrue(pt in points)
def test_adding(self): # rectangles have all even numbered coordinates. Doesn't really matter since # range returns regions that intersect target, rather than being wholly contained for _ in range(500): self.tree.add(Region(2*random.randint(2,250), 2*random.randint(2,250), 2*random.randint(2,250), 2*random.randint(2,250))) # make sure not to overlap! q_ne = self.expand(Region(125, 125, 501, 501)) q_nw = self.expand(Region(1, 125, 125, 501)) q_sw = self.expand(Region(1, 1, 125, 125)) q_se = self.expand(Region(125, 1, 511, 125)) q_all = self.expand(Region(1,1, 501, 501)) # quick check to see if any were missing combined = [] for r in q_ne + q_nw + q_sw + q_se: if r not in combined: combined.append(r) for q in q_all: if q not in combined: print (q, " missing ") # check duplicates for i in range(len(combined)): for j in range(len(combined)): if j <= i: pass else: if combined[i] == combined[j]: print ("Duplicate:", combined[i]) self.assertEquals(len(q_all), len(combined))
def test_remove_less_than_four(self): self.qt = QuadTree(Region(0, 0, 1024, 1024)) self.qt.add([22, 40, 10, False, False]) self.qt.add([13, 59, 20, False, False]) self.qt.add([57, 37, 30, False, False]) self.assertTrue(self.qt.remove([57, 37, 30])) self.assertTrue(self.qt.remove([13, 59, 20])) self.assertTrue(self.qt.remove([22, 40, 10])) self.assertFalse(self.qt.remove([57, 37, 30])) self.assertFalse(self.qt.remove([13, 59, 20])) self.assertFalse(self.qt.remove([22, 40, 10]))
def __init__(self, master): """App for creating Quad tree dynamically with fixed circles that detect collisions.""" master.title("Click to add fixed circles for QuadTree collision detection.") self.master = master # QuadTree holds the events self.tree = QuadTree(Region(0,0,512,512)) self.canvas = Canvas(master, width=512, height=512) self.canvas.bind("<Button-1>", self.click) self.canvas.bind("<Button-2>", self.reset) # needed for Mac self.canvas.bind("<Button-3>", self.reset) # this is PC self.canvas.pack()
def range(self, event): """Initiate a range search using a selected rectangular region.""" p = (event.x, self.toCartesian(event.y)) if self.selectedRegion is None: self.selectedStart = Region(p[X],p[Y], p[X],p[Y]) self.selectedRegion = self.selectedStart.unionPoint(p) self.paint() # return (node,0,True) where status is True if draining entire tree rooted at node. If False, # then (rect,id,False). Draw these # as shaded red rectangle to identify whole sub-tree is selected. for triple in self.tree.range(self.selectedRegion): if triple[2]: r = triple[0].region self.canvas.create_rectangle(r.x_min, self.toTk(r.y_min), r.x_max, self.toTk(r.y_max), fill='Red', stipple='gray12') else: r = triple[0] self.canvas.create_rectangle(r.x_min, self.toTk(r.y_min), r.x_max, self.toTk(r.y_max), fill='Red')
class KDTreeApp: def __init__(self): """App for creating KD tree dynamically and executing range queries.""" self.tree = KDTree() self.static = False # for range query self.selectedRegion = None self.queryRect = None self.master = tkinter.Tk() self.master.title('KD Tree Range Query Application') self.w = tkinter.Frame(self.master, width=410, height=410) self.canvas = tkinter.Canvas(self.w, width=400, height=400) self.paint() self.canvas.bind("<Button-1>", self.click) self.canvas.bind("<Motion>", self.moved) self.canvas.bind("<Button-3>", self.range) # when right mouse clicked self.canvas.bind("<ButtonRelease-3>", self.clear) self.canvas.bind("<B3-Motion>", self.range) # only when right mouse dragged self.w.pack() def toCartesian(self, y): """Convert tkinter point into Cartesian.""" return self.w.winfo_height() - y def toTk(self,y): """Convert Cartesian into tkinter point.""" if y == maxValue: return 0 tk_y = self.w.winfo_height() if y != minValue: tk_y -= y return tk_y def clear(self, event): """End of range search.""" self.selectedRegion = None self.paint() def range(self, event): """Initiate a range search using a selected rectangular region.""" p = (event.x, self.toCartesian(event.y)) if self.selectedRegion is None: self.selectedStart = Region(p[X],p[Y], p[X],p[Y]) self.selectedRegion = self.selectedStart.unionPoint(p) self.paint() # return (node,status) where status is True if draining entire tree rooted at node. Draw these # as shaded red rectangle to identify whole sub-tree is selected. for pair in self.tree.range(self.selectedRegion): p = pair[0].point if pair[1]: self.canvas.create_rectangle(pair[0].region.x_min, self.toTk(pair[0].region.y_min), pair[0].region.x_max, self.toTk(pair[0].region.y_max), fill='Red', stipple='gray12') else: self.canvas.create_rectangle(p[X] - RectangleSize, self.toTk(p[Y]) - RectangleSize, p[X] + RectangleSize, self.toTk(p[Y]) + RectangleSize, fill='Red') self.queryRect = self.canvas.create_rectangle(self.selectedRegion.x_min, self.toTk(self.selectedRegion.y_min), self.selectedRegion.x_max, self.toTk(self.selectedRegion.y_max), outline='Red', dash=(2, 4)) def moved(self, event): """Only here for static option.""" if self.static: self.paint() def click(self, event): """Add point to KDtree.""" p = (event.x, self.toCartesian(event.y)) self.tree.add(p) self.paint() def drawPartition (self, r, p, orient): """Draw partitioning line and points itself as a small square.""" if orient == VERTICAL: self.canvas.create_line(p[X], self.toTk(r.y_min), p[X], self.toTk(r.y_max)) else: xlow = r.x_min if r.x_min <= minValue: xlow = 0 xhigh = r.x_max if r.x_max >= maxValue: xhigh = self.w.winfo_width() self.canvas.create_line(xlow, self.toTk(p[Y]), xhigh, self.toTk(p[Y])) self.canvas.create_rectangle(p[X] - RectangleSize, self.toTk(p[Y]) - RectangleSize, p[X] + RectangleSize, self.toTk(p[Y]) + RectangleSize, fill='Black') def visit (self, n): """ Visit node to paint properly.""" if n == None: return self.drawPartition(n.region, n.point, n.orient) self.visit (n.below) self.visit (n.above) def prepare(self, event): """prepare to add points.""" if self.label: self.label.destroy() self.label = None self.canvas.pack() def paint(self): """Paint quad tree by visiting all nodes, or show introductory message.""" if self.tree.root: self.canvas.delete(tkinter.ALL) self.visit(self.tree.root) else: self.label = tkinter.Label(self.w, width=100, height = 40, text="Click To Add Points") self.label.bind("<Button-1>", self.prepare) self.label.pack()
class QuadTreeApp: def __init__(self): """App for creating Quad tree dynamically and executing range queries.""" self.tree = QuadTree(Region(0,0,W,H)) self.match = None # for range query self.selectedRegion = None self.queryRect = None self.master = tkinter.Tk() self.master.title('Quad Tree Range Application') self.w = tkinter.Frame(self.master, width=W, height=H) self.canvas = tkinter.Canvas(self.w, width=W, height=H) self.paint() self.canvas.bind("<Button-1>", self.click) self.canvas.bind("<Button-3>", self.range) # when right mouse clicked self.canvas.bind("<ButtonRelease-3>", self.clear) self.canvas.bind("<B3-Motion>", self.range) # only when right mouse dragged self.w.pack() def toCartesian(self, y): """Convert tkinter point into Cartesian""" return self.w.winfo_height() - y def toTk(self,y): """Convert Cartesian into tkinter point.""" if y == maxValue: return 0 tk_y = self.w.winfo_height() if y != minValue: tk_y -= y return tk_y def clear(self, event): """End of range search.""" self.selectedRegion = None self.paint() def range(self, event): """Initiate a range search using a selected rectangular region.""" p = (event.x, self.toCartesian(event.y)) if self.selectedRegion is None: self.selectedStart = Region(p[X],p[Y], p[X],p[Y]) self.selectedRegion = self.selectedStart.unionPoint(p) self.paint() # return (node,status) where status is True if draining entire tree rooted at node. Draw these # all in Yellow using another inorder traversal for pair in self.tree.range(self.selectedRegion): if pair[1]: self.canvas.create_rectangle(pair[0].region.x_min, self.toTk(pair[0].region.y_min), pair[0].region.x_max, self.toTk(pair[0].region.y_max), fill='Red', stipple='gray12') else: p = pair[0][0] # ignore data and grab point self.canvas.create_rectangle(p[X] - RectangleSize, self.toTk(p[Y]) - RectangleSize, p[X] + RectangleSize, self.toTk(p[Y]) + RectangleSize, fill='Red') self.queryRect = self.canvas.create_rectangle(self.selectedRegion.x_min, self.toTk(self.selectedRegion.y_min), self.selectedRegion.x_max, self.toTk(self.selectedRegion.y_max), outline='Red', dash=(2, 4)) def click(self, event): """Add point to QuadTree.""" p = (event.x, self.toCartesian(event.y)) self.tree.add(p) self.paint() def visit (self, node): """ Visit node to paint properly.""" if node == None: return if node.points is None: r = node.region self.canvas.create_rectangle(r.x_min, self.toTk(r.y_min), r.x_max, self.toTk(r.y_max)) self.canvas.create_line(r.x_min, self.toTk(node.origin[Y]), r.x_max, self.toTk(node.origin[Y]), fill='black', dash=(2, 4)) self.canvas.create_line(node.origin[X], self.toTk(r.y_min), node.origin[X], self.toTk(r.y_max), fill='black', dash=(2, 4)) for n in node.children: self.visit(n) else: for p in node.points: self.canvas.create_rectangle(p[X] - RectangleSize, self.toTk(p[Y]) - RectangleSize, p[X] + RectangleSize, self.toTk(p[Y]) + RectangleSize, fill='Black') def prepare(self, event): """prepare to add points.""" if self.label: self.label.destroy() self.label = None self.canvas.pack() def paint(self): """Paint Quad tree by visiting all nodes, or show introductory message.""" if self.tree.root: self.canvas.delete(tkinter.ALL) self.visit(self.tree.root) else: self.label = tkinter.Label(self.w, width=100, height = 40, text="Click To Add Points") self.label.bind("<Button-1>", self.prepare) self.label.pack()
class RTreeApp: def __init__(self): """App for creating R tree dynamically and executing range queries""" self.tree = RTree(m=2, M=4) self.ready = False # for range query self.selectedRegion = None self.newRegionStart = None self.newRegion = None # for identifiers self.counter = 0 self.master = tkinter.Tk() self.master.title('R Tree Range Query Application') self.w = tkinter.Frame(self.master, width=512, height=512) self.canvas = tkinter.Canvas(self.w, width=512, height=512) self.paint() self.canvas.bind("<Button-1>", self.startAdd) self.canvas.bind("<B1-Motion>", self.extendAdd) # only when right mouse dragged self.canvas.bind("<ButtonRelease-1>", self.endAdd) self.canvas.bind("<Button-3>", self.range) # when right mouse clicked self.canvas.bind("<ButtonRelease-3>", self.clear) self.canvas.bind("<B3-Motion>", self.range) # only when right mouse dragged self.w.pack() def startAdd(self, event): """End of range search.""" x = event.x y = self.toCartesian(event.y) self.newRegionStart = (x,y) def extendAdd(self, event): """End of range search.""" if self.newRegionStart: x = event.x y = self.toCartesian(event.y) self.newRegion = Region (x,y,x,y).unionPoint(self.newRegionStart) self.paint() def endAdd(self, event): """End of range search.""" if self.newRegionStart: self.newRegionStart = None self.counter += 1 if self.newRegion: self.tree.add(self.newRegion, str(self.counter)) self.newRegion = None self.paint() def toCartesian(self, y): """Convert tkinter point into Cartesian.""" return self.w.winfo_height() - y def toTk(self,y): """Convert Cartesian into tkinter point.""" if y == maxValue: return 0 tk_y = self.w.winfo_height() if y != minValue: tk_y -= y return tk_y def clear(self, event): """End of range search.""" self.selectedRegion = None self.paint() def range(self, event): """Initiate a range search using a selected rectangular region.""" p = (event.x, self.toCartesian(event.y)) if self.selectedRegion is None: self.selectedStart = Region(p[X],p[Y], p[X],p[Y]) self.selectedRegion = self.selectedStart.unionPoint(p) self.paint() # return (node,0,True) where status is True if draining entire tree rooted at node. If False, # then (rect,id,False). Draw these # as shaded red rectangle to identify whole sub-tree is selected. for triple in self.tree.range(self.selectedRegion): if triple[2]: r = triple[0].region self.canvas.create_rectangle(r.x_min, self.toTk(r.y_min), r.x_max, self.toTk(r.y_max), fill='Red', stipple='gray12') else: r = triple[0] self.canvas.create_rectangle(r.x_min, self.toTk(r.y_min), r.x_max, self.toTk(r.y_max), fill='Red') def click(self, event): """Add point to KDtree.""" p = (event.x, self.toCartesian(event.y)) self.tree.add(p) self.paint() def visit (self, n): """ Visit node to paint properly.""" if n == None: return if n.level == 0: for idx in range(n.count): r = n.children[idx].region self.canvas.create_rectangle(r.x_min, self.toTk(r.y_min), r.x_max, self.toTk(r.y_max), fill='Gray') for idx in range(n.count): self.visit(n.children[idx]) # Do after all children so we can see interior nodes, too. r = n.region self.canvas.create_rectangle(r.x_min, self.toTk(r.y_min), r.x_max, self.toTk(r.y_max), outline='Black', dash=(2, 4)) color = 'Gray' if n.level == 0: color='Black' self.canvas.create_text(r.x_max - 16*len(n.id), self.toTk(r.y_max) + 16, anchor = tkinter.W, font = "Times 16 bold", fill=color, text=n.id) def prepare(self, event): """prepare to add points.""" if self.label: self.label.destroy() self.label = None self.canvas.pack() def paint(self): """Paint R tree by visiting all nodes, or show introductory message.""" if self.ready: self.canvas.delete(tkinter.ALL) self.visit(self.tree.root) if self.newRegion: self.canvas.create_rectangle(self.newRegion.x_min, self.toTk(self.newRegion.y_min), self.newRegion.x_max, self.toTk(self.newRegion.y_max), outline='Black', dash=(2, 4)) if self.selectedRegion: self.canvas.create_rectangle(self.selectedRegion.x_min, self.toTk(self.selectedRegion.y_min), self.selectedRegion.x_max, self.toTk(self.selectedRegion.y_max), outline='Red', dash=(2, 4)) else: self.label = tkinter.Label(self.w, width=100, height = 40, text="Click To Add Points") self.label.bind("<Button-1>", self.prepare) self.label.pack() self.ready = True