/* * Copyright (C) 2010 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.timeseries.ui.matrix; import com.bc.ceres.glayer.support.ImageLayer; import com.bc.ceres.glayer.swing.LayerCanvas; import com.bc.ceres.swing.TableLayout; import org.esa.snap.core.datamodel.Band; import org.esa.snap.core.datamodel.ProductData; import org.esa.snap.core.datamodel.ProductNode; import org.esa.snap.core.datamodel.ProductNodeEvent; import org.esa.snap.core.datamodel.RasterDataNode; import org.esa.snap.core.ui.PixelPositionListener; import org.esa.snap.core.ui.UIUtils; import org.esa.snap.core.ui.product.ProductSceneView; import org.esa.snap.core.ui.tool.ToolButtonFactory; import org.esa.snap.rcp.SnapApp; import org.esa.snap.rcp.util.SelectionSupport; import org.esa.snap.timeseries.core.TimeSeriesMapper; import org.esa.snap.timeseries.core.timeseries.datamodel.AbstractTimeSeries; import org.esa.snap.timeseries.core.timeseries.datamodel.TimeCoding; import org.esa.snap.timeseries.core.timeseries.datamodel.TimeSeriesListener; import org.esa.snap.util.math.MathUtils; 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.swing.AbstractButton; import javax.swing.BorderFactory; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JSpinner; import javax.swing.JTable; import javax.swing.SpinnerNumberModel; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Insets; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.text.DateFormat; import java.util.Arrays; import java.util.Date; import java.util.List; /** * Main class for the matrix tool. * * @author Marco Peters * @author Thomas Storm */ @TopComponent.Description( preferredID = "TimeSeriesMatrixTopComponent", iconBase = "org/esa/snap/timeseries/ui/icons/timeseries-matrix.png", persistenceType = TopComponent.PERSISTENCE_ALWAYS ) @TopComponent.Registration( mode = "navigator", openAtStartup = false, position = 4 ) @TopComponent.OpenActionRegistration( displayName = "#CTL_TimeSeriesMatrixTopComponentName", preferredID = "TimeSeriesMatrixTopComponent" ) @ActionID(category = "Window", id = "org.esa.snap.timeseries.ui.matrix.TimeSeriesMatrixTopComponent") @ActionReferences({ @ActionReference(path = "Menu/View/Tool Windows/Time Series", position = 1240, separatorAfter = 1250), @ActionReference(path = "Toolbars/Time Series", position = 40) }) @NbBundle.Messages({ "CTL_TimeSeriesMatrixTopComponentName=Time Series Matrix" }) public class TimeSeriesMatrixTopComponent extends TopComponent { private static final String HELP_ID = "timeSeriesMatrix"; private static final int MATRIX_MINIMUM = 3; private static final int MATRIX_DEFAULT_VALUE = MATRIX_MINIMUM; private static final int MATRIX_MAXIMUM = 15; private static final int MATRIX_STEP_SIZE = 2; private JSpinner matrixSizeSpinner; private JLabel dateLabel; private ProductSceneView currentView; private AbstractTimeSeries timeSeries; private final SceneViewHandler sceneViewListener; private final TimeSeriesPPL pixelPosListener; private final MatrixMouseWheelListener mouseWheelListener; private final TimeSeriesListener timeSeriesMatrixTSL; private static final String DATE_PREFIX = "Date: "; private MatrixTableModel matrixModel; private final DateFormat dateFormat; private MatrixCellRenderer matrixCellRenderer; public TimeSeriesMatrixTopComponent() { pixelPosListener = new TimeSeriesPPL(); sceneViewListener = new SceneViewHandler(); mouseWheelListener = new MatrixMouseWheelListener(); timeSeriesMatrixTSL = new TimeSeriesMatrixTSL(); dateFormat = ProductData.UTC.createDateFormat("dd-MMM-yyyy HH:mm:ss"); initUI(); } private void initUI() { SnapApp.getDefault().getSelectionSupport(ProductSceneView.class).addHandler(sceneViewListener); dateLabel = new JLabel(String.format(DATE_PREFIX + " %s", getStartDateString())); matrixSizeSpinner = new JSpinner(new SpinnerNumberModel(MATRIX_DEFAULT_VALUE, MATRIX_MINIMUM, MATRIX_MAXIMUM, MATRIX_STEP_SIZE)); final JComponent editor = matrixSizeSpinner.getEditor(); if (editor instanceof JSpinner.DefaultEditor) { ((JSpinner.DefaultEditor) editor).getTextField().setEditable(false); } matrixSizeSpinner.addChangeListener(e -> matrixModel.setMatrixSize((Integer) matrixSizeSpinner.getModel().getValue())); final TableLayout tableLayout = new TableLayout(2); tableLayout.setTablePadding(4, 4); tableLayout.setTableFill(TableLayout.Fill.BOTH); tableLayout.setTableAnchor(TableLayout.Anchor.NORTHWEST); tableLayout.setTableWeightX(0.0); tableLayout.setTableWeightY(0.0); tableLayout.setColumnWeightX(0, 1.0); tableLayout.setRowWeightY(1, 1.0); tableLayout.setCellColspan(0, 0, 2); setLayout(tableLayout); setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4)); JPanel buttonPanel = createButtonPanel(); JPanel tablePanel = createTablePanel(); add(dateLabel); add(tablePanel); add(buttonPanel); setCurrentView(SnapApp.getDefault().getSelectedProductSceneView()); setDisplayName(Bundle.CTL_TimeSeriesMatrixTopComponentName()); } @Override public HelpCtx getHelpCtx() { return new HelpCtx(HELP_ID); } @Override protected void componentShowing() { addMouseWheelListener(); } @Override public void componentOpened() { addMouseWheelListener(); } @Override public void componentClosed() { removeMouseWheelListener(); } @Override public void componentHidden() { removeMouseWheelListener(); } private JPanel createTablePanel() { JPanel mainPanel = new JPanel(new BorderLayout(4, 4)); matrixModel = new MatrixTableModel(); JTable matrixTable = new JTable(matrixModel); matrixCellRenderer = new MatrixCellRenderer(matrixModel); matrixTable.setDefaultRenderer(Double.class, matrixCellRenderer); matrixTable.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1)); mainPanel.add(BorderLayout.CENTER, matrixTable); return mainPanel; } private String getStartDateString() { String startDateString = ""; if (currentView != null && timeSeries != null) { final TimeCoding timeCoding = timeSeries.getRasterTimeMap().get(currentView.getRaster()); Date startDate = timeCoding.getStartTime().getAsDate(); startDateString = dateFormat.format(startDate); } return startDateString; } private JPanel createButtonPanel() { final TableLayout tableLayout = new TableLayout(1); tableLayout.setTablePadding(4, 4); tableLayout.setRowPadding(0, new Insets(0, 4, 4, 4)); tableLayout.setTableAnchor(TableLayout.Anchor.NORTHWEST); tableLayout.setTableFill(TableLayout.Fill.HORIZONTAL); tableLayout.setTableWeightX(1.0); tableLayout.setTableWeightY(0.0); JPanel buttonPanel = new JPanel(tableLayout); AbstractButton helpButton = ToolButtonFactory.createButton(UIUtils.loadImageIcon("icons/Help22.png"), false); helpButton.addActionListener(e -> new HelpCtx(HELP_ID).display()); helpButton.setToolTipText("Help"); buttonPanel.add(matrixSizeSpinner); buttonPanel.add(tableLayout.createVerticalSpacer()); buttonPanel.add(helpButton); return buttonPanel; } /* * Checks if the view displays a timeseries product. * If so it is set as the current view. */ private void setCurrentView(ProductSceneView newView) { if (currentView == newView) { return; } if (currentView != null) { currentView.removePixelPositionListener(pixelPosListener); removeMouseWheelListener(); if (timeSeries != null) { timeSeries.removeTimeSeriesListener(timeSeriesMatrixTSL); } } currentView = newView; if (isTimeSeriesView(currentView)) { currentView.addPixelPositionListener(pixelPosListener); timeSeries = TimeSeriesMapper.getInstance().getTimeSeries(currentView.getProduct()); timeSeries.addTimeSeriesListener(timeSeriesMatrixTSL); addMouseWheelListener(); final RasterDataNode raster = currentView.getRaster(); if (raster instanceof Band) { matrixModel.setBand((Band) raster); matrixModel.setMatrixSize((Integer) matrixSizeSpinner.getValue()); matrixCellRenderer.setInvalidColor(currentView.getLayerCanvas().getBackground()); updateDateLabel((Band) currentView.getRaster()); } } else { timeSeries = null; matrixModel.setMatrixSize(0); } } private void updateDateLabel(Band band) { String dateString = ""; if (band != null) { final TimeCoding timeCoding = timeSeries.getRasterTimeMap().get(band); final Date startTime = timeCoding.getStartTime().getAsDate(); dateString = dateFormat.format(startTime); } dateLabel.setText(String.format(DATE_PREFIX + " %s", dateString)); } // Depending on the direction value this method returns the next // band in the list of available bands in the time series. // Negative value of direction means previous band. // If there is no next band the current band is returned. private Band getNextBand(Band currentBand, int direction) { final String varName = AbstractTimeSeries.rasterToVariableName(currentBand.getName()); final List<Band> bandList = timeSeries.getBandsForVariable(varName); final int currentIndex = bandList.indexOf(currentBand); if (direction < 0) { if (currentIndex > 0) { return bandList.get(currentIndex - 1); } } else { if (currentIndex + 1 < bandList.size()) { return bandList.get(currentIndex + 1); } } return currentBand; } private boolean isTimeSeriesView(ProductSceneView view) { if (view != null) { final RasterDataNode viewRaster = view.getRaster(); final String viewProductType = viewRaster.getProduct().getProductType(); return !view.isRGB() && viewProductType.equals(AbstractTimeSeries.TIME_SERIES_PRODUCT_TYPE) && TimeSeriesMapper.getInstance().getTimeSeries(view.getProduct()) != null; } return false; } private void addMouseWheelListener() { if (currentView != null) { final LayerCanvas layerCanvas = currentView.getLayerCanvas(); final List<MouseWheelListener> listeners = Arrays.asList(layerCanvas.getMouseWheelListeners()); if (!listeners.contains(mouseWheelListener)) { layerCanvas.addMouseWheelListener(mouseWheelListener); } } } private void removeMouseWheelListener() { if (currentView != null) { currentView.getLayerCanvas().removeMouseWheelListener(mouseWheelListener); } } private class SceneViewHandler implements SelectionSupport.Handler<ProductSceneView> { @Override public void selectionChange(ProductSceneView oldValue, ProductSceneView newValue) { if (currentView == oldValue) { setCurrentView(null); } setCurrentView(newValue); } } private class TimeSeriesPPL implements PixelPositionListener { @Override public void pixelPosChanged(ImageLayer imageLayer, int pixelX, int pixelY, int currentLevel, boolean pixelPosValid, MouseEvent e) { if (isVisible() && currentView != null) { AffineTransform i2mTransform = imageLayer.getImageToModelTransform(currentLevel); Point2D modelP = i2mTransform.transform(new Point2D.Double(pixelX + 0.5, pixelY + 0.5), null); AffineTransform m2iTransform = imageLayer.getModelToImageTransform(); Point2D levelZeroP = m2iTransform.transform(modelP, null); matrixModel.setCenterPixel(MathUtils.floorInt(levelZeroP.getX()), MathUtils.floorInt(levelZeroP.getY())); } } @Override public void pixelPosNotAvailable() { matrixModel.clearMatrix(); } } private class MatrixMouseWheelListener implements MouseWheelListener { @Override public void mouseWheelMoved(MouseWheelEvent e) { if (e.isAltDown()) { Band nextBand = getNextBand(matrixModel.getBand(), e.getWheelRotation()); if (nextBand != null) { matrixModel.setBand(nextBand); updateDateLabel(nextBand); } } } } private class TimeSeriesMatrixTSL extends TimeSeriesListener { @Override public void nodeRemoved(ProductNodeEvent event) { final ProductNode node = event.getSourceNode(); if (node == matrixModel.getBand()) { final Band band = matrixModel.getBand(); Band nextBand = getNextBand(band, 1); if (nextBand == band) { nextBand = getNextBand(band, -1); } if (nextBand == band) { nextBand = null; } updateDateLabel(nextBand); } } } }