add QuadTree implementing SpatialIndex
- add SpatialIndex interface
This commit is contained in:
parent
4e94a8f269
commit
b460b4ebaf
234
vtm-tests/test/org/oscim/utils/QuadTreeTest.java
Normal file
234
vtm-tests/test/org/oscim/utils/QuadTreeTest.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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> {
|
||||
|
||||
}
|
||||
|
||||
|
76
vtm/src/org/oscim/utils/QuadTree.java
Normal file
76
vtm/src/org/oscim/utils/QuadTree.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
21
vtm/src/org/oscim/utils/SpatialIndex.java
Normal file
21
vtm/src/org/oscim/utils/SpatialIndex.java
Normal file
@ -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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
/** dont keep dangling references */
|
||||
///* gwt optimizer found this cannot be reached :) */
|
||||
while (stack != null)
|
||||
stack = stack.next;
|
||||
|
||||
return drop ? 1 : 0;
|
||||
}
|
||||
|
||||
public abstract boolean collectAll(BoxNode<Box> node);
|
||||
|
||||
public int all() {
|
||||
return all(root);
|
||||
}
|
||||
|
||||
public int all(BoxNode<Box> node) {
|
||||
|
||||
BoxNode<Box> cur, c;
|
||||
BoxNode<Box> stack = node;
|
||||
|
||||
while (stack != null) {
|
||||
cur = stack;
|
||||
stack = stack.next;
|
||||
|
||||
if (cur.item != null && !collectAll(cur))
|
||||
/* 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 ((c = cur.child00) != null) {
|
||||
c.next = stack;
|
||||
stack = c;
|
||||
public interface SearchBoxCb<T extends BoxItem<?>> {
|
||||
boolean call(T item);
|
||||
}
|
||||
if ((c = cur.child01) != null) {
|
||||
c.next = stack;
|
||||
stack = c;
|
||||
|
||||
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;
|
||||
}
|
||||
if ((c = cur.child10) != null) {
|
||||
c.next = stack;
|
||||
stack = c;
|
||||
}
|
||||
if ((c = cur.child11) != null) {
|
||||
c.next = stack;
|
||||
stack = c;
|
||||
}
|
||||
}
|
||||
|
||||
// dont keep dangling references
|
||||
while (stack != null)
|
||||
stack = stack.next;
|
||||
BoxNode<T> p = n.parent;
|
||||
|
||||
return 0;
|
||||
/* 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;
|
||||
}
|
||||
|
||||
public abstract int process(Box box, Box it);
|
||||
/* 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 true;
|
||||
}
|
||||
|
||||
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 interface SearchNodeCb<E extends BoxNode<?>> {
|
||||
boolean call(E item);
|
||||
}
|
||||
|
||||
public boolean collect(SearchNodeCb<BoxNode<T>> cb) {
|
||||
BoxNode<T> n;
|
||||
|
||||
Stack<BoxNode<T>> stack = stackPool.get();
|
||||
|
||||
stack.push(root);
|
||||
|
||||
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);
|
||||
}
|
||||
stackPool.release(stack);
|
||||
return false;
|
||||
}
|
||||
|
||||
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 (level < maxDepth) {
|
||||
if (x2 < cx) {
|
||||
if (y2 < cy) {
|
||||
if ((child = cur.child00) == null)
|
||||
child = create(cur, 0);
|
||||
}
|
||||
// should be else?
|
||||
if (y1 >= cy) {
|
||||
if ((child = cur.child10) == null)
|
||||
} else if (y1 >= cy) {
|
||||
if ((child = cur.child01) == null)
|
||||
child = create(cur, 1);
|
||||
idX++;
|
||||
}
|
||||
}
|
||||
if (x1 >= cx) {
|
||||
if (y2 <= cy) {
|
||||
if ((child = cur.child01) == null)
|
||||
if (y2 < cy) {
|
||||
if ((child = cur.child10) == null)
|
||||
child = create(cur, 2);
|
||||
idY++;
|
||||
}
|
||||
if (y1 >= cy) {
|
||||
} else 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;
|
||||
}
|
||||
|
||||
public abstract boolean process(BoxNode<Box> nodes);
|
||||
cur = child;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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 int size() {
|
||||
return root.refs;
|
||||
}
|
||||
|
||||
public Item() {
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
// }
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user