/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2009-2012, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.index.tree.basic;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.sis.util.ArgumentChecks;
import org.geotoolkit.index.tree.AbstractTree;
import static org.geotoolkit.internal.tree.TreeUtilities.*;
import org.geotoolkit.index.tree.Node;
import org.geotoolkit.internal.tree.TreeAccess;
import static org.geotoolkit.index.tree.basic.SplitCase.*;
import org.geotoolkit.index.tree.StoreIndexException;
import org.geotoolkit.index.tree.TreeElementMapper;
/**
* BasicRTree : Tree implementation.<br/><br/>
*
* It's a Tree implementation with a faster insertion and remove action,
* but search is lesser fast than other Trees.<br/>
* If stored datas are often updated, which mean more insertions or removes action,
* it's a Tree implementation which respond to this criteria.<br/><br/>
*
* Note : In this RTree version it exist two made to split a Node, named : LINEAR and QUADRATIC.<br/>
* For more informations see {@link SplitCase} javadoc.
*
* @author Remi Marechal (Geomatys).
* @see SplitCase.
*/
public class BasicRTree<E> extends AbstractTree<E> {
/**
* Split made choice.
*/
private final SplitCase choice;
/**
* Create a Basic RTree implementation.
*
* @param treeAccess object in which all Tree information are stored.
* @param choice split made choice.
* @param treeEltMap object in which data and tree identifier are stored.
* @throws StoreIndexException
* @see TreeAccess
* @see SplitCase
* @see TreeElementMapper
*/
public BasicRTree(final TreeAccess treeAccess, final TreeElementMapper treeEltMap) throws StoreIndexException {
super(treeAccess, treeAccess.getCRS(), treeEltMap);
ArgumentChecks.ensureNonNull("Create AbstractBasicRTree : treeAF", treeAccess);
ArgumentChecks.ensureNonNull("Create AbstractBasicRTree : CRS", crs);
this.choice = treeAccess.getSplitMade();
ArgumentChecks.ensureNonNull("Create AbstractBasicRTree : SplitCase choice", choice);
super.setRoot(treeAccess.getRoot());
treeIdentifier = treeAccess.getTreeIdentifier();
}
/**
* {@inheritDoc }.
*/
@Override
protected Node nodeInsert(Node candidate, int identifier, double... coordinates) throws IOException {
assert candidate instanceof Node;
Node fileCandidate = (Node) candidate;
assert !fileCandidate.isData() : "nodeInsert : candidate should never be data type.";
/**
* During travel down recursively candidate parent may be modified.
* When travel up recursively if candidate should be modified, get
* new candidate object updated from sub-method.
*/
Node subCandidateParent = null;
if (fileCandidate.isLeaf()) {
assert fileCandidate.checkInternal() : "nodeInsert : leaf before add.";
fileCandidate.addChild(createNode(coordinates, IS_DATA, fileCandidate.getNodeId(), 0, -identifier));
assert fileCandidate.checkInternal() : "nodeInsert : leaf after add.";
} else {
assert fileCandidate.checkInternal() : "nodeInsert : Node before insert.";
subCandidateParent = (Node)nodeInsert(chooseSubtree(fileCandidate, coordinates), identifier, coordinates);
add(fileCandidate.getBoundary(), coordinates);
}
/**
* Currently candidate was modified from precedently sub-Insert() call.
* Affect candidate object with new candidate from sub-Insert method.
*/
if (subCandidateParent != null) {
fileCandidate = subCandidateParent;
}
treeAccess.writeNode(fileCandidate);
assert fileCandidate.checkInternal() : "nodeInsert : after insert.";
if (fileCandidate.getChildCount() > getMaxElements()) {
assert fileCandidate.checkInternal() : "nodeInsert : before Branch grafting.";
// /*********************** Branch grafting **************************/
// if (fileCandidate.isLeaf() && fileCandidate.getParentId() != 0) {
// /**
// * We search to, travel candidate sibling from candidate parent first child, to last child.
// */
// final FileNode candidateParent = tAF.readNode(fileCandidate.getParentId());
// int sibl = candidateParent.getChildId();
// final double[] candidateBound = fileCandidate.getBoundary();
// while (sibl != 0) {
// if (sibl != fileCandidate.getNodeId()) {
// final FileNode cuSibling = tAF.readNode(sibl);
// assert cuSibling.checkInternal() : "candidate sibling.";
// if (intersects(candidateBound, cuSibling.getBoundary(), true
// && cuSibling.getChildCount() > 1)) {
// branchGrafting(fileCandidate, cuSibling);
// assert cuSibling.checkInternal() : "candidate sibling after branch grafting.";
// assert fileCandidate.checkInternal() : "candidate after branch grafting.";
// if (fileCandidate.getChildCount() <= tree.getMaxElements()) return null;
// }
// sibl = cuSibling.getSiblingId();
// } else {
// sibl = fileCandidate.getSiblingId();
// }
// }
// }
// /******************************************************************/
assert fileCandidate.checkInternal() : "nodeInsert : after Branch grafting.";
// split
final Node[] splitTable = splitNode(fileCandidate);
final Node split1 = (Node)splitTable[0];
final Node split2 = (Node)splitTable[1];
final int candidateParentID = fileCandidate.getParentId();
if (candidateParentID == 0) { // on est sur le noeud root
// on clear le candidate
assert fileCandidate.getSiblingId() == 0 : "nodeInsert : split root : root should not have sibling.";
fileCandidate.clear();
fileCandidate.setProperties(IS_OTHER);
fileCandidate.addChild(split1);
fileCandidate.addChild(split2);
assert split1.checkInternal() : "nodeInsert : split1.";
assert split2.checkInternal() : "nodeInsert : split2.";
assert fileCandidate.checkInternal() : "nodeInsert : split root.";
} else {
final Node parent = treeAccess.readNode(candidateParentID);
parent.removeChild(fileCandidate);
parent.addChild(split1);
parent.addChild(split2);
assert split1.checkInternal() : "nodeInsert : split1.";
assert split2.checkInternal() : "nodeInsert : split2.";
assert parent.checkInternal() : "nodeInsert : split node.";
/**
* Candidate parent is modified, return it to re-affect appropriate parent object in up-Insert().
*/
return parent;
}
}
/**
* If last travel up (on Root Node) and currently candidate was changed,
* return currently Node else return null.
*/
return (subCandidateParent != null && fileCandidate.getParentId() == 0) ? fileCandidate : null;
}
/**
* Split a overflow {@code Node} in accordance with R-Tree properties.
*
* @param candidate {@code Node} to Split.
* @throws IllegalArgumentException if candidate is null.
* @throws IllegalArgumentException if candidate elements number is lesser 2.
* @return {@code Node} List which contains two {@code Node} (split result of candidate).
*/
@Override
protected Node[] splitNode(final Node candidate) throws IllegalArgumentException, IOException {
ArgumentChecks.ensureNonNull("splitNode : candidate", candidate);
assert candidate.checkInternal() : "splitNode : begin.";
int childNumber = candidate.getChildCount();
if (childNumber < 2)
throw new IllegalArgumentException("not enought elements within " + candidate + " to split.");
final int maxElmnts = getMaxElements();
final Node[] children = candidate.getChildren();
assert childNumber == children.length : "SplitNode : childnumber should be same as children length value.";
final byte candidateProperties = candidate.getProperties();
Node s1 = null;
Node s2 = null;
double refValue = Double.NEGATIVE_INFINITY;
double tempValue;
int index1 = 0;
int index2 = 0;
switch (choice) {
/**
* Find the two further Nodes.
*/
case LINEAR: {
for (int i = 0; i < childNumber - 1; i++) {
for (int j = i + 1; j < childNumber; j++) {
tempValue = calculator.getDistanceEnvelope(children[i].getBoundary(), children[j].getBoundary());
if (tempValue > refValue) {
s1 = children[i];
s2 = children[j];
index1 = i;
index2 = j;
refValue = tempValue;
}
}
}
}
break;
/**
* Find the two which create as much dead space as possible.<br/>
* With dead space mean the most empty area between them.
*/
case QUADRATIC: {
double[] rectGlobal, bound1, bound2;
for (int i = 0; i < childNumber - 1; i++) {
for (int j = i + 1; j < childNumber; j++) {
bound1 = children[i].getBoundary();
bound2 = children[j].getBoundary();
rectGlobal = bound1.clone();
add(rectGlobal, bound2);
tempValue = calculator.getSpace(rectGlobal) - calculator.getSpace(bound1) - calculator.getSpace(bound2);
if (tempValue > refValue) {
s1 = children[i];
s2 = children[j];
index1 = i;
index2 = j;
refValue = tempValue;
}
}
}
}
break;
}
assert (s1 != null && s2 != null) : "s1 || s2 == null";
final int maxid = Math.max(index1, index2);
System.arraycopy(children, maxid+1, children, maxid, children.length-maxid-1);
children[children.length-1] = null;
final int minid = Math.min(index1, index2);
System.arraycopy(children, minid+1, children, minid, children.length-minid-1);
children[children.length-1] = null;
childNumber -= 2;
double[] r1Temp, r2Temp;
double demimaxE = maxElmnts / 3.0;
demimaxE = Math.max(demimaxE, 1);
//result1 attributs
int r1ChCount = 0;
Node[] result1Children = new Node[childNumber+1];
//result2 attributs
int r2ChCount = 0;
Node[] result2Children = new Node[childNumber+1];
//add s1 s2
result1Children[r1ChCount++] = s1;
result2Children[r2ChCount++] = s2;
for (int i = 0, s = childNumber; i < s; i++) {
Node currentFileNode = children[i];
r1Temp = s1.getBoundary().clone();
add(r1Temp, currentFileNode.getBoundary());
r2Temp = s2.getBoundary().clone();
add(r2Temp, currentFileNode.getBoundary());
final double area1 = calculator.getSpace(r1Temp);
final double area2 = calculator.getSpace(r2Temp);
if (area1 < area2) {
if (r2ChCount <= demimaxE && r1ChCount > demimaxE) {
result2Children[r2ChCount++] = currentFileNode;
} else {
result1Children[r1ChCount++] = currentFileNode;
}
} else if (area1 == area2) {
if (r1ChCount < r2ChCount) {
result1Children[r1ChCount++] = currentFileNode;
} else {
result2Children[r2ChCount++] = currentFileNode;
}
} else {
if (r1ChCount <= demimaxE && r2ChCount > demimaxE) {
result1Children[r1ChCount++] = currentFileNode;
} else {
result2Children[r2ChCount++] = currentFileNode;
}
}
}
result1Children = Arrays.copyOf(result1Children, r1ChCount);
result2Children = Arrays.copyOf(result2Children, r2ChCount);
final Node result1, result2;
final boolean isLeaf = candidate.isLeaf();
if (!isLeaf && r1ChCount == 1) {
result1 = result1Children[0];
((Node)result1).setSiblingId(0);
} else {
result1 = createNode(null, candidateProperties, 0, 0, 0);
result1.addChildren(result1Children);
}
if (!isLeaf && r2ChCount == 1) {
result2 = result2Children[0];
((Node)result2).setSiblingId(0);
} else {
result2 = createNode(null, candidateProperties, 0, 0, 0);
result2.addChildren(result2Children);
}
// check result
assert result1.checkInternal() : "splitNode : result1.";
assert result2.checkInternal() : "splitNode : result2.";
return new Node[]{result1, result2};
}
/**
* Exchange some entry(ies) between two nodes in aim to find best form with lesser overlaps.
* Also branchGrafting will be able to avoid splitting node.
*
* @param nodeA Node
* @param nodeB Node
* @throws IllegalArgumentException if nodeA or nodeB are null.
* @throws IllegalArgumentException if nodeA and nodeB have different "parent".
* @throws IllegalArgumentException if nodeA or nodeB are not tree leaf.
* @throws IllegalArgumentException if nodeA or nodeB, and their subnodes, don't contains some {@code Entry}.
*/
private void branchGrafting(final Node nodeA, final Node nodeB ) throws IllegalArgumentException, IOException {
if(!nodeA.isLeaf() || !nodeB.isLeaf()) throw new IllegalArgumentException("branchGrafting : not leaf");
assert nodeA.getParentId()== nodeB.getParentId(): "branchGrafting : NodeA and NodeB should have same parent.";
assert treeAccess.readNode(nodeA.getParentId()).checkInternal() : "branchGrafting : nodeA and B parent not conform.";
assert nodeA.checkInternal() : "branchGrafting : at begin candidate not conform";
assert nodeB.checkInternal() : "branchGrafting : at begin candidate not conform";
final int nodeACount = nodeA.getChildCount();
final int nodeBCount = nodeB.getChildCount();
final int size = nodeACount + nodeBCount;
final List<Node> listFN = new ArrayList<Node>(size);
final Node[] nodeAChildren = nodeA.getChildren();
final Node[] nodeBChildren = nodeB.getChildren();
final double[] globalE = nodeAChildren[0].getBoundary().clone();
for (Node nod : nodeAChildren) {
add(globalE, nod.getBoundary());
listFN.add(nod);
}
for (Node nod : nodeBChildren) {
add(globalE, nod.getBoundary());
listFN.add(nod);
}
if(listFN.isEmpty()) throw new IllegalArgumentException("branchGrafting : empty list");
final int maxEltsPermit = getMaxElements();
final int dim = globalE.length >> 1;
double lengthDimRef = -1;
int indexSplit = -1;
//split along the longest span
for(int i = 0; i < dim; i++) {
double lengthDimTemp = getSpan(globalE, i);
if (lengthDimTemp > lengthDimRef) {
lengthDimRef = lengthDimTemp;
indexSplit = i;
}
}
assert indexSplit != -1 : "BranchGrafting : indexSplit not find"+indexSplit;
calculator.sortList(indexSplit, true, listFN);
double[] envB, envA;
double overLapsRef = Double.POSITIVE_INFINITY;
int index = -1;
final int size04 = (int) Math.max(size * 0.4, 1);
for (int cut = size04; cut < size-size04; cut++) {
envA = listFN.get(0).getBoundary().clone();
for (int i = 1; i < cut; i++) {
add(envA, listFN.get(i).getBoundary());
}
envB = listFN.get(cut).getBoundary().clone();
for (int i = cut + 1; i < size; i++) {
add(envB, listFN.get(i).getBoundary());
}
double overLapsTemp = calculator.getOverlaps(envA, envB);
if (overLapsTemp < overLapsRef) {
overLapsRef = overLapsTemp;
index = cut;
}
}
//index not wrong a split is better.
if (index > maxEltsPermit || (size-index) > maxEltsPermit) return;
nodeA.clear();
nodeB.clear();
for (int i = 0; i < index; i++) {
final Node newChild = (Node)listFN.get(i);
newChild.setSiblingId(0);
nodeA.addChild(newChild);
}
for (int i = index; i < size; i++) {
final Node newChild = (Node)listFN.get(i);
newChild.setSiblingId(0);
nodeB.addChild(newChild);
}
assert nodeA.getParentId()== nodeB.getParentId() : "branchGrafting : NodeA and NodeB should have same parent.";
assert treeAccess.readNode(nodeA.getParentId()).checkInternal() : "branchGrafting : nodeA and B parent not conform.";
assert nodeA.checkInternal() : "branchGrafting : at end candidate not conform";
assert nodeB.checkInternal() : "branchGrafting : at end candidate not conform";
}
/**
* Travel {@code Tree}, find {@code Entry} if it exist and delete it from reference.
*
* <blockquote><font size=-1>
* <strong>NOTE: Moreover {@code Tree} is condensate after a deletion to stay conform about R-Tree properties.</strong>
* </font></blockquote>
*
* @param candidate {@code Node} where to delete.
* @param entry {@code Envelope} to delete.
* @throws IllegalArgumentException if candidate or entry is null.
* @return true if entry is find and deleted else false.
*/
@Override
protected boolean removeNode(final Node candidate, final int identifier, final double... coordinate) throws StoreIndexException, IOException {
ArgumentChecks.ensureNonNull("removeNode : Node candidate", candidate);
ArgumentChecks.ensureNonNull("removeNode : Object object", identifier);
ArgumentChecks.ensureNonNull("removeNode : double[] coordinate", coordinate);
if(intersects(candidate.getBoundary(), coordinate, true)){
if (candidate.isLeaf()) {
boolean removed = candidate.removeData(identifier, coordinate);
if (removed) {
setElementsNumber(getElementsNumber()-1);
trim(candidate);
return true;
}
} else {
int sibl = candidate.getChildId();
while (sibl != 0) {
final Node currentChild = treeAccess.readNode(sibl);
final boolean removed = removeNode(currentChild, identifier, coordinate);
if (removed) return true;
sibl = currentChild.getSiblingId();
}
}
}
return false;
}
/**
* Condense R-Tree.
*
* Condense made, travel up from leaf to tree trunk.
*
* @param candidate {@code Node} to begin condense.
* @throws IllegalArgumentException if candidate is null.
*/
@Override
protected void trim(final Node candidate) throws IllegalArgumentException, IOException, StoreIndexException {
ArgumentChecks.ensureNonNull("trim : Node candidate", candidate);
List<double[]> reinsertListCoords = null;
List<Integer> reinsertListObjects = null;
double[] candiBound = null;
if (candidate.getChildId() != 0 && !candidate.isLeaf()) {
int sibl = candidate.getChildId();
while (sibl != 0) {
final Node currentChild = treeAccess.readNode(sibl);
// empty child
if (currentChild.isEmpty()) {
candidate.removeChild(currentChild);
} else if (currentChild.isLeaf()
&& currentChild.getChildCount() <= getMaxElements() / 3) {// other condition
if (reinsertListCoords == null) {
reinsertListCoords = new ArrayList<double[]>();
reinsertListObjects = new ArrayList<Integer>();
}
int cuChildSibl = currentChild.getChildId();
while (cuChildSibl != 0) {
final Node currentData = treeAccess.readNode(cuChildSibl);
reinsertListCoords.add(currentData.getBoundary());// risk de .clone a voir
reinsertListObjects.add(-currentData.getChildId());
cuChildSibl = currentData.getSiblingId();
currentChild.removeChild(currentData);
setElementsNumber(getElementsNumber()-1);
}
candidate.removeChild(currentChild);
} else {
if (candiBound == null) {
candiBound = currentChild.getBoundary().clone();
} else {
add(candiBound, currentChild.getBoundary());
}
// child own a single sub-child and its not a leaf.
if (currentChild.getChildCount() == 1) {
assert !currentChild.isLeaf() : "Trim : current child should not be leaf.";
final Node cChild = treeAccess.readNode(currentChild.getChildId());
assert Arrays.equals(currentChild.getBoundary(), cChild.getBoundary()) : "Node with only one element should have same boundary than its stored element.";
candidate.removeChild(currentChild);
candidate.addChild(cChild);
}
}
sibl = currentChild.getSiblingId();
}
}
if (candiBound != null) {
candidate.setBoundary(candiBound);
treeAccess.writeNode(candidate);
assert candidate.checkInternal() : "trim : candidate not conform";
}
if (candidate.getParentId()!= 0) {
trim (treeAccess.readNode(candidate.getParentId()));
} else {
// generaly root have some changes.
if (candidate.isEmpty()) {
setRoot(null);
} else {
setRoot(candidate);
}
}
if (reinsertListCoords != null) {
assert (reinsertListObjects != null) : "trim : listObjects should not be null.";
final int reSize = reinsertListCoords.size();
assert (reSize == reinsertListObjects.size()) : "reinsertLists should have same size";
for (int i = 0; i < reSize; i++) {
insert((int)reinsertListObjects.get(i), reinsertListCoords.get(i));
}
} else {
assert (reinsertListObjects == null) : "trim : listObjects should be null.";
}
}
}