/*
* Copyright (C) 2014 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.rcp.spectrum;
import com.bc.ceres.glayer.support.ImageLayer;
import com.bc.ceres.glevel.MultiLevelModel;
import com.vividsolutions.jts.geom.Point;
import org.esa.snap.core.datamodel.Band;
import org.esa.snap.core.datamodel.DataNode;
import org.esa.snap.core.datamodel.PixelPos;
import org.esa.snap.core.datamodel.Placemark;
import org.esa.snap.core.datamodel.PlacemarkGroup;
import org.esa.snap.core.datamodel.Product;
import org.esa.snap.core.datamodel.ProductManager;
import org.esa.snap.core.datamodel.ProductNodeEvent;
import org.esa.snap.core.datamodel.ProductNodeGroup;
import org.esa.snap.core.datamodel.ProductNodeListenerAdapter;
import org.esa.snap.core.datamodel.RasterDataNode;
import org.esa.snap.core.image.ImageManager;
import org.esa.snap.core.util.ProductUtils;
import org.esa.snap.core.util.StringUtils;
import org.esa.snap.rcp.SnapApp;
import org.esa.snap.rcp.actions.help.HelpAction;
import org.esa.snap.rcp.placemark.PlacemarkUtils;
import org.esa.snap.rcp.statistics.XYPlotMarker;
import org.esa.snap.rcp.util.Dialogs;
import org.esa.snap.rcp.windows.ToolTopComponent;
import org.esa.snap.ui.GridBagUtils;
import org.esa.snap.ui.ModalDialog;
import org.esa.snap.ui.PixelPositionListener;
import org.esa.snap.ui.UIUtils;
import org.esa.snap.ui.product.ProductSceneView;
import org.esa.snap.ui.product.spectrum.DisplayableSpectrum;
import org.esa.snap.ui.product.spectrum.SpectrumBand;
import org.esa.snap.ui.product.spectrum.SpectrumChooser;
import org.esa.snap.ui.product.spectrum.SpectrumShapeProvider;
import org.esa.snap.ui.product.spectrum.SpectrumStrokeProvider;
import org.esa.snap.ui.tool.ToolButtonFactory;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.LegendItem;
import org.jfree.chart.LegendItemCollection;
import org.jfree.chart.LegendItemSource;
import org.jfree.chart.annotations.XYTitleAnnotation;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.block.BlockBorder;
import org.jfree.chart.block.LineBorder;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.Range;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.HorizontalAlignment;
import org.jfree.ui.RectangleAnchor;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import org.openide.windows.TopComponent;
import javax.media.jai.PlanarImage;
import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.BevelBorder;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@TopComponent.Description(preferredID = "SpectrumTopComponent", iconBase = "org/esa/snap/rcp/icons/Spectrum.gif")
@TopComponent.Registration(mode = "Spectrum", openAtStartup = false, position = 80)
@ActionID(category = "Window", id = "org.esa.snap.rcp.statistics.SpectrumTopComponent")
@ActionReferences({
@ActionReference(path = "Menu/Optical", position = 0),
@ActionReference(path = "Menu/View/Tool Windows/Optical"),
@ActionReference(path = "Toolbars/Tool Windows")
})
@TopComponent.OpenActionRegistration(displayName = "#CTL_SpectrumTopComponent_Name", preferredID = "SpectrumTopComponent")
@NbBundle.Messages({"CTL_SpectrumTopComponent_Name=Spectrum View", "CTL_SpectrumTopComponent_HelpId=showSpectrumWnd"})
/**
* A window which displays spectra at selected pixel positions.
*/
public class SpectrumTopComponent extends ToolTopComponent {
public static final String ID = SpectrumTopComponent.class.getName();
private static final String SUPPRESS_MESSAGE_KEY = "plugin.spectrum.tip";
private final Map<RasterDataNode, DisplayableSpectrum[]> rasterToSpectraMap;
private final Map<RasterDataNode, List<SpectrumBand>> rasterToSpectralBandsMap;
private final ProductNodeListenerAdapter productNodeHandler;
private final PinSelectionChangeListener pinSelectionChangeListener;
private final PixelPositionListener pixelPositionListener;
private AbstractButton filterButton;
private AbstractButton showSpectrumForCursorButton;
private AbstractButton showSpectraForSelectedPinsButton;
private AbstractButton showSpectraForAllPinsButton;
private AbstractButton showGridButton;
private boolean tipShown;
private ProductSceneView currentView;
private Product currentProduct;
private ChartPanel chartPanel;
private ChartHandler chartHandler;
private boolean domainAxisAdjustmentIsFrozen;
private boolean rangeAxisAdjustmentIsFrozen;
private boolean isCodeInducedAxisChange;
private boolean isUserInducedAutomaticAdjustmentChosen;
public SpectrumTopComponent() {
productNodeHandler = new ProductNodeHandler();
pinSelectionChangeListener = new PinSelectionChangeListener();
rasterToSpectraMap = new HashMap<>();
rasterToSpectralBandsMap = new HashMap<>();
pixelPositionListener = new CursorSpectrumPixelPositionListener(this);
initUI();
}
@Override
public HelpCtx getHelpCtx() {
return new HelpCtx(Bundle.CTL_SpectrumTopComponent_HelpId());
}
private void setCurrentView(ProductSceneView view) {
ProductSceneView oldView = currentView;
currentView = view;
if (oldView != currentView) {
if (oldView != null) {
oldView.removePropertyChangeListener(ProductSceneView.PROPERTY_NAME_SELECTED_PIN, pinSelectionChangeListener);
}
if (currentView != null) {
currentView.addPropertyChangeListener(ProductSceneView.PROPERTY_NAME_SELECTED_PIN, pinSelectionChangeListener);
setCurrentProduct(currentView.getProduct());
if (!rasterToSpectraMap.containsKey(currentView.getRaster())) {
setUpSpectra();
}
recreateChart();
}
updateUIState();
}
}
private Product getCurrentProduct() {
return currentProduct;
}
private void setCurrentProduct(Product product) {
Product oldProduct = currentProduct;
currentProduct = product;
if (currentProduct != oldProduct) {
if (oldProduct != null) {
oldProduct.removeProductNodeListener(productNodeHandler);
}
if (currentProduct != null) {
currentProduct.addProductNodeListener(productNodeHandler);
}
if (currentProduct == null) {
chartHandler.setEmptyPlot();
}
updateUIState();
}
}
private void updateUIState() {
boolean hasView = currentView != null;
boolean hasProduct = getCurrentProduct() != null;
boolean hasSelectedPins = hasView && currentView.getSelectedPins().length > 0;
boolean hasPins = hasProduct && getCurrentProduct().getPinGroup().getNodeCount() > 0;
filterButton.setEnabled(hasProduct);
showSpectrumForCursorButton.setEnabled(hasView);
showSpectraForSelectedPinsButton.setEnabled(hasSelectedPins);
showSpectraForAllPinsButton.setEnabled(hasPins);
showGridButton.setEnabled(hasView);
chartPanel.setEnabled(hasProduct); // todo - hasSpectraGraphs
showGridButton.setSelected(hasView);
chartHandler.setGridVisible(showGridButton.isSelected());
}
void setPrepareForUpdateMessage() {
chartHandler.setCollectingSpectralInformationMessage();
}
void updateData(int pixelX, int pixelY, int level, boolean pixelPosInRasterBounds) {
chartHandler.setPosition(pixelX, pixelY, level, pixelPosInRasterBounds);
chartHandler.updateData();
}
void updateChart(boolean adjustAxes) {
chartHandler.setAutomaticRangeAdjustments(adjustAxes);
updateChart();
}
void updateChart() {
maybeShowTip();
chartHandler.updateChart();
chartPanel.repaint();
}
private void maybeShowTip() {
if (!tipShown) {
final String message = "<html>Tip: If you press the SHIFT key while moving the mouse cursor over<br/>" +
"an image, " + SnapApp.getDefault().getInstanceName() + " adjusts the diagram axes " +
"to the local values at the<br/>" +
"current pixel position, if you release the SHIFT key again, then the<br/>" +
"min/max are accumulated again.</html>";
Dialogs.showInformation("Spectrum Tip", message, SUPPRESS_MESSAGE_KEY);
tipShown = true;
}
}
private SpectrumBand[] getAvailableSpectralBands(RasterDataNode currentRaster) {
if (!rasterToSpectralBandsMap.containsKey(currentRaster)) {
rasterToSpectralBandsMap.put(currentRaster, new ArrayList<>());
}
List<SpectrumBand> spectrumBands = rasterToSpectralBandsMap.get(currentRaster);
Band[] bands = currentProduct.getBands();
for (Band band : bands) {
if (isSpectralBand(band) && !band.isFlagBand()) {
boolean isAlreadyIncluded = false;
for (SpectrumBand spectrumBand : spectrumBands) {
if (spectrumBand.getOriginalBand() == band) {
isAlreadyIncluded = true;
break;
}
}
if (!isAlreadyIncluded) {
spectrumBands.add(new SpectrumBand(band, true));
}
}
}
return spectrumBands.toArray(new SpectrumBand[spectrumBands.size()]);
}
private boolean isSpectralBand(Band band) {
return band.getSpectralWavelength() > 0.0;
}
private void initUI() {
final JFreeChart chart = ChartFactory.createXYLineChart(Bundle.CTL_SpectrumTopComponent_Name(),
"Wavelength (nm)", "", null, PlotOrientation.VERTICAL,
true, true, false);
chart.getXYPlot().getRangeAxis().addChangeListener(axisChangeEvent -> {
if (!isCodeInducedAxisChange) {
rangeAxisAdjustmentIsFrozen = !((ValueAxis) axisChangeEvent.getAxis()).isAutoRange();
}
});
chart.getXYPlot().getDomainAxis().addChangeListener(axisChangeEvent -> {
if (!isCodeInducedAxisChange) {
domainAxisAdjustmentIsFrozen = !((ValueAxis) axisChangeEvent.getAxis()).isAutoRange();
}
});
chart.getXYPlot().getRangeAxis().setAutoRange(false);
rangeAxisAdjustmentIsFrozen = false;
chart.getXYPlot().getDomainAxis().setAutoRange(false);
domainAxisAdjustmentIsFrozen = false;
chartPanel = new ChartPanel(chart);
chartHandler = new ChartHandler(chart);
final XYPlotMarker plotMarker = new XYPlotMarker(chartPanel, new XYPlotMarker.Listener() {
@Override
public void pointSelected(XYDataset xyDataset, int seriesIndex, Point2D dataPoint) {
//do nothing
}
@Override
public void pointDeselected() {
//do nothing
}
});
filterButton = ToolButtonFactory.createButton(UIUtils.loadImageIcon("icons/Filter24.gif"), false);
filterButton.setName("filterButton");
filterButton.setEnabled(false);
filterButton.addActionListener(e -> {
selectSpectralBands();
recreateChart();
});
showSpectrumForCursorButton = ToolButtonFactory.createButton(
UIUtils.loadImageIcon("icons/CursorSpectrum24.gif"), true);
showSpectrumForCursorButton.addActionListener(e -> recreateChart());
showSpectrumForCursorButton.setName("showSpectrumForCursorButton");
showSpectrumForCursorButton.setSelected(true);
showSpectrumForCursorButton.setToolTipText("Show spectrum at cursor position.");
showSpectraForSelectedPinsButton = ToolButtonFactory.createButton(
UIUtils.loadImageIcon("icons/SelectedPinSpectra24.gif"), true);
showSpectraForSelectedPinsButton.addActionListener(e -> {
if (isShowingSpectraForAllPins()) {
showSpectraForAllPinsButton.setSelected(false);
} else if (!isShowingSpectraForSelectedPins()) {
plotMarker.setInvisible();
}
recreateChart();
});
showSpectraForSelectedPinsButton.setName("showSpectraForSelectedPinsButton");
showSpectraForSelectedPinsButton.setToolTipText("Show spectra for selected pins.");
showSpectraForAllPinsButton = ToolButtonFactory.createButton(UIUtils.loadImageIcon("icons/PinSpectra24.gif"),
true);
showSpectraForAllPinsButton.addActionListener(e -> {
if (isShowingSpectraForSelectedPins()) {
showSpectraForSelectedPinsButton.setSelected(false);
} else if (!isShowingSpectraForAllPins()) {
plotMarker.setInvisible();
}
recreateChart();
});
showSpectraForAllPinsButton.setName("showSpectraForAllPinsButton");
showSpectraForAllPinsButton.setToolTipText("Show spectra for all pins.");
showGridButton = ToolButtonFactory.createButton(UIUtils.loadImageIcon("icons/SpectrumGrid24.gif"), true);
showGridButton.addActionListener(e -> chartHandler.setGridVisible(showGridButton.isSelected()));
showGridButton.setName("showGridButton");
showGridButton.setToolTipText("Show diagram grid.");
AbstractButton exportSpectraButton = ToolButtonFactory.createButton(UIUtils.loadImageIcon("icons/Export24.gif"),
false);
exportSpectraButton.addActionListener(new SpectraExportAction(this));
exportSpectraButton.setToolTipText("Export spectra to text file.");
exportSpectraButton.setName("exportSpectraButton");
AbstractButton helpButton = ToolButtonFactory.createButton(new HelpAction(this), false);
helpButton.setName("helpButton");
helpButton.setToolTipText("Help.");
final JPanel buttonPane = GridBagUtils.createPanel();
final GridBagConstraints gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.CENTER;
gbc.fill = GridBagConstraints.NONE;
gbc.insets.top = 2;
gbc.gridy = 0;
buttonPane.add(filterButton, gbc);
gbc.gridy++;
buttonPane.add(showSpectrumForCursorButton, gbc);
gbc.gridy++;
buttonPane.add(showSpectraForSelectedPinsButton, gbc);
gbc.gridy++;
buttonPane.add(showSpectraForAllPinsButton, gbc);
gbc.gridy++;
buttonPane.add(showGridButton, gbc);
gbc.gridy++;
buttonPane.add(exportSpectraButton, gbc);
gbc.gridy++;
gbc.insets.bottom = 0;
gbc.fill = GridBagConstraints.VERTICAL;
gbc.weighty = 1.0;
gbc.gridwidth = 2;
buttonPane.add(new JLabel(" "), gbc); // filler
gbc.fill = GridBagConstraints.NONE;
gbc.weighty = 0.0;
gbc.gridy = 10;
gbc.anchor = GridBagConstraints.EAST;
buttonPane.add(helpButton, gbc);
chartPanel.setPreferredSize(new Dimension(300, 200));
chartPanel.setBackground(Color.white);
chartPanel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createBevelBorder(BevelBorder.LOWERED),
BorderFactory.createEmptyBorder(2, 2, 2, 2)));
chartPanel.addChartMouseListener(plotMarker);
JPanel mainPane = new JPanel(new BorderLayout(4, 4));
mainPane.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
mainPane.add(BorderLayout.CENTER, chartPanel);
mainPane.add(BorderLayout.EAST, buttonPane);
mainPane.setPreferredSize(new Dimension(320, 200));
SnapApp.getDefault().getProductManager().addListener(new ProductManager.Listener() {
@Override
public void productAdded(ProductManager.Event event) {
// ignored
}
@Override
public void productRemoved(ProductManager.Event event) {
final Product product = event.getProduct();
if (getCurrentProduct() == product) {
chartPanel.getChart().getXYPlot().setDataset(null);
setCurrentView(null);
setCurrentProduct(null);
}
if (currentView != null) {
final RasterDataNode currentRaster = currentView.getRaster();
if (rasterToSpectraMap.containsKey(currentRaster)) {
rasterToSpectraMap.remove(currentRaster);
}
if (rasterToSpectralBandsMap.containsKey(currentRaster)) {
rasterToSpectralBandsMap.remove(currentRaster);
}
}
PlacemarkGroup pinGroup = product.getPinGroup();
for (int i = 0; i < pinGroup.getNodeCount(); i++) {
chartHandler.removePinInformation(pinGroup.get(i));
}
}
});
final ProductSceneView view = getSelectedProductSceneView();
if (view != null) {
productSceneViewSelected(view);
}
setDisplayName(Bundle.CTL_SpectrumTopComponent_Name());
setLayout(new BorderLayout());
add(mainPane, BorderLayout.CENTER);
updateUIState();
}
private void selectSpectralBands() {
final RasterDataNode currentRaster = currentView.getRaster();
final DisplayableSpectrum[] allSpectra = rasterToSpectraMap.get(currentRaster);
final SpectrumChooser spectrumChooser = new SpectrumChooser(SwingUtilities.getWindowAncestor(this), allSpectra);
if (spectrumChooser.show() == ModalDialog.ID_OK) {
final DisplayableSpectrum[] spectra = spectrumChooser.getSpectra();
rasterToSpectraMap.put(currentRaster, spectra);
}
}
boolean isShowingCursorSpectrum() {
return showSpectrumForCursorButton.isSelected();
}
private boolean isShowingPinSpectra() {
return isShowingSpectraForSelectedPins() || isShowingSpectraForAllPins();
}
private boolean isShowingSpectraForAllPins() {
return showSpectraForAllPinsButton.isSelected();
}
private void recreateChart() {
chartHandler.updateData();
chartHandler.updateChart();
chartPanel.repaint();
updateUIState();
}
Placemark[] getDisplayedPins() {
if (isShowingSpectraForSelectedPins() && currentView != null) {
return currentView.getSelectedPins();
} else if (isShowingSpectraForAllPins() && getCurrentProduct() != null) {
ProductNodeGroup<Placemark> pinGroup = getCurrentProduct().getPinGroup();
return pinGroup.toArray(new Placemark[pinGroup.getNodeCount()]);
} else {
return new Placemark[0];
}
}
private void setUpSpectra() {
if (currentView == null) {
return;
}
DisplayableSpectrum[] spectra;
final RasterDataNode raster = currentView.getRaster();
final SpectrumBand[] availableSpectralBands = getAvailableSpectralBands(raster);
if (availableSpectralBands.length == 0) {
spectra = new DisplayableSpectrum[]{};
} else {
final Product.AutoGrouping autoGrouping = currentProduct.getAutoGrouping();
if (autoGrouping != null) {
final int selectedSpectrumIndex = autoGrouping.indexOf(raster.getName());
DisplayableSpectrum[] autoGroupingSpectra = new DisplayableSpectrum[autoGrouping.size()];
final Iterator<String[]> iterator = autoGrouping.iterator();
int i = 0;
while (iterator.hasNext()) {
final String[] autoGroupingNameAsArray = iterator.next();
StringBuilder spectrumNameBuilder = new StringBuilder(autoGroupingNameAsArray[0]);
if (autoGroupingNameAsArray.length > 1) {
for (int j = 1; j < autoGroupingNameAsArray.length; j++) {
String autoGroupingNamePart = autoGroupingNameAsArray[j];
spectrumNameBuilder.append("_").append(autoGroupingNamePart);
}
}
final String spectrumName = spectrumNameBuilder.toString();
int symbolIndex = SpectrumShapeProvider.getValidIndex(i, false);
DisplayableSpectrum spectrum = new DisplayableSpectrum(spectrumName, symbolIndex);
spectrum.setSelected(i == selectedSpectrumIndex);
spectrum.setLineStyle(SpectrumStrokeProvider.getStroke(i));
autoGroupingSpectra[i++] = spectrum;
}
List<SpectrumBand> ungroupedBandsList = new ArrayList<>();
for (SpectrumBand availableSpectralBand : availableSpectralBands) {
final String bandName = availableSpectralBand.getName();
final int spectrumIndex = autoGrouping.indexOf(bandName);
if (spectrumIndex != -1) {
autoGroupingSpectra[spectrumIndex].addBand(availableSpectralBand);
} else {
ungroupedBandsList.add(availableSpectralBand);
}
}
if (ungroupedBandsList.size() == 0) {
spectra = autoGroupingSpectra;
} else {
final DisplayableSpectrum[] spectraFromUngroupedBands =
createSpectraFromUngroupedBands(ungroupedBandsList.toArray(new SpectrumBand[ungroupedBandsList.size()]),
SpectrumShapeProvider.getValidIndex(i, false), i);
spectra = new DisplayableSpectrum[autoGroupingSpectra.length + spectraFromUngroupedBands.length];
System.arraycopy(autoGroupingSpectra, 0, spectra, 0, autoGroupingSpectra.length);
System.arraycopy(spectraFromUngroupedBands, 0, spectra, autoGroupingSpectra.length, spectraFromUngroupedBands.length);
}
} else {
spectra = createSpectraFromUngroupedBands(availableSpectralBands, 1, 0);
}
}
rasterToSpectraMap.put(raster, spectra);
}
//package local for testing
static DisplayableSpectrum[] createSpectraFromUngroupedBands(SpectrumBand[] ungroupedBands, int symbolIndex, int strokeIndex) {
List<String> knownUnits = new ArrayList<>();
List<DisplayableSpectrum> displayableSpectrumList = new ArrayList<>();
DisplayableSpectrum defaultSpectrum = new DisplayableSpectrum("tbd", -1);
for (SpectrumBand ungroupedBand : ungroupedBands) {
final String unit = ungroupedBand.getOriginalBand().getUnit();
if (StringUtils.isNullOrEmpty(unit)) {
defaultSpectrum.addBand(ungroupedBand);
} else if (knownUnits.contains(unit)) {
displayableSpectrumList.get(knownUnits.indexOf(unit)).addBand(ungroupedBand);
} else {
knownUnits.add(unit);
final DisplayableSpectrum spectrum = new DisplayableSpectrum("Bands measured in " + unit, symbolIndex++);
spectrum.setLineStyle(SpectrumStrokeProvider.getStroke(strokeIndex++));
spectrum.addBand(ungroupedBand);
displayableSpectrumList.add(spectrum);
}
}
if (strokeIndex == 0) {
defaultSpectrum.setName(DisplayableSpectrum.DEFAULT_SPECTRUM_NAME);
} else {
defaultSpectrum.setName(DisplayableSpectrum.REMAINING_BANDS_NAME);
}
defaultSpectrum.setSymbolIndex(symbolIndex);
defaultSpectrum.setLineStyle(SpectrumStrokeProvider.getStroke(strokeIndex));
displayableSpectrumList.add(defaultSpectrum);
return displayableSpectrumList.toArray(new DisplayableSpectrum[displayableSpectrumList.size()]);
}
private DisplayableSpectrum[] getAllSpectra() {
if (currentView == null || !rasterToSpectraMap.containsKey(currentView.getRaster())) {
return new DisplayableSpectrum[0];
}
return rasterToSpectraMap.get(currentView.getRaster());
}
private boolean isShowingSpectraForSelectedPins() {
return showSpectraForSelectedPinsButton.isSelected();
}
List<DisplayableSpectrum> getSelectedSpectra() {
List<DisplayableSpectrum> selectedSpectra = new ArrayList<>();
if (currentView != null) {
final RasterDataNode currentRaster = currentView.getRaster();
if (currentProduct != null && rasterToSpectraMap.containsKey(currentRaster)) {
DisplayableSpectrum[] allSpectra = rasterToSpectraMap.get(currentRaster);
for (DisplayableSpectrum displayableSpectrum : allSpectra) {
if (displayableSpectrum.isSelected()) {
selectedSpectra.add(displayableSpectrum);
}
}
}
}
return selectedSpectra;
}
private void updateSpectraUnits() {
for (DisplayableSpectrum spectrum : getAllSpectra()) {
spectrum.updateUnit();
}
}
void removeCursorSpectraFromDataset() {
chartHandler.removeCursorSpectraFromDataset();
}
@Override
protected void productSceneViewSelected(ProductSceneView view) {
view.addPixelPositionListener(pixelPositionListener);
setCurrentView(view);
}
@Override
protected void productSceneViewDeselected(ProductSceneView view) {
view.removePixelPositionListener(pixelPositionListener);
setCurrentView(null);
}
@Override
protected void componentOpened() {
final ProductSceneView selectedProductSceneView = getSelectedProductSceneView();
if (selectedProductSceneView != null) {
selectedProductSceneView.addPixelPositionListener(pixelPositionListener);
setCurrentView(selectedProductSceneView);
}
}
@Override
protected void componentClosed() {
if (currentView != null) {
currentView.removePixelPositionListener(pixelPositionListener);
}
}
public boolean showsValidCursorSpectra() {
return chartHandler.showsValidCursorSpectra();
}
private class ChartHandler {
private static final String MESSAGE_NO_SPECTRAL_BANDS = "No spectral bands available"; /*I18N*/
private static final String MESSAGE_NO_PRODUCT_SELECTED = "No product selected";
private static final String MESSAGE_NO_SPECTRA_SELECTED = "No spectra selected";
private static final String MESSAGE_COLLECTING_SPECTRAL_INFORMATION = "Collecting spectral information...";
private final JFreeChart chart;
private final ChartUpdater chartUpdater;
private ChartHandler(JFreeChart chart) {
chartUpdater = new ChartUpdater();
this.chart = chart;
setLegend(chart);
setAutomaticRangeAdjustments(false);
final XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) chart.getXYPlot().getRenderer();
renderer.setBaseShapesVisible(true);
renderer.setBaseShapesFilled(false);
setPlotMessage(MESSAGE_NO_PRODUCT_SELECTED);
}
private void setAutomaticRangeAdjustments(boolean userInducesAutomaticAdjustment) {
final XYPlot plot = chart.getXYPlot();
boolean adjustmentHasChanged = false;
if (userInducesAutomaticAdjustment) {
if (!isUserInducedAutomaticAdjustmentChosen) {
isUserInducedAutomaticAdjustmentChosen = true;
if (!isAutomaticDomainAdjustmentSet()) {
plot.getDomainAxis().setAutoRange(true);
domainAxisAdjustmentIsFrozen = false;
adjustmentHasChanged = true;
}
if (!isAutomaticRangeAdjustmentSet()) {
plot.getRangeAxis().setAutoRange(true);
rangeAxisAdjustmentIsFrozen = false;
adjustmentHasChanged = true;
}
}
} else {
if (isUserInducedAutomaticAdjustmentChosen) {
isUserInducedAutomaticAdjustmentChosen = false;
if (isAutomaticDomainAdjustmentSet()) {
plot.getDomainAxis().setAutoRange(false);
domainAxisAdjustmentIsFrozen = false;
adjustmentHasChanged = true;
}
if (isAutomaticRangeAdjustmentSet()) {
plot.getRangeAxis().setAutoRange(false);
rangeAxisAdjustmentIsFrozen = false;
adjustmentHasChanged = true;
}
}
}
if (adjustmentHasChanged) {
chartUpdater.invalidatePlotBounds();
}
}
private boolean isAutomaticDomainAdjustmentSet() {
return chart.getXYPlot().getDomainAxis().isAutoRange();
}
private boolean isAutomaticRangeAdjustmentSet() {
return chart.getXYPlot().getRangeAxis().isAutoRange();
}
private void setLegend(JFreeChart chart) {
chart.removeLegend();
final LegendTitle legend = new LegendTitle(new SpectrumLegendItemSource());
legend.setPosition(RectangleEdge.BOTTOM);
LineBorder border = new LineBorder(Color.BLACK, new BasicStroke(), new RectangleInsets(2, 2, 2, 2));
legend.setFrame(border);
chart.addLegend(legend);
}
private void setPosition(int pixelX, int pixelY, int level, boolean pixelPosInRasterBounds) {
chartUpdater.setPosition(pixelX, pixelY, level, pixelPosInRasterBounds);
}
private void updateChart() {
if (chartUpdater.isDatasetEmpty()) {
setEmptyPlot();
return;
}
List<DisplayableSpectrum> spectra = getSelectedSpectra();
chartUpdater.updateChart(chart, spectra);
chart.getXYPlot().clearAnnotations();
}
private void updateData() {
List<DisplayableSpectrum> spectra = getSelectedSpectra();
chartUpdater.updateData(chart, spectra);
}
private void setEmptyPlot() {
chart.getXYPlot().setDataset(null);
if (getCurrentProduct() == null) {
setPlotMessage(MESSAGE_NO_PRODUCT_SELECTED);
} else if (!chartUpdater.showsValidCursorSpectra()) {
return;
} else if (getAllSpectra().length == 0) {
setPlotMessage(MESSAGE_NO_SPECTRA_SELECTED);
} else {
setPlotMessage(MESSAGE_NO_SPECTRAL_BANDS);
}
}
private void setGridVisible(boolean visible) {
chart.getXYPlot().setDomainGridlinesVisible(visible);
chart.getXYPlot().setRangeGridlinesVisible(visible);
}
private void removePinInformation(Placemark pin) {
chartUpdater.removePinInformation(pin);
}
private void removeBandInformation(Band band) {
chartUpdater.removeBandinformation(band);
}
private void setPlotMessage(String messageText) {
chart.getXYPlot().clearAnnotations();
TextTitle tt = new TextTitle(messageText);
tt.setTextAlignment(HorizontalAlignment.RIGHT);
tt.setFont(chart.getLegend().getItemFont());
tt.setBackgroundPaint(new Color(200, 200, 255, 50));
tt.setFrame(new BlockBorder(Color.white));
tt.setPosition(RectangleEdge.BOTTOM);
XYTitleAnnotation message = new XYTitleAnnotation(0.5, 0.5, tt, RectangleAnchor.CENTER);
chart.getXYPlot().addAnnotation(message);
}
public boolean showsValidCursorSpectra() {
return chartUpdater.showsValidCursorSpectra();
}
public void removeCursorSpectraFromDataset() {
chartUpdater.removeCursorSpectraFromDataset();
}
public void setCollectingSpectralInformationMessage() {
setPlotMessage(MESSAGE_COLLECTING_SPECTRAL_INFORMATION);
}
}
private class ChartUpdater {
private final static int domain_axis_index = 0;
private final static int range_axis_index = 1;
private final static double relativePlotInset = 0.05;
private final Map<Placemark, Map<Band, Double>> pinToEnergies;
private boolean showsValidCursorSpectra;
private boolean pixelPosInRasterBounds;
private int rasterPixelX;
private int rasterPixelY;
private int rasterLevel;
private Range[] plotBounds;
private XYSeriesCollection dataset;
private Point2D modelP;
private ChartUpdater() {
pinToEnergies = new HashMap<>();
plotBounds = new Range[2];
invalidatePlotBounds();
}
void invalidatePlotBounds() {
plotBounds[domain_axis_index] = null;
plotBounds[range_axis_index] = null;
}
private void setPosition(int pixelX, int pixelY, int level, boolean pixelPosInRasterBounds) {
this.rasterPixelX = pixelX;
this.rasterPixelY = pixelY;
this.rasterLevel = level;
this.pixelPosInRasterBounds = pixelPosInRasterBounds;
final AffineTransform i2m = currentView.getBaseImageLayer().getImageToModelTransform(level);
modelP = i2m.transform(new Point2D.Double(pixelX + 0.5, pixelY + 0.5), new Point2D.Double());
}
private void updateData(JFreeChart chart, List<DisplayableSpectrum> spectra) {
dataset = new XYSeriesCollection();
if (rasterLevel >= 0) {
fillDatasetWithPinSeries(spectra, dataset, chart);
fillDatasetWithCursorSeries(spectra, dataset, chart);
}
}
private void updateChart(JFreeChart chart, List<DisplayableSpectrum> spectra) {
final XYPlot plot = chart.getXYPlot();
if (!chartHandler.isAutomaticDomainAdjustmentSet() && !domainAxisAdjustmentIsFrozen) {
isCodeInducedAxisChange = true;
updatePlotBounds(dataset.getDomainBounds(true), plot.getDomainAxis(), domain_axis_index);
isCodeInducedAxisChange = false;
}
if (!chartHandler.isAutomaticRangeAdjustmentSet() && !rangeAxisAdjustmentIsFrozen) {
isCodeInducedAxisChange = true;
updatePlotBounds(dataset.getRangeBounds(true), plot.getRangeAxis(), range_axis_index);
isCodeInducedAxisChange = false;
}
plot.setDataset(dataset);
setPlotUnit(spectra, plot);
}
private void setPlotUnit(List<DisplayableSpectrum> spectra, XYPlot plot) {
String unitToBeDisplayed = "";
if (spectra.size() > 0) {
unitToBeDisplayed = spectra.get(0).getUnit();
int i = 1;
while (i < spectra.size() && !unitToBeDisplayed.equals(DisplayableSpectrum.MIXED_UNITS)) {
DisplayableSpectrum displayableSpectrum = spectra.get(i++);
if (displayableSpectrum.hasSelectedBands() && !unitToBeDisplayed.equals(displayableSpectrum.getUnit())) {
unitToBeDisplayed = DisplayableSpectrum.MIXED_UNITS;
}
}
}
isCodeInducedAxisChange = true;
plot.getRangeAxis().setLabel(unitToBeDisplayed);
isCodeInducedAxisChange = false;
}
private void updatePlotBounds(Range newBounds, ValueAxis axis, int index) {
if (newBounds != null) {
final Range axisBounds = axis.getRange();
final Range oldBounds = this.plotBounds[index];
this.plotBounds[index] = getNewRange(newBounds, this.plotBounds[index], axisBounds);
if (oldBounds != this.plotBounds[index]) {
axis.setRange(getNewPlotBounds(this.plotBounds[index]));
}
}
}
private Range getNewRange(Range newBounds, Range currentBounds, Range plotBounds) {
if (currentBounds == null) {
currentBounds = newBounds;
} else {
if (plotBounds.getLowerBound() > 0 && newBounds.getLowerBound() < currentBounds.getLowerBound() ||
newBounds.getUpperBound() > currentBounds.getUpperBound()) {
currentBounds = new Range(Math.min(currentBounds.getLowerBound(), newBounds.getLowerBound()),
Math.max(currentBounds.getUpperBound(), newBounds.getUpperBound()));
}
}
return currentBounds;
}
private Range getNewPlotBounds(Range bounds) {
double range = bounds.getLength();
double delta = range * relativePlotInset;
return new Range(Math.max(0, bounds.getLowerBound() - delta),
bounds.getUpperBound() + delta);
}
private void fillDatasetWithCursorSeries(List<DisplayableSpectrum> spectra, XYSeriesCollection dataset, JFreeChart chart) {
showsValidCursorSpectra = false;
if (modelP == null) {
return;
}
if (isShowingCursorSpectrum() && currentView != null) {
for (DisplayableSpectrum spectrum : spectra) {
XYSeries series = new XYSeries(spectrum.getName());
final Band[] spectralBands = spectrum.getSelectedBands();
if (!currentProduct.isMultiSize()) {
for (Band spectralBand : spectralBands) {
final float wavelength = spectralBand.getSpectralWavelength();
if (pixelPosInRasterBounds && isPixelValid(spectralBand, rasterPixelX, rasterPixelY, rasterLevel)) {
addToSeries(spectralBand, rasterPixelX, rasterPixelY, rasterLevel, series, wavelength);
showsValidCursorSpectra = true;
}
}
} else {
for (Band spectralBand : spectralBands) {
final float wavelength = spectralBand.getSpectralWavelength();
final AffineTransform i2m = spectralBand.getImageToModelTransform();
if (i2m.equals(currentView.getRaster().getImageToModelTransform())) {
if (pixelPosInRasterBounds && isPixelValid(spectralBand, rasterPixelX, rasterPixelY, rasterLevel)) {
addToSeries(spectralBand, rasterPixelX, rasterPixelY, rasterLevel, series, wavelength);
showsValidCursorSpectra = true;
}
} else {
//todo [Multisize_products] use scenerastertransform here
final PixelPos rasterPos = new PixelPos();
final MultiLevelModel multiLevelModel = spectralBand.getMultiLevelModel();
int level = getLevel(multiLevelModel);
multiLevelModel.getModelToImageTransform(level).transform(modelP, rasterPos);
final int rasterX = (int) rasterPos.getX();
final int rasterY = (int) rasterPos.getY();
if (coordinatesAreInRasterBounds(spectralBand, rasterX, rasterY, level) &&
isPixelValid(spectralBand, rasterX, rasterY, level)) {
addToSeries(spectralBand, rasterX, rasterY, level, series, wavelength);
showsValidCursorSpectra = true;
}
}
}
}
updateRenderer(dataset.getSeriesCount(), Color.BLACK, spectrum, chart);
dataset.addSeries(series);
}
}
}
private void addToSeries(Band spectralBand, int x, int y, int level, XYSeries series, double wavelength) {
final double energy = ProductUtils.getGeophysicalSampleAsDouble(spectralBand, x, y, level);
if (energy != spectralBand.getGeophysicalNoDataValue()) {
series.add(wavelength, energy);
}
}
//todo code duplication with pixelinfoviewmodelupdater - move to single class - tf 20151119
private boolean coordinatesAreInRasterBounds(RasterDataNode raster, int x, int y, int level) {
final RenderedImage levelImage = raster.getSourceImage().getImage(level);
return x >= 0 && y >= 0 && x < levelImage.getWidth() && y < levelImage.getHeight();
}
private void fillDatasetWithPinSeries(List<DisplayableSpectrum> spectra, XYSeriesCollection dataset, JFreeChart chart) {
Placemark[] pins = getDisplayedPins();
for (Placemark pin : pins) {
List<XYSeries> pinSeries = createXYSeriesFromPin(pin, dataset.getSeriesCount(), spectra, chart);
pinSeries.forEach(dataset::addSeries);
}
}
private List<XYSeries> createXYSeriesFromPin(Placemark pin, int seriesIndex, List<DisplayableSpectrum> spectra, JFreeChart chart) {
List<XYSeries> pinSeries = new ArrayList<>();
Color pinColor = PlacemarkUtils.getPlacemarkColor(pin, currentView);
for (DisplayableSpectrum spectrum : spectra) {
XYSeries series = new XYSeries(spectrum.getName() + "_" + pin.getLabel());
final Band[] spectralBands = spectrum.getSelectedBands();
Map<Band, Double> bandToEnergy;
if (pinToEnergies.containsKey(pin)) {
bandToEnergy = pinToEnergies.get(pin);
} else {
bandToEnergy = new HashMap<>();
pinToEnergies.put(pin, bandToEnergy);
}
for (Band spectralBand : spectralBands) {
double energy;
if (bandToEnergy.containsKey(spectralBand)) {
energy = bandToEnergy.get(spectralBand);
} else {
energy = readEnergy(pin, spectralBand);
bandToEnergy.put(spectralBand, energy);
}
final float wavelength = spectralBand.getSpectralWavelength();
if (energy != spectralBand.getGeophysicalNoDataValue()) {
series.add(wavelength, energy);
}
}
updateRenderer(seriesIndex++, pinColor, spectrum, chart);
pinSeries.add(series);
}
return pinSeries;
}
private void updateRenderer(int seriesIndex, Color seriesColor, DisplayableSpectrum spectrum, JFreeChart chart) {
final XYItemRenderer renderer = chart.getXYPlot().getRenderer();
renderer.setSeriesPaint(seriesIndex, seriesColor);
final Stroke lineStyle = spectrum.getLineStyle();
renderer.setSeriesStroke(seriesIndex, lineStyle);
Shape symbol = spectrum.getScaledShape();
renderer.setSeriesShape(seriesIndex, symbol);
}
private double readEnergy(Placemark pin, Band spectralBand) {
//todo [Multisize_products] use scenerastertransform here
final Object pinGeometry = pin.getFeature().getDefaultGeometry();
if (pinGeometry == null || !(pinGeometry instanceof Point)) {
return spectralBand.getGeophysicalNoDataValue();
}
final Point2D.Double modelPoint = new Point2D.Double(((Point) pinGeometry).getCoordinate().x,
((Point) pinGeometry).getCoordinate().y);
final MultiLevelModel multiLevelModel = spectralBand.getMultiLevelModel();
int level = getLevel(multiLevelModel);
final AffineTransform m2iTransform = multiLevelModel.getModelToImageTransform(level);
final PixelPos pinLevelRasterPos = new PixelPos();
m2iTransform.transform(modelPoint, pinLevelRasterPos);
int pinLevelRasterX = (int) Math.floor(pinLevelRasterPos.getX());
int pinLevelRasterY = (int) Math.floor(pinLevelRasterPos.getY());
if (coordinatesAreInRasterBounds(spectralBand, pinLevelRasterX, pinLevelRasterY, level) &&
isPixelValid(spectralBand, pinLevelRasterX, pinLevelRasterY, level)) {
return ProductUtils.getGeophysicalSampleAsDouble(spectralBand, pinLevelRasterX, pinLevelRasterY, level);
}
return spectralBand.getGeophysicalNoDataValue();
}
private void removePinInformation(Placemark pin) {
pinToEnergies.remove(pin);
}
private void removeBandinformation(Band band) {
for (Placemark pin : pinToEnergies.keySet()) {
Map<Band, Double> bandToEnergiesMap = pinToEnergies.get(pin);
if (bandToEnergiesMap.containsKey(band)) {
bandToEnergiesMap.remove(band);
}
}
}
public boolean showsValidCursorSpectra() {
return showsValidCursorSpectra;
}
void removeCursorSpectraFromDataset() {
modelP = null;
if (showsValidCursorSpectra) {
int numberOfSelectedSpectra = getSelectedSpectra().size();
int numberOfPins = getDisplayedPins().length;
int numberOfDisplayedGraphs = numberOfPins * numberOfSelectedSpectra;
while (dataset.getSeriesCount() > numberOfDisplayedGraphs) {
dataset.removeSeries(dataset.getSeriesCount() - 1);
}
}
}
public boolean isDatasetEmpty() {
return dataset == null || dataset.getSeriesCount() == 0;
}
//todo code duplication with pixelinfoviewmodelupdater - move to single class - tf 20151119
private boolean isPixelValid(RasterDataNode raster, int pixelX, int pixelY, int level) {
if (raster.isValidMaskUsed()) {
PlanarImage image = ImageManager.getInstance().getValidMaskImage(raster, level);
Raster data = getRasterTile(image, pixelX, pixelY);
return data.getSample(pixelX, pixelY, 0) != 0;
} else {
return true;
}
}
//todo code duplication with pixelinfoviewmodelupdater - move to single class - tf 20151119
private Raster getRasterTile(PlanarImage image, int pixelX, int pixelY) {
final int tileX = image.XToTileX(pixelX);
final int tileY = image.YToTileY(pixelY);
return image.getTile(tileX, tileY);
}
//todo code duplication with pixelinfoviewmodelupdater - move to single class - tf 20151119
private int getLevel(MultiLevelModel multiLevelModel) {
if (rasterLevel < multiLevelModel.getLevelCount()) {
return rasterLevel;
}
return ImageLayer.getLevel(multiLevelModel, currentView.getViewport());
}
}
private class SpectrumLegendItemSource implements LegendItemSource {
@Override
public LegendItemCollection getLegendItems() {
LegendItemCollection itemCollection = new LegendItemCollection();
final Placemark[] displayedPins = getDisplayedPins();
final List<DisplayableSpectrum> spectra = getSelectedSpectra();
for (Placemark pin : displayedPins) {
Paint pinPaint = PlacemarkUtils.getPlacemarkColor(pin, currentView);
spectra.stream().filter(DisplayableSpectrum::hasSelectedBands).forEach(spectrum -> {
String legendLabel = pin.getLabel() + "_" + spectrum.getName();
LegendItem item = createLegendItem(spectrum, pinPaint, legendLabel);
itemCollection.add(item);
});
}
if (isShowingCursorSpectrum() && showsValidCursorSpectra()) {
spectra.stream().filter(DisplayableSpectrum::hasSelectedBands).forEach(spectrum -> {
Paint defaultPaint = Color.BLACK;
LegendItem item = createLegendItem(spectrum, defaultPaint, spectrum.getName());
itemCollection.add(item);
});
}
return itemCollection;
}
private LegendItem createLegendItem(DisplayableSpectrum spectrum, Paint paint, String legendLabel) {
Stroke outlineStroke = new BasicStroke();
Line2D lineShape = new Line2D.Double(0, 5, 40, 5);
Stroke lineStyle = spectrum.getLineStyle();
Shape symbol = spectrum.getScaledShape();
return new LegendItem(legendLabel, legendLabel, legendLabel, legendLabel,
true, symbol, false,
paint, true, paint, outlineStroke,
true, lineShape, lineStyle, paint);
}
}
/////////////////////////////////////////////////////////////////////////
// Product change handling
private class ProductNodeHandler extends ProductNodeListenerAdapter {
@Override
public void nodeChanged(final ProductNodeEvent event) {
boolean chartHasChanged = false;
if (event.getSourceNode() instanceof Band) {
final String propertyName = event.getPropertyName();
if (propertyName.equals(DataNode.PROPERTY_NAME_UNIT)) {
updateSpectraUnits();
chartHasChanged = true;
} else if (propertyName.equals(Band.PROPERTY_NAME_SPECTRAL_WAVELENGTH)) {
setUpSpectra();
chartHasChanged = true;
}
} else if (event.getSourceNode() instanceof Placemark) {
if (event.getPropertyName().equals("geoPos") || event.getPropertyName().equals("pixelPos")) {
chartHandler.removePinInformation((Placemark) event.getSourceNode());
}
if (isShowingPinSpectra()) {
chartHasChanged = true;
}
} else if (event.getSourceNode() instanceof Product) {
if (event.getPropertyName().equals("autoGrouping")) {
setUpSpectra();
chartHasChanged = true;
}
}
if (isActive() && chartHasChanged) {
recreateChart();
}
}
@Override
public void nodeAdded(final ProductNodeEvent event) {
if (!isActive()) {
return;
}
if (event.getSourceNode() instanceof Band) {
Band newBand = (Band) event.getSourceNode();
if (isSpectralBand(newBand)) {
addBandToSpectra((Band) event.getSourceNode());
recreateChart();
}
} else if (event.getSourceNode() instanceof Placemark) {
if (isShowingPinSpectra()) {
recreateChart();
} else {
updateUIState();
}
}
}
@Override
public void nodeRemoved(final ProductNodeEvent event) {
if (!isActive()) {
return;
}
if (event.getSourceNode() instanceof Band) {
Band band = (Band) event.getSourceNode();
removeBandFromSpectra(band);
chartHandler.removeBandInformation(band);
recreateChart();
} else if (event.getSourceNode() instanceof Placemark) {
if (isShowingPinSpectra()) {
recreateChart();
}
}
}
private void addBandToSpectra(Band band) {
DisplayableSpectrum[] allSpectra = rasterToSpectraMap.get(currentView.getRaster());
Product.AutoGrouping autoGrouping = currentProduct.getAutoGrouping();
if (autoGrouping != null) {
final int bandIndex = autoGrouping.indexOf(band.getName());
final DisplayableSpectrum spectrum;
if (bandIndex != -1) {
spectrum = allSpectra[bandIndex];
} else {
spectrum = allSpectra[allSpectra.length - 1];
}
spectrum.addBand(new SpectrumBand(band, spectrum.isSelected()));
} else {
allSpectra[0].addBand(new SpectrumBand(band, true));
}
}
private void removeBandFromSpectra(Band band) {
DisplayableSpectrum[] allSpectra = rasterToSpectraMap.get(currentView.getRaster());
for (DisplayableSpectrum displayableSpectrum : allSpectra) {
Band[] spectralBands = displayableSpectrum.getSpectralBands();
for (int j = 0; j < spectralBands.length; j++) {
Band spectralBand = spectralBands[j];
if (spectralBand == band) {
displayableSpectrum.remove(j);
if (displayableSpectrum.getSelectedBands().length == 0) {
displayableSpectrum.setSelected(false);
}
return;
}
}
}
}
private boolean isActive() {
return isVisible() && getCurrentProduct() != null;
}
}
private class PinSelectionChangeListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
recreateChart();
}
}
}