package org.osm2world.core.map_data.creation.index; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.osm2world.core.map_data.data.MapArea; import org.osm2world.core.map_data.data.MapElement; import org.osm2world.core.map_data.data.MapNode; import org.osm2world.core.map_data.data.MapWaySegment; import org.osm2world.core.math.AxisAlignedBoundingBoxXZ; import org.osm2world.core.math.SimplePolygonXZ; import org.osm2world.core.math.VectorXZ; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; /** * a Quadtree managing {@link MapElement}s of a data set * according on their coordinates in the XZ plane. */ public class MapQuadtree implements MapDataIndex { static final int LEAF_SPLIT_SIZE = 11; final QuadInnerNode root; static abstract class QuadNode { public final double minX, maxX, minZ, maxZ; private final SimplePolygonXZ boundary; QuadNode(double minX2, double maxX2, double minZ2, double maxZ2) { this.minX = minX2; this.maxX = maxX2; this.minZ = minZ2; this.maxZ = maxZ2; List<VectorXZ> vertices = new ArrayList<VectorXZ>(5); vertices.add(new VectorXZ(minX2, minZ2)); vertices.add(new VectorXZ(maxX2, minZ2)); vertices.add(new VectorXZ(maxX2, maxZ2)); vertices.add(new VectorXZ(minX2, maxZ2)); vertices.add(vertices.get(0)); boundary = new SimplePolygonXZ(vertices); } /** returns true if this node's bounds contain at least a part of the element */ boolean contains(MapElement element) { if (element instanceof MapNode) { return contains(((MapNode)element).getPos()); } else if (element instanceof MapWaySegment) { MapWaySegment line = (MapWaySegment)element; VectorXZ lineStart = line.getStartNode().getPos(); VectorXZ lineEnd = line.getEndNode().getPos(); if (contains(lineStart) || contains(lineEnd)) { return true; } else if (boundary.intersects(lineStart, lineEnd)) { //SUGGEST (performance): use that the box is axis-aligned? return true; } return false; } else { // element instanceof MapArea MapArea area = ((MapArea)element); for (MapNode node : area.getBoundaryNodes()) { if (contains(node.getPos())) { return true; } } if (boundary.intersects(area.getPolygon().getOuter()) || area.getPolygon().contains(boundary)) { //SUGGEST (performance): use that the box is axis-aligned? return true; } return false; } } boolean contains(VectorXZ pos) { return pos.x >= minX && pos.x <= maxX && pos.z >= minZ && pos.z <= maxZ; } abstract void add(MapElement element); abstract void addAll(Collection<MapElement> elements); /** adds all leaves in the subtree starting at this node to a list */ abstract void collectLeaves(List<QuadLeaf> leaves); } static class QuadInnerNode extends QuadNode { /** array with four elements */ final QuadNode childNodes[]; QuadInnerNode(double minX, double maxX, double minZ, double maxZ) { super(minX, maxX, minZ, maxZ); childNodes = new QuadNode[4]; double halfX = (minX+maxX)/2; double halfZ = (minZ+maxZ)/2; childNodes[0] = new QuadLeaf(this, minX, halfX, minZ, halfZ); childNodes[1] = new QuadLeaf(this, halfX, maxX, minZ, halfZ); childNodes[2] = new QuadLeaf(this, minX, halfX, halfZ, maxZ); childNodes[3] = new QuadLeaf(this, halfX, maxX, halfZ, maxZ); } @Override void add(MapElement element) { for (int i=0; i<4; i++) { if (childNodes[i].contains(element)) { childNodes[i].add(element); //continue loop, the element can cross leaf borders } } } @Override void addAll(Collection<MapElement> elements) { for (MapElement element : elements) { add(element); } } void trySplitLeaf(QuadLeaf leaf) { QuadInnerNode newChild = new QuadInnerNode(leaf.minX, leaf.maxX, leaf.minZ, leaf.maxZ); /* check whether splitting will reduce the maximum node size */ boolean nodeSizeReduced = true; for (int i=0; i<4; i++) { boolean newLeafContainsAllElements = true; for (MapElement element : leaf) { if (!newChild.childNodes[i].contains(element)) { newLeafContainsAllElements = false; break; } } if (newLeafContainsAllElements) { nodeSizeReduced = false; break; } } if (nodeSizeReduced) { /* replace the leaf with the new child node */ for (int i=0; i<4; i++) { if (childNodes[i] == leaf) { childNodes[i] = newChild; childNodes[i].addAll(leaf.elements); return; } } throw new AssertionError("leaf is not a child of this node"); } } void collectLeaves(List<QuadLeaf> leaves) { for (int i=0; i<4; i++) { childNodes[i].collectLeaves(leaves); } } } static public class QuadLeaf extends QuadNode implements Iterable<MapElement> { final QuadInnerNode parent; final ArrayList<MapElement> elements; QuadLeaf(QuadInnerNode parent, double minX, double maxX, double minZ, double maxZ) { super(minX, maxX, minZ, maxZ); this.parent = parent; elements = new ArrayList<MapElement>(LEAF_SPLIT_SIZE); } @Override void add(MapElement element) { elements.add(element); if (elements.size() >= LEAF_SPLIT_SIZE) { parent.trySplitLeaf(this); } } @Override void addAll(Collection<MapElement> element) { /* addAll cannot be implemented by iterating over add: * if the leaf would be "split"(replaced with an inner node) * during the iteration, the remaining elements would still * be added to the now-useless leaf object */ elements.addAll(element); if (elements.size() >= LEAF_SPLIT_SIZE) { parent.trySplitLeaf(this); } } @Override public Iterator<MapElement> iterator() { return elements.iterator(); } @Override void collectLeaves(List<QuadLeaf> leaves) { leaves.add(this); } } public MapQuadtree(AxisAlignedBoundingBoxXZ dataBoundary) { root = new QuadInnerNode( dataBoundary.minX, dataBoundary.maxX, dataBoundary.minZ, dataBoundary.maxZ); } @Override public void insert(MapElement e) { root.add(e); } @Override public Collection<? extends Iterable<MapElement>> insertAndProbe( final MapElement e) { insert(e); return Collections2.<QuadLeaf>filter(getLeaves(), new Predicate<QuadLeaf>() { @Override public boolean apply(QuadLeaf leaf) { return leaf.contains(e); } }); } @Override public Collection<QuadLeaf> getLeaves() { List<QuadLeaf> leaves = new ArrayList<QuadLeaf>(); root.collectLeaves(leaves); return leaves; } }