/*
* 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.hilbert;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.Classes;
import org.geotoolkit.gui.swing.tree.Trees;
import static org.geotoolkit.internal.tree.TreeUtilities.*;
import org.geotoolkit.index.tree.Node;
import org.geotoolkit.internal.tree.TreeAccess;
import org.geotoolkit.path.iterator.HilbertIterator;
import org.apache.sis.util.logging.Logging;
/**
* Appropriate Node which match with {@link HilbertRTree} properties.<br/><br/>
*
* In the case where HilbertNode is not a leaf, it adopte same comportement like a "standard" {@link Node}.<br/>
* Whereas in case where HilbertNode is a leaf, this Node own an additionnal Node level above datas storage which named cells.<br/>
* Each of them cells, sub-divide leaf in area of equal size and they are scheduled successively following <a href = "http://en.wikipedia.org/wiki/Hilbert_curve">Hilbert curve</a>.<br/>
* Shedule this cells from this curve permit to determine more faster in which cell store or find data that considerably accelerate search action.
*
* @author Remi Marechal (Geomatys).
*/
final class HilbertNode extends Node {
/**
* Space dimension needed to compute appropriate leaf cells number.
*/
private int dimension;
/**
* Data list use durring increase or decrease Hilbert order.
*/
private final List<Node> data = new ArrayList<Node>();
/**
* data number within a leaf.<br/>
* If Node is not a leaf data number always equal to zero.
*/
private int dataCount;
/**
* Current leaf Hilbert value.<br/>
*
* If Node is not a leaf hilbert order number always equal to zero.
*/
private int currentHilbertOrder;
private static final double LN2 = 0.6931471805599453;
/**
* Create a Node adapted for Hilbert Tree implementation.
*
* @param tAF Object which store Node attributs.
* @param nodeId invariable single integer Node identifier.
* @param boundary double table which represent boundary Node coordinates.
* @param properties define type of Node. see ({@link Node#properties}).
* @param parentId identifier of parent Node Tree architecture.
* @param siblingId identifier of sibling Node.
* @param childId if Node is a data it is the identifier of the data which is
* store in this tree (see {@link Node#childId}) else it is the first child of this Node.
* @see TreeAccess
*/
HilbertNode(TreeAccess tAF, int nodeId, double[] boundary, byte properties, int parentId, int siblingId, int childId) {
super(tAF, nodeId, boundary, properties, parentId, siblingId, childId);
dimension = tAF.getCRS().getCoordinateSystem().getDimension();
dataCount = 0;
currentHilbertOrder = 0;
}
/**
* Return true if all leaf Cells are full else false.<br/>
* A cell is full when it contains maximum elements number permit by tree.
*
* @return true if all leaf Cells are full else false.
* @throws IOException
*/
private boolean isInternalyFull() throws IOException {
int sibl = getChildId();
while (sibl != 0) {
final HilbertNode fhn = (HilbertNode) tAF.readNode(sibl);
if (!fhn.isFull()) {
return false;
}
sibl = fhn.getSiblingId();
}
return true;
}
/**
* Return current hilbert value of HilbertNode.<br/>
* If Node is not a leaf it is always 0.<br/>
* Else if node is a leaf value begin at 0 to n where n is the higher value permit by tree.
*
* @return current hilbert value of HilbertNode.
*/
public int getCurrentHilbertOrder() {
return currentHilbertOrder;
}
/**
* Affect an appropriate hilbert order to this HilbertNode.
*
* @param currentHilbertOrder
*/
public void setCurrentHilbertOrder(int currentHilbertOrder) {
this.currentHilbertOrder = currentHilbertOrder;
}
/**
* Return number of datas within this HilbertNode.<br/>
* If Node is not a leaf it is always 0.<br/>
* Else data number is equal to sum of all child number from leaf cells.
*
* @return number of datas within this HilbertNode.
*/
public int getDataCount() {
return dataCount;
}
/**
* Affect an appropriate data number to this HilbertNode.
*
* @param dataCount
*/
public void setDataCount(int dataCount) {
this.dataCount = dataCount;
}
/**
* {@inheritDoc }.
*/
@Override
public void addChildren(Node[] nodes) throws IOException {
for (Node nod : nodes) {
final Node fnod = (Node) nod;
fnod.setSiblingId(0);
addChild(fnod);
}
}
/**
* {@inheritDoc }.
*/
@Override
public void addChild(Node node) throws IOException {
if (isLeaf() && node.isData()) {
if ((boundary == null || ArraysExt.hasNaN(boundary)) && getChildCount() == 0) {
super.addChild(tAF.createNode(null, (byte) IS_CELL, this.getNodeId(), 0, 0));
}
assert node.isData() : "future added child should be data.";
final double[] nodeBound = node.getBoundary().clone();
if (boundary == null) {
boundary = nodeBound;
} else {
add(boundary, nodeBound);
}
final Node[] children = super.getChildren();
final int index = getAppropriateCellIndex(children, nodeBound);
if (index == -1) {
final double[] boundIncrease = boundary.clone();
// increase hilbert order
assert currentHilbertOrder++ < tAF.getHilbertOrder() : "impossible to increase node hilbert order";
// get all data within this leaf
data.clear();
for (Node cnod : children) {
final Node fcnod = (Node)cnod;
int dataSibl = fcnod.getChildId();
while (dataSibl != 0) {
final HilbertNode currentData = (HilbertNode) tAF.readNode(dataSibl);
dataSibl = currentData.getSiblingId();
currentData.setSiblingId(0);// become distinc
data.add(currentData);
}
tAF.removeNode(fcnod);
}
clear();
// empty cells creation.
final int nbCell = currentHilbertOrder == 0 ? 1 : 2 << (dimension * currentHilbertOrder - 1);
for (int i = 0; i < nbCell; i++) {
super.addChild(tAF.createNode(null, IS_CELL, this.getNodeId(), 0, 0));
}
data.add(node);
for (Node dat : data) {
setBoundary(boundIncrease);
addChild(dat);
}
} else {
children[index].addChild(node);
dataCount++;
double[] cuChildBound = children[index].getBoundary();
if (boundary == null || ArraysExt.hasNaN(boundary)) {
boundary = cuChildBound.clone();
} else {
add(boundary, cuChildBound);
}
tAF.writeNode(this);
}
} else {
super.addChild(node);
}
}
/**
* {@inheritDoc }.
*/
@Override
public boolean removeData(final int identifier, final double... coordinates) throws IOException {
if (!((properties & 5) != 0))// test isleaf or iscell
throw new IllegalStateException("You should not call removeData() method on a no leaf or cell Node.");
if (isLeaf()) {
final Node[] children = super.getChildren();
if (currentHilbertOrder < 1) {
assert children.length == 1 : "removeChild : hilbertLeaf : leaf should have only one cell.";
final boolean removed = children[0].removeData(identifier, coordinates);
if (removed) {
dataCount--;
boundary = (dataCount > 0) ? children[0].getBoundary().clone() : null;
tAF.writeNode(this);
}
return removed;
} else {
final double[] objectBoundary = coordinates.clone();
int index = getHVOfEntry(objectBoundary);
assert index < children.length : "index out of children bound. Expected : "+children.length+" found index : "+index;
boolean removed = false;
boolean oneTime = true;
int s = children.length;
final int imax = s-1;
double[] boundAdd = null;// voir pour travailler directement avec la boundary.
/**
* Begin loop at index to be in accordance with Hilbert RTree properties.
*/
for (int i = index; i < s; i++) {
if (!children[i].isEmpty()) {
if (!removed) {
removed = children[i].removeData(identifier, coordinates);
if (removed) dataCount--;
}
if (!children[i].isEmpty()) {
// boundary
if (boundAdd == null) {
boundAdd = children[i].getBoundary().clone();
} else {
add(boundAdd, children[i].getBoundary());
}
}
}
if (i == imax && oneTime) {
oneTime = false;
i = -1;
s = index;
}
}
if (!removed) return false;
// affect new boundary
boundary = boundAdd;
tAF.writeNode(this);
final int maxElts = tAF.getMaxElementPerCells();
final int hOrder = (dataCount <= maxElts) ? 0 : (int)((Math.log(dataCount-1)-Math.log(maxElts))/(dimension*LN2)) + 1;
assert hOrder <= currentHilbertOrder : "HilbertLeaf : hilbert Order compute after remove is not correct. Expected : <= "+currentHilbertOrder+" found : "+hOrder;
if (hOrder < currentHilbertOrder) {
// on recupere tout les elements contenu dans cette feuille.
data.clear();
for (Node cnod : children) {
final Node fcnod = (Node)cnod;
int dataSibl = fcnod.getChildId();
while (dataSibl != 0) {
final HilbertNode currentData = (HilbertNode) tAF.readNode(dataSibl);
dataSibl = currentData.getSiblingId();
currentData.setSiblingId(0);// become distinc
data.add(currentData);
}
tAF.removeNode(fcnod);
}
clear();
currentHilbertOrder = hOrder;
// on creer les cells null
final int nbCell = currentHilbertOrder == 0 ? 1 : 2 << (dimension * currentHilbertOrder - 1);
for (int i = 0; i < nbCell; i++) {
super.addChild(tAF.createNode(null, IS_CELL, this.getNodeId(), 0, 0));
}
for (Node dat : data) {
setBoundary(boundAdd);
addChild(dat);
}
}
return true;
}
}
return super.removeData(identifier, coordinates);
}
/**
* Return true if this HilbertNode is a Tree cell else false.
*
* @return true if this HilbertNode is a Tree cell else false.
*/
public boolean isCell() {
return (properties & IS_CELL) != 0;
}
/**
* {@inheritDoc }.
*/
@Override
public void clear() {
super.clear();
dataCount = 0;
}
/**
* {@inheritDoc }.
*/
@Override
public Node[] getChildren() throws IOException {
final Node[] superChilds = super.getChildren();
if (!isLeaf()) return superChilds;
final Node[] dataChilds = new Node[dataCount];
int dcID = 0;
for (Node sc : superChilds) {
final Node[] currentDataTab = sc.getChildren();
final int cuDTLength = currentDataTab.length;
System.arraycopy(currentDataTab, 0, dataChilds, dcID, cuDTLength);
dcID += cuDTLength;
}
assert dcID == dataCount : "FileHilbertNode : getChildren : dataCount and data number doesn't match.";
return dataChilds;
}
/**
* {@inheritDoc }.
*/
@Override
public boolean checkInternal() throws IOException {
if (isLeaf()) {
if (isEmpty()) return true;
double[] superBound = null;
Node[] superChilds = super.getChildren();
int nbrData = 0;
for (Node nod : superChilds) {
final Node currSC = (Node)nod;
if (currSC.getParentId() != nodeId)
throw new IllegalStateException("cell parent ID should be equals to this.nodeID.");
if (!currSC.isEmpty()) {
final double[] currBound = currSC.getBoundary().clone();
final int currNID = currSC.getNodeId();
if (superBound == null) {
superBound = currBound;
} else {
add(superBound, currBound);
}
double[] dataBound = null;
Node[] dataChilds = currSC.getChildren();
for (Node dat : dataChilds) {
Node fdat = (Node)dat;
if (dataBound == null) {
dataBound = fdat.getBoundary().clone();
} else {
add(dataBound, fdat.getBoundary());
}
if (fdat.getParentId() != currNID)
throw new IllegalStateException("data parent ID should be equals to its parent cell ID. Expected : "+currNID+". Found : "+fdat.getParentId());
nbrData++;
}
if (!Arrays.equals(dataBound, currBound))
throw new IllegalStateException("add data boundary should have same boundary than its cell boundary for node : "+currSC.getNodeId()+". \nExpected : "+Arrays.toString(dataBound)+"\nFound : "+Arrays.toString(currBound));
}
}
if (!Arrays.equals(superBound, getBoundary()))
throw new IllegalStateException("add cells boundary should have same boundary than this boundary. expected : "+Arrays.toString(getBoundary())+" found : "+Arrays.toString(superBound));
if (dataCount != nbrData)
throw new IllegalStateException("data number should be same than leaf data count. expected : "+nbrData+" found : "+dataCount);
return true;
} else {
return super.checkInternal();
}
}
/**
* Return true if this HilbertNode is empty.<br/>
* If hilbertNode is a tree leaf, it define empty when its all leaf cells are empty.<br/>
* Else it define empty like normaly comportement when it has no children.
*
* @return true if this HilbertNode is empty.
*/
@Override
public boolean isEmpty() {
if (isLeaf()) return dataCount == 0;
return super.isEmpty();
}
/**
* Return true if HilbertNode is full.<br/>
* If hilbertNode is a tree leaf, it define full when its all leaf cells are full
* and its hilbertOrder reach the maximum hilbert order permit by Hilbert Tree implementation.<br/>
* Else it define full like normaly comportement when it has children number equal to maximum elements per Node permit by tree.
*
* @return true if HilbertNode is full.
* @throws IOException
*/
@Override
public boolean isFull() throws IOException {
if (isLeaf()) {
return isInternalyFull() && currentHilbertOrder == tAF.getHilbertOrder();
} else {
return super.isFull();
}
}
/**
* Return the appropriate table index of Hilbert cell within {@link #children} table.
*
* @param coordinate boundary of element which will be insert.
* @return Return the appropriate table index of Hilbert cell else return -1 if all cell are full.
*/
private int getAppropriateCellIndex(final Node[] children, final double... coordinate) throws IOException {
if (currentHilbertOrder < 1) {//only one cell.
return (children[0].isFull()) ? -1 : 0;
}
final int index = getHVOfEntry(coordinate);
return findCell(children, index);
}
/**
* To answer Hilbert criterion and to avoid call split method, in some case
* we constrain tree leaf to choose another cell to insert Entry.<br/>
* Return -1 if all cells are full.
*
* @param index of subnode which is normally chosen.
* @param ptEntryCentroid subnode chosen centroid.
* @throws IllegalArgumentException if method call by none leaf {@code Node}.
* @throws IllegalArgumentException if index is out of required limit.
* @throws IllegalStateException if no another cell is find.
* @return index of another subnode.
*/
private int findCell(final Node[] children, final int index) throws IOException {
if (!isLeaf()) throw new IllegalArgumentException("impossible to find another leaf in Node which isn't LEAF tree");
final int siz = getChildCount();
assert (index < siz) : "wrong index in findAnotherCell";
boolean oneTime = false;
int indexTemp1 = index;
for (int i = index; i < siz; i++) {
if (!children[i].isFull()) {
return i;
}
if (i == siz - 1) {
if (oneTime) return - 1;//all cells are full
oneTime = true;
i = -1;
}
}
return indexTemp1;
}
/**
* Find Hilbert order of an entry from candidate.
*
* @param candidate entry 's hilbert value from it.
* @param objectBoundary which we looking for its Hilbert order.
* @throws IllegalArgumentException if parameter "entry" is out of this node
* boundary.
* @throws IllegalArgumentException if entry is null.
* @return integer the entry Hilbert order.
*/
private int getHVOfEntry(double[] objectBoundary) throws IOException {
ArgumentChecks.ensureNonNull("impossible to define Hilbert coordinate with null entry", objectBoundary);
final double[] ptCE = getMedian(objectBoundary);
final double[] bound = getBoundary().clone();
add(bound, objectBoundary);
final int order = currentHilbertOrder;
if (! contains(bound, ptCE)) throw new IllegalArgumentException("entry is out of this node boundary");
int[] hCoord = getHilbCoord(ptCE, bound, order);
final int spaceHDim = hCoord.length;
if (spaceHDim == 1) return hCoord[0];
final HilbertIterator hIt = new HilbertIterator(order, spaceHDim);
int hilberValue = 0;
while (hIt.hasNext()) {
final int[] currentCoords = hIt.next();
assert hilberValue < getChildCount() : "getHVOfEntry : hilbert value out of bound.";
if (Arrays.equals(hCoord, currentCoords)) return hilberValue;
hilberValue++;
}
throw new IllegalArgumentException("should never throw");
}
/**
* Find {@code DirectPosition} Hilbert coordinate from this Node.
*
* @param pt {@code DirectPosition}
* @throws IllegalArgumentException if parameter "dPt" is out of this node
* boundary.
* @throws IllegalArgumentException if parameter dPt is null.
* @return int[] table of length 3 which contains 3 coordinates.
*/
private static int[] getHilbCoord(final double[] point, final double[] envelope, final int hilbertOrder) {
ArgumentChecks.ensureNonNull("DirectPosition dPt : ", point);
if (!contains(envelope, point)) {
throw new IllegalArgumentException("Point is out of this node boundary");
}
final double div = 2 << hilbertOrder - 1;
List<Integer> lInt = new ArrayList<Integer>();
for(int d = 0, dim = envelope.length/2; d < dim; d++){
final double span = getSpan(envelope, d);
if (span <= 1E-9) continue;
final double currentDiv = span/div;
int val = (int) (Math.abs(point[d] - getMinimum(envelope, d)) / currentDiv);
if (val == div) val--;
lInt.add(val);
}
final int[] result = new int[lInt.size()];
int i = 0;
for (Integer val : lInt) result[i++] = val;
return result;
}
/**
* {@inheritDoc}.
*/
@Override
public String toString() {
final List toString;
try {
if (!isData()) {
toString = Arrays.asList(super.getChildren());
String strparent = (getParentId() == 0) ? "null" : (""+getParentId());
final String strHilbertLeaf = (isLeaf())
? " hilbert Order : "+getCurrentHilbertOrder() +" children number : "+getChildCount()+" data number : "+getDataCount()
: " children number : "+getChildCount();
return Trees.toString(Classes.getShortClassName(this)+" parent : "+strparent+" : ID : "+getNodeId()
+ " leaf : "+isLeaf()+" sibling : "+getSiblingId()+" child "+getChildId()+strHilbertLeaf+" "+Arrays.toString(getBoundary()), toString);
} else {
return Classes.getShortClassName(this)+"Data : parent : "+getParentId()+" ID : "+getNodeId()+" sibling : "+getSiblingId()+" value : "+(-getChildId())+" bound : "+Arrays.toString(getBoundary());
}
} catch (IOException ex) {
Logging.getLogger("org.geotoolkit.index.tree.hilbert").log(Level.SEVERE, null, ex);
}
return null;
}
}