diff --git a/vtm-tests/test/org/oscim/utils/QuadTreeTest.java b/vtm-tests/test/org/oscim/utils/QuadTreeTest.java new file mode 100644 index 00000000..d0553c9d --- /dev/null +++ b/vtm-tests/test/org/oscim/utils/QuadTreeTest.java @@ -0,0 +1,234 @@ +package org.oscim.utils; + +import static java.lang.System.currentTimeMillis; +import static java.lang.System.out; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import org.junit.Assert; +import org.junit.Test; +import org.oscim.core.Box; +import org.oscim.utils.SpatialIndex.SearchCb; + +public class QuadTreeTest { + final static Random rand = new Random((long) (Math.PI * 10000000)); + + public class Item { + final int val; + final Box bbox;; + + Item(Box bbox, int val) { + this.val = val; + this.bbox = new Box(bbox); + } + + @Override + public String toString() { + return String.valueOf(val) + ' ' + bbox; + } + } + + ArrayList<Item> fillRandomTree(SpatialIndex<Item> q, int numItems) { + Box box = new Box(); + ArrayList<Item> items = new ArrayList<Item>(numItems + 16); + + for (int i = 0; i < numItems; i++) { + box.minX = (int) (rand.nextDouble() * 10000 - 5000); + box.minY = (int) (rand.nextDouble() * 10000 - 5000); + box.maxX = (int) (box.minX + rand.nextDouble() * 500); + box.maxY = (int) (box.minY + rand.nextDouble() * 500); + + Item it = new Item(box, i); + q.insert(box, it); + + items.add(it); + + } + return items; + } + + @Test + public void shouldWork0() { + + SpatialIndex<Item> q = new QuadTree<Item>(Short.MAX_VALUE + 1, 16); + //SpatialIndex<Item> q = new RTree<Item>(); + + int numItems = 10000; + + List<Item> items = fillRandomTree(q, numItems); + + final int[] found = { 0 }; + final int[] matched = { 0 }; + + for (Item it : items) { + int f = matched[0]; + q.search(it.bbox, new SearchCb<Item>() { + @Override + public boolean call(Item item, Object context) { + found[0]++; + if (context == item) { + matched[0]++; + return false; + } + return true; + } + }, it); + + if (f == matched[0]) + out.println((it.bbox.maxX - it.bbox.minX) + + " x " + (it.bbox.maxY - it.bbox.minY) + + " ==> " + it); + } + + //out.println("m:" + matched[0] + " f:" + found[0]); + Assert.assertEquals(numItems, matched[0]); + Assert.assertEquals(numItems, q.size()); + } + + @Test + public void shouldWork1() { + long time = currentTimeMillis(); + + for (int i = 0; i < 10; i++) { + shouldWork0(); + } + + long now = currentTimeMillis(); + out.println("==>" + (now - time) / 10.0f + "ms"); + } + + @Test + public void shouldWork6() { + SpatialIndex<Item> q = new QuadTree<Item>(Short.MAX_VALUE + 1, 16); + + Box box = new Box(-4184.653317773969, + 3183.6174297948446, + -4088.3197324911957, + 3222.7770427421046); + + Item it = new Item(box, 1); + q.insert(box, it); + + q.search(it.bbox, new SearchCb<Item>() { + @Override + public boolean call(Item item, Object context) { + out.println("==> " + item + " " + (context == item)); + return true; + } + }, it); + Assert.assertEquals(1, q.size()); + } + + @Test + public void shouldWork7() { + SpatialIndex<Item> q = new QuadTree<Item>(Short.MAX_VALUE + 1, 14); + //SpatialIndex<Item> q = new RTree<Item>(); + + int numItems = 10000; + + List<Item> items = fillRandomTree(q, numItems); + + Assert.assertEquals(numItems, q.size()); + + int cnt = numItems; + for (Item it : items) { + if (!q.remove(it.bbox, it)) { + out.println((it.bbox.maxX - it.bbox.minX) + + " x " + (it.bbox.maxY - it.bbox.minY) + + " ==> " + it); + + q.search(it.bbox, new SearchCb<Item>() { + @Override + public boolean call(Item item, Object context) { + if (context == item) { + out.println("found..."); + return false; + } + return true; + } + }, it); + } + Assert.assertEquals(--cnt, q.size()); + } + + items = fillRandomTree(q, numItems); + + Assert.assertEquals(numItems, q.size()); + + cnt = numItems; + for (Item it : items) { + if (!q.remove(it.bbox, it)) + out.println((it.bbox.maxX - it.bbox.minX) + + " x " + (it.bbox.maxY - it.bbox.minY) + + " => " + it); + + Assert.assertEquals(--cnt, q.size()); + } + Assert.assertEquals(0, q.size()); + out.println(""); + } + + @Test + public void shouldWork8() { + + SpatialIndex<Item> q = new QuadTree<Item>(Short.MAX_VALUE + 1, 16); + //SpatialIndex<Item> q = new RTree<Item>(); + + int numItems = 10000; + + List<Item> items = fillRandomTree(q, numItems); + + final int[] found = { 0 }; + final int[] matched = { 0 }; + + for (Item it : items) { + int f = matched[0]; + int cnt = 0; + for (Item it2 : items) { + if (it2.bbox.overlap(it.bbox)) + cnt++; + } + found[0] = 0; + q.search(it.bbox, new SearchCb<Item>() { + @Override + public boolean call(Item item, Object context) { + found[0]++; + if (context == item) { + matched[0]++; + //return false; + } + return true; + } + }, it); + + if (found[0] != cnt) { + out.println("not found " + (found[0] - cnt)); + } + //Assert.assertEquals(cnt, found[0]); + if (f == matched[0]) + out.println((it.bbox.maxX - it.bbox.minX) + + " x " + (it.bbox.maxY - it.bbox.minY) + + " ==> " + it); + } + + //out.println("m:" + matched[0] + " f:" + found[0]); + Assert.assertEquals(numItems, matched[0]); + Assert.assertEquals(numItems, q.size()); + } + + public static void main(String[] args) { + QuadTreeTest qt = new QuadTreeTest(); + + long time = currentTimeMillis(); + + for (int i = 0; i < 100; i++) { + qt.shouldWork0(); + long now = currentTimeMillis(); + + out.println("==>" + (now - time)); + time = now; + } + } +} diff --git a/vtm/src/org/oscim/layers/tile/MapTile.java b/vtm/src/org/oscim/layers/tile/MapTile.java index b0195bc6..59e763fb 100644 --- a/vtm/src/org/oscim/layers/tile/MapTile.java +++ b/vtm/src/org/oscim/layers/tile/MapTile.java @@ -21,7 +21,7 @@ import org.oscim.layers.tile.vector.VectorTileLoader; import org.oscim.layers.tile.vector.labeling.LabelTileLoaderHook; import org.oscim.renderer.elements.ElementLayers; import org.oscim.utils.pool.Inlist; -import org.oscim.utils.quadtree.Node; +import org.oscim.utils.quadtree.TreeNode; import org.oscim.utils.quadtree.QuadTree; /** @@ -32,7 +32,7 @@ import org.oscim.utils.quadtree.QuadTree; */ public class MapTile extends Tile { - public static class TileNode extends Node<TileNode, MapTile> { + public static class TileNode extends TreeNode<TileNode, MapTile> { } diff --git a/vtm/src/org/oscim/utils/QuadTree.java b/vtm/src/org/oscim/utils/QuadTree.java new file mode 100644 index 00000000..1daf206b --- /dev/null +++ b/vtm/src/org/oscim/utils/QuadTree.java @@ -0,0 +1,76 @@ +package org.oscim.utils; + +import java.util.List; + +import org.oscim.core.Box; +import org.oscim.utils.pool.Pool; +import org.oscim.utils.quadtree.BoxTree; +import org.oscim.utils.quadtree.BoxTree.BoxItem; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class QuadTree<T> extends BoxTree<BoxItem<T>, T> implements SpatialIndex<T> { + + public QuadTree(int extents, int maxDepth) { + super(extents, maxDepth); + } + + static final Logger log = LoggerFactory.getLogger(QuadTree.class); + + final Pool<BoxItem<T>> boxPool = new Pool<BoxItem<T>>() { + @Override + protected BoxItem<T> createItem() { + return new BoxItem<T>(); + } + }; + + private BoxItem<T> getBox(Box box) { + BoxItem<T> it = boxPool.get(); + it.x1 = (int) box.minX; + it.y1 = (int) box.minY; + it.x2 = (int) box.maxX; + it.y2 = (int) box.maxY; + return it; + } + + @Override + public void insert(Box box, T item) { + insert(new BoxItem<T>(box, item)); + } + + @Override + public boolean remove(Box box, T item) { + BoxItem<T> bbox = getBox(box); + boolean ok = remove(bbox, item); + boxPool.release(bbox); + return ok; + } + + static class CollectCb<T> implements SearchCb<T> { + @SuppressWarnings("unchecked") + @Override + public boolean call(T item, Object context) { + List<T> l = (List<T>) context; + l.add(item); + return true; + } + } + + final CollectCb<T> collectCb = new CollectCb<T>(); + + @Override + public List<T> search(Box bbox, List<T> results) { + BoxItem<T> box = getBox(bbox); + search(box, collectCb, results); + boxPool.release(box); + return results; + } + + @Override + public int search(Box bbox, SearchCb<T> cb, Object context) { + BoxItem<T> box = getBox(bbox); + search(box, cb, context); + boxPool.release(box); + return 0; + } +} diff --git a/vtm/src/org/oscim/utils/SpatialIndex.java b/vtm/src/org/oscim/utils/SpatialIndex.java new file mode 100644 index 00000000..b6e74509 --- /dev/null +++ b/vtm/src/org/oscim/utils/SpatialIndex.java @@ -0,0 +1,21 @@ +package org.oscim.utils; + +import java.util.List; + +import org.oscim.core.Box; + +public interface SpatialIndex<T> { + public interface SearchCb<T> { + boolean call(T item, Object context); + } + + public void insert(Box box, T item); + + public boolean remove(Box box, T item); + + public List<T> search(Box bbox, List<T> results); + + public int search(Box bbox, SearchCb<T> cb, Object context); + + public int size(); +} diff --git a/vtm/src/org/oscim/utils/quadtree/BoxTree.java b/vtm/src/org/oscim/utils/quadtree/BoxTree.java index 998ce43e..df78ed5a 100644 --- a/vtm/src/org/oscim/utils/quadtree/BoxTree.java +++ b/vtm/src/org/oscim/utils/quadtree/BoxTree.java @@ -1,6 +1,10 @@ package org.oscim.utils.quadtree; +import java.util.Arrays; + +import org.oscim.utils.SpatialIndex.SearchCb; import org.oscim.utils.pool.Inlist; +import org.oscim.utils.pool.Pool; import org.oscim.utils.quadtree.BoxTree.BoxItem; import org.oscim.utils.quadtree.BoxTree.BoxNode; import org.slf4j.Logger; @@ -12,28 +16,55 @@ import org.slf4j.LoggerFactory; * * ... in case this generic isnt obvious at first sight. * */ -public abstract class BoxTree<Box extends BoxItem<E>, E> extends QuadTree<BoxNode<Box>, Box> { +public class BoxTree<T extends BoxItem<E>, E> extends TileIndex<BoxNode<T>, T> { final static Logger log = LoggerFactory.getLogger(BoxTree.class); static boolean dbg = false; protected final int extents; protected final int maxDepth; + private final static int MAX_STACK = 32; - public static class BoxNode<T extends BoxItem<?>> extends Node<BoxNode<T>, T> { - // for non-recursive traversal - BoxNode<T> next; - // TODO make final? or update to the actual used extent? + static class Stack<E> extends Inlist<Stack<E>> { + /** Top of stack index */ + int tos; + + final E[] nodes; + + @SuppressWarnings("unchecked") + Stack() { + nodes = (E[]) new BoxNode[MAX_STACK]; + } + + void push(E node) { + nodes[tos] = node; + tos++; + } + + /** Pop element off iteration stack (For internal use only) */ + E pop() { + // assert (tos > 0); + nodes[tos--] = null; + return (E) nodes[tos]; + } + + E node() { + return (E) nodes[tos]; + } + + boolean empty() { + return tos <= 0; + } + } + + public static class BoxNode<T extends BoxItem<?>> extends TreeNode<BoxNode<T>, T> { + // TODO this is redundant - use tile ids public int x1; public int y1; public int x2; public int y2; - //BoxItem<T> list; - - public boolean overlaps(T it) { - return (x1 < it.x2) && (y1 < it.y2) && (it.x1 < x2) && (it.y1 < y2); - } + // inherits BoxItem<E> item; @Override public String toString() { @@ -44,11 +75,19 @@ public abstract class BoxTree<Box extends BoxItem<E>, E> extends QuadTree<BoxNod public static class BoxItem<T> extends Inlist<BoxItem<T>> { public BoxItem(int x1, int y1, int x2, int y2) { this.x1 = x1; - this.x2 = x2; this.y1 = y1; + this.x2 = x2; this.y2 = y2; } + public BoxItem(org.oscim.core.Box box, T item) { + this.x1 = (int) box.minX; + this.y1 = (int) box.minY; + this.x2 = (int) box.maxX; + this.y2 = (int) box.maxY; + this.item = item; + } + @Override public String toString() { return x1 + "," + y1 + "/" + x2 + "," + y2; @@ -66,7 +105,7 @@ public abstract class BoxTree<Box extends BoxItem<E>, E> extends QuadTree<BoxNod public int y2; public boolean overlaps(BoxItem<T> it) { - return (x1 < it.x2) && (it.x1 < x2) && (y1 < it.y2) && (it.y1 < y2); + return !((x1 > it.x2) || (it.x1 > x2) || (y1 > it.y2) || (it.y1 > y2)); } } @@ -74,19 +113,16 @@ public abstract class BoxTree<Box extends BoxItem<E>, E> extends QuadTree<BoxNod boolean process(T item); } - // public class NodeVistor<T> implements Visitor<BoxNode<T>> { - // - // @Override - // public boolean process(BoxNode<T> item) { - // - // return false; - // } - // - // } + boolean isPowerOfTwo(int x) { + return ((x > 0) && (x & (x - 1)) == 0); + } public BoxTree(int extents, int maxDepth) { super(); - // size is -extents to +extents + if (!isPowerOfTwo(extents)) + throw new IllegalArgumentException("Extents must be power of two!"); + + //size is -extents to +extents this.root.x1 = -extents; this.root.y1 = -extents; this.root.x2 = extents; @@ -97,169 +133,231 @@ public abstract class BoxTree<Box extends BoxItem<E>, E> extends QuadTree<BoxNod } @Override - public BoxNode<Box> create() { - return new BoxNode<Box>(); + public BoxNode<T> create() { + return new BoxNode<T>(); } @Override - public void removeItem(Box item) { + public void removeItem(T item) { } - @SuppressWarnings("unchecked") - public int query(Box box) { - if (box.x1 > box.x2 || box.y1 > box.y2) - throw new IllegalArgumentException(); + Pool<Stack<BoxNode<T>>> stackPool = new Pool<Stack<BoxNode<T>>>() { + @Override + protected Stack<BoxNode<T>> createItem() { + return new Stack<BoxNode<T>>(); + } - int x1 = box.x1; - int x2 = box.x2; - int y1 = box.y1; - int y2 = box.y2; + protected boolean clearItem(Stack<BoxNode<T>> item) { + if (item.tos != 0) { + item.tos = 0; + Arrays.fill(item.nodes, null); + } + return true; + }; + }; - BoxNode<Box> cur, c; - BoxNode<Box> stack = root; - int result = 0; + public boolean search(T box, SearchCb<E> cb, Object ctxt) { + BoxNode<T> n; - boolean drop = false; + Stack<BoxNode<T>> stack = stackPool.get(); + stack.push(root); - O: while (stack != null) { + O: while (!stack.empty()) { - /** pop cur from stack */ - cur = stack; - stack = stack.next; + n = stack.pop(); - /** process overlapping items from cur node */ - Box prev = cur.item; - - for (Box it = cur.item; it != null; it = (Box) it.next) { - if ((x1 <= it.x2) && (x2 >= it.x1) && - (y1 <= it.y2) && (y2 >= it.y1)) { - - result = process(box, it); - - if (result > 0) { - if (dbg) - log.debug("{} overlap {} {}", result, box, it); - drop = true; + /* process overlapping items from cur node */ + for (BoxItem<E> it = n.item; it != null; it = it.next) { + if (it.overlaps(box)) { + if (!cb.call(it.item, ctxt)) break O; + } + } + + BoxNode<T> p = n.parent; + + /* push next node on same level onto stack */ + switch (n.id) { + case 0: + if (overlaps(p.child01, box)) { + stack.push(p.child01); + break; } + case 1: + if (overlaps(p.child10, box)) { + stack.push(p.child10); + break; + } + case 2: + if (overlaps(p.child11, box)) { + stack.push(p.child11); + break; + } + default: + break; + } + /* push next level child onto stack */ + if (overlaps(n.child00, box)) + stack.push(n.child00); + else if (overlaps(n.child01, box)) + stack.push(n.child01); + else if (overlaps(n.child10, box)) + stack.push(n.child10); + else if (overlaps(n.child11, box)) + stack.push(n.child11); + } + stackPool.release(stack); + return false; + } - if (result < 0) { - result = 0; - if (dbg) - log.debug("remove overlap {} {}", box, it); - // remove this itemchild = cur.child11; - //cur.item = Inlist.remove(cur.item, it); - if (it == cur.item) - prev = cur.item = it; - else - prev.next = it.next; + public interface SearchBoxCb<T extends BoxItem<?>> { + boolean call(T item); + } - continue; + public boolean search(T box, SearchBoxCb<T> cb) { + BoxNode<T> n; + BoxNode<T> start = getNode(box, false); + if (start == null) + return false; + + Stack<BoxNode<T>> stack = stackPool.get(); + stack.push(start); + + while (!stack.empty()) { + n = stack.pop(); + + /* process overlapping items from cur node */ + for (BoxItem<E> it = n.item; it != null; it = it.next) { + if (it.overlaps(box)) { + @SuppressWarnings("unchecked") + T item = (T) it; + if (!cb.call(item)) { + stackPool.release(stack); + return false; } } - prev = it; } - /** put children on stack which overlap with box */ - if ((c = cur.child00) != null && - (x1 < c.x2) && (y1 < c.y2) && - (c.x1 < x2) && (c.y1 < y2)) { - c.next = stack; - stack = c; - } - if ((c = cur.child01) != null && - (x1 < c.x2) && (y1 < c.y2) && - (c.x1 < x2) && (c.y1 < y2)) { - c.next = stack; - stack = c; - } - if ((c = cur.child10) != null && - (x1 < c.x2) && (y1 < c.y2) && - (c.x1 < x2) && (c.y1 < y2)) { - c.next = stack; - stack = c; - } - if ((c = cur.child11) != null && - (x1 < c.x2) && (y1 < c.y2) && - (c.x1 < x2) && (c.y1 < y2)) { - c.next = stack; - stack = c; + BoxNode<T> p = n.parent; + + /* push next node on same level onto stack */ + switch (n.id) { + case 0: + if (overlaps(p.child01, box)) { + stack.push(p.child01); + break; + } + case 1: + if (overlaps(p.child10, box)) { + stack.push(p.child10); + break; + } + case 2: + if (overlaps(p.child11, box)) { + stack.push(p.child11); + break; + } + default: + break; } + + /* push next level child onto stack */ + if (overlaps(n.child00, box)) + stack.push(n.child00); + else if (overlaps(n.child01, box)) + stack.push(n.child01); + else if (overlaps(n.child10, box)) + stack.push(n.child10); + else if (overlaps(n.child11, box)) + stack.push(n.child11); } + stackPool.release(stack); - /** dont keep dangling references */ - ///* gwt optimizer found this cannot be reached :) */ - while (stack != null) - stack = stack.next; - - return drop ? 1 : 0; + return true; } - public abstract boolean collectAll(BoxNode<Box> node); - - public int all() { - return all(root); + private static boolean overlaps(BoxNode<?> a, BoxItem<?> b) { + return a != null && !((a.x1 > b.x2) || (b.x1 > a.x2) || (a.y1 > b.y2) || (b.y1 > a.y2)); } - public int all(BoxNode<Box> node) { + public interface SearchNodeCb<E extends BoxNode<?>> { + boolean call(E item); + } - BoxNode<Box> cur, c; - BoxNode<Box> stack = node; + public boolean collect(SearchNodeCb<BoxNode<T>> cb) { + BoxNode<T> n; - while (stack != null) { - cur = stack; - stack = stack.next; + Stack<BoxNode<T>> stack = stackPool.get(); - if (cur.item != null && !collectAll(cur)) - break; + stack.push(root); - if ((c = cur.child00) != null) { - c.next = stack; - stack = c; - } - if ((c = cur.child01) != null) { - c.next = stack; - stack = c; - } - if ((c = cur.child10) != null) { - c.next = stack; - stack = c; - } - if ((c = cur.child11) != null) { - c.next = stack; - stack = c; + while (!stack.empty()) { + n = stack.pop(); + + /* process overlapping items from cur node */ + cb.call(n); + + BoxNode<T> p = n.parent; + + /* push next node on same level onto stack */ + switch (n.id) { + case 0: + if (p.child01 != null) { + stack.push(p.child01); + break; + } + case 1: + if (p.child10 != null) { + stack.push(p.child10); + break; + } + case 2: + if (p.child11 != null) { + stack.push(p.child11); + break; + } + default: + break; } + + /* push next level child onto stack */ + if (n.child00 != null) + stack.push(n.child00); + else if (n.child01 != null) + stack.push(n.child01); + else if (n.child10 != null) + stack.push(n.child10); + else if (n.child11 != null) + stack.push(n.child11); } - - // dont keep dangling references - while (stack != null) - stack = stack.next; - - return 0; + stackPool.release(stack); + return false; } - public abstract int process(Box box, Box it); + public BoxNode<T> create(BoxNode<T> parent, int i) { + BoxNode<T> node; + if (pool != null) { + node = pool; + pool = pool.parent; + } else + node = new BoxNode<T>(); + + node.parent = parent; - public BoxNode<Box> create(BoxNode<Box> parent, int i) { - BoxNode<Box> node = new BoxNode<Box>(); int size = (parent.x2 - parent.x1) >> 1; node.x1 = parent.x1; node.y1 = parent.y1; if (i == 0) { - // top-left parent.child00 = node; } else if (i == 1) { - // bottom-left - parent.child10 = node; + parent.child01 = node; node.y1 += size; } else if (i == 2) { - // top-right - parent.child01 = node; + parent.child10 = node; node.x1 += size; } else { - // bottom-right parent.child11 = node; node.x1 += size; node.y1 += size; @@ -267,93 +365,196 @@ public abstract class BoxTree<Box extends BoxItem<E>, E> extends QuadTree<BoxNod node.x2 = node.x1 + size; node.y2 = node.y1 + size; - - node.parent = parent; + node.id = (byte) i; return node; } - public void insert(Box box) { + public void insert(T box) { if (box.x1 > box.x2 || box.y1 > box.y2) throw new IllegalArgumentException(); - BoxNode<Box> cur = root; - BoxNode<Box> child = null; + if (box.next != null) + throw new IllegalStateException("BoxItem is list"); + + BoxNode<T> cur = root; + BoxNode<T> child = null; - // tile position in tree - //int px = 0, py = 0; - int idX = 0, idY = 0; int x1 = box.x1; int x2 = box.x2; int y1 = box.y1; int y2 = box.y2; for (int level = 0; level <= maxDepth; level++) { - // half size of tile at current z - //int hsize = (extents >> level); + cur.refs++; + + /* half size of tile at current level */ int hsize = (cur.x2 - cur.x1) >> 1; - // center of tile (shift by -extents) - //int cx = px + hsize - extents; - //int cy = py + hsize - extents; + /* center of tile */ int cx = cur.x1 + hsize; int cy = cur.y1 + hsize; child = null; - //int childPos = -1; - //log.debug(cx + ":" + cy + " " + hsize); - if (x2 <= cx) { - if (y2 <= cy) { - if ((child = cur.child00) == null) - child = create(cur, 0); + + if (level < maxDepth) { + if (x2 < cx) { + if (y2 < cy) { + if ((child = cur.child00) == null) + child = create(cur, 0); + } else if (y1 >= cy) { + if ((child = cur.child01) == null) + child = create(cur, 1); + } } - // should be else? - if (y1 >= cy) { - if ((child = cur.child10) == null) - child = create(cur, 1); - idX++; + if (x1 >= cx) { + if (y2 < cy) { + if ((child = cur.child10) == null) + child = create(cur, 2); + } else if (y1 >= cy) { + if ((child = cur.child11) == null) + child = create(cur, 3); + } } } - if (x1 >= cx) { - if (y2 <= cy) { - if ((child = cur.child01) == null) - child = create(cur, 2); - idY++; - } - if (y1 >= cy) { - if ((child = cur.child11) == null) - child = create(cur, 3); - idX++; - idY++; - } - } - //log.debug("child {}", child); - if (cur == minNode && child != null) - minNode = cur; - - if (child == null || level == maxDepth) { - // push item onto list of this node + if (child == null) { + /* push item onto list of this node */ box.next = cur.item; cur.item = box; if (dbg) - log.debug("insert at: " + level + " / " + idX + ":" - + idY + " -- " + x1 + ":" + y1 - + " /" + (x2) + "x" + (y2)); + log.debug("insert: " + level + + " cnt:" + Inlist.size(cur.item) + " " + x1 + ":" + y1 + + " /" + (x2) + "x" + (y2) + " " + box.item); break; } cur = child; } - } - public void setMinNode(int x1, int y1, int x2, int y2) { - /* TODO find lowest node that fully contains the region - * and set it as start for following queries */ + public boolean remove(T box, E item) { + if (box.x1 > box.x2 || box.y1 > box.y2) + throw new IllegalArgumentException(); + + BoxNode<T> cur = root; + BoxNode<T> child = null; + + int x1 = box.x1; + int x2 = box.x2; + int y1 = box.y1; + int y2 = box.y2; + + for (int level = 0; level <= maxDepth; level++) { + /* half size of tile at current level */ + int hsize = (cur.x2 - cur.x1) >> 1; + + /* center of tile */ + int cx = cur.x1 + hsize; + int cy = cur.y1 + hsize; + + child = null; + if (level < maxDepth) { + if (x2 < cx) { + if (y2 < cy) { + if ((child = cur.child00) == null) + return false; + } else if (y1 >= cy) { + if ((child = cur.child01) == null) + return false; + } + } else if (x1 >= cx) { + if (y2 < cy) { + if ((child = cur.child10) == null) + return false; + } else if (y1 >= cy) { + if ((child = cur.child11) == null) + return false; + } + } + } + if (child == null) { + /* push item onto list of this node */ + BoxItem<E> prev = cur.item; + + for (BoxItem<E> it = cur.item; it != null; it = it.next) { + if (it.item == item) { + if (dbg) + log.debug("remove: " + level + + " cnt:" + Inlist.size(cur.item) + " " + x1 + ":" + y1 + + " /" + (x2) + "x" + (y2) + " " + item); + + if (cur.item == it) { + // FUNKY GENERICS... + @SuppressWarnings("unchecked") + T b = (T) it.next; + cur.item = b; + } else + prev.next = it.next; + + remove(cur); + return true; + } + prev = it; + } + return false; + } + + cur = child; + } + return false; } - public abstract boolean process(BoxNode<Box> nodes); + public BoxNode<T> getNode(T box, boolean create) { + if (box.x1 > box.x2 || box.y1 > box.y2) + throw new IllegalArgumentException(); + + BoxNode<T> cur = root; + BoxNode<T> child = null; + + int x1 = box.x1; + int x2 = box.x2; + int y1 = box.y1; + int y2 = box.y2; + + for (int level = 0; level <= maxDepth; level++) { + cur.refs++; + + /* half size of tile at current z */ + int hsize = (cur.x2 - cur.x1) >> 1; + + /* center of tile (shift by -extents) */ + int cx = cur.x1 + hsize; + int cy = cur.y1 + hsize; + + child = null; + + if (x2 < cx) { + if (y2 < cy) { + if ((child = cur.child00) == null && create) + child = create(cur, 0); + } else if (y1 >= cy) { + if ((child = cur.child01) == null && create) + child = create(cur, 1); + } + } + if (x1 >= cx) { + if (y2 < cy) { + if ((child = cur.child10) == null && create) + child = create(cur, 2); + } else if (y1 >= cy) { + if ((child = cur.child11) == null && create) + child = create(cur, 3); + } + } + + if (child == null || level == maxDepth) + return cur; + + cur = child; + } + return null; + } public void clear() { root.child00 = null; @@ -361,67 +562,10 @@ public abstract class BoxTree<Box extends BoxItem<E>, E> extends QuadTree<BoxNod root.child10 = null; root.child11 = null; root.item = null; - - //root = create(); - //root.parent = root; + root.refs = 0; } - static class Item extends BoxItem<Integer> { - public Item(int x1, int y1, int x2, int y2) { - super(x1, y1, x2, y2); - } - - public Item() { - // TODO Auto-generated constructor stub - } + public int size() { + return root.refs; } - - // static { - // System.setProperty(org.slf4j.impl.SimpleLogger.DEFAULT_LOG_LEVEL_KEY, "TRACE"); - //} - // public static void main(String[] args) { - // - // BoxTree<Item, Integer> tree = new BoxTree<Item, Integer>(4096, 12) { - // - // @Override - // public int process(Item box, Item it) { - // System.out.println("found ... " + box + "\t\t" + it); - // return 1; - // } - // - // @Override - // public boolean process(BoxNode<Item> nodes) { - // System.out.println("found ... "); - // //for (BoxItem it = nodes.item; it != null; it = it.next) { - // // System.out.println("it: " + it.x1 + "/" + it.y1); - // //} - // - // // TODO Auto-generated method stub - // return false; - // } - // - // @Override - // public boolean collectAll(BoxNode<Item> nodes) { - // for (Item it = nodes.item; it != null; it = (Item) it.next) { - // System.out.println("all: " + it); - // } - // return false; - // } - // - // }; - // //[VtmAsyncExecutor] DEBUG org.oscim.utils.quadtree.BoxTree - insert at: 8 / 9:0 -- -631:266 /106x187 - // - // tree.insert(new Item(-631, 266, -631 + 106, 266 + 187)); - // - // //tree.insert(new Item(-40, -40, -32, -32)); - // // tree.insert(new Item(-60, -60, -40, -40)); - // // tree.insert(new Item(100, 10, 200, 100)); - // // tree.insert(new Item(100, 200, 200, 300)); - // // tree.insert(new Item(100, 200, 1000, 1000)); - // // - // // tree.query(new Item(-100, -100, 10, 10)); - // //tree.query(new Item(10, 10, 100, 100)); - // - // tree.all(); - // } } diff --git a/vtm/src/org/oscim/utils/quadtree/QuadTree.java b/vtm/src/org/oscim/utils/quadtree/QuadTree.java index 49717b8b..33cc0c77 100644 --- a/vtm/src/org/oscim/utils/quadtree/QuadTree.java +++ b/vtm/src/org/oscim/utils/quadtree/QuadTree.java @@ -19,16 +19,17 @@ package org.oscim.utils.quadtree; /** * A quad tree for the standard map tiling schema. */ -public abstract class QuadTree<T extends Node<T, E>, E> { +public abstract class QuadTree<T extends TreeNode<T, E>, E> { protected final T root; - protected T minNode; + //protected T minNode; protected T pool; public QuadTree() { root = create(); + root.id = -1; root.parent = root; } @@ -153,15 +154,15 @@ public abstract class QuadTree<T extends Node<T, E>, E> { while (cur != root) { if (cur == null) - throw new IllegalArgumentException("item not in index"); + throw new IllegalStateException("Item not in index"); - // keep pointer to parent + /* keep pointer to parent */ next = cur.parent; cur.refs--; - // if current node has no children + /* if current node has no children */ if (cur.refs == 0) { - // unhook from parent + /* unhook from parent */ switch (cur.id) { case 0: @@ -178,7 +179,7 @@ public abstract class QuadTree<T extends Node<T, E>, E> { break; } - // add item back to pool + /* add item back to pool */ cur.parent = pool; pool = cur; } diff --git a/vtm/src/org/oscim/utils/quadtree/Node.java b/vtm/src/org/oscim/utils/quadtree/TreeNode.java similarity index 75% rename from vtm/src/org/oscim/utils/quadtree/Node.java rename to vtm/src/org/oscim/utils/quadtree/TreeNode.java index 0efcbb19..85f0276c 100644 --- a/vtm/src/org/oscim/utils/quadtree/Node.java +++ b/vtm/src/org/oscim/utils/quadtree/TreeNode.java @@ -16,36 +16,31 @@ */ package org.oscim.utils.quadtree; -public class Node<T extends Node<T, E>, E> { +public class TreeNode<T extends TreeNode<T, E>, E> { + public T parent; + + /** top-left */ public T child00; - public T child10; + + /** bottom-left */ public T child01; + + /** top-right */ + public T child10; + + /** bottom-right */ public T child11; + /** payload */ public E item; - // id of this child relative to parent - byte id; + /** id of this child relative to parent */ + int id; - // number of children and grandchildren + /** number of children and grandchildren */ int refs = 0; - // public byte getId() { - // if (parent.child00 == this) - // return 0; - // if (parent.child01 == this) - // return 1; - // if (parent.child10 == this) - // return 2; - // if (parent.child00 == this) - // return 3; - // - // // is root node - // //if (parent == this) - // return -1; - // } - public E parent() { return parent.item; }