/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2008-2012, Open Source Geospatial Foundation (OSGeo)
* (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.image.io.mosaic;
import java.util.Set;
import java.util.Random;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.io.IOException;
import org.junit.*;
import org.apache.sis.test.DependsOn;
import org.geotoolkit.gui.swing.tree.Trees;
import static org.geotoolkit.test.Assert.*;
/**
* Tests {@link TreeNode} and {@link RTree}. The later is merely a wrapper around
* {@link TreeNode} except for the {@link RTree#searchTiles} method, which is not
* tested here (see {@link TileManagerTest} for that).
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.00
*
* @since 2.5
*/
@DependsOn(Tile.class)
public final strictfp class TreeNodeTest extends MosaicTestBase {
/**
* The root of an RTree for {@link #targetTiles}.
*/
private TreeNode root;
/**
* Initializes every fields in this class.
*
* @throws IOException If an I/O operation was required and failed.
*/
@Before
public void initTreeNode() throws IOException {
assertEquals(4733, targetTiles.length);
root = new GridNode(targetTiles);
}
/**
* Ensures that the view as a Swing tree is the same one that we get if we copy every
* nodes in the default Swing tree node implementations.
*/
@Test
public void testSwingTree() {
final javax.swing.tree.TreeNode copy = Trees.copy(root);
final int n = assertTreeEquals(copy, root);
assertEquals(4737, n);
final String text1 = Trees.toString(root);
final String text2 = Trees.toString(copy);
assertEquals(text1, text2);
}
/**
* Tests with a set of files corresponding to a Blue Marble mosaic.
*
* @throws IOException If an I/O operation was required and failed.
*/
@Test
public void testTreeNode() throws IOException {
// GridNode has many assert statements, so we want them enabled.
assertTrue(GridNode.class.desiredAssertionStatus());
assertNotNull(root.getUserObject());
assertEquals(root, root);
assertTrue (root.containsAll(manager.getTiles()));
assertFalse(root.containsAll(Arrays.asList(sourceTiles)));
final Rectangle bounds = new Rectangle(SOURCE_SIZE*4, SOURCE_SIZE*2);
final Rectangle roi = new Rectangle();
final Random random = new Random(4353223575290515986L);
for (int i=0; i<100; i++) {
roi.x = random.nextInt(bounds.width);
roi.y = random.nextInt(bounds.height);
roi.width = random.nextInt(bounds.width / 4);
roi.height = random.nextInt(bounds.height / 4);
final Set<Tile> intersect1 = toSet(root.intersecting(roi));
final Set<Tile> intersect2 = intersecting(targetTiles, roi);
final Set<Tile> contained1 = toSet(root.containedIn(roi));
final Set<Tile> contained2 = containedIn(targetTiles, roi);
assertEquals(intersect2, intersect1);
assertEquals(contained2, contained1);
assertFalse (intersect1.isEmpty()); // Only for our test suite (since empty set are not forbidden)
assertTrue (intersect1.containsAll(contained1));
assertFalse (contained1.containsAll(intersect1));
if (false) {
System.out.print(roi);
System.out.print(" intersect=");
System.out.print(intersect1.size());
System.out.print(" contained=");
System.out.println(contained1.size());
}
}
/*
* Creates a copy and ensure it is identical to the original one.
*/
final GridNode tree2 = new GridNode(targetTiles);
assertEquals(root, tree2);
/*
* Tests removal of nodes.
*/
assertEquals(root, tree2);
for (int i=0; i<targetTiles.length; i += 10) {
assertTrue(tree2.remove(targetTiles[i]));
}
assertFalse(root.deepEquals(tree2));
for (int i=0; i<20; i++) {
roi.x = random.nextInt(bounds.width);
roi.y = random.nextInt(bounds.height);
roi.width = random.nextInt(bounds.width / 4);
roi.height = random.nextInt(bounds.height / 4);
final Set<Tile> intersect1 = toSet(tree2.intersecting(roi));
final Set<Tile> intersect2 = intersecting(targetTiles, roi);
final Set<Tile> contained1 = toSet(tree2.containedIn(roi));
final Set<Tile> contained2 = containedIn(targetTiles, roi);
boolean removedSome = false;
for (int j=0; j<targetTiles.length; j += 10) {
final Tile tile = targetTiles[j];
removedSome |= intersect2.remove(tile);
removedSome |= contained2.remove(tile);
}
assertTrue (removedSome);
assertEquals(intersect2, intersect1);
assertEquals(contained2, contained1);
assertTrue (intersect1.containsAll(contained1));
assertFalse (contained1.containsAll(intersect1));
}
}
/**
* Tests the {@link RTree} class.
*
* @throws IOException If an I/O operation was required and failed.
*/
@Test
public void testRTree() throws IOException {
final RTree tree = new RTree(root);
if (false) {
/*
* For some unknown reason, using directly the root as the TreeNode leads to display
* anomalies. We have to copy in Swing default implementation. I don't know why since
* we have done our best in "testSwingTree" for ensuring that the original and the
* copy were identical.
*/
show(Trees.copy(root));
}
assertEquals(new Rectangle(SOURCE_SIZE*4, SOURCE_SIZE*2), tree.getBounds());
assertEquals(new Dimension(TARGET_SIZE, TARGET_SIZE), tree.getTileSize());
int[] subsamplings;
/*
* Subsampling 15 in the first series and subsampling 9 in the second series are repeated
* twice because the last occurrence is a "virtual tile" generated in order to separate
* tiles that would otherwise overlap. In the schema below, the tree on the left side is
* what we get before overlapping tiles are separated. Tiles with subsamplings 15 and 9
* are mixed in the same level because both of them are divisors of 45 while none of them
* are the divisor of the other (9 is not a divisor of 15). So GridNode automatically
* generate an extra level with same subsampling in order to separate them.
*
* 90 90
* ├───45 ├───45
* │ ├───15 │ ├───15
* │ ├───15 │ │ ├───15
* │ │ ... │ │ ├───15
* │ ├───09 │ │ ...
* │ ├───09 │ └───09
* │ ... │ ├───09
* └───45 │ ├───09
* ├───15 │ ...
* ├───15 └───45
* | ... ├───15
* ├───09 │ ├───15
* ├───09 │ ├───15
* ... │ ...
*/
subsamplings = new int[] {5,15,15,45,90};
checkSubsampling((GridNode) root, subsamplings, subsamplings.length, 3, 0);
subsamplings = new int[] {1,3,9,9,45,90};
checkSubsampling((GridNode) root, subsamplings, subsamplings.length, 4, 1);
}
/**
* Ensures that every children have the expected subsampling. This method invokes itself
* recursively down the tree. It is an helper method for {@link #testRTree} only. Checking
* subsampling is a convenient way to ensure that every tiles are where they should be.
*
* @param node The node to test.
* @param subsamplings The expected subsamplings for every levels in the tree.
* @param level The level as an index in the {@code subsamplings} array.
* @param branching For the first node without tile, the branch to select.
*/
private static void checkSubsampling(final GridNode node, final int[] subsamplings,
int level, final int branchPoint, final int branchToSelect) throws IOException
{
final String message = node.toString();
assertTrue(message, --level >= 0);
final int subsampling = subsamplings[level];
assertEquals(message, subsampling, node.getXSubsampling());
assertEquals(message, subsampling, node.getYSubsampling());
final GridNode parent = (GridNode) node.getParent();
if (parent != null) {
assertTrue(message, parent.contains(node));
assertTrue(message, parent.getIndex(node) >= 0);
}
final Tile tile = node.getUserObject();
if (tile != null) {
final Dimension d = tile.getSubsampling();
assertEquals(message, subsampling, d.width);
assertEquals(message, subsampling, d.height);
assertEquals(message, tile.getAbsoluteRegion(), node);
assertEquals(message, node, tile.getAbsoluteRegion()); // Tests reflexibility.
} else if (parent != null) {
assertTrue(message, node.equals(parent));
}
if (level != branchPoint) {
for (final TreeNode child : node) {
assertTrue(message, node.contains(child));
checkSubsampling((GridNode) child, subsamplings, level, branchPoint, branchToSelect);
}
} else {
assertEquals(message, 2, node.getChildCount());
final TreeNode child = node.getChildAt(branchToSelect);
assertNull(message, child.getUserObject());
assertTrue(message, node.contains(child));
checkSubsampling((GridNode) child, subsamplings, level, branchPoint, branchToSelect);
}
}
/**
* Copies the given collection into a set.
*/
private static Set<Tile> toSet(final Collection<Tile> tiles) {
final Set<Tile> asSet = new LinkedHashSet<>(tiles);
assertEquals(tiles.size(), asSet.size());
return asSet;
}
/**
* Returns the tiles intersecting the given region.
*/
private static Set<Tile> intersecting(final Tile[] tiles, final Rectangle region) throws IOException {
final Set<Tile> interest = new LinkedHashSet<>();
for (final Tile tile : tiles) {
if (region.intersects(tile.getAbsoluteRegion())) {
assertTrue(interest.add(tile));
}
}
return interest;
}
/**
* Returns the tiles entirely contained in the given region.
*/
private static Set<Tile> containedIn(final Tile[] tiles, final Rectangle region) throws IOException {
final Set<Tile> interest = new LinkedHashSet<>();
for (final Tile tile : tiles) {
if (region.contains(tile.getAbsoluteRegion())) {
assertTrue(interest.add(tile));
}
}
return interest;
}
}