/* * 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.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.AbstractSet; import java.util.Objects; import java.awt.Dimension; import java.awt.Rectangle; import java.io.IOException; import javax.imageio.spi.ImageReaderSpi; import org.geotoolkit.util.collection.FrequencySortedSet; import static org.apache.sis.util.ArgumentChecks.ensureNonNull; /** * A tile manager for the particular case of tile distributed on a regular grid. * * @author Martin Desruisseaux (Geomatys) * @version 3.00 * * @since 2.5 * @module */ final class GridTileManager extends TileManager { /** * For cross-version inter-operability. */ private static final long serialVersionUID = -3140767174475649400L; /** * The root level of overviews. This is the level with highest subsampling. * It is also the root of a linked list toward finest levels. */ private final OverviewLevel root; /** * The region enclosing all tiles in absolute coordinates. This is the coordinates * relative to the tiles having a subsampling of 1. */ private final Rectangle region; /** * The number of tiles. */ private final int count; /** * A view over all tiles as a set. Will be created only when first needed. */ private transient Collection<Tile> tiles; /** * Creates a new tile manager from an existing collection of overviews. * This is for use by {@link MosaicBuilder} only. * * @param root The root overview to assign to the tile manageR. */ GridTileManager(final OverviewLevel root) { this.root = root; region = new Rectangle(-1, -1); int count = 0; OverviewLevel level = root; do { region.add(level.getAbsoluteRegion()); count += level.getNumTiles(); level = level.getFinerLevel(); } while (level != null); this.count = count; } /** * Creates a new tile manager for the given tiles, which must be distributed on a grid. * This constructor is protected for subclassing, but should not be invoked directly. * {@code GridTileManager} instances should be created by {@link TileManagerFactory}. * * @param tiles The tiles. * @throws IOException if an I/O operation was required and failed. * @throws IllegalArgumentException if this class can not handle the given tiles. */ protected GridTileManager(final Tile[] tiles) throws IOException, IllegalArgumentException { ensureNonNull("tiles", tiles); Tile[] modifiedOrder = tiles; // May be modified later. final Map<Dimension,OverviewLevel> levelsBySubsampling = new HashMap<>(); for (int i=0; i<modifiedOrder.length; i++) { Tile tile = modifiedOrder[i]; Dimension subsampling = tile.getSubsampling(); OverviewLevel level = levelsBySubsampling.get(subsampling); if (level == null) { /* * We are about to create a new OverviewLevel. We need to know the grid cell size. * We assume that this is the size of the largest tiles. Since the last row and the * last column may contain smaller tiles, and since the order of tiles in the user- * supplied array may be random, we search for larger tiles now. */ for (int j=i; ++j<modifiedOrder.length;) { final Tile candidate = modifiedOrder[j]; if (candidate.isLargerThan(tile)) { if (modifiedOrder == tiles) { modifiedOrder = modifiedOrder.clone(); } modifiedOrder[j] = tile; tile = candidate; subsampling = tile.getSubsampling(); } } level = new OverviewLevel(tile, subsampling); levelsBySubsampling.put(subsampling, level); } else { level.add(tile, subsampling); } } final OverviewLevel[] levels; levels = levelsBySubsampling.values().toArray(new OverviewLevel[levelsBySubsampling.size()]); Arrays.sort(levels); region = new Rectangle(-1, -1); int count = 0; for (int i=0; i<levels.length; i++) { final OverviewLevel level = levels[i]; level.createLinkedList(i, (i != 0) ? levels[i-1] : null); region.add(level.getAbsoluteRegion()); count += level.getNumTiles(); } this.count = count; root = (levels.length != 0) ? levels[levels.length - 1] : null; } /** * Returns the region enclosing all tiles. * * @return The region. <strong>Do not modify</strong> since it is a direct reference to * internal structures. */ @Override final Rectangle getRegion() { return region; } /** * Returns an estimation of tiles dimension. This method looks only to the first level * having more than 1 tile. * * @return The tiles dimension. */ @Override final Dimension getTileSize() { for (OverviewLevel level=root; level!=null; level=level.getFinerLevel()) { final Dimension size = level.getTileSize(); if (size != null) { return size; } } return region.getSize(); } /** * Returns {@code true} if there is more than one tile. * * @return {@code true} if the image is tiled. * @throws IOException If an I/O operation was required and failed. */ @Override final boolean isImageTiled() throws IOException { return count >= 2; } /** * Returns a reference to the tiles used internally by this tile manager. * This implementation returns an instance of {@link FrequencySortedSet} with * {@linkplain FrequencySortedSet#frequencies frequency values} greater than 1 * for the tiles that actually represent a pattern. */ @Override final Collection<Tile> getInternalTiles() { final FrequencySortedSet<Tile> tiles = new FrequencySortedSet<>(); for (OverviewLevel level=root; level!=null; level=level.getFinerLevel()) { level.getInternalTiles(tiles); } return tiles; } /** * Returns all tiles as an unmodifiable set. The tiles are generated on the fly * during the iteration. */ @Override public synchronized Collection<Tile> getTiles() { if (tiles == null) { tiles = new AbstractSet<Tile>() { @Override public int size() { return count; } @Override public Iterator<Tile> iterator() { return new GridTileIterator(root); } @Override public boolean contains(final Object object) { return (object instanceof Tile) && OverviewLevel.contains(root, (Tile) object); } }; } return tiles; } /** * Returns every tiles that intersect the given region. * * @throws IOException If it was necessary to fetch an image dimension from its * {@linkplain Tile#getImageReader reader} and this operation failed. */ @Override public Collection<Tile> getTiles(final Rectangle region, final Dimension subsampling, final boolean subsamplingChangeAllowed) throws IOException { for (OverviewLevel level=root; level!=null; level=level.getFinerLevel()) { final Tile sample = level.getSampleTile(); final Dimension doable = sample.getSubsamplingFloor(subsampling); if (doable == null) { // The current level can not handle the given subsampling or any finer one. continue; } if (doable != subsampling) { if (!subsamplingChangeAllowed) { // The current level can not handle the given subsampling // and we are not allowed to use a finer one. continue; } } /* * Gets the tiles at current level and checks if the cost of reading them is lower * than the cost of reading the tiles at the previous (coarser) level. They could * be lower if the region to read is small enough so that reading smaller tiles * compensate the cost of applying a higher subsampling. */ final ArrayList<Tile> tiles = new ArrayList<>(); level.getTiles(tiles, region, subsampling, Long.MAX_VALUE); // TODO: The search in finer levels is not yet implemented. subsampling.setSize(doable); return tiles; } return Collections.emptyList(); } /** * {@inheritDoc} * * @throws IOException If it was necessary to fetch an image dimension from its * {@linkplain Tile#getImageReader reader} and this operation failed. */ @Override public boolean intersects(final Rectangle region, final Dimension subsampling) throws IOException { for (OverviewLevel level=root; level!=null; level=level.getFinerLevel()) { final int c = level.compareTo(subsampling); if (c <= 0 && level.intersects(region)) { return true; } } return false; } /** * Returns {@code true} in evert cases, since the JAI TIFF image reader bug doesn't * manifest when the tile are organized on a regular grid. * * @since 3.15 */ @Override boolean canWriteInPlace(final ImageReaderSpi spi) { return true; } /** * Returns a hash code value for this tile manager. */ @Override public int hashCode() { return (int) serialVersionUID ^ root.hashCode(); } /** * Compares this tile manager with the specified object for equality. * * @param object The object to compare with. */ @Override public boolean equals(final Object object) { if (object != null && object.getClass() == getClass()) { final GridTileManager that = (GridTileManager) object; return count == that.count && Objects.equals(region, that.region) && Objects.equals(root, that.root); // Tests last since it may be expansive. } return false; } }