package org.osm2world.core.map_data.creation.index;
import static java.util.Collections.*;
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.VectorXZ;
/**
* a 2D tree (two-dimensional k-d tree) managing {@link MapElement}s of a
* data set according on their coordinates in the XZ plane.
*
* Data is contained within the leafs.
* The inner nodes split the XZ plane along parallels to the Z and X axes,
* alternatingly.
*/
public class Map2dTree implements MapDataIndex {
protected static final int LEAF_SPLIT_SIZE = 11;
protected final Node root;
protected static interface Node {
void add(MapElement element, boolean suppressSplits);
List<Leaf> probe(MapElement element);
/** adds all leaves in the subtree starting at this node to a list */
void collectLeaves(List<Leaf> leaves);
}
protected static class InnerNode implements Node {
public final boolean splitAlongX;
public final double splitValue;
public Node lowerChild;
public Node upperChild;
protected InnerNode(boolean splitAlongX, double splitValue) {
this.splitAlongX = splitAlongX;
this.splitValue = splitValue;
this.lowerChild = new Leaf(this);
this.upperChild = new Leaf(this);
}
@Override
public void add(MapElement element, boolean suppressSplits) {
boolean addToLowerChild = false;
boolean addToUpperChild = false;
for (MapNode node : getMapNodes(element)) {
VectorXZ pos = node.getPos();
if (splitAlongX) {
addToLowerChild |= pos.x <= splitValue;
addToUpperChild |= pos.x >= splitValue;
} else {
addToLowerChild |= pos.z <= splitValue;
addToUpperChild |= pos.z >= splitValue;
}
}
if (addToLowerChild) {
lowerChild.add(element, suppressSplits);
}
if (addToUpperChild) {
upperChild.add(element, suppressSplits);
}
}
private void trySplitLeaf(Leaf leaf) {
boolean splitChildAlongX = !splitAlongX;
double splitChildValue = 0;
/* determine split value as average of all values */
int numNodes = 0;
for (MapElement element : leaf) {
for (MapNode node : getMapNodes(element)) {
if (splitChildAlongX) {
splitChildValue += node.getPos().x;
} else {
splitChildValue += node.getPos().z;
}
numNodes += 1;
}
}
splitChildValue /= numNodes;
/* create the new inner node */
InnerNode newChild = new InnerNode(splitChildAlongX, splitChildValue);
/* check whether splitting will reduce the maximum node size */
for (MapElement element : leaf.elements) {
newChild.add(element, true);
}
if (((Leaf)(newChild.lowerChild)).elements.size() < leaf.elements.size() - 5
&& ((Leaf)(newChild.upperChild)).elements.size() < leaf.elements.size() - 5) {
/* replace the leaf with the new child node */
if (lowerChild == leaf) {
lowerChild = newChild;
} else if (upperChild == leaf) {
upperChild = newChild;
} else {
throw new AssertionError("leaf is not a child of this node");
}
}
}
@Override
public List<Leaf> probe(MapElement element) {
boolean addToLowerChild = false;
boolean addToUpperChild = false;
for (MapNode node : getMapNodes(element)) {
VectorXZ pos = node.getPos();
if (splitAlongX) {
addToLowerChild |= pos.x <= splitValue;
addToUpperChild |= pos.x >= splitValue;
} else {
addToLowerChild |= pos.z <= splitValue;
addToUpperChild |= pos.z >= splitValue;
}
}
if (addToLowerChild && addToUpperChild) {
List<Leaf> leaves = new ArrayList<Leaf>();
leaves.addAll(lowerChild.probe(element));
leaves.addAll(upperChild.probe(element));
return leaves;
} else if (addToLowerChild) {
return lowerChild.probe(element);
} else if (addToUpperChild) {
return upperChild.probe(element);
} else {
throw new AssertionError ("The element is not in this Node");
}
}
public void collectLeaves(List<Leaf> leaves) {
lowerChild.collectLeaves(leaves);
upperChild.collectLeaves(leaves);
}
}
protected static class Leaf implements Node, Iterable<MapElement> {
protected final InnerNode parent;
protected final ArrayList<MapElement> elements;
protected int numberWaysAndAreas = 0;
protected Leaf(InnerNode parent) {
this.parent = parent;
elements = new ArrayList<MapElement>(LEAF_SPLIT_SIZE);
}
@Override
public void add(MapElement element, boolean suppressSplits) {
elements.add(element);
if (!(element instanceof MapNode)) {
numberWaysAndAreas += 1;
}
if (!suppressSplits && numberWaysAndAreas >= LEAF_SPLIT_SIZE) {
parent.trySplitLeaf(this);
}
}
@Override
public Iterator<MapElement> iterator() {
return elements.iterator();
}
@Override
public List<Leaf> probe(MapElement element) {
return singletonList(this);
}
@Override
public void collectLeaves(List<Leaf> leaves) {
leaves.add(this);
}
}
public Map2dTree(AxisAlignedBoundingBoxXZ dataBoundary) {
root = new InnerNode(true, (dataBoundary.minX + dataBoundary.maxX) / 2);
}
@Override
public void insert(MapElement element) {
root.add(element, false);
}
@Override
public Collection<Leaf> insertAndProbe(MapElement e) {
insert(e);
return root.probe(e);
}
protected static Iterable<MapNode> getMapNodes(MapElement element) {
if (element instanceof MapNode) {
return singleton((MapNode)element);
} else if (element instanceof MapWaySegment) {
return ((MapWaySegment)element).getStartEndNodes();
} else { // element instanceof MapArea
return ((MapArea)element).getBoundaryNodes();
}
}
@Override
public Iterable<Leaf> getLeaves() {
List<Leaf> leaves = new ArrayList<Leaf>();
root.collectLeaves(leaves);
return leaves;
}
}