/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2009-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.gui.swing.image; import java.io.IOException; import java.util.Arrays; import java.util.Set; import java.util.HashSet; import java.util.Collection; import java.util.Collections; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.geom.Rectangle2D; import java.awt.geom.AffineTransform; import org.opengis.metadata.spatial.PixelOrientation; import org.geotoolkit.gui.swing.ZoomPane; import org.apache.sis.util.logging.Logging; import org.geotoolkit.image.io.mosaic.Tile; import org.geotoolkit.image.io.mosaic.TileManager; import org.geotoolkit.coverage.grid.ImageGeometry; import org.apache.sis.referencing.operation.matrix.AffineTransforms2D; /** * Paints the silhouette of a set of tiles. * * @author Martin Desruisseaux (Geomatys) * @version 3.15 * * @since 3.00 * @module */ @SuppressWarnings("serial") final class MosaicPanel extends ZoomPane { /** * The margin to add around the tiles area, as a fraction of area width and height. */ private static final double MARGIN = 0.125; /** * An empty array of tile managers. */ static final TileManager[] NO_TILES = new TileManager[0]; /** * The manager of the tiles to be displayed. * May be empty but shall never be null. */ private TileManager[] managers; /** * The colors to be given to tiles of each tile managers. If this array is shorter * than {@code managers} array, then the last color is reused for all remaining managers. */ private Color[] tileColors = new Color[] { new Color(0, 64, 255, 92), new Color(255, 0, 0, 92) }; /** * The colors of selected tiles. By default they are derived from {@link #tileColors}. */ private Color[] selectedColors; /** * The area covered by all tiles in "real world" units. Will be computed when * {@link #getArea} will be invoked. */ private Rectangle2D area; /** * The selected tiles. */ private Set<Tile> selected; /** * Creates an initially empty canvas. */ public MosaicPanel() { super(UNIFORM_SCALE | TRANSLATE_X | TRANSLATE_Y | ROTATE | RESET); managers = NO_TILES; deriveSelectedColors(); } /** * Derives the selected colors from the current tile colors. */ private void deriveSelectedColors() { selectedColors = tileColors.clone(); for (int i=0; i<selectedColors.length; i++) { selectedColors[i] = selectedColors[i].darker(); } } /** * Sets the tiles to be displayed as selected tiles. Elements that are not in the set of tiles * managed by a {@link TileManger} will be ignored. * * @param selected The selected tiles. */ public void setSelectedTiles(final Tile... tiles) { if (tiles == null || tiles.length == 0) { selected = null; } else { final Collection<Tile> asList = Arrays.asList(tiles); if (selected == null) { selected = new HashSet<>(asList); } else { selected.clear(); selected.addAll(asList); } } repaint(); } /** * Returns the tiles to be displayed as selected tiles, or an empty array if none. */ public Tile[] getSelectedTiles() { Set<Tile> selected = this.selected; if (selected == null) { selected = Collections.emptySet(); } return selected.toArray(new Tile[selected.size()]); } /** * Sets the tiles to display. Only one tile manager is usually provided. However more managers * can be provided if, for example, {@link org.geotoolkit.image.io.mosaic.TileManagerFactory} * failed to create only one instance from a set of tiles. Only the first manager will be painted * with the foreground color. The other ones will be painted in a different color in order to * suggest that they should not be there. * * @param managers The new tile managers. */ public void setTileManagers(final TileManager... managers) { this.managers = managers.clone(); area = null; // For forcing computation. reset(); } /** * Returns the tile managers. The returned array may be empty but should never be null. * * @return The tile managers. */ public TileManager[] getTileManagers() { return managers.clone(); } /** * Convenience method returning the first tile manager, or {@code null} if none. * * @return The first tile mamanger, or {@code null}. */ public TileManager getTileManager() { return managers.length != 0 ? managers[0] : null; } /** * Returns the bounds of all tiles, or {@code null} if unknown. */ @Override public Rectangle2D getArea() { if (area == null) { for (int j=managers.length; --j>=0;) { final TileManager manager = managers[j]; if (manager == null) { continue; } final ImageGeometry geometry; try { geometry = manager.getGridGeometry(); } catch (IOException e) { Logging.recoverableException(null, MosaicPanel.class, "getArea", e); managers[j] = null; continue; // We will just ignore that tile manager. } if (geometry != null) { final Rectangle2D region = geometry.getEnvelope(PixelOrientation.UPPER_LEFT); if (area == null) { area = region; } else { area.add(region); } } } if (area == null) { return null; } final double width = area.getWidth(); final double height = area.getHeight(); area.setRect(area.getX() - width*(MARGIN/2), area.getY() - height*(MARGIN/2), width * (MARGIN+1), height * (MARGIN+1)); } return (Rectangle2D) area.clone(); } /** * Paints the tiles. */ @Override protected void paintComponent(final Graphics2D graphics) { final Set<Tile> selected = this.selected; final AffineTransform imageToDisplay = new AffineTransform(zoom); AffineTransform lastTr = null; for (int j=managers.length; --j>=0;) { final TileManager manager = managers[j]; if (manager == null) { continue; } final Collection<Tile> tiles; try { tiles = manager.getTiles(); } catch (IOException e) { Logging.recoverableException(null, MosaicPanel.class, "paintComponent", e); managers[j] = null; continue; // We will just ignore that tile manager. } final int ci = Math.min(tileColors.length-1, j); final Color selectedColor = selectedColors[ci]; final Color color = tileColors[ci]; graphics.setColor(color); boolean isSelected = false; for (final Tile tile : tiles) { final Rectangle bounds; try { bounds = tile.getRegion(); } catch (IOException e) { // Unexpected because if this exception were to occur, it should have // been thrown sooner (when we asked for the bounds of the whole mosaic). Logging.unexpectedException(null, MosaicPanel.class, "paintComponent", e); continue; } // The affine transform is usually the same instance for every tiles at // the same pyramid level, so it is worth to perform the check below. final AffineTransform tr = tile.getGridToCRS(); if (tr != lastTr) { imageToDisplay.setTransform(zoom); imageToDisplay.concatenate(tr); lastTr = tr; } AffineTransforms2D.transform(imageToDisplay, bounds, bounds); if (bounds.width >= 5 && bounds.height >= 5) { bounds.x++; bounds.width -= 2; bounds.y++; bounds.height -= 2; } if (selected != null && selected.contains(tile) != isSelected) { isSelected = !isSelected; graphics.setColor(isSelected ? selectedColor : color); } graphics.fill(bounds); } } } }