/*
* 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.image.io.mosaic;
import java.io.*;
import java.awt.*;
import javax.swing.*;
import java.awt.geom.AffineTransform;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import org.geotoolkit.util.Exceptions;
import org.geotoolkit.gui.swing.tree.TreeNode;
import org.geotoolkit.internal.GraphicsUtilities;
/**
* A panel showing the content of a {@link TreeTileManager}. This is a component of the
* {@link MosaicImageViewer} widget, but can also be used as a standalone widget when
* the manager to debug is known to be an instance of {@code TreeTileManager} and we
* don't want to load the image.
* <p>
* The tiles having a subsampling of (1,1) are outlined in gray, because they are often
* the basic building blocks of the grid. When a tile is selected, that tile is outlined
* in black, and all its parent tiles are outlines in black as well. The direct children
* (and only them, not the children of children) of the selected tiles are filled in blue,
* so we can check if the tile contains all the expected children.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.04
*
* @since 3.00
*/
@SuppressWarnings("serial")
final strictfp class TreeTileManagerViewer extends JPanel implements TreeSelectionListener {
/**
* The margin to put on left, right, top and bottom of the area where tile are drawn.
*/
private static final int MARGIN = 6;
/**
* The tile manager.
*/
private final TreeTileManager tiles;
/**
* The region enclosing all tiles.
*/
private final Rectangle bounds;
/**
* The currently selected tile.
*/
private TreeNode selected;
/**
* The fill color of tiles.
*/
private final Color tileColor = new Color(248, 248, 248);
/**
* The color to use for filling the children of the selected node.
*/
private final Color selectedChildren = new Color(0, 0, 255, 32);
/**
* Creates a viewer for the given manager.
*
* @param tiles The tile manager to display.
* @throws IOException if an I/O operation was required and failed.
*/
public TreeTileManagerViewer(final TreeTileManager tiles) throws IOException {
this.tiles = tiles;
this.bounds = tiles.getRegion();
setBackground(Color.WHITE);
}
/**
* Invoked when the tree selection changed. This method is public as
* an implementation side-effect and should not be invoked directly.
*
* @param event Contains the path to the selected node.
*/
@Override
public void valueChanged(final TreeSelectionEvent event) {
selected = (TreeNode) event.getPath().getLastPathComponent();
repaint();
}
/**
* Paints the outlines of tiles. First we paint with a gray color every tiles having a
* subsampling of (1,1), since they are the most "basic" tiles typically used as the
* source of other tiles. Next we paint the selected tiles on top of the previous ones.
*
* @param graphics The handler to use for drawing the tiles.
*/
@Override
protected void paintComponent(final Graphics graphics) {
super.paintComponent(graphics);
final Graphics2D gr = (Graphics2D) graphics;
final Paint oldPaint = gr.getPaint();
final AffineTransform oldTransform = gr.getTransform();
gr.translate(MARGIN, MARGIN);
gr.scale((getWidth() - MARGIN*2) / (double) bounds.width,
(getHeight() - MARGIN*2) / (double) bounds.height);
gr.translate(-bounds.x, -bounds.y);
try {
try {
/*
* Outline in light gray the tiles having a subsampling of (1,1).
*/
for (final Tile tile : tiles.getTiles()) {
final Dimension s = tile.getSubsampling();
if (s.width == 1 && s.height == 1) {
final Rectangle region = tile.getAbsoluteRegion();
gr.setColor(tileColor);
gr.fill(region);
gr.setColor(Color.LIGHT_GRAY);
gr.draw(region);
}
}
/*
* Paint the children of the selected tile in a transparent blue.
* Only immediate children are painted, not the children of children.
* Overlapping area are painted in red.
*/
if (selected != null) {
final int count = selected.getChildCount();
final Rectangle[] regions = new Rectangle[count];
int n = 0;
for (int i=0; i<count; i++) {
final Rectangle region = getAbsoluteRegion((TreeNode) selected.getChildAt(i));
if (region != null) {
regions[n++] = region;
gr.setPaint(selectedChildren);
gr.fill(region);
gr.setPaint(Color.BLUE);
gr.draw(region);
}
}
gr.setPaint(Color.RED);
for (int i=0; i<n; i++) {
for (int j=i+1; j<n; j++) {
final Rectangle overlaps = regions[i].intersection(regions[j]);
if (!overlaps.isEmpty()) {
gr.draw(overlaps);
}
}
}
}
/*
* Outline in black the selected tile and all its parents.
*/
gr.setColor(Color.BLACK);
for (TreeNode node=selected; node!=null; node=(TreeNode) node.getParent()) {
final Rectangle region = getAbsoluteRegion(node);
if (region != null) {
gr.draw(region);
}
}
} finally {
gr.setTransform(oldTransform);
gr.setPaint(oldPaint);
}
} catch (IOException e) {
Exceptions.paintStackTrace(gr, getBounds(), e);
}
}
/**
* Returns the absolute region for the tile at the given node, or {@code null} if unknown.
*/
private static Rectangle getAbsoluteRegion(final TreeNode node) throws IOException {
if (node instanceof Rectangle) {
return (Rectangle) node;
} else {
final Object value = node.getUserObject();
if (value instanceof Rectangle) {
return (Rectangle) value;
} else if (value instanceof Tile) {
final Tile tile = (Tile) node.getUserObject();
return tile.getAbsoluteRegion();
} else {
return null;
}
}
}
/**
* Creates a control panel for this viewer.
*
* @return A new control panel.
* @throws IOException if an I/O operation was required and failed.
*/
protected JComponent createControlPanel() throws IOException {
final JTree tree = new JTree(tiles.toTree());
tree.addTreeSelectionListener(this);
final JSplitPane panel = new JSplitPane();
panel.setLeftComponent(new JScrollPane(tree));
panel.setRightComponent(this);
return panel;
}
/**
* Displays this viewer in a frame. This is a convenience method only.
*
* @throws IOException If an I/O operation was required and failed.
*/
public void showInFrame() throws IOException {
final JFrame frame = new JFrame("TreeTileManagerViewer");
frame.add(createControlPanel());
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setSize(800, 600);
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
/**
* Loads a serialized {@link TreeTileManager} and display it.
*
* @param args A single command-line argument which is the name of the serialized mosaic.
* @throws IOException If the tiles can not be deserialized.
* @throws ClassNotFoundException If the serialized stream contains an unknown class.
*/
public static void main(final String[] args) throws IOException, ClassNotFoundException {
final Object tiles;
try (ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(args[0])))) {
tiles = in.readObject();
}
final TreeTileManager manager;
if (tiles instanceof ComparedTileManager) {
final ComparedTileManager cm = (ComparedTileManager) tiles;
if (cm.first instanceof TreeTileManager) {
manager = (TreeTileManager) cm.first;
} else {
manager = (TreeTileManager) cm.second;
}
} else {
manager = (TreeTileManager) tiles;
}
GraphicsUtilities.setLookAndFeel(MosaicImageViewer.class, "main");
final TreeTileManagerViewer viewer = new TreeTileManagerViewer(manager);
viewer.showInFrame();
}
}