/*
* Copyright (C) 2012 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
* This program 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see http://www.gnu.org/licenses/
*/
package org.esa.snap.ui.product;
import com.bc.ceres.core.Assert;
import com.bc.ceres.core.ProgressMonitor;
import com.bc.ceres.core.SubProgressMonitor;
import com.bc.ceres.glayer.support.ImageLayer;
import com.bc.ceres.glevel.MultiLevelSource;
import com.bc.ceres.grender.Viewport;
import com.bc.ceres.grender.support.BufferedImageRendering;
import com.bc.ceres.grender.support.DefaultViewport;
import com.bc.ceres.swing.progress.ProgressMonitorSwingWorker;
import org.esa.snap.core.dataio.ProductSubsetDef;
import org.esa.snap.core.datamodel.Band;
import org.esa.snap.core.datamodel.GeoCoding;
import org.esa.snap.core.datamodel.GeoPos;
import org.esa.snap.core.datamodel.Mask;
import org.esa.snap.core.datamodel.MetadataElement;
import org.esa.snap.core.datamodel.PixelPos;
import org.esa.snap.core.datamodel.Product;
import org.esa.snap.core.datamodel.ProductNode;
import org.esa.snap.core.datamodel.ProductNodeGroup;
import org.esa.snap.core.datamodel.RasterDataNode;
import org.esa.snap.core.datamodel.TiePointGrid;
import org.esa.snap.core.datamodel.VirtualBand;
import org.esa.snap.core.dataop.barithm.BandArithmetic;
import org.esa.snap.core.image.ColoredBandImageMultiLevelSource;
import org.esa.snap.core.jexp.ParseException;
import org.esa.snap.core.jexp.Term;
import org.esa.snap.core.param.ParamChangeEvent;
import org.esa.snap.core.param.ParamChangeListener;
import org.esa.snap.core.param.ParamGroup;
import org.esa.snap.core.param.Parameter;
import org.esa.snap.core.util.BeamConstants;
import org.esa.snap.core.util.Debug;
import org.esa.snap.core.util.Guardian;
import org.esa.snap.core.util.ProductUtils;
import org.esa.snap.core.util.StringUtils;
import org.esa.snap.core.util.math.MathUtils;
import org.esa.snap.ui.GridBagUtils;
import org.esa.snap.ui.ModalDialog;
import org.esa.snap.ui.SliderBoxImageDisplay;
import org.esa.snap.ui.UIUtils;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* A modal dialog used to specify data product subsets.
*/
public class ProductSubsetDialog extends ModalDialog {
private static final String MEM_LABEL_TEXT = "Estimated, raw storage size: "; /*I18N*/
private static final Color MEM_LABEL_WARN_COLOR = Color.red;
private static final Color MEM_LABEL_NORM_COLOR = Color.black;
private static final int MAX_THUMBNAIL_WIDTH = 148;
private static final int MIN_SUBSET_SIZE = 1;
private static final Font SMALL_PLAIN_FONT = new Font("SansSerif", Font.PLAIN, 10);
private static final Font SMALL_ITALIC_FONT = SMALL_PLAIN_FONT.deriveFont(Font.ITALIC);
private Product product;
private ProductSubsetDef productSubsetDef;
private ProductSubsetDef givenProductSubsetDef;
private JLabel memLabel;
private SpatialSubsetPane spatialSubsetPane;
private ProductNodeSubsetPane bandSubsetPane;
private ProductNodeSubsetPane tiePointGridSubsetPane;
private ProductNodeSubsetPane metadataSubsetPane;
private double memWarnLimit;
private static final double DEFAULT_MEM_WARN_LIMIT = 1000.0;
private AtomicBoolean updatingUI;
/**
* Constructs a new subset dialog.
*
* @param window the parent window
* @param product the product for which the subset is to be specified, must not be {@code null}
*/
public ProductSubsetDialog(Window window, Product product) {
this(window, product, DEFAULT_MEM_WARN_LIMIT);
}
/**
* Constructs a new subset dialog.
*
* @param window the parent window
* @param product the product for which the subset is to be specified, must not be {@code null}
* @param memWarnLimit the warning limit in megabytes
*/
public ProductSubsetDialog(Window window, Product product, double memWarnLimit) {
this(window, product, null, memWarnLimit);
}
/**
* Constructs a new subset dialog.
*
* @param window the parent window
* @param product the product for which the subset is to be specified, must not be {@code null}
* @param productSubsetDef the initial product subset definition, can be {@code null}
*/
public ProductSubsetDialog(Window window,
Product product,
ProductSubsetDef productSubsetDef) {
this(window, product, productSubsetDef, DEFAULT_MEM_WARN_LIMIT);
}
/**
* Constructs a new subset dialog.
*
* @param window the parent window
* @param product the product for which the subset is to be specified, must not be {@code null}
* @param productSubsetDef the initial product subset definition, can be {@code null}
* @param memWarnLimit the warning limit in megabytes
*/
public ProductSubsetDialog(Window window,
Product product,
ProductSubsetDef productSubsetDef,
double memWarnLimit) {
super(window, "Specify Product Subset", ID_OK | ID_CANCEL | ID_HELP, "subsetDialog");
Guardian.assertNotNull("product", product);
this.product = product;
givenProductSubsetDef = productSubsetDef;
this.productSubsetDef = new ProductSubsetDef("undefined");
this.memWarnLimit = memWarnLimit;
updatingUI = new AtomicBoolean(false);
createUI();
}
public Product getProduct() {
return product;
}
public ProductSubsetDef getProductSubsetDef() {
return productSubsetDef;
}
@Override
protected void onOK() {
boolean ok;
ok = checkReferencedRastersIncluded();
if (!ok) {
return;
}
ok = checkFlagDatasetIncluded();
if (!ok) {
return;
}
spatialSubsetPane.cancelThumbnailLoader();
if (productSubsetDef != null && productSubsetDef.isEntireProductSelected()) {
productSubsetDef = null;
}
super.onOK();
}
private boolean checkReferencedRastersIncluded() {
final Set<String> notIncludedNames = new TreeSet<>();
String[] nodeNames = productSubsetDef.getNodeNames();
if (nodeNames != null) {
final List<String> includedNodeNames = Arrays.asList(nodeNames);
for (final String nodeName : includedNodeNames) {
final RasterDataNode rasterDataNode = product.getRasterDataNode(nodeName);
if (rasterDataNode != null) {
collectNotIncludedReferences(rasterDataNode, notIncludedNames);
}
}
}
boolean ok = true;
if (!notIncludedNames.isEmpty()) {
StringBuilder nameListText = new StringBuilder();
for (String notIncludedName : notIncludedNames) {
nameListText.append(" '").append(notIncludedName).append("'\n");
}
final String pattern = "The following dataset(s) are referenced but not included\n" +
"in your current subset definition:\n" +
"{0}\n" +
"If you do not include these dataset(s) into your selection,\n" +
"you might get unexpected results while working with the\n" +
"resulting product.\n\n" +
"Do you wish to include the referenced dataset(s) into your\n" +
"subset definition?\n"; /*I18N*/
final MessageFormat format = new MessageFormat(pattern);
int status = JOptionPane.showConfirmDialog(getJDialog(),
format.format(new Object[]{nameListText.toString()}),
"Incomplete Subset Definition", /*I18N*/
JOptionPane.YES_NO_CANCEL_OPTION);
if (status == JOptionPane.YES_OPTION) {
final String[] nodenames = notIncludedNames.toArray(new String[notIncludedNames.size()]);
productSubsetDef.addNodeNames(nodenames);
ok = true;
} else if (status == JOptionPane.NO_OPTION) {
ok = true;
} else if (status == JOptionPane.CANCEL_OPTION) {
ok = false;
}
}
return ok;
}
private void collectNotIncludedReferences(final RasterDataNode rasterDataNode, final Set<String> notIncludedNames) {
final RasterDataNode[] referencedNodes = getReferencedNodes(rasterDataNode);
for (final RasterDataNode referencedNode : referencedNodes) {
final String name = referencedNode.getName();
if (!productSubsetDef.isNodeAccepted(name) && !notIncludedNames.contains(name)) {
notIncludedNames.add(name);
collectNotIncludedReferences(referencedNode, notIncludedNames);
}
}
}
private static RasterDataNode[] getReferencedNodes(final RasterDataNode node) {
final Product product = node.getProduct();
if (product != null) {
final List<String> expressions = new ArrayList<>(10);
if (node.getValidPixelExpression() != null) {
expressions.add(node.getValidPixelExpression());
}
final ProductNodeGroup<Mask> overlayMaskGroup = node.getOverlayMaskGroup();
if (overlayMaskGroup.getNodeCount() > 0) {
final Mask[] overlayMasks = overlayMaskGroup.toArray(new Mask[overlayMaskGroup.getNodeCount()]);
for (final Mask overlayMask : overlayMasks) {
final String expression;
if (overlayMask.getImageType() == Mask.BandMathsType.INSTANCE) {
expression = Mask.BandMathsType.getExpression(overlayMask);
} else if (overlayMask.getImageType() == Mask.RangeType.INSTANCE) {
expression = Mask.RangeType.getExpression(overlayMask);
} else {
expression = null;
}
if (expression != null) {
expressions.add(expression);
}
}
}
if (node instanceof VirtualBand) {
final VirtualBand virtualBand = (VirtualBand) node;
expressions.add(virtualBand.getExpression());
}
final ArrayList<Term> termList = new ArrayList<>(10);
for (final String expression : expressions) {
try {
final Term term = product.parseExpression(expression);
if (term != null) {
termList.add(term);
}
} catch (ParseException e) {
// @todo se handle parse exception
Debug.trace(e);
}
}
return BandArithmetic.getRefRasters(termList.toArray(new Term[termList.size()]));
}
return new RasterDataNode[0];
}
private boolean checkFlagDatasetIncluded() {
final String[] nodeNames = productSubsetDef.getNodeNames();
final List<String> flagDsNameList = new ArrayList<>(10);
boolean flagDsInSubset = false;
for (int i = 0; i < product.getNumBands(); i++) {
Band band = product.getBandAt(i);
if (band.getFlagCoding() != null) {
flagDsNameList.add(band.getName());
if (StringUtils.contains(nodeNames, band.getName())) {
flagDsInSubset = true;
}
break;
}
}
final int numFlagDs = flagDsNameList.size();
boolean ok = true;
if (numFlagDs > 0 && !flagDsInSubset) {
int status = JOptionPane.showConfirmDialog(getJDialog(),
"No flag dataset selected.\n\n"
+ "If you do not include a flag dataset in the subset,\n"
+ "you will not be able to create bitmask overlays.\n\n"
+ "Do you wish to include the available flag dataset(s)\n"
+ "in the current subset?\n",
"No Flag Dataset Selected",
JOptionPane.YES_NO_CANCEL_OPTION
);
if (status == JOptionPane.YES_OPTION) {
productSubsetDef.addNodeNames(flagDsNameList.toArray(new String[numFlagDs]));
ok = true;
} else if (status == JOptionPane.NO_OPTION) {
/* OK, no flag datasets wanted */
ok = true;
} else if (status == JOptionPane.CANCEL_OPTION) {
ok = false;
}
}
return ok;
}
@Override
protected void onCancel() {
spatialSubsetPane.cancelThumbnailLoader();
super.onCancel();
}
private void createUI() {
memLabel = new JLabel("####", JLabel.RIGHT);
JTabbedPane tabbedPane = new JTabbedPane();
setComponentName(tabbedPane, "TabbedPane");
spatialSubsetPane = createSpatialSubsetPane();
setComponentName(spatialSubsetPane, "SpatialSubsetPane");
if (spatialSubsetPane != null) {
tabbedPane.addTab("Spatial Subset", spatialSubsetPane); /*I18N*/
}
bandSubsetPane = createBandSubsetPane();
setComponentName(bandSubsetPane, "BandSubsetPane");
if (bandSubsetPane != null) {
tabbedPane.addTab("Band Subset", bandSubsetPane);
}
tiePointGridSubsetPane = createTiePointGridSubsetPane();
setComponentName(tiePointGridSubsetPane, "TiePointGridSubsetPane");
if (tiePointGridSubsetPane != null) {
tabbedPane.addTab("Tie-Point Grid Subset", tiePointGridSubsetPane);
}
metadataSubsetPane = createAnnotationSubsetPane();
setComponentName(metadataSubsetPane, "MetadataSubsetPane");
if (metadataSubsetPane != null) {
tabbedPane.addTab("Metadata Subset", metadataSubsetPane);
}
tabbedPane.setPreferredSize(new Dimension(512, 380));
tabbedPane.setSelectedIndex(0);
JPanel contentPane = new JPanel(new BorderLayout(4, 4));
setComponentName(contentPane, "ContentPane");
contentPane.add(tabbedPane, BorderLayout.CENTER);
contentPane.add(memLabel, BorderLayout.SOUTH);
setContent(contentPane);
updateSubsetDefNodeNameList();
}
private SpatialSubsetPane createSpatialSubsetPane() {
return new SpatialSubsetPane();
}
private ProductNodeSubsetPane createBandSubsetPane() {
Band[] bands = product.getBands();
if (bands.length == 0) {
return null;
}
return new ProductNodeSubsetPane(product.getBands(), true);
}
private ProductNodeSubsetPane createTiePointGridSubsetPane() {
TiePointGrid[] tiePointGrids = product.getTiePointGrids();
if (tiePointGrids.length == 0) {
return null;
}
return new ProductNodeSubsetPane(product.getTiePointGrids(),
new String[]{BeamConstants.LAT_DS_NAME, BeamConstants.LON_DS_NAME},
true);
}
private ProductNodeSubsetPane createAnnotationSubsetPane() {
final MetadataElement metadataRoot = product.getMetadataRoot();
final MetadataElement[] metadataElements = metadataRoot.getElements();
final String[] metaNodes;
if (metadataElements.length == 0) {
return null;
}
// metadata elements must be added to includeAlways list
// to ensure that they are selected if isIgnoreMetada is set to false
if (givenProductSubsetDef != null && !givenProductSubsetDef.isIgnoreMetadata()) {
metaNodes = new String[metadataElements.length];
for (int i = 0; i < metadataElements.length; i++) {
final MetadataElement metadataElement = metadataElements[i];
metaNodes[i] = metadataElement.getName();
}
} else {
metaNodes = new String[0];
}
final String[] includeNodes = StringUtils.addToArray(metaNodes, Product.HISTORY_ROOT_NAME);
return new ProductNodeSubsetPane(metadataElements, includeNodes, true);
}
private static void setComponentName(JComponent component, String name) {
if (component != null) {
Container parent = component.getParent();
if (parent != null) {
component.setName(parent.getName() + "." + name);
} else {
component.setName(name);
}
}
}
private void updateSubsetDefRegion(int x1, int y1, int x2, int y2, int sx, int sy) {
productSubsetDef.setRegion(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
productSubsetDef.setSubSampling(sx, sy);
updateMemDisplay();
}
private void updateSubsetDefNodeNameList() {
/* We don't use this option! */
productSubsetDef.setIgnoreMetadata(false);
productSubsetDef.setNodeNames(null);
if (bandSubsetPane != null) {
productSubsetDef.addNodeNames(bandSubsetPane.getSubsetNames());
}
if (tiePointGridSubsetPane != null) {
productSubsetDef.addNodeNames(tiePointGridSubsetPane.getSubsetNames());
}
if (metadataSubsetPane != null) {
productSubsetDef.addNodeNames(metadataSubsetPane.getSubsetNames());
}
updateMemDisplay();
}
private void updateMemDisplay() {
if (product != null) {
long storageMem = product.getRawStorageSize(productSubsetDef);
double factor = 1.0 / (1024 * 1024);
double megas = MathUtils.round(factor * storageMem, 10);
if (megas > memWarnLimit) {
memLabel.setForeground(MEM_LABEL_WARN_COLOR);
} else {
memLabel.setForeground(MEM_LABEL_NORM_COLOR);
}
memLabel.setText(MEM_LABEL_TEXT + megas + "M");
} else {
memLabel.setText(" ");
}
}
private class SpatialSubsetPane extends JPanel
implements ActionListener, ParamChangeListener, SliderBoxImageDisplay.SliderBoxChangeListener {
private Parameter paramX1;
private Parameter paramY1;
private Parameter paramX2;
private Parameter paramY2;
private Parameter paramSX;
private Parameter paramSY;
private Parameter paramWestLon1;
private Parameter paramEastLon2;
private Parameter paramNorthLat1;
private Parameter paramSouthLat2;
private SliderBoxImageDisplay imageCanvas;
private JCheckBox fixSceneWidthCheck;
private JCheckBox fixSceneHeightCheck;
private JLabel subsetWidthLabel;
private JLabel subsetHeightLabel;
private int thumbNailSubSampling;
private JButton setToVisibleButton;
private JScrollPane imageScrollPane;
private ProgressMonitorSwingWorker<BufferedImage, Object> thumbnailLoader;
private SpatialSubsetPane() {
initParameters();
createUI();
}
private void createUI() {
setThumbnailSubsampling();
final Dimension imageSize = getScaledImageSize();
thumbnailLoader = new ProgressMonitorSwingWorker<BufferedImage, Object>(this,
"Loading thumbnail image...") {
@Override
protected BufferedImage doInBackground(ProgressMonitor pm) throws Exception {
return createThumbNailImage(imageSize, pm);
}
@Override
protected void done() {
BufferedImage thumbnail = null;
try {
thumbnail = get();
} catch (Exception ignored) {
}
if (thumbnail != null) {
imageCanvas.setImage(thumbnail);
}
}
};
thumbnailLoader.execute();
imageCanvas = new SliderBoxImageDisplay(imageSize.width, imageSize.height, this);
imageCanvas.setSize(imageSize.width, imageSize.height);
setComponentName(imageCanvas, "ImageCanvas");
imageScrollPane = new JScrollPane(imageCanvas);
imageScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
imageScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
imageScrollPane.getViewport().setExtentSize(new Dimension(MAX_THUMBNAIL_WIDTH, 2 * MAX_THUMBNAIL_WIDTH));
setComponentName(imageScrollPane, "ImageScrollPane");
subsetWidthLabel = new JLabel("####", JLabel.RIGHT);
subsetHeightLabel = new JLabel("####", JLabel.RIGHT);
setToVisibleButton = new JButton("Use Preview");/*I18N*/
setToVisibleButton.setMnemonic('v');
setToVisibleButton.setToolTipText("Use coordinates of visible thumbnail area"); /*I18N*/
setToVisibleButton.addActionListener(this);
setComponentName(setToVisibleButton, "UsePreviewButton");
fixSceneWidthCheck = new JCheckBox("Fix full width");
fixSceneWidthCheck.setMnemonic('w');
fixSceneWidthCheck.setToolTipText("Checks whether or not to fix the full scene width");
fixSceneWidthCheck.addActionListener(this);
setComponentName(fixSceneWidthCheck, "FixWidthCheck");
fixSceneHeightCheck = new JCheckBox("Fix full height");
fixSceneHeightCheck.setMnemonic('h');
fixSceneHeightCheck.setToolTipText("Checks whether or not to fix the full scene height");
fixSceneHeightCheck.addActionListener(this);
setComponentName(fixSceneHeightCheck, "FixHeightCheck");
JPanel textInputPane = GridBagUtils.createPanel();
setComponentName(textInputPane, "TextInputPane");
final JTabbedPane tabbedPane = new JTabbedPane();
setComponentName(tabbedPane, "coordinatePane");
tabbedPane.addTab("Pixel Coordinates", createPixelCoordinatesPane());
tabbedPane.addTab("Geo Coordinates", createGeoCoordinatesPane());
tabbedPane.setEnabledAt(1, canUseGeoCoordinates(product));
GridBagConstraints gbc = GridBagUtils.createConstraints(
"insets.left=7,anchor=WEST,fill=HORIZONTAL, weightx=1.0");
GridBagUtils.setAttributes(gbc, "gridwidth=2");
GridBagUtils.addToPanel(textInputPane, tabbedPane, gbc, "gridx=0,gridy=0");
GridBagUtils.setAttributes(gbc, "insets.top=7,gridwidth=1");
GridBagUtils.addToPanel(textInputPane, new JLabel("Scene step X:"), gbc, "gridx=0,gridy=1");
GridBagUtils.addToPanel(textInputPane, UIUtils.createSpinner(paramSX, 1, "#0"), gbc, "gridx=1,gridy=1");
GridBagUtils.setAttributes(gbc, "insets.top=1");
GridBagUtils.addToPanel(textInputPane, new JLabel("Scene step Y:"), gbc, "gridx=0,gridy=2");
GridBagUtils.addToPanel(textInputPane, UIUtils.createSpinner(paramSY, 1, "#0"), gbc, "gridx=1,gridy=2");
GridBagUtils.setAttributes(gbc, "insets.top=4");
GridBagUtils.addToPanel(textInputPane, new JLabel("Subset scene width:"), gbc, "gridx=0,gridy=3");
GridBagUtils.addToPanel(textInputPane, subsetWidthLabel, gbc, "gridx=1,gridy=3");
GridBagUtils.setAttributes(gbc, "insets.top=1");
GridBagUtils.addToPanel(textInputPane, new JLabel("Subset scene height:"), gbc, "gridx=0,gridy=4");
GridBagUtils.addToPanel(textInputPane, subsetHeightLabel, gbc, "gridx=1,gridy=4");
GridBagUtils.setAttributes(gbc, "insets.top=4,gridwidth=1");
GridBagUtils.addToPanel(textInputPane, new JLabel("Source scene width:"), gbc, "gridx=0,gridy=5");
GridBagUtils.addToPanel(textInputPane, new JLabel(String.valueOf(product.getSceneRasterWidth()),
JLabel.RIGHT), gbc, "gridx=1,gridy=5");
GridBagUtils.setAttributes(gbc, "insets.top=1");
GridBagUtils.addToPanel(textInputPane, new JLabel("Source scene height:"), gbc, "gridx=0,gridy=6");
GridBagUtils.addToPanel(textInputPane, new JLabel(String.valueOf(product.getSceneRasterHeight()),
JLabel.RIGHT), gbc, "gridx=1,gridy=6");
GridBagUtils.setAttributes(gbc, "insets.top=7,gridwidth=1, gridheight=2");
GridBagUtils.addToPanel(textInputPane, setToVisibleButton, gbc, "gridx=0,gridy=7");
GridBagUtils.setAttributes(gbc, "insets.top=7,gridwidth=1, gridheight=1");
GridBagUtils.addToPanel(textInputPane, fixSceneWidthCheck, gbc, "gridx=1,gridy=7");
GridBagUtils.setAttributes(gbc, "insets.top=1,gridwidth=1");
GridBagUtils.addToPanel(textInputPane, fixSceneHeightCheck, gbc, "gridx=1,gridy=8");
setLayout(new BorderLayout(4, 4));
add(imageScrollPane, BorderLayout.WEST);
add(textInputPane, BorderLayout.CENTER);
setBorder(BorderFactory.createEmptyBorder(7, 7, 7, 7));
updateUIState(null);
imageCanvas.scrollRectToVisible(imageCanvas.getSliderBoxBounds());
}
private boolean canUseGeoCoordinates(Product product) {
final GeoCoding geoCoding = product.getSceneGeoCoding();
return geoCoding != null && geoCoding.canGetPixelPos() && geoCoding.canGetGeoPos();
}
private JPanel createGeoCoordinatesPane() {
JPanel geoCoordinatesPane = GridBagUtils.createPanel();
setComponentName(geoCoordinatesPane, "geoCoordinatesPane");
GridBagConstraints gbc = GridBagUtils.createConstraints(
"insets.left=3,anchor=WEST,fill=HORIZONTAL, weightx=1.0");
GridBagUtils.setAttributes(gbc, "insets.top=4");
GridBagUtils.addToPanel(geoCoordinatesPane, new JLabel("North latitude bound:"), gbc, "gridx=0,gridy=0");
GridBagUtils.addToPanel(geoCoordinatesPane, UIUtils.createSpinner(paramNorthLat1, 1.0, "#0.00#"),
gbc, "gridx=1,gridy=0");
GridBagUtils.setAttributes(gbc, "insets.top=1");
GridBagUtils.addToPanel(geoCoordinatesPane, new JLabel("West longitude bound:"), gbc, "gridx=0,gridy=1");
GridBagUtils.addToPanel(geoCoordinatesPane, UIUtils.createSpinner(paramWestLon1, 1.0, "#0.00#"),
gbc, "gridx=1,gridy=1");
GridBagUtils.setAttributes(gbc, "insets.top=4");
GridBagUtils.addToPanel(geoCoordinatesPane, new JLabel("South latitude bound:"), gbc, "gridx=0,gridy=2");
GridBagUtils.addToPanel(geoCoordinatesPane, UIUtils.createSpinner(paramSouthLat2, 1.0, "#0.00#"),
gbc, "gridx=1,gridy=2");
GridBagUtils.setAttributes(gbc, "insets.top=1");
GridBagUtils.addToPanel(geoCoordinatesPane, new JLabel("East longitude bound:"), gbc, "gridx=0,gridy=3");
GridBagUtils.addToPanel(geoCoordinatesPane, UIUtils.createSpinner(paramEastLon2, 1.0, "#0.00#"),
gbc, "gridx=1,gridy=3");
return geoCoordinatesPane;
}
private JPanel createPixelCoordinatesPane() {
GridBagConstraints gbc = GridBagUtils.createConstraints(
"insets.left=3,anchor=WEST,fill=HORIZONTAL, weightx=1.0");
JPanel pixelCoordinatesPane = GridBagUtils.createPanel();
setComponentName(pixelCoordinatesPane, "pixelCoordinatesPane");
GridBagUtils.setAttributes(gbc, "insets.top=4");
GridBagUtils.addToPanel(pixelCoordinatesPane, new JLabel("Scene start X:"), gbc, "gridx=0,gridy=0");
GridBagUtils.addToPanel(pixelCoordinatesPane, UIUtils.createSpinner(paramX1, 25, "#0"),
gbc, "gridx=1,gridy=0");
GridBagUtils.setAttributes(gbc, "insets.top=1");
GridBagUtils.addToPanel(pixelCoordinatesPane, new JLabel("Scene start Y:"), gbc, "gridx=0,gridy=1");
GridBagUtils.addToPanel(pixelCoordinatesPane, UIUtils.createSpinner(paramY1, 25, "#0"),
gbc, "gridx=1,gridy=1");
GridBagUtils.setAttributes(gbc, "insets.top=4");
GridBagUtils.addToPanel(pixelCoordinatesPane, new JLabel("Scene end X:"), gbc, "gridx=0,gridy=2");
GridBagUtils.addToPanel(pixelCoordinatesPane, UIUtils.createSpinner(paramX2, 25, "#0"),
gbc, "gridx=1,gridy=2");
GridBagUtils.setAttributes(gbc, "insets.top=1");
GridBagUtils.addToPanel(pixelCoordinatesPane, new JLabel("Scene end Y:"), gbc, "gridx=0,gridy=3");
GridBagUtils.addToPanel(pixelCoordinatesPane, UIUtils.createSpinner(paramY2, 25, "#0"),
gbc, "gridx=1,gridy=3");
return pixelCoordinatesPane;
}
private void setThumbnailSubsampling() {
int w = product.getSceneRasterWidth();
thumbNailSubSampling = w / MAX_THUMBNAIL_WIDTH;
if (thumbNailSubSampling <= 1) {
thumbNailSubSampling = 1;
}
}
public void cancelThumbnailLoader() {
if (thumbnailLoader != null) {
thumbnailLoader.cancel(true);
}
}
public boolean isThumbnailLoaderCanceled() {
return thumbnailLoader != null && thumbnailLoader.isCancelled();
}
@Override
public void sliderBoxChanged(Rectangle sliderBoxBounds) {
int x1 = sliderBoxBounds.x * thumbNailSubSampling;
int y1 = sliderBoxBounds.y * thumbNailSubSampling;
int x2 = x1 + sliderBoxBounds.width * thumbNailSubSampling;
int y2 = y1 + sliderBoxBounds.height * thumbNailSubSampling;
int w = product.getSceneRasterWidth();
int h = product.getSceneRasterHeight();
if (x1 < 0) {
x1 = 0;
}
if (x1 > w - 2) {
x1 = w - 2;
}
if (y1 < 0) {
y1 = 0;
}
if (y1 > h - 2) {
y1 = h - 2;
}
if (x2 < 1) {
x2 = 1;
}
if (x2 > w - 1) {
x2 = w - 1;
}
if (y2 < 1) {
y2 = 1;
}
if (y2 > h - 1) {
y2 = h - 1;
}
// first reset the bounds, otherwise negative regions can occur
paramX1.setValue(0, null);
paramY1.setValue(0, null);
paramX2.setValue(w - 1, null);
paramY2.setValue(h - 1, null);
paramX1.setValue(x1, null);
paramY1.setValue(y1, null);
paramX2.setValue(x2, null);
paramY2.setValue(y2, null);
}
/**
* Invoked when an action occurs.
*/
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource().equals(fixSceneWidthCheck)) {
imageCanvas.setImageWidthFixed(fixSceneWidthCheck.isSelected());
final boolean enable = !fixSceneWidthCheck.isSelected();
paramX1.setUIEnabled(enable);
paramX2.setUIEnabled(enable);
}
if (e.getSource().equals(fixSceneHeightCheck)) {
imageCanvas.setImageHeightFixed(fixSceneHeightCheck.isSelected());
final boolean enable = !fixSceneHeightCheck.isSelected();
paramY1.setUIEnabled(enable);
paramY2.setUIEnabled(enable);
}
if (e.getSource().equals(setToVisibleButton)) {
imageCanvas.setSliderBoxBounds(imageScrollPane.getViewport().getViewRect(), true);
}
}
/**
* Called if the value of a parameter changed.
*
* @param event the parameter change event
*/
@Override
public void parameterValueChanged(ParamChangeEvent event) {
updateUIState(event);
}
private void initParameters() {
ParamGroup pg = new ParamGroup();
addPixelParameter(pg);
addGeoParameter(pg);
pg.addParamChangeListener(this);
}
private void addGeoParameter(ParamGroup pg) {
paramNorthLat1 = new Parameter("geo_lat1", 90.0);
paramNorthLat1.getProperties().setDescription("North bound latitude");
paramNorthLat1.getProperties().setPhysicalUnit("°");
paramNorthLat1.getProperties().setMinValue(-90.0);
paramNorthLat1.getProperties().setMaxValue(90.0);
pg.addParameter(paramNorthLat1);
paramWestLon1 = new Parameter("geo_lon1", -180.0);
paramWestLon1.getProperties().setDescription("West bound longitude");
paramWestLon1.getProperties().setPhysicalUnit("°");
paramWestLon1.getProperties().setMinValue(-180.0);
paramWestLon1.getProperties().setMaxValue(180.0);
pg.addParameter(paramWestLon1);
paramSouthLat2 = new Parameter("geo_lat2", -90.0);
paramSouthLat2.getProperties().setDescription("South bound latitude");
paramSouthLat2.getProperties().setPhysicalUnit("°");
paramSouthLat2.getProperties().setMinValue(-90.0);
paramSouthLat2.getProperties().setMaxValue(90.0);
pg.addParameter(paramSouthLat2);
paramEastLon2 = new Parameter("geo_lon2", 180.0);
paramEastLon2.getProperties().setDescription("East bound longitude");
paramEastLon2.getProperties().setPhysicalUnit("°");
paramEastLon2.getProperties().setMinValue(-180.0);
paramEastLon2.getProperties().setMaxValue(180.0);
pg.addParameter(paramEastLon2);
if (canUseGeoCoordinates(product)) {
syncLatLonWithXYParams();
}
}
private void addPixelParameter(ParamGroup pg) {
int w = product.getSceneRasterWidth();
int h = product.getSceneRasterHeight();
int x1 = 0;
int y1 = 0;
int x2 = w - 1;
int y2 = h - 1;
int sx = 1;
int sy = 1;
if (givenProductSubsetDef != null) {
Rectangle region = givenProductSubsetDef.getRegion();
if (region != null) {
x1 = region.x;
y1 = region.y;
final int preX2 = x1 + region.width - 1;
if (preX2 < x2) {
x2 = preX2;
}
final int preY2 = y1 + region.height - 1;
if (preY2 < y2) {
y2 = preY2;
}
}
sx = givenProductSubsetDef.getSubSamplingX();
sy = givenProductSubsetDef.getSubSamplingY();
}
final int wMin = MIN_SUBSET_SIZE;
final int hMin = MIN_SUBSET_SIZE;
paramX1 = new Parameter("source_x1", x1);
paramX1.getProperties().setDescription("Start X co-ordinate given in pixels"); /*I18N*/
paramX1.getProperties().setMinValue(0);
paramX1.getProperties().setMaxValue((w - wMin - 1) > 0 ? w - wMin - 1 : 0);
paramY1 = new Parameter("source_y1", y1);
paramY1.getProperties().setDescription("Start Y co-ordinate given in pixels"); /*I18N*/
paramY1.getProperties().setMinValue(0);
paramY1.getProperties().setMaxValue((h - hMin - 1) > 0 ? h - hMin - 1 : 0);
paramX2 = new Parameter("source_x2", x2);
paramX2.getProperties().setDescription("End X co-ordinate given in pixels");/*I18N*/
paramX2.getProperties().setMinValue(wMin - 1);
final Integer maxValue = w - 1;
paramX2.getProperties().setMaxValue(maxValue);
paramY2 = new Parameter("source_y2", y2);
paramY2.getProperties().setDescription("End Y co-ordinate given in pixels");/*I18N*/
paramY2.getProperties().setMinValue(hMin - 1);
paramY2.getProperties().setMaxValue(h - 1);
paramSX = new Parameter("source_sx", sx);
paramSX.getProperties().setDescription("Sub-sampling in X-direction given in pixels");/*I18N*/
paramSX.getProperties().setMinValue(1);
paramSX.getProperties().setMaxValue(w / wMin + 1);
paramSY = new Parameter("source_sy", sy);
paramSY.getProperties().setDescription("Sub-sampling in Y-direction given in pixels");/*I18N*/
paramSY.getProperties().setMinValue(1);
paramSY.getProperties().setMaxValue(h / hMin + 1);
pg.addParameter(paramX1);
pg.addParameter(paramY1);
pg.addParameter(paramX2);
pg.addParameter(paramY2);
pg.addParameter(paramSX);
pg.addParameter(paramSY);
}
private void updateUIState(ParamChangeEvent event) {
if (updatingUI.compareAndSet(false, true)) {
try {
if (event != null && canUseGeoCoordinates(product)) {
final String parmName = event.getParameter().getName();
if (parmName.startsWith("geo_")) {
final GeoPos geoPos1 = new GeoPos((Double) paramNorthLat1.getValue(),
(Double) paramWestLon1.getValue());
final GeoPos geoPos2 = new GeoPos((Double) paramSouthLat2.getValue(),
(Double) paramEastLon2.getValue());
updateXYParams(geoPos1, geoPos2);
} else if (parmName.startsWith("source_x") || parmName.startsWith("source_y")) {
syncLatLonWithXYParams();
}
}
int x1 = ((Number) paramX1.getValue()).intValue();
int y1 = ((Number) paramY1.getValue()).intValue();
int x2 = ((Number) paramX2.getValue()).intValue();
int y2 = ((Number) paramY2.getValue()).intValue();
int sx = ((Number) paramSX.getValue()).intValue();
int sy = ((Number) paramSY.getValue()).intValue();
updateSubsetDefRegion(x1, y1, x2, y2, sx, sy);
Dimension s = productSubsetDef.getSceneRasterSize(product.getSceneRasterWidth(),
product.getSceneRasterHeight());
subsetWidthLabel.setText(String.valueOf(s.getWidth()));
subsetHeightLabel.setText(String.valueOf(s.getHeight()));
int sliderBoxX1 = x1 / thumbNailSubSampling;
int sliderBoxY1 = y1 / thumbNailSubSampling;
int sliderBoxX2 = x2 / thumbNailSubSampling;
int sliderBoxY2 = y2 / thumbNailSubSampling;
int sliderBoxW = sliderBoxX2 - sliderBoxX1 + 1;
int sliderBoxH = sliderBoxY2 - sliderBoxY1 + 1;
Rectangle box = getScaledRectangle(new Rectangle(sliderBoxX1, sliderBoxY1, sliderBoxW, sliderBoxH));
imageCanvas.setSliderBoxBounds(box);
} finally {
updatingUI.set(false);
}
}
}
private void syncLatLonWithXYParams() {
final PixelPos pixelPos1 = new PixelPos(((Number) paramX1.getValue()).intValue(),
((Number) paramY1.getValue()).intValue());
final PixelPos pixelPos2 = new PixelPos(((Number) paramX2.getValue()).intValue(),
((Number) paramY2.getValue()).intValue());
final GeoCoding geoCoding = product.getSceneGeoCoding();
final GeoPos geoPos1 = geoCoding.getGeoPos(pixelPos1, null);
final GeoPos geoPos2 = geoCoding.getGeoPos(pixelPos2, null);
paramNorthLat1.setValue(geoPos1.getLat(), null);
paramWestLon1.setValue(geoPos1.getLon(), null);
paramSouthLat2.setValue(geoPos2.getLat(), null);
paramEastLon2.setValue(geoPos2.getLon(), null);
}
private void updateXYParams(GeoPos geoPos1, GeoPos geoPos2) {
final GeoCoding geoCoding = product.getSceneGeoCoding();
final PixelPos pixelPos1 = geoCoding.getPixelPos(geoPos1, null);
if (!pixelPos1.isValid()) {
pixelPos1.setLocation(0, 0);
}
final PixelPos pixelPos2 = geoCoding.getPixelPos(geoPos2, null);
if (!pixelPos2.isValid()) {
pixelPos2.setLocation(product.getSceneRasterWidth(),
product.getSceneRasterHeight());
}
final Rectangle.Float region = new Rectangle.Float();
region.setFrameFromDiagonal(pixelPos1.x, pixelPos1.y, pixelPos2.x, pixelPos2.y);
final Rectangle.Float productBounds = new Rectangle.Float(0, 0,
product.getSceneRasterWidth(),
product.getSceneRasterHeight());
Rectangle2D finalRegion = productBounds.createIntersection(region);
paramX1.setValue((int) finalRegion.getMinX(), null);
paramY1.setValue((int) finalRegion.getMinY(), null);
paramX2.setValue((int) finalRegion.getMaxX() - 1, null);
paramY2.setValue((int) finalRegion.getMaxY() - 1, null);
}
private Dimension getScaledImageSize() {
final int w = (product.getSceneRasterWidth() - 1) / thumbNailSubSampling + 1;
final int h = (product.getSceneRasterHeight() - 1) / thumbNailSubSampling + 1;
final Rectangle rectangle = new Rectangle(w, h);
return getScaledRectangle(rectangle).getSize();
}
private Rectangle getScaledRectangle(Rectangle rectangle) {
final AffineTransform i2mTransform = Product.findImageToModelTransform(product.getSceneGeoCoding());
final double scaleX = i2mTransform.getScaleX();
final double scaleY = i2mTransform.getScaleY();
double scaleFactorY = Math.abs(scaleY / scaleX);
final AffineTransform scaleTransform = AffineTransform.getScaleInstance(1.0, scaleFactorY);
return scaleTransform.createTransformedShape(rectangle).getBounds();
}
private BufferedImage createThumbNailImage(Dimension imgSize, ProgressMonitor pm) {
Assert.notNull(pm, "pm");
String thumbNailBandName = getThumbnailBandName();
Band thumbNailBand = product.getBand(thumbNailBandName);
Debug.trace("ProductSubsetDialog: Reading thumbnail data for band '" + thumbNailBandName + "'...");
pm.beginTask("Creating thumbnail image", 5);
BufferedImage image = null;
try {
MultiLevelSource multiLevelSource = ColoredBandImageMultiLevelSource.create(thumbNailBand,
SubProgressMonitor.create(pm, 1));
final ImageLayer imageLayer = new ImageLayer(multiLevelSource);
final int imageWidth = imgSize.width;
final int imageHeight = imgSize.height;
final int imageType = BufferedImage.TYPE_3BYTE_BGR;
image = new BufferedImage(imageWidth, imageHeight, imageType);
Viewport snapshotVp = new DefaultViewport(isModelYAxisDown(imageLayer));
final BufferedImageRendering imageRendering = new BufferedImageRendering(image, snapshotVp);
final Graphics2D graphics = imageRendering.getGraphics();
graphics.setColor(getBackground());
graphics.fillRect(0, 0, imageWidth, imageHeight);
snapshotVp.zoom(imageLayer.getModelBounds());
snapshotVp.moveViewDelta(snapshotVp.getViewBounds().x, snapshotVp.getViewBounds().y);
imageLayer.render(imageRendering);
pm.worked(4);
} finally {
pm.done();
}
return image;
}
private boolean isModelYAxisDown(ImageLayer baseImageLayer) {
return baseImageLayer.getImageToModelTransform().getDeterminant() > 0.0;
}
private String getThumbnailBandName() {
return ProductUtils.findSuitableQuicklookBandName(product);
}
}
private class ProductNodeSubsetPane extends JPanel {
private ProductNode[] productNodes;
private String[] includeAlways;
private List<JCheckBox> checkers;
private JCheckBox allCheck;
private JCheckBox noneCheck;
private boolean selected;
private ProductNodeSubsetPane(ProductNode[] productNodes, boolean selected) {
this(productNodes, null, selected);
}
private ProductNodeSubsetPane(ProductNode[] productNodes, String[] includeAlways, boolean selected) {
this.productNodes = productNodes;
this.includeAlways = includeAlways;
this.selected = selected;
createUI();
}
private void createUI() {
ActionListener productNodeCheckListener = e -> updateUIState();
checkers = new ArrayList<>(10);
JPanel checkersPane = GridBagUtils.createPanel();
setComponentName(checkersPane, "CheckersPane");
GridBagConstraints gbc = GridBagUtils.createConstraints("insets.left=4,anchor=WEST,fill=HORIZONTAL");
for (int i = 0; i < productNodes.length; i++) {
ProductNode productNode = productNodes[i];
String name = productNode.getName();
JCheckBox productNodeCheck = new JCheckBox(name);
productNodeCheck.setSelected(selected);
productNodeCheck.setFont(SMALL_PLAIN_FONT);
productNodeCheck.addActionListener(productNodeCheckListener);
if (includeAlways != null
&& StringUtils.containsIgnoreCase(includeAlways, name)) {
productNodeCheck.setSelected(true);
productNodeCheck.setEnabled(false);
} else if (givenProductSubsetDef != null) {
productNodeCheck.setSelected(givenProductSubsetDef.containsNodeName(name));
}
checkers.add(productNodeCheck);
String description = productNode.getDescription();
JLabel productNodeLabel = new JLabel(description != null ? description : " ");
productNodeLabel.setFont(SMALL_ITALIC_FONT);
GridBagUtils.addToPanel(checkersPane, productNodeCheck, gbc, "weightx=0,gridx=0,gridy=" + i);
GridBagUtils.addToPanel(checkersPane, productNodeLabel, gbc, "weightx=1,gridx=1,gridy=" + i);
}
// Add a last 'filler' row
GridBagUtils.addToPanel(checkersPane, new JLabel(" "), gbc,
"gridwidth=2,weightx=1,weighty=1,gridx=0,gridy=" + productNodes.length);
ActionListener allCheckListener = e -> {
if (e.getSource() == allCheck) {
checkAllProductNodes(true);
} else if (e.getSource() == noneCheck) {
checkAllProductNodes(false);
}
updateUIState();
};
allCheck = new JCheckBox("Select all");
allCheck.setName("selectAll");
allCheck.setMnemonic('a');
allCheck.addActionListener(allCheckListener);
noneCheck = new JCheckBox("Select none");
noneCheck.setName("SelectNone");
noneCheck.setMnemonic('n');
noneCheck.addActionListener(allCheckListener);
JScrollPane scrollPane = new JScrollPane(checkersPane);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
JPanel buttonRow = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 4));
buttonRow.add(allCheck);
buttonRow.add(noneCheck);
setLayout(new BorderLayout());
add(scrollPane, BorderLayout.CENTER);
add(buttonRow, BorderLayout.SOUTH);
setBorder(BorderFactory.createEmptyBorder(7, 7, 7, 7));
updateUIState();
}
void updateUIState() {
allCheck.setSelected(areAllProductNodesChecked(true));
noneCheck.setSelected(areAllProductNodesChecked(false));
updateSubsetDefNodeNameList();
}
String[] getSubsetNames() {
String[] names = new String[countChecked(true)];
int pos = 0;
for (int i = 0; i < checkers.size(); i++) {
JCheckBox checker = checkers.get(i);
if (checker.isSelected()) {
ProductNode productNode = productNodes[i];
names[pos] = productNode.getName();
pos++;
}
}
return names;
}
void checkAllProductNodes(boolean checked) {
for (JCheckBox checker : checkers) {
if (checker.isEnabled()) {
checker.setSelected(checked);
}
}
}
boolean areAllProductNodesChecked(boolean checked) {
return countChecked(checked) == checkers.size();
}
int countChecked(boolean checked) {
int counter = 0;
for (JCheckBox checker : checkers) {
if (checker.isSelected() == checked) {
counter++;
}
}
return counter;
}
}
}