def split_leaf(self): """ Splits the given RTreeNode into 2 separate Nodes Then moves its entries into one of the two nodes """ if len(self.children) > 0: raise NotImplementedError('[DEBUG] Unexpected behavior') # 1. Find the two most far-apart objects max_distance = -1 max_obj_a, max_obj_b = None, None for idx, entry in enumerate(self.entries): for idx_2 in range(len(self.entries)): if idx == idx_2: continue other_entry = self.entries[idx_2] dist = entry.mbr.distance_between(other_entry.mbr) if dist > max_distance: max_distance = dist max_obj_a = entry max_obj_b = other_entry # 2. Create two new nodes to accommodate the new objects node_a = self.__class__(min_order=self.minimum_order, max_order=self.maximum_order, mbr=Rectangle.containing(max_obj_a.mbr)) node_a.add(max_obj_a) node_b = self.__class__(min_order=self.minimum_order, max_order=self.maximum_order, mbr=Rectangle.containing(max_obj_b.mbr)) node_b.add(max_obj_b) # 3. Move rest of entries to appropriate nodes rest_entries = [entry for entry in self.entries if entry != max_obj_a and entry != max_obj_b] for idx, entry in enumerate(rest_entries): rest_entry_count = len(rest_entries) - idx needed_a_entries = node_a.minimum_order - len(node_a.entries) needed_b_entries = node_b.minimum_order - len(node_b.entries) if rest_entry_count == needed_a_entries: node_a.add(entry) # All the rest should go to A elif rest_entry_count == needed_b_entries: node_b.add(entry) # All the rest should go to B else: # Put it in the one whose MBR requires least expansion # TODO: Handle case where both bound entry node: 'RTreeNode' = self.find_min_expansion_node([node_a, node_b], entry) node.add(entry) # 4. Expand node's MBR to fir their biggest for node in [node_a, node_b]: for entry in node.entries: entry: Entry if not node.mbr.is_bounding(entry.mbr): node.mbr.expand_to(entry.mbr) return node_a, node_b
def test_add_without_root_should_add_root(self): entry_bounds = Rectangle(Point(10, 10), Point(20, 0)) entry = Entry(name='Tank', bounds=entry_bounds) r_tree = RTree(2, 4) r_tree.add(entry) self.assertIsNotNone(r_tree.root) self.assertIsInstance(r_tree.root, RTree.RTreeNode) self.assertEqual(r_tree.root.mbr, Rectangle.containing(entry_bounds)) self.assertEqual(len(r_tree.root.entries), 1) self.assertEqual(r_tree.root.entries[0], entry)
def test_containing(self): """ The class method containing() should return a Rectangle object that can contain the passed rectangle """ expected_tl = Point(self.rect_a.top_left.x - Point.MOVE_DISTANCE, self.rect_a.top_left.y + Point.MOVE_DISTANCE) expected_br = Point(self.rect_a.bottom_right.x + Point.MOVE_DISTANCE, self.rect_a.bottom_right.y - Point.MOVE_DISTANCE) expected_rectangle = Rectangle(top_left=expected_tl, bottom_right=expected_br) self.assertEqual(expected_rectangle, Rectangle.containing(self.rect_a))
def add(self, object: Entry): if self.root is None: self.root: self.RTreeNode = self.RTreeNode(mbr=Rectangle.containing(object.mbr), min_order=self.minimum_order, max_order=self.maximum_order) self.root.add(object)