Code Monkey home page Code Monkey logo

quadtree's Introduction

crates.io badge docs.rs badge license

Point/region Quadtree with support for overlapping regions.

For documentation, see docs.rs/quadtree_rs.

Quick Start

use quadtree_rs::{area::AreaBuilder, point::Point, Quadtree};

// Instantiate a new quadtree which associates String values with u64
// coordinates.
let mut qt = Quadtree::<u64, String>::new(/*depth=*/4);

// A depth of four means a square with width (and height) 2^4.
assert_eq!(qt.width(), 16);

// Associate the value "foo" with a rectangle of size 2x1, anchored at (0, 0).
let region_a = AreaBuilder::default()
    .anchor(Point {x: 0, y: 0})
    .dimensions((2, 1))
    .build().unwrap();
qt.insert(region_a, "foo".to_string());

// Query over a region of size 2x2, anchored at (1, 0).
let region_b = AreaBuilder::default()
    .anchor(Point {x: 1, y: 0})
    .dimensions((2, 2))
    .build().unwrap();
let mut query = qt.query(region_b);

// The query region (region_b) intersects the region "foo" is associated with
// (region_a), so the query iterator returns "foo" by reference.
assert_eq!(query.next().unwrap().value_ref(), "foo");

Questions?

Please file an issue on GitHub.

Authors

See Cargo.toml.

Contributing

See CONTRIBUTING.md and NOTES.md

License

This project is licensed under the Apache 2.0 license.

Disclaimer

This is not an official Google product.

TODO

  • Pretty-print quadtree function which plots a density map
  • Benchmark tests

quadtree's People

Contributors

ambuc avatar armandburger avatar askandera avatar nosuchthingasrandom avatar tooxo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

quadtree's Issues

Noob question regarding region sizes

Hi, please forgive my naivety here, I am new to Rust & quadtrees.

If I understand your docs correctly, the shape of the space must start at 0,0. So, using bevy engine which uses a graphing coordinate system where -,- is bottom left of screen, I just need to translate that so that -,. is 0,0 in your system. That seems fine.

I'm going to have regular polygonal objects moving across the entire screen space. (Think: A circle or a square). Should I use the sizes for those as pixel sizes, and the total size of the Tree space as the entire range of the window pixel size?

The examples you gave, which are thoughtful, use relatively low numbers (going up to 4,4) compared to what a full screen will use.

Or is the idea that I should start with 4,4 mapping to the full screen width and the library subdivides from there?

Stack overflow in Query::next

After inserting a large amount of points into a quadtree, making queries can cause a stack overflow when iterating through results. I've cut down to a reproducing example here:

use quadtree_rs::{area::AreaBuilder, point::Point, Quadtree};

// Insert regions into the quadtree in this pattern:
// +------+------+------+------+
// |      |      |      |      |
// |      |      |      |      |
// +------+------+------+------+
// |      |             |      |
// |      |             |      |
// +------+  (recurse)  +------+
// |      |             |      |
// |      |             |      |
// +------+------+------+------+
// |      |      |      |      |
// |      |      |      |      |
// +------+------+------+------+
// then recurse into the centre, and repeat.
fn prepare_quadtree(tree: &mut Quadtree<usize, ()>) {
    let mut step_size = 2usize.pow(tree.depth() as u32) / 4;
    let mut x = 0;
    let mut y = 0;
    while step_size > 0 {
        for i in 0..4 {
            for j in 0..4 {
                if i == 0 || i == 3 || j == 0 || j == 3 {
                    tree.insert(
                        AreaBuilder::default()
                            .anchor(Point {
                                x: x + i * step_size,
                                y: y + j * step_size,
                            })
                            .dimensions((step_size, step_size))
                            .build()
                            .unwrap(),
                        (),
                    );
                }
            }
        }

        x += step_size;
        y += step_size;
        step_size /= 2;
    }
}

fn main() {
    let mut tree = Quadtree::new(20);
    for _ in 0..32 {
        prepare_quadtree(&mut tree);
    }

    let result = tree
        .query_strict(
            AreaBuilder::default()
                .anchor(Point {
                    x: tree.width() / 2 - 1,
                    y: tree.height() / 2 - 1,
                })
                .dimensions((2, 2))
                .build()
                .unwrap(),
        )
        .next();
    println!("{:?}", result);
}

Expected output:

None

Actual output:

$ cargo run --release
    Finished release [optimized] target(s) in 0.07s
     Running `target\release\quadtree-test.exe`

thread 'main' has overflowed its stack
error: process didn't exit successfully: `target\release\quadtree-test.exe` (exit code: 0xc00000fd, STATUS_STACK_OVERFLOW)

My active toolchain:

stable-x86_64-pc-windows-msvc (default)
rustc 1.46.0 (04488afe3 2020-08-24)

Looking at it through a debugger, it looks like the problem is that Rust can't figure out Query::next is tail-recursive:

#[inline]
fn next(&mut self) -> Option<Self::Item> {
    if let Some(handle) = self.handle_iter.next() {
        if let Some(entry) = self.store.get(&handle) {
            if self.traversal_method.eval(entry.area(), self.query_region) {
                return Some(entry);
            }
        }
        return self.next();
    }
    None
}

I'm not sure why, since it looks like all this function is dealing with is references and u64s. Maybe the Options confuse it?
In any case, replacing it with a semantically-equivalent iterative version fixes the issue:

#[inline]
fn next(&mut self) -> Option<Self::Item> {
    while let Some(handle) = self.handle_iter.next() {
        if let Some(entry) = self.store.get(&handle) {
            if self.traversal_method.eval(entry.area(), self.query_region) {
                return Some(entry);
            }
        }
    }
    None
}

By the way, the pattern of regions I used for the example seems to demonstrate the worst case for HandlerIter, because HandleIter::query_optimization can't optimise at all since the query area spans all four subquadrants of the root, and the behaviour of HandleIter::next() appears to be to evaluate every single handle past that, which in this case means going through literally every single subquadrant andhandle in the entire tree.

Surely this could be improved by HandleIter skipping over subquadrants that do not overlap with the query area at all? Then HandleIter::query_optimization shouldn't even be needed.

Query and Entry API changes

After pondering this for a while, I've come to realisation that what we call Entry is a distraction, and in fact, Query is the closest we have to HashMap's Entry.
This is because while HashMap returns Entry in response to it's key, we query using Areas and return Query in response. It would make sense therefore to make Query an enum with an Empty variant.

Another question is if changes to how we store data are needed. One thought i've had is that SlotMap<Handle, V> and an associated SecondaryMap<Handle, Area> might be a good solution.

Panic with depths greater than 31

Starting with depth values equal to or larger than 31, the following panic occurs:

thread 'main' panicked at 'attempt to multiply with overflow', /rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c\library\core\src\num\mod.rs:210:5
stack backtrace:
   0: std::panicking::begin_panic_handler
             at /rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c\/library\std\src\panicking.rs:584
   1: core::panicking::panic_fmt
             at /rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c\/library\core\src\panicking.rs:143
   2: core::panicking::panic
             at /rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c\/library\core\src\panicking.rs:48
   3: core::num::impl$2::pow
             at /rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c\library\core\src\num\int_macros.rs:1927
   4: num_traits::int::impl$8::pow
             at C:\Users\michi\.cargo\registry\src\github.com-1ecc6299db9ec823\num-traits-0.2.15\src\int.rs:492
   5: quadtree_rs::qtinner::QTInner<i32>::new<i32>
             at C:\Users\michi\.cargo\registry\src\github.com-1ecc6299db9ec823\quadtree_rs-0.1.2\src\qtinner.rs:76
   6: quadtree_rs::Quadtree<i32,enum$<citypol_server::map::Building, House> >::new_with_anchor<i32,enum$<citypol_server::map::Building, House> >
             at C:\Users\michi\.cargo\registry\src\github.com-1ecc6299db9ec823\quadtree_rs-0.1.2\src\lib.rs:247
   7: quadtree_rs::Quadtree<i32,enum$<citypol_server::map::Building, House> >::new<i32,enum$<citypol_server::map::Building, House> >
             at C:\Users\michi\.cargo\registry\src\github.com-1ecc6299db9ec823\quadtree_rs-0.1.2\src\lib.rs:217
   8: citypol_server::map::Map::new
             at .\src\map.rs:45
   9: citypol_server::GameState::new
             at .\src\main.rs:55
  10: citypol_server::main::async_block$0
             at .\src\main.rs:117
  11: core::future::from_generator::impl$1::poll<citypol_server::main::async_block_env$0>
             at /rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c\library\core\src\future\mod.rs:84
  12: tokio::park::thread::impl$5::block_on::closure$0<core::future::from_generator::GenFuture<citypol_server::main::async_block_env$0> >
             at C:\Users\michi\.cargo\registry\src\github.com-1ecc6299db9ec823\tokio-1.18.2\src\park\thread.rs:263
  13: tokio::coop::with_budget::closure$0<enum$<core::task::poll::Poll<tuple$<> > >,tokio::park::thread::impl$5::block_on::closure_env$0<core::future::from_generator::GenFuture<citypol_server::main::async_block_env$0> > >
             at C:\Users\michi\.cargo\registry\src\github.com-1ecc6299db9ec823\tokio-1.18.2\src\coop.rs:102
  14: std::thread::local::LocalKey<core::cell::Cell<tokio::coop::Budget> >::try_with<core::cell::Cell<tokio::coop::Budget>,tokio::coop::with_budget::closure_env$0<enum$<core::task::poll::Poll<tuple$<> > >,tokio::park::thread::impl$5::block_on::closure_env$0<cor
             at /rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c\library\std\src\thread\local.rs:413
  15: std::thread::local::LocalKey<core::cell::Cell<tokio::coop::Budget> >::with<core::cell::Cell<tokio::coop::Budget>,tokio::coop::with_budget::closure_env$0<enum$<core::task::poll::Poll<tuple$<> > >,tokio::park::thread::impl$5::block_on::closure_env$0<core::f
             at /rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c\library\std\src\thread\local.rs:389
  16: tokio::coop::with_budget
             at C:\Users\michi\.cargo\registry\src\github.com-1ecc6299db9ec823\tokio-1.18.2\src\coop.rs:95
  17: tokio::coop::budget
             at C:\Users\michi\.cargo\registry\src\github.com-1ecc6299db9ec823\tokio-1.18.2\src\coop.rs:72
  18: tokio::park::thread::CachedParkThread::block_on<core::future::from_generator::GenFuture<citypol_server::main::async_block_env$0> >
             at C:\Users\michi\.cargo\registry\src\github.com-1ecc6299db9ec823\tokio-1.18.2\src\park\thread.rs:263
  19: tokio::runtime::enter::Enter::block_on<core::future::from_generator::GenFuture<citypol_server::main::async_block_env$0> >
             at C:\Users\michi\.cargo\registry\src\github.com-1ecc6299db9ec823\tokio-1.18.2\src\runtime\enter.rs:151
  20: tokio::runtime::thread_pool::ThreadPool::block_on<core::future::from_generator::GenFuture<citypol_server::main::async_block_env$0> >
             at C:\Users\michi\.cargo\registry\src\github.com-1ecc6299db9ec823\tokio-1.18.2\src\runtime\thread_pool\mod.rs:81
  21: tokio::runtime::Runtime::block_on<core::future::from_generator::GenFuture<citypol_server::main::async_block_env$0> >
             at C:\Users\michi\.cargo\registry\src\github.com-1ecc6299db9ec823\tokio-1.18.2\src\runtime\mod.rs:477
  22: citypol_server::main
             at .\src\main.rs:123
  23: core::ops::function::FnOnce::call_once<void (*)(),tuple$<> >
             at /rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c\library\core\src\ops\function.rs:227
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
error: process didn't exit successfully: `target\debug\citypol-server.exe` (exit code: 101)

Is there any way to get the inner tree?

Thanks very much for this nice crate. Here's what I'm trying to do: after inserting a bunch of points, I'd like to query a handle and find all of the regions and subregions it's in, up to the root region. I can see that this data is available in the inner field, just by printing the quadtree with the debug {:?} formatting.

Is there a way to do this with the current API? If not, I'll look at implementing it myself and submit a pull request as this functionality would be very powerful for my application.

Feature Request: Add Serde Feature Flag

It would super convenient to be able to serialize and deserialize quadtrees. I've already implemented this change locally, and am happy to open a PR, but I can't seem to push a branch. I signed the Google Open Source contrib thing, and read through the contributing page, but I can't seem to push my branch to open a PR from it.

Maintenance state?

@ambuc Seems like you don't have much time for maintaining this crate.perhaps you could add someone as a maintainer?

I have some interest in working on this crate for my needs, and I'd like not to split the userbase by forking.

Why are you using a hashmap ?

Hello, I was looking your code to learn how a Quadtree could be implemented in rust (I never did it before in any other language too).
But I was wondering why are you using a hashmap for your store ? Wouldn't it be faster with an array ? So your handle would just be a index of you array ? I mean getting a value in a hashmap is O(1) but it's still slower than getting a value in an array.

Improve performance of `HandleIter`

As @t-veor pointed out in #1:

[HandleIter] could be improved by [...] skipping over subquadrants that do not overlap with the query area at all? Then HandleIter::query_optimization shouldn't even be needed.

I will implement this.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.