/*
* 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.Map;
import java.awt.Rectangle;
/**
* A tree node selected because of its inclusion in a Region Of Interest (ROI).
* It contains an estimation of the cost of reading this tile and its children.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.00
*
* @since 2.5
* @module
*/
@SuppressWarnings("serial") // Not expected to be serialized.
final class SelectedNode extends TreeNode {
/**
* An estimation of the cost of reading this tile, including children.
*/
protected long cost;
/**
* Creates a new node for the given region.
*
* @param readRegion The region to be read.
*/
SelectedNode(final Rectangle readRegion) {
super(readRegion);
assert !isEmpty();
}
/**
* Adds the given tile as a child of this tile.
*
* @throws ClassCastException if the given child is not an instance of {@code SelectedNode}.
* This is an intentional restriction in order to avoid more subtile bugs later.
*/
@Override
public void addChild(final TreeNode child) throws ClassCastException {
super.addChild(child);
if (child != null) {
final long added = ((SelectedNode) child).cost;
if (added != 0) {
SelectedNode parent = this;
do {
parent.cost += added;
} while ((parent = (SelectedNode) parent.getParent()) != null);
}
}
}
/**
* Removes all children.
*/
@Override
public void removeChildren() {
final long removed = childrenCost();
if (removed != 0) {
SelectedNode parent = this;
do {
parent.cost -= removed;
} while ((parent = (SelectedNode) parent.getParent()) != null);
}
super.removeChildren();
}
/**
* Removes this tile and all its children from the tree.
*/
@Override
public void remove() {
if (cost != 0) {
TreeNode parent = this;
while ((parent = parent.getParent()) != null) {
((SelectedNode) parent).cost -= cost;
}
}
super.remove();
}
/**
* Returns the cost of children only, not including the {@linkplain #tile} in this node.
*/
private long childrenCost() {
long c = 0;
SelectedNode child = (SelectedNode) firstChildren();
while (child != null) {
c += child.cost;
child = (SelectedNode) child.nextSibling();
}
assert cost >= c;
return c;
}
/**
* Returns {@code true} if this node has a lower cost than the specified one.
*/
public final boolean isCheaperThan(final SelectedNode other) {
if (cost < other.cost) {
return true;
}
if (cost == other.cost) {
return getTileCount() < other.getTileCount();
}
return false;
}
/**
* Removes the nodes having the same bounding box than this tile, then process recursively for
* children. If such matchs are found, they are probably tiles at a different resolution.
* Retains the one which minimize the disk reading, and discards the other ones.
* <p>
* This check is not generic since we search for an exact match, but this case is common
* enough. Handling it with a {@link java.util.HashMap} will help to reduce the amount of
* tiles to handle in a more costly way later.
* <p>
* As a side effect, this method trims the bounding box of selected nodes to the tiles
* that they contain.
*
* @param overlaps An initially empty map. Will be filled through recursive invocation
* of this method while we iterate down the tree.
*/
final void removeTrivialOverlaps(final Map<Rectangle,SelectedNode> overlaps) {
/*
* Must process children first because if any of them are removed, it will lower
* the cost and consequently can change the decision taken at the end of this method.
*/
SelectedNode child = (SelectedNode) firstChildren();
while (child != null) {
// Must ask for next sibling before to filter since the later
// may set it to null if the child is removed from the tree.
final SelectedNode next = (SelectedNode) child.nextSibling();
child.removeTrivialOverlaps(overlaps);
child = next;
}
/*
* If this node is just a container for other nodes, trims the bounding box to the tiles
* that it contains. We would not need to do that if the size of parent nodes was always
* a multiple of child nodes. But sometime they are not, in which case some child nodes
* may live on the boundary between two parent nodes. It is not easy to predict in which
* parent such child will end up and what will be its bounding box since the computation
* involves intersection, but the end result is that two parents may have identical bbox
* while their child do not overlaps at all.
*
* We don't perform this computation if this node contains a tile, because in such case
* we assume that the bounding box was carefully chosen by the user. This is different
* than a null tile in which case the bounding box was calculated by our code.
*/
if (tile == null) {
child = (SelectedNode) firstChildren();
if (child != null) {
int xmin=x, ymin=y, w=width, h=height;
width = height = -1;
do {
assert !child.isEmpty() : child;
add(child);
child = (SelectedNode) child.nextSibling();
} while (child != null);
if (x < xmin) {w -= (xmin - x); x = xmin;}
if (y < ymin) {h -= (ymin - y); y = ymin;}
if (width > w) width = w;
if (height > h) height = h;
}
}
/*
* Now searches for overlaps.
*/
SelectedNode existing = overlaps.put(this, this);
if (existing != null && existing != this) {
if (!isCheaperThan(existing)) {
/*
* A cheaper tiles existed for the same bounds. Reinsert the previous tile in the
* map. We will delete this node from the tree later, except if the previous node
* is a children of this node. In the later case, we can't remove completely this
* node since it would remove its children as well, so we just nullify the tile.
*/
if (existing.getParent() == this) {
if (tile != null) {
tile = null;
cost = childrenCost();
}
return;
}
overlaps.put(existing, existing);
existing = this;
}
existing.remove();
existing.removeFrom(overlaps);
assert overlaps.get(existing) != existing;
}
}
/**
* Removes all children from the given map. Note that we do not removes this node directly
* because the corresponding Map.Entry is presumed already used by an other node.
*/
private void removeFrom(final Map<Rectangle,SelectedNode> overlaps) {
SelectedNode child = (SelectedNode) firstChildren();
while (child != null) {
child.removeFrom(overlaps);
final SelectedNode existing = overlaps.remove(child);
if (existing != null && existing != child) {
overlaps.put(existing, existing);
}
child = (SelectedNode) child.nextSibling();
}
}
/**
* Invoked in assertion for checking the validity of the whole tree. Returns a string
* representation of this selection as a tree. We do not override {@link #toString}
* because the later is used for formatting the nodes in the tree.
*/
@Override
boolean checkValidity() {
if (childrenCost() > cost) {
throw new AssertionError(this);
}
return super.checkValidity();
}
}