/* * 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.rcp.placemark.gcp; import com.bc.ceres.swing.TableLayout; import org.esa.snap.core.datamodel.GcpGeoCoding; import org.esa.snap.core.datamodel.GeoCoding; import org.esa.snap.core.datamodel.GeoPos; 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.ProductNode; import org.esa.snap.core.datamodel.ProductNodeEvent; import org.esa.snap.core.datamodel.ProductNodeGroup; import org.esa.snap.core.datamodel.ProductNodeListener; import org.esa.snap.core.dataop.maptransf.Datum; import org.esa.snap.core.util.Debug; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.JToggleButton; import javax.swing.SwingWorker; import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.text.DecimalFormat; import java.text.FieldPosition; import java.text.Format; import java.text.NumberFormat; import java.text.ParsePosition; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; /** * GCP geo-coding form. * * @author Marco Peters * @version $Revision$ $Date$ */ class GcpGeoCodingForm extends JPanel { private JTextField methodTextField; private JTextField rmseLatTextField; private JTextField rmseLonTextField; private JComboBox methodComboBox; private JToggleButton attachButton; private JTextField warningLabel; private Product currentProduct; private Format rmseNumberFormat; private GcpGroupListener currentGcpGroupListener; public GcpGeoCodingForm() { rmseNumberFormat = new RmseNumberFormat(); currentGcpGroupListener = new GcpGroupListener(); initComponents(); } private void initComponents() { TableLayout layout = new TableLayout(2); this.setLayout(layout); layout.setTableAnchor(TableLayout.Anchor.WEST); layout.setTableWeightY(1.0); layout.setTableFill(TableLayout.Fill.BOTH); layout.setTablePadding(2, 2); layout.setColumnWeightX(0, 0.5); layout.setColumnWeightX(1, 0.5); add(createInfoPanel()); add(createAttachDetachPanel()); updateUIState(); } private JPanel createInfoPanel() { TableLayout layout = new TableLayout(2); layout.setTablePadding(2, 4); layout.setColumnWeightX(0, 0.0); layout.setColumnWeightX(1, 1.0); layout.setTableAnchor(TableLayout.Anchor.WEST); layout.setTableFill(TableLayout.Fill.BOTH); JPanel panel = new JPanel(layout); panel.setBorder(BorderFactory.createTitledBorder("Current GCP Geo-Coding")); panel.add(new JLabel("Method:")); methodTextField = new JTextField(); setComponentName(methodTextField, "methodTextField"); methodTextField.setEditable(false); methodTextField.setHorizontalAlignment(JLabel.TRAILING); panel.add(methodTextField); rmseLatTextField = new JTextField(); setComponentName(rmseLatTextField, "rmseLatTextField"); rmseLatTextField.setEditable(false); rmseLatTextField.setHorizontalAlignment(JLabel.TRAILING); panel.add(new JLabel("RMSE Lat:")); panel.add(rmseLatTextField); rmseLonTextField = new JTextField(); setComponentName(rmseLonTextField, "rmseLonTextField"); rmseLonTextField.setEditable(false); rmseLonTextField.setHorizontalAlignment(JLabel.TRAILING); panel.add(new JLabel("RMSE Lon:")); panel.add(rmseLonTextField); return panel; } private JPanel createAttachDetachPanel() { methodComboBox = new JComboBox(GcpGeoCoding.Method.values()); setComponentName(methodComboBox, "methodComboBox"); methodComboBox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { updateUIState(); } }); attachButton = new JToggleButton(); setComponentName(attachButton, "attachButton"); attachButton.setName("attachButton"); AbstractAction attachDetachAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { if (!(currentProduct.getSceneGeoCoding() instanceof GcpGeoCoding)) { attachGeoCoding(currentProduct); } else { detachGeoCoding(currentProduct); } } }; attachButton.setAction(attachDetachAction); attachButton.setHideActionText(true); warningLabel = new JTextField(); warningLabel.setEditable(false); TableLayout layout = new TableLayout(2); layout.setTablePadding(2, 4); layout.setColumnWeightX(0, 0.0); layout.setColumnWeightX(1, 1.0); layout.setTableAnchor(TableLayout.Anchor.WEST); layout.setTableFill(TableLayout.Fill.BOTH); layout.setCellColspan(2, 0, 2); layout.setCellFill(2, 0, TableLayout.Fill.VERTICAL); layout.setCellAnchor(2, 0, TableLayout.Anchor.CENTER); JPanel panel = new JPanel(layout); panel.setBorder(BorderFactory.createTitledBorder("Attach / Detach GCP Geo-Coding")); panel.add(new JLabel("Method:")); panel.add(methodComboBox); panel.add(new JLabel("Status:")); panel.add(warningLabel); panel.add(attachButton); return panel; } void updateUIState() { if (currentProduct != null && currentProduct.getSceneGeoCoding() instanceof GcpGeoCoding) { final GcpGeoCoding gcpGeoCoding = (GcpGeoCoding) currentProduct.getSceneGeoCoding(); rmseLatTextField.setText(rmseNumberFormat.format(gcpGeoCoding.getRmseLat())); rmseLonTextField.setText(rmseNumberFormat.format(gcpGeoCoding.getRmseLon())); methodTextField.setText(gcpGeoCoding.getMethod().getName()); methodComboBox.setSelectedItem(gcpGeoCoding.getMethod()); methodComboBox.setEnabled(false); attachButton.setText("Detach"); attachButton.setSelected(true); attachButton.setEnabled(true); warningLabel.setText("GCP geo-coding attached"); warningLabel.setForeground(Color.BLACK); } else { methodComboBox.setEnabled(true); methodTextField.setText("n/a"); rmseLatTextField.setText(rmseNumberFormat.format(Double.NaN)); rmseLonTextField.setText(rmseNumberFormat.format(Double.NaN)); attachButton.setText("Attach"); attachButton.setSelected(false); updateAttachButtonAndStatus(); } } private void updateAttachButtonAndStatus() { final GcpGeoCoding.Method method = (GcpGeoCoding.Method) methodComboBox.getSelectedItem(); if (currentProduct != null && getValidGcpCount(currentProduct.getGcpGroup()) >= method.getTermCountP()) { attachButton.setEnabled(true); warningLabel.setText("OK, enough GCPs for selected method"); warningLabel.setForeground(Color.GREEN.darker()); } else { attachButton.setEnabled(false); warningLabel.setText("Not enough (valid) GCPs for selected method"); warningLabel.setForeground(Color.RED.darker()); } } private void detachGeoCoding(Product product) { if (product.getSceneGeoCoding() instanceof GcpGeoCoding) { GeoCoding gc = ((GcpGeoCoding) product.getSceneGeoCoding()).getOriginalGeoCoding(); product.setSceneGeoCoding(gc); } updateUIState(); } private void attachGeoCoding(final Product product) { final GcpGeoCoding.Method method = (GcpGeoCoding.Method) methodComboBox.getSelectedItem(); final Placemark[] gcps = getValidGcps(product.getGcpGroup()); final GeoCoding geoCoding = product.getSceneGeoCoding(); final Datum datum; if (geoCoding == null) { datum = Datum.WGS_84; } else { datum = geoCoding.getDatum(); } SwingWorker sw = new SwingWorker<GcpGeoCoding, GcpGeoCoding>() { @Override protected GcpGeoCoding doInBackground() throws Exception { GcpGeoCoding gcpGeoCoding = new GcpGeoCoding(method, gcps, product.getSceneRasterWidth(), product.getSceneRasterHeight(), datum); gcpGeoCoding.setOriginalGeoCoding(product.getSceneGeoCoding()); return gcpGeoCoding; } @Override protected void done() { final GcpGeoCoding gcpGeoCoding; try { gcpGeoCoding = get(); product.setSceneGeoCoding(gcpGeoCoding); updateUIState(); } catch (InterruptedException e) { Debug.trace(e); } catch (ExecutionException e) { Debug.trace(e.getCause()); } } }; sw.execute(); } public void setProduct(Product product) { if (product == currentProduct) { return; } if (currentProduct != null) { currentProduct.removeProductNodeListener(currentGcpGroupListener); } currentProduct = product; if (currentProduct != null) { currentProduct.addProductNodeListener(currentGcpGroupListener); } } private void setComponentName(JComponent component, String name) { component.setName(getClass().getName() + name); } private static Placemark[] getValidGcps(ProductNodeGroup<Placemark> gcpGroup) { final List<Placemark> gcpList = new ArrayList<Placemark>(gcpGroup.getNodeCount()); for (int i = 0; i < gcpGroup.getNodeCount(); i++) { final Placemark p = gcpGroup.get(i); final PixelPos pixelPos = p.getPixelPos(); final GeoPos geoPos = p.getGeoPos(); if (pixelPos != null && pixelPos.isValid() && geoPos != null && geoPos.isValid()) { gcpList.add(p); } } return gcpList.toArray(new Placemark[gcpList.size()]); } private static int getValidGcpCount(PlacemarkGroup gcpGroup) { int count = 0; for (int i = 0; i < gcpGroup.getNodeCount(); i++) { final Placemark p = gcpGroup.get(i); if (isValid(p)) { count++; } } return count; } private static boolean isValid(Placemark p) { final PixelPos pixelPos = p.getPixelPos(); final GeoPos geoPos = p.getGeoPos(); return pixelPos != null && pixelPos.isValid() && geoPos != null && geoPos.isValid(); } private static class RmseNumberFormat extends NumberFormat { DecimalFormat format = new DecimalFormat("0.0####"); @Override public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) { if (Double.isNaN(number)) { return toAppendTo.append("n/a"); } else { return format.format(number, toAppendTo, pos); } } @Override public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) { return format.format(number, toAppendTo, pos); } @Override public Number parse(String source, ParsePosition parsePosition) { return format.parse(source, parsePosition); } } private class GcpGroupListener implements ProductNodeListener { @Override public void nodeChanged(ProductNodeEvent event) { // exclude geo-coding changes to prevent recursion if (Product.PROPERTY_NAME_SCENE_GEO_CODING.equals(event.getPropertyName())) { return; } final ProductNode sourceNode = event.getSourceNode(); if (sourceNode instanceof Placemark) { if (currentProduct.getGcpGroup().contains((Placemark) sourceNode)) { updateGcpGeoCoding(); } } } @Override public void nodeDataChanged(ProductNodeEvent event) { nodeChanged(event); } @Override public void nodeAdded(ProductNodeEvent event) { if (event.getGroup() == currentProduct.getGcpGroup()) { updateGcpGeoCoding(); } } @Override public void nodeRemoved(ProductNodeEvent event) { if (event.getGroup() == currentProduct.getGcpGroup()) { updateGcpGeoCoding(); } } private void updateGcpGeoCoding() { final GeoCoding geoCoding = currentProduct.getSceneGeoCoding(); if (geoCoding instanceof GcpGeoCoding) { final GcpGeoCoding gcpGeoCoding = ((GcpGeoCoding) geoCoding); final PlacemarkGroup gcpGroup = currentProduct.getGcpGroup(); final int gcpCount = gcpGroup.getNodeCount(); if (gcpCount < gcpGeoCoding.getMethod().getTermCountP()) { detachGeoCoding(currentProduct); } else { gcpGeoCoding.setGcps(gcpGroup.toArray(new Placemark[gcpCount])); currentProduct.fireProductNodeChanged(Product.PROPERTY_NAME_SCENE_GEO_CODING); updateUIState(); } } } } }