/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2007-2008, Open Source Geospatial Foundation (OSGeo)
*
* 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.geotools.image.io.mosaic;
import java.util.Map;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import java.io.IOException;
import java.awt.geom.AffineTransform; // For javadoc
import org.geotools.factory.Hints;
import org.geotools.factory.AbstractFactory;
import org.geotools.coverage.grid.ImageGeometry;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.util.logging.Logging;
/**
* Creates {@link TileManager} instances from a collection of tiles.
*
* @since 2.5
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux
*/
public class TileManagerFactory extends AbstractFactory {
/**
* The default instance.
*/
public static final TileManagerFactory DEFAULT = new TileManagerFactory(null);
/**
* Creates a new factory from the specified hints.
*
* @param hints Optional hints, or {@code null} if none.
*/
protected TileManagerFactory(final Hints hints) {
// We have no usage for those hints at this time, but some may be added later.
}
/**
* Creates tile managers from the specified object, which may be {@code null}. If non-null, the
* object shall be an instance of {@code TileManager[]}, {@code TileManager}, {@code Tile[]} or
* {@code Collection<Tile>}.
*
* @param tiles The tiles, or {@code null}.
* @return The tile managers, or {@code null} if {@code tiles} was null.
* @throws IllegalArgumentException if {@code tiles} is not an instance of a valid class,
* or if it is an array or a collection containing null elements.
* @throws IOException If an I/O operation was required and failed.
*/
public TileManager[] createFromObject(final Object tiles)
throws IOException, IllegalArgumentException
{
final TileManager[] managers;
if (tiles == null) {
managers = null;
} else if (tiles instanceof TileManager[]) {
managers = ((TileManager[]) tiles).clone();
} else if (tiles instanceof TileManager) {
managers = new TileManager[] {(TileManager) tiles};
} else if (tiles instanceof Tile[]) {
managers = create((Tile[]) tiles);
} else if (tiles instanceof Collection) {
@SuppressWarnings("unchecked") // create(Collection) will checks indirectly.
final Collection<Tile> c = (Collection<Tile>) tiles;
managers = create(c);
} else {
throw new IllegalArgumentException(Errors.format(
ErrorKeys.ILLEGAL_CLASS_$2, tiles.getClass(), TileManager.class));
}
if (managers != null) {
for (int i=0; i<managers.length; i++) {
if (managers[i] == null) {
throw new IllegalArgumentException(Errors.format(
ErrorKeys.NULL_ARGUMENT_$1, "input[" + i + ']'));
}
}
}
return managers;
}
/**
* Creates tile managers from the specified array of tiles.
* This method usually returns a single tile manager, but more could be
* returned if this factory has been unable to put every tiles in a single mosaic
* (for example if the ratio between {@linkplain AffineTransform affine transform} given to
* {@linkplain Tile#Tile(ImageReaderSpi,Object,int,Dimension,AffineTransform) tile constructor}
* would lead to fractional {@linkplain Tile#getSubsampling subsampling}).
*
* @param tiles The tiles to give to a tile manager.
* @return A tile manager created from the given tiles.
* @throws IOException If an I/O operation was required and failed.
*/
public TileManager[] create(final Tile[] tiles) throws IOException {
// The default called invokes Collection.toArray(), which will copy the array.
return create(Arrays.asList(tiles));
}
/**
* Creates tile managers from the specified collection of tiles.
* This method usually returns a single tile manager, but more could be
* returned if this factory has been unable to put every tiles in a single mosaic
* (for example if the ratio between {@linkplain AffineTransform affine transform} given to
* {@linkplain Tile#Tile(ImageReaderSpi,Object,int,Dimension,AffineTransform) tile constructor}
* would lead to fractional {@linkplain Tile#getSubsampling subsampling}).
*
* @param tiles The tiles to give to a tile manager.
* @return A tile manager created from the given tiles.
* @throws IOException If an I/O operation was required and failed.
*/
public TileManager[] create(Collection<Tile> tiles) throws IOException {
int count = 0;
final TileManager[] managers;
if (!hasPendingGridToCRS(tiles)) {
/*
* There is no tile having a "gridToCRS" transform pending RegionCalculator work. So we
* can create (at the end of this method) a single TileManager using all those tiles.
*/
if (!tiles.isEmpty()) {
count = 1;
}
managers = new TileManager[count];
} else {
/*
* At least one tile have a pending "gridToCRS" transform (actually we should have
* more than one - typically all of them - otherwise the RegionCalculator work will
* be useless). Computes their region now. Note that we could execute this block
* inconditionnaly. The 'hasPendingGridToCRS' check we just for avoiding the cost
* of creating RegionCalculator in the common case where it is not needed. So it is
* not a big deal if 'hasPendingGridToCRS' conservatively returned 'true'.
*/
final Collection<Tile> remainings = new ArrayList<Tile>(Math.min(16, tiles.size()));
final RegionCalculator calculator = new RegionCalculator();
for (final Tile tile : tiles) {
if (!calculator.add(tile)) {
remainings.add(tile);
}
}
if (!remainings.isEmpty()) {
count = 1;
}
final Map<ImageGeometry,Tile[]> split = calculator.tiles();
managers = new TileManager[split.size() + count];
for (final Map.Entry<ImageGeometry,Tile[]> entry : split.entrySet()) {
final TileManager manager = createGeneric(entry.getValue());
manager.geometry = entry.getKey();
managers[count++] = manager;
}
tiles = remainings;
}
if (!tiles.isEmpty()) {
managers[0] = createGeneric(tiles.toArray(new Tile[tiles.size()]));
}
return managers;
}
/**
* Returns {@code true} if at least one tile in the given collection has at "<cite>grid to
* real world</cite>" transform waiting to be processed by {@link RegionCalculator}. It is
* okay to conservatively returns {@code true} in situations where we would have got
* {@code false} if synchronization was performed on every tiles.
*/
private static boolean hasPendingGridToCRS(final Collection<Tile> tiles) {
for (final Tile tile : tiles) {
if (tile.getPendingGridToCRS(false) != null) {
return true;
}
}
return false;
}
/**
* Creates a single {@linkplain TileManager tile manager} from the given array
* of tiles. This method is automatically invoked by {@code create} methods.
* The tile array has already been cloned and can be stored directly by the
* tile manager constructors.
* <p>
* Subclasses can override this method if they want to create other kinds of tile managers.
*
* @param tiles A copy of user-supplied tiles.
* @return The tile manager for the given tiles.
* @throws IOException If an I/O operation was required and failed.
*/
protected TileManager createGeneric(final Tile[] tiles) throws IOException {
TileManager manager;
try {
manager = new GridTileManager(tiles);
} catch (IllegalArgumentException e) {
// Failed to created the instance optimized for grid.
// Fallback on the more generic instance using RTree.
Logging.recoverableException(TileManagerFactory.class, "createGeneric", e);
return new TreeTileManager(tiles);
}
// Intentional side effect: use ComparedTileManager only if assertions are enabled.
assert (manager = new ComparedTileManager(manager, new TreeTileManager(tiles))) != null;
return manager;
}
}