/*
* 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;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import junit.framework.Assert;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.util.ArgumentChecks;
import org.junit.Test;
import static org.geotoolkit.internal.tree.TreeUtilities.*;
import static org.geotoolkit.index.tree.TreeTest.createEntry;
import org.geotoolkit.internal.tree.TreeAccess;
import static org.junit.Assert.assertTrue;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.cs.CoordinateSystemAxis;
/**
* Test suite adapted for all {@link Tree} implementation.
*
* @author Remi Marechal (Geomatys).
*/
public abstract class AbstractTreeTest extends TreeTest {
/**
* data number inserted in Tree.
*/
private final int lSize = 300;
/**
* Data list which contain data use in this test series.
*/
private final List<double[]> lData = new ArrayList<double[]>();
/**
* Tree CRS.
*/
protected final CoordinateReferenceSystem crs;
/**
* Dimension of Tree CRS space.
*/
private final int dimension;
/**
* double table which contain "extends" area of all data.
*/
private final double[] minMax;
/**
* Contain Tree Node architecture.
*/
protected TreeAccess tAF;
/**
* Tested Tree.
*/
protected Tree<double[]> tree;
/**
* Do link between between TreeIdentifier and objects.
*/
protected TreeElementMapper<double[]> tEM;
/**
* Create tests series from specified {@link CoordinateReferenceSystem}.
*
* @param crs
*/
protected AbstractTreeTest(final CoordinateReferenceSystem crs) throws IOException {
super();
this.crs = crs;
this.dimension = crs.getCoordinateSystem().getDimension();
ArgumentChecks.ensurePositive("dimension", this.dimension);
final CoordinateSystem cs = crs.getCoordinateSystem();
minMax = new double[2 * dimension];
final double cartesianValue = 1E6;
for (int i = 0; i < dimension; i++) {
final CoordinateSystemAxis csa = cs.getAxis(i);
final double minV = csa.getMinimumValue();
minMax[i] = ( ! Double.isInfinite(minV)) ? minV : (minV < 0) ? -cartesianValue : cartesianValue;
final double maxV = csa.getMaximumValue();
minMax[i + dimension] = ( ! Double.isInfinite(maxV)) ? maxV : (maxV < 0) ? -cartesianValue : cartesianValue;
}
final double[] centerEntry = new double[dimension];
for (int i = 0; i < lSize; i++) {
for (int d = 0; d < dimension; d++) {
centerEntry[d] = (minMax[d+dimension]-minMax[d]) * Math.random() * Math.random() + minMax[d];
}
lData.add(createEntry(centerEntry));
}
}
/**
* Create test series from specified {@link Tree}.
*
* @param tree Tree which will be test.
*/
protected AbstractTreeTest(final Tree tree) throws IOException {
this(tree.getCrs());
this.tree = tree;
this.tEM = tree.getTreeElementMapper();
}
/**
* Insert appropriate elements in Tree.
*
* @throws StoreIndexException if problem during insertion.
* @throws IOException if problem during {@link TreeElementMapper#clear() } method.
*/
protected void insert() throws StoreIndexException, IOException {
tEM.clear();
for (int i = 0, s = lData.size(); i < s; i++) {
final double[] envData = lData.get(i).clone();
tree.insert(envData);
tree.flush(); //-- add persistence comportement
}
assertTrue("after massive insertion root node should not be null", tree.getRoot() != null);
}
/**
* Test if tree contain all elements inserted.
*
* @throws TransformException if entry can't be transform into tree crs.
*/
@Test
public void insertTest() throws StoreIndexException, IOException {
tree.setRoot(null);
insert();
final double[] gr = tree.getRoot().getBoundary();
final double[] envSearch = gr.clone();
final GeneralEnvelope rG = new GeneralEnvelope(crs);
rG.setEnvelope(gr);
int[] tabSearch = tree.searchID(rG);
final TreeIdentifierIterator triter = tree.search(rG);
final int[] tabIterSearch = new int[tabSearch.length];
int tabID = 0;
while (triter.hasNext()) {
tabIterSearch[tabID++] = triter.nextInt();
}
assertTrue("comparison between tabSearch from iterator not equals with tabSearch", compareID(tabSearch, tabIterSearch));
assertTrue(tabSearch.length == lData.size());
assertTrue(tree.getElementsNumber() == lData.size());
try {
final double[] ge = new double[]{ Double.NaN, 10, 5, Double.NaN};
tree.insert(ge);
Assert.fail("test should have fail");
} catch (Exception ex) {
assertTrue(ex instanceof IllegalArgumentException);
//ok
}
}
/**
* Compare node properties from its children.<br/>
* Compare Node boundary from its sub-Nodes boundary sum.<br/>
* Moreover verify conformity of stored datas.
*/
protected void checkNode(final Node node, List<double[]> listRef) throws StoreIndexException, IOException {
final double[] nodeBoundary = node.getBoundary();
double[] subNodeBound = null;
int sibl = node.getChildId();
while (sibl != 0) {
final Node currentChild = tAF.readNode(sibl);
assertTrue("Node child should never be empty.", !currentChild.isEmpty());
if (subNodeBound == null) {
subNodeBound = currentChild.getBoundary().clone();
} else {
add(subNodeBound, currentChild.getBoundary());
}
if (node.isLeaf()) {
assertTrue(currentChild.isData());
final int currentValue = - currentChild.getChildId();
final int listId = currentValue -1;
assertTrue("bad ID = "+(currentValue)
+" expected : "+Arrays.toString(listRef.get(listId))
+" found : "+Arrays.toString(currentChild.getBoundary()), Arrays.equals(currentChild.getBoundary(), listRef.get(listId)));
} else {
checkNode(currentChild, listRef);
}
sibl = currentChild.getSiblingId();
}
assertTrue("Node should have a boundary equals from its sub-Nodes boundary sum : "
+" \nNode boundary = "+Arrays.toString(nodeBoundary)
+"\nsub-nodes sum = "+Arrays.toString(subNodeBound), Arrays.equals(nodeBoundary, subNodeBound));
}
/**
* Compare all boundary node from their children boundary.
*
* @throws TransformException if entry can't be transform into tree crs.
*/
@Test
public void checkNodeTest() throws StoreIndexException, IOException {
tAF = ((AbstractTree)tree).getTreeAccess();
if (tree.getRoot() == null) insert();
checkNode(tree.getRoot(), lData);
}
/**
* Test search query on tree border.
*
* @throws TransformException if entry can't be transform into tree crs.
*/
@Test
public void queryOnBorderTest() throws StoreIndexException, IOException {
tree.setRoot(null);
tEM.clear();
final List<double[]> lGE = new ArrayList<double[]>();
final List<double[]> lGERef = new ArrayList<double[]>();
final double[] gR ;
assertTrue(tree.getElementsNumber() == 0);
if (dimension == 2) {
for (int i = 0; i < 20; i++) {
for (int j = 0; j < 20; j++) {
final double[] gE = new double[]{5 * i, 5 * j, 5 * i, 5 * j};
lGE.add(gE);
if (i == 19 && j > 3 && j < 18) {
lGERef.add(gE);
}
}
}
gR = new double[]{93, 18, 130, 87};
} else {
for (int i = 0; i < 20; i++) {
for (int j = 0; j < 20; j++) {
final double[] gE = new double[]{5 * i, 5 * j, 20, 5 * i, 5 * j, 20};
lGE.add(gE);
if (i == 19 && j > 3 && j < 18) {
lGERef.add(gE);
}
}
}
gR = new double[]{93, 18, 19, 130, 87, 21};
}
for (int i = 0, s = lGE.size(); i < s; i++) {
tree.insert(lGE.get(i));
tree.flush();
tEM.flush();
}
final GeneralEnvelope rG = new GeneralEnvelope(crs);
rG.setEnvelope(gR);
final int[] tabSearch = tree.searchID(rG);
final TreeIdentifierIterator triter = tree.search(rG);
final int[] tabIterSearch = new int[tabSearch.length];
int tabID = 0;
while (triter.hasNext()) {
tabIterSearch[tabID++] = triter.nextInt();
}
assertTrue("comparison between tabSearch from iterator not equals with tabSearch", compareID(tabSearch, tabIterSearch));
assertTrue(compareLists(lGERef, Arrays.asList(getResult(tabSearch))));
}
/**
* Test search query inside tree.
*/
@Test
public void queryInsideTest() throws StoreIndexException, IOException {
if (tree.getRoot() == null) insert();
final List<double[]> lDataTemp = new ArrayList<double[]>();
for (int i = 0; i < lSize; i++) {
lDataTemp.add(lData.get(i));
}
final GeneralEnvelope rG = new GeneralEnvelope(crs);
rG.setEnvelope(getExtent(lData));
int[] tabSearch = tree.searchID(rG);
final TreeIdentifierIterator triter = tree.search(rG);
final int[] tabIterSearch = new int[tabSearch.length];
int tabID = 0;
while (triter.hasNext()) {
tabIterSearch[tabID++] = triter.nextInt();
}
assertTrue("comparison between tabSearch from iterator not equals with tabSearch", compareID(tabSearch, tabIterSearch));
assertTrue(compareLists(lDataTemp, Arrays.asList(getResult(tabSearch))));
}
/**
* Test query outside of tree area.
*
* @throws TransformException if entry can't be transform into tree crs.
*/
@Test
public void queryOutsideTest() throws StoreIndexException, IOException {
if (tree.getRoot() == null) insert();
final double[] areaSearch = new double[dimension<<1];
for (int i = 0; i < dimension; i++) {
areaSearch[i] = minMax[i+1]+100;
areaSearch[dimension+i] = minMax[i+1]+2000;
}
final GeneralEnvelope rG = new GeneralEnvelope(crs);
rG.setEnvelope(areaSearch);
int[] tabResult = tree.searchID(rG);
final TreeIdentifierIterator triter = tree.search(rG);
final int[] tabIterSearch = new int[tabResult.length];
int tabID = 0;
while (triter.hasNext()) {
tabIterSearch[tabID++] = triter.nextInt();
}
assertTrue("comparison between tabSearch from iterator not equals with tabSearch", compareID(tabResult, tabIterSearch));
assertTrue(tabResult.length == 0);
}
/**
* Test insertion and deletion in tree.
*
* @throws TransformException if entry can't be transform into tree crs.
*/
@Test
public void insertDelete() throws StoreIndexException, IOException {
if (tree.getRoot() == null) insert();
Collections.shuffle(lData);
for (int i = 0, s = lData.size(); i < s; i++) {
assertTrue(tree.remove(lData.get(i)));
}
final GeneralEnvelope rG = new GeneralEnvelope(crs);
rG.setEnvelope(minMax.clone());
int[] tabSearch = tree.searchID(rG);
TreeIdentifierIterator triter = tree.search(rG);
int[] tabIterSearch = new int[tabSearch.length];
int tabID = 0;
while (triter.hasNext()) {
Assert.fail("test should not be pass here.");
tabIterSearch[tabID++] = triter.nextInt();
}
assertTrue(tabSearch.length == 0);
assertTrue(tree.getElementsNumber() == 0);
insert();
tabSearch = tree.searchID(rG);
triter = tree.search(rG);
tabIterSearch = new int[tabSearch.length];
tabID = 0;
while (triter.hasNext()) {
tabIterSearch[tabID++] = triter.nextInt();
}
assertTrue("comparison between tabSearch from iterator not equals with tabSearch", compareID(tabSearch, tabIterSearch));
assertTrue(compareLists(lData, Arrays.asList(getResult(tabSearch))));
}
/**
* Return result given by {@link TreeElementMapper} from tree identifier table given in parameter.
*
* @param tabID tree identifier table results.
* @return all object result (in our case Object = double[]).
* @throws IOException if problem during tree identifier "translate".
*/
protected double[][] getResult(int[] tabID) throws IOException {
final int l = tabID.length;
double[][] tabResult = new double[l][];
for (int i = 0; i < l; i++) {
tabResult[i] = tEM.getObjectFromTreeIdentifier(tabID[i]);
}
return tabResult;
}
}