From 54b5d9f5d682c0831473e2687fe056c38a2ab820 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 16 Mar 2025 13:35:51 +0100 Subject: [PATCH] WIP (quad tree) --- src/db/db/dbQuadTree.h | 156 +++++++++++++++++----- src/db/unit_tests/dbQuadTreeTests.cc | 187 ++++++++++++++++++++++++++- 2 files changed, 310 insertions(+), 33 deletions(-) diff --git a/src/db/db/dbQuadTree.h b/src/db/db/dbQuadTree.h index e3ab61ef2..353f7ddcd 100644 --- a/src/db/db/dbQuadTree.h +++ b/src/db/db/dbQuadTree.h @@ -24,6 +24,7 @@ #define HDR_dbQuadTree #include "dbBox.h" +#include "tlLog.h" #include namespace db @@ -38,6 +39,7 @@ public: typedef db::point point_type; typedef db::vector vector_type; typedef std::vector objects_vector; + typedef db::coord_traits coord_traits; quad_tree_node (const point_type ¢er) : m_split (false), m_center (center) @@ -65,23 +67,31 @@ public: insert (value, propose_ucenter (total_box)); } - bool remove (const T &value) + bool erase (const T &value) { box_type b = BC () (value); - if (! m_split || b.contains (m_center)) { + int n = quad_for (b); + + if (! m_split || n < 0) { + for (auto i = m_objects.begin (); i != m_objects.end (); ++i) { if (*i == value) { m_objects.erase (i); return true; } } - } - for (unsigned int i = 0; i < 4; ++i) { - if (m_q[i] && b.inside (m_q[i]->box (m_center))) { - return b.remove (value); + } else if (m_q[n]) { + + if (m_q[n]->erase (value)) { + if (m_q[n]->empty ()) { + delete m_q[n]; + m_q[n] = 0; + } + return true; } + } return false; @@ -131,12 +141,39 @@ public: return count; } + size_t levels () const + { + size_t l = 1; + for (unsigned int n = 0; n < 4; ++n) { + if (m_q[n]) { + l = std::max (l, m_q[n]->levels () + 1); + } + } + return l; + } + + bool check_top (const box_type &total_box) const + { + return check (propose_ucenter (total_box)); + } + private: bool m_split; point_type m_center; quad_tree_node *m_q [4]; objects_vector m_objects; + int quad_for (const box_type &box) const + { + int sx = coord_traits::less (box.right (), m_center.x ()) ? 0 : (coord_traits::less (m_center.x (), box.left ()) ? 1 : -1); + int sy = coord_traits::less (box.top (), m_center.y ()) ? 0 : (coord_traits::less (m_center.y (), box.bottom ()) ? 2 : -1); + if (sx < 0 || sy < 0) { + return -1; + } else { + return sx + sy; + } + } + box_type box (const point_type &ucenter) const { return box_type (ucenter, ucenter - (ucenter - m_center) * 2.0); @@ -185,33 +222,24 @@ private: } box_type b = BC () (value); - // @@@ should exclude m_center on box - if (b.contains (m_center)) { + int n = quad_for (b); + + if (n < 0) { m_objects.push_back (value); return; } - for (unsigned int i = 0; i < 4; ++i) { - box_type bq = q (i, ucenter); - if (b.inside (bq)) { - if (! m_q[i]) { - m_q[i] = new quad_tree_node (bq.center ()); - } - m_q[i]->insert (value, m_center); - return; + if (b.inside (box (ucenter))) { + if (! m_q[n]) { + box_type bq = q (n, ucenter); + m_q[n] = new quad_tree_node (bq.center ()); } + m_q[n]->insert (value, m_center); + } else { + grow (m_center - (m_center - ucenter) * 2.0); + insert (value, ucenter); } - for (unsigned int i = 0; i < 4; ++i) { - if (m_q[i]) { - grow (m_center - (m_center - m_q[i]->center ()) * 2.0); - insert (value, ucenter); - return; - } - } - - tl_assert (false); - } } @@ -238,6 +266,61 @@ private: coord_type dy = std::max (std::abs (total_box.bottom () - m_center.y ()), std::abs (total_box.top () - m_center.y ())); return m_center - vector_type (dx, dy); } + + bool check (const point_type &ucenter) const + { + bool result = true; + + box_type bq = box (ucenter); + + for (auto i = m_objects.begin (); i != m_objects.end (); ++i) { + box_type b = BC () (*i); + if (! b.inside (bq)) { + tl::error << "Box " << b.to_string () << " not inside quad box " << bq.to_string (); + result = false; + } + } + + if (m_split) { + + for (auto i = m_objects.begin (); i != m_objects.end (); ++i) { + box_type b = BC () (*i); + int n = quad_for (b); + if (n >= 0) { + tl::error << "Box " << b.to_string () << " on quad level not overlapping multiple quads"; + result = false; + } + } + + for (unsigned int n = 0; n < 4; ++n) { + if (m_q[n]) { + m_q[n]->check (m_center); + box_type bbq = m_q[n]->box (m_center); + if (bbq != q (n, ucenter)) { + tl::error << "Quad not centered (quad box is " << bbq.to_string () << ", should be " << q (n, ucenter).to_string (); + result = false; + } + } + } + + } else { + + if (m_objects.size () > thr) { + tl::error << "Non-split object count exceeds threshold " << m_objects.size () << " > " << thr; + result = false; + } + + for (unsigned int n = 0; n < 4; ++n) { + if (m_q[n]) { + tl::error << "Non-split node has child nodes"; + result = false; + } + } + + } + + return result; + } }; template @@ -315,9 +398,9 @@ public: } } - while (! m_stack.empty ()) { + m_stack.pop_back (); - m_stack.pop_back (); + while (! m_stack.empty ()) { int &n = m_stack.back ().second; while (++n < 4) { @@ -329,6 +412,8 @@ public: } } + m_stack.pop_back (); + } } }; @@ -415,6 +500,7 @@ private: box_type m_box; }; +// @@@ TODO: copy, assignment, move, swap template class quad_tree { @@ -445,6 +531,16 @@ public: return m_root.size (); } + size_t levels () const + { + return m_root.levels (); + } + + bool check () const + { + return m_root.check_top (m_total_box); + } + void insert (const T &value) { box_type b = BC () (value); @@ -456,9 +552,9 @@ public: m_root.insert_top (value, m_total_box); } - void erase (const T &value) + bool erase (const T &value) { - m_root.remove (value); + return m_root.erase (value); } quad_tree_flat_iterator begin () const diff --git a/src/db/unit_tests/dbQuadTreeTests.cc b/src/db/unit_tests/dbQuadTreeTests.cc index fd74e164b..62eec169e 100644 --- a/src/db/unit_tests/dbQuadTreeTests.cc +++ b/src/db/unit_tests/dbQuadTreeTests.cc @@ -28,6 +28,18 @@ typedef db::quad_tree, size_t (1)> my_quad_tree; +std::string find_all (const my_quad_tree &qt) +{ + std::vector v; + auto i = qt.begin (); + while (! i.at_end ()) { + v.push_back (i->to_string ()); + ++i; + } + std::sort (v.begin (), v.end ()); + return tl::join (v, "/"); +} + std::string find_touching (const my_quad_tree &qt, const db::DBox &box) { std::vector v; @@ -40,6 +52,20 @@ std::string find_touching (const my_quad_tree &qt, const db::DBox &box) return tl::join (v, "/"); } +std::string find_touching_from_all (const my_quad_tree &qt, const db::DBox &box) +{ + std::vector v; + auto i = qt.begin (); + while (! i.at_end ()) { + if (i->touches (box)) { + v.push_back (i->to_string ()); + } + ++i; + } + std::sort (v.begin (), v.end ()); + return tl::join (v, "/"); +} + std::string find_overlapping (const my_quad_tree &qt, const db::DBox &box) { std::vector v; @@ -52,26 +78,181 @@ std::string find_overlapping (const my_quad_tree &qt, const db::DBox &box) return tl::join (v, "/"); } +std::string find_overlapping_from_all (const my_quad_tree &qt, const db::DBox &box) +{ + std::vector v; + auto i = qt.begin (); + while (! i.at_end ()) { + if (i->overlaps (box)) { + v.push_back (i->to_string ()); + } + ++i; + } + std::sort (v.begin (), v.end ()); + return tl::join (v, "/"); +} + TEST(basic) { my_quad_tree tree; EXPECT_EQ (tree.empty (), true); EXPECT_EQ (tree.size (), size_t (0)); + EXPECT_EQ (tree.check (), true); + EXPECT_EQ (tree.levels (), size_t (1)); tree.insert (db::DBox ()); EXPECT_EQ (tree.empty (), true); EXPECT_EQ (tree.size (), size_t (0)); + EXPECT_EQ (tree.check (), true); + EXPECT_EQ (tree.levels (), size_t (1)); tree.insert (db::DBox (-1, -2, 3, 4)); EXPECT_EQ (tree.empty (), false); EXPECT_EQ (tree.size (), size_t (1)); + EXPECT_EQ (tree.check (), true); + EXPECT_EQ (tree.levels (), size_t (1)); - EXPECT_EQ (find_touching (tree, db::DBox (-2, 0, -1, 0)), "..."); + EXPECT_EQ (find_all (tree), "(-1,-2;3,4)"); + + db::DBox bx; + + bx = db::DBox (-2, 0, -1, 0); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, -3, -1, -2); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, -3, -1, -2.5); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, 4, -1, 5); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, 4.5, -1, 5); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, 3, -1, 5); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, 3, -1.5, 5); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); tree.insert (db::DBox (-1, -3, 3, 0)); EXPECT_EQ (tree.empty (), false); - EXPECT_EQ (tree.size (), size_t (1)); + EXPECT_EQ (tree.size (), size_t (2)); + EXPECT_EQ (tree.check (), true); + EXPECT_EQ (tree.levels (), size_t (1)); - EXPECT_EQ (find_touching (tree, db::DBox (-2, 0, -1, 0)), "..."); + EXPECT_EQ (find_all (tree), "(-1,-2;3,4)/(-1,-3;3,0)"); + + bx = db::DBox (-2, 0, -1, 0); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, -3, -1, -2); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, -3, -1, -2.5); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, 4, -1, 5); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, 4.5, -1, 5); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, 3, -1, 5); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, 3, -1.5, 5); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + + tree.insert (db::DBox (-1, -3, -0.5, -2)); + EXPECT_EQ (tree.empty (), false); + EXPECT_EQ (tree.size (), size_t (3)); + EXPECT_EQ (tree.check (), true); + EXPECT_EQ (tree.levels (), size_t (2)); + + EXPECT_EQ (find_all (tree), "(-1,-2;3,4)/(-1,-3;-0.5,-2)/(-1,-3;3,0)"); + + bx = db::DBox (-2, 0, -1, 0); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, -3, -1, -2); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, -3, -1, -2.5); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, 4, -1, 5); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, 4.5, -1, 5); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, 3, -1, 5); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, 3, -1.5, 5); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + + tree.insert (db::DBox (-1, -3, -0.5, 2)); + EXPECT_EQ (tree.empty (), false); + EXPECT_EQ (tree.size (), size_t (4)); + EXPECT_EQ (tree.check (), true); + EXPECT_EQ (tree.levels (), size_t (2)); + + EXPECT_EQ (find_all (tree), "(-1,-2;3,4)/(-1,-3;-0.5,-2)/(-1,-3;-0.5,2)/(-1,-3;3,0)"); + + bx = db::DBox (-2, 0, -1, 0); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, -3, -1, -2); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, -3, -1, -2.5); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, 4, -1, 5); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, 4.5, -1, 5); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, 3, -1, 5); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); + bx = db::DBox (-2, 3, -1.5, 5); + EXPECT_EQ (find_touching (tree, bx), find_touching_from_all (tree, bx)); + EXPECT_EQ (find_overlapping (tree, bx), find_overlapping_from_all (tree, bx)); +} + +TEST(remove) +{ + my_quad_tree tree; + tree.insert (db::DBox (-1, -2, 3, 4)); + tree.insert (db::DBox (-1, -3, 3, 0)); + tree.insert (db::DBox (-1, -3, -0.5, -2)); + tree.insert (db::DBox (-1, -3, -0.5, 2)); + + EXPECT_EQ (tree.check (), true); + + EXPECT_EQ (find_all (tree), "(-1,-2;3,4)/(-1,-3;-0.5,-2)/(-1,-3;-0.5,2)/(-1,-3;3,0)"); + + EXPECT_EQ (tree.erase (db::DBox (-1, -3, -0.5, -1)), false); + EXPECT_EQ (tree.erase (db::DBox (-1, -3, -0.5, -2)), true); + EXPECT_EQ (tree.check (), true); + + EXPECT_EQ (find_all (tree), "(-1,-2;3,4)/(-1,-3;-0.5,2)/(-1,-3;3,0)"); + + while (! tree.empty ()) { + EXPECT_EQ (tree.erase (*tree.begin ()), true); + EXPECT_EQ (tree.check (), true); + } + + EXPECT_EQ (tree.size (), size_t (0)); + EXPECT_EQ (tree.levels (), size_t (1)); }