/* * Copyright (C) 2011 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.core.gpf.ui.reproject; import com.bc.ceres.binding.ConversionException; import com.bc.ceres.binding.Property; import com.bc.ceres.binding.PropertyContainer; import com.bc.ceres.binding.PropertySet; import com.bc.ceres.binding.ValidationException; import com.bc.ceres.swing.TableLayout; import com.bc.ceres.swing.binding.BindingContext; import com.bc.ceres.swing.selection.AbstractSelectionChangeListener; import com.bc.ceres.swing.selection.SelectionChangeEvent; import org.esa.snap.core.datamodel.GeoCoding; import org.esa.snap.core.datamodel.GeoPos; import org.esa.snap.core.datamodel.ImageGeometry; import org.esa.snap.core.datamodel.Product; import org.esa.snap.core.datamodel.ProductFilter; import org.esa.snap.core.gpf.ui.CollocationCrsForm; import org.esa.snap.core.gpf.ui.SourceProductSelector; import org.esa.snap.core.gpf.ui.TargetProductSelector; import org.esa.snap.core.gpf.ui.TargetProductSelectorModel; import org.esa.snap.core.param.ParamParseException; import org.esa.snap.core.param.ParamValidateException; import org.esa.snap.core.util.ProductUtils; import org.esa.snap.ui.AbstractDialog; import org.esa.snap.ui.AppContext; import org.esa.snap.ui.DemSelector; import org.esa.snap.ui.ModalDialog; import org.esa.snap.ui.crs.CrsForm; import org.esa.snap.ui.crs.CrsSelectionPanel; import org.esa.snap.ui.crs.CustomCrsForm; import org.esa.snap.ui.crs.OutputGeometryForm; import org.esa.snap.ui.crs.OutputGeometryFormModel; import org.esa.snap.ui.crs.PredefinedCrsForm; import org.geotools.referencing.CRS; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.crs.ProjectedCRS; import org.opengis.referencing.datum.GeodeticDatum; import org.opengis.referencing.operation.OperationMethod; import org.opengis.referencing.operation.Projection; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JTextArea; import javax.swing.JTextField; import java.awt.Insets; import java.awt.Rectangle; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; /** * @author Marco Zuehlke * @author Marco Peters * @since BEAM 4.7 */ class ReprojectionForm extends JTabbedPane { private static final String[] RESAMPLING_IDENTIFIER = {"Nearest", "Bilinear", "Bicubic"}; private final boolean orthoMode; private final String targetProductSuffix; private final AppContext appContext; private final SourceProductSelector sourceProductSelector; private final TargetProductSelector targetProductSelector; private final Model reprojectionModel; private final PropertyContainer reprojectionContainer; private DemSelector demSelector; private CrsSelectionPanel crsSelectionPanel; private OutputGeometryFormModel outputGeometryModel; private JButton outputParamButton; private InfoForm infoForm; private CoordinateReferenceSystem crs; private CollocationCrsForm collocationCrsUI; private CustomCrsForm customCrsUI; ReprojectionForm(TargetProductSelector targetProductSelector, boolean orthorectify, AppContext appContext) { this.targetProductSelector = targetProductSelector; this.orthoMode = orthorectify; this.appContext = appContext; this.sourceProductSelector = new SourceProductSelector(appContext, "Source Product:"); if (orthoMode) { targetProductSuffix = "orthorectified"; this.sourceProductSelector.setProductFilter(new OrthorectifyProductFilter()); } else { targetProductSuffix = "reprojected"; this.sourceProductSelector.setProductFilter(new GeoCodingProductFilter()); } this.reprojectionModel = new Model(); this.reprojectionContainer = PropertyContainer.createObjectBacked(reprojectionModel); createUI(); } void updateParameterMap(Map<String, Object> parameterMap) { parameterMap.clear(); parameterMap.put("resamplingName", reprojectionModel.resamplingName); parameterMap.put("includeTiePointGrids", reprojectionModel.includeTiePointGrids); parameterMap.put("addDeltaBands", reprojectionModel.addDeltaBands); parameterMap.put("noDataValue", reprojectionModel.noDataValue); if (!collocationCrsUI.getRadioButton().isSelected()) { CoordinateReferenceSystem selectedCrs = getSelectedCrs(); if (selectedCrs != null) { parameterMap.put("crs", selectedCrs.toWKT()); } } if (orthoMode) { parameterMap.put("orthorectify", orthoMode); if (demSelector.isUsingExternalDem()) { parameterMap.put("elevationModelName", demSelector.getDemName()); } else { parameterMap.put("elevationModelName", null); } } if (!reprojectionModel.preserveResolution && outputGeometryModel != null) { PropertySet container = outputGeometryModel.getPropertySet(); parameterMap.put("referencePixelX", container.getValue("referencePixelX")); parameterMap.put("referencePixelY", container.getValue("referencePixelY")); parameterMap.put("easting", container.getValue("easting")); parameterMap.put("northing", container.getValue("northing")); parameterMap.put("orientation", container.getValue("orientation")); parameterMap.put("pixelSizeX", container.getValue("pixelSizeX")); parameterMap.put("pixelSizeY", container.getValue("pixelSizeY")); parameterMap.put("width", container.getValue("width")); parameterMap.put("height", container.getValue("height")); } } public void updateFormModel(Map<String, Object> parameterMap) throws ValidationException, ConversionException { Property[] properties = reprojectionContainer.getProperties(); for (Property property : properties) { String propertyName = property.getName(); Object newValue = parameterMap.get(propertyName); if (newValue != null) { property.setValue(newValue); } } if (orthoMode) { Object elevationModelName = parameterMap.get("elevationModelName"); if (elevationModelName instanceof String) { try { demSelector.setDemName((String) elevationModelName); } catch (ParamValidateException e) { throw new ValidationException(e.getMessage(), e); } catch (ParamParseException e) { throw new ConversionException(e.getMessage(), e); } } } Object crsAsWKT = parameterMap.get("crs"); if (crsAsWKT instanceof String) { try { CoordinateReferenceSystem crs; crs = CRS.parseWKT((String) crsAsWKT); if (crs instanceof ProjectedCRS) { ProjectedCRS projectedCRS = (ProjectedCRS) crs; Projection conversionFromBase = projectedCRS.getConversionFromBase(); OperationMethod operationMethod = conversionFromBase.getMethod(); ParameterValueGroup parameterValues = conversionFromBase.getParameterValues(); GeodeticDatum geodeticDatum = projectedCRS.getDatum(); customCrsUI.setCustom(geodeticDatum, operationMethod, parameterValues); } else { throw new ConversionException("Failed to convert CRS from WKT."); } } catch (FactoryException e) { throw new ConversionException("Failed to convert CRS from WKT.", e); } } if (parameterMap.containsKey("referencePixelX")) { PropertyContainer propertySet = PropertyContainer.createMapBacked(parameterMap); outputGeometryModel = new OutputGeometryFormModel(propertySet); reprojectionContainer.setValue(Model.PRESERVE_RESOLUTION, false); } else { outputGeometryModel = null; reprojectionContainer.setValue(Model.PRESERVE_RESOLUTION, true); } updateCRS(); } Map<String, Product> getProductMap() { final Map<String, Product> productMap = new HashMap<>(5); productMap.put("source", getSourceProduct()); if (collocationCrsUI.getRadioButton().isSelected()) { productMap.put("collocateWith", collocationCrsUI.getCollocationProduct()); } return productMap; } Product getSourceProduct() { return sourceProductSelector.getSelectedProduct(); } CoordinateReferenceSystem getSelectedCrs() { return crs; } void prepareShow() { sourceProductSelector.initProducts(); crsSelectionPanel.prepareShow(); } void prepareHide() { sourceProductSelector.releaseProducts(); crsSelectionPanel.prepareHide(); if (outputGeometryModel != null) { outputGeometryModel.setSourceProduct(null); } } String getExternalDemName() { if (orthoMode && demSelector.isUsingExternalDem()) { return demSelector.getDemName(); } return null; } private void createUI() { addTab("I/O Parameters", createIOPanel()); addTab("Reprojection Parameters", createParametersPanel()); } private JPanel createIOPanel() { final TableLayout tableLayout = new TableLayout(1); tableLayout.setTableWeightX(1.0); tableLayout.setTableWeightY(0.0); tableLayout.setTableFill(TableLayout.Fill.BOTH); tableLayout.setTablePadding(3, 3); final JPanel ioPanel = new JPanel(tableLayout); ioPanel.add(createSourceProductPanel()); ioPanel.add(targetProductSelector.createDefaultPanel()); ioPanel.add(tableLayout.createVerticalSpacer()); return ioPanel; } private JPanel createParametersPanel() { final JPanel parameterPanel = new JPanel(); final TableLayout layout = new TableLayout(1); layout.setTablePadding(4, 4); layout.setTableFill(TableLayout.Fill.HORIZONTAL); layout.setTableAnchor(TableLayout.Anchor.WEST); layout.setTableWeightX(1.0); parameterPanel.setLayout(layout); customCrsUI = new CustomCrsForm(appContext); CrsForm predefinedCrsUI = new PredefinedCrsForm(appContext); collocationCrsUI = new CollocationCrsForm(appContext); CrsForm[] crsForms = new CrsForm[]{customCrsUI, predefinedCrsUI, collocationCrsUI}; crsSelectionPanel = new CrsSelectionPanel(crsForms); sourceProductSelector.addSelectionChangeListener(new AbstractSelectionChangeListener() { @Override public void selectionChanged(SelectionChangeEvent event) { final Product product = (Product) event.getSelection().getSelectedValue(); crsSelectionPanel.setReferenceProduct(product); } }); parameterPanel.add(crsSelectionPanel); if (orthoMode) { demSelector = new DemSelector(); parameterPanel.add(demSelector); } parameterPanel.add(createOuputSettingsPanel()); infoForm = new InfoForm(); parameterPanel.add(infoForm.createUI()); crsSelectionPanel.addPropertyChangeListener("crs", evt -> updateCRS()); updateCRS(); return parameterPanel; } private void updateCRS() { final Product sourceProduct = getSourceProduct(); try { if (sourceProduct != null) { crs = crsSelectionPanel.getCrs(ProductUtils.getCenterGeoPos(sourceProduct)); if (crs != null) { infoForm.setCrsInfoText(crs.getName().getCode(), crs.toString()); } else { infoForm.setCrsErrorText("No valid 'Coordinate Reference System' selected."); } } else { infoForm.setCrsErrorText("No source product selected."); crs = null; } } catch (FactoryException e) { infoForm.setCrsErrorText(e.getMessage()); crs = null; } if (outputGeometryModel != null) { outputGeometryModel.setTargetCrs(crs); } updateOutputParameterState(); } private void updateProductSize() { int width = 0; int height = 0; final Product sourceProduct = getSourceProduct(); if (sourceProduct != null && crs != null) { if (!reprojectionModel.preserveResolution && outputGeometryModel != null) { PropertySet container = outputGeometryModel.getPropertySet(); width = container.getValue("width"); height = container.getValue("height"); } else { ImageGeometry iGeometry; final Product collocationProduct = collocationCrsUI.getCollocationProduct(); if (collocationCrsUI.getRadioButton().isSelected() && collocationProduct != null) { iGeometry = ImageGeometry.createCollocationTargetGeometry(sourceProduct, collocationProduct); } else { iGeometry = ImageGeometry.createTargetGeometry(sourceProduct, crs, null, null, null, null, null, null, null, null, null); } Rectangle imageRect = iGeometry.getImageRect(); width = imageRect.width; height = imageRect.height; } } infoForm.setWidth(width); infoForm.setHeight(height); } private class InfoForm { private JLabel widthLabel; private JLabel heightLabel; private JLabel centerLatLabel; private JLabel centerLonLabel; private JLabel crsLabel; private String wkt; private JButton wktButton; void setWidth(int width) { widthLabel.setText(Integer.toString(width)); } void setHeight(int height) { heightLabel.setText(Integer.toString(height)); } void setCenterPos(GeoPos geoPos) { if (geoPos != null) { centerLatLabel.setText(geoPos.getLatString()); centerLonLabel.setText(geoPos.getLonString()); } else { centerLatLabel.setText(""); centerLonLabel.setText(""); } } void setCrsErrorText(String infoText) { setCrsInfoText("<html><b>" + infoText + "</b>", null); } void setCrsInfoText(String infoText, String wkt) { this.wkt = wkt; crsLabel.setText(infoText); boolean hasWKT = (wkt != null); wktButton.setEnabled(hasWKT); } JPanel createUI() { widthLabel = new JLabel(); heightLabel = new JLabel(); centerLatLabel = new JLabel(); centerLonLabel = new JLabel(); crsLabel = new JLabel(); final TableLayout tableLayout = new TableLayout(5); tableLayout.setTableAnchor(TableLayout.Anchor.WEST); tableLayout.setTableFill(TableLayout.Fill.HORIZONTAL); tableLayout.setTablePadding(4, 4); tableLayout.setColumnWeightX(0, 0.0); tableLayout.setColumnWeightX(1, 0.0); tableLayout.setColumnWeightX(2, 1.0); tableLayout.setColumnWeightX(3, 0.0); tableLayout.setColumnWeightX(4, 1.0); tableLayout.setCellColspan(2, 1, 3); tableLayout.setCellPadding(0, 3, new Insets(4, 24, 4, 20)); tableLayout.setCellPadding(1, 3, new Insets(4, 24, 4, 20)); final JPanel panel = new JPanel(tableLayout); panel.setBorder(BorderFactory.createTitledBorder("Output Information")); panel.add(new JLabel("Scene width:")); panel.add(widthLabel); panel.add(new JLabel("pixel")); panel.add(new JLabel("Center longitude:")); panel.add(centerLonLabel); panel.add(new JLabel("Scene height:")); panel.add(heightLabel); panel.add(new JLabel("pixel")); panel.add(new JLabel("Center latitude:")); panel.add(centerLatLabel); panel.add(new JLabel("CRS:")); panel.add(crsLabel); wktButton = new JButton("Show WKT"); wktButton.addActionListener(e -> { JTextArea wktArea = new JTextArea(30, 40); wktArea.setEditable(false); wktArea.setText(wkt); final JScrollPane scrollPane = new JScrollPane(wktArea); final ModalDialog dialog = new ModalDialog(appContext.getApplicationWindow(), "Coordinate reference system as well known text", scrollPane, ModalDialog.ID_OK, null); dialog.show(); }); wktButton.setEnabled(false); panel.add(wktButton); return panel; } } private JPanel createOuputSettingsPanel() { final TableLayout tableLayout = new TableLayout(3); tableLayout.setTableAnchor(TableLayout.Anchor.WEST); tableLayout.setTableFill(TableLayout.Fill.HORIZONTAL); tableLayout.setColumnFill(0, TableLayout.Fill.NONE); tableLayout.setTablePadding(4, 4); tableLayout.setColumnPadding(0, new Insets(4, 4, 4, 20)); tableLayout.setColumnWeightX(0, 0.0); tableLayout.setColumnWeightX(1, 0.0); tableLayout.setColumnWeightX(2, 1.0); tableLayout.setCellColspan(0, 1, 2); tableLayout.setCellPadding(1, 0, new Insets(4, 24, 4, 20)); final JPanel outputSettingsPanel = new JPanel(tableLayout); outputSettingsPanel.setBorder(BorderFactory.createTitledBorder("Output Settings")); final BindingContext context = new BindingContext(reprojectionContainer); final JCheckBox preserveResolutionCheckBox = new JCheckBox("Preserve resolution"); context.bind(Model.PRESERVE_RESOLUTION, preserveResolutionCheckBox); collocationCrsUI.getCrsUI().addPropertyChangeListener("collocate", evt -> { final boolean collocate = (Boolean) evt.getNewValue(); reprojectionContainer.setValue(Model.PRESERVE_RESOLUTION, collocate || reprojectionModel.preserveResolution); preserveResolutionCheckBox.setEnabled(!collocate); }); outputSettingsPanel.add(preserveResolutionCheckBox); JCheckBox includeTPcheck = new JCheckBox("Reproject tie-point grids", true); context.bind(Model.REPROJ_TIEPOINTS, includeTPcheck); outputSettingsPanel.add(includeTPcheck); outputParamButton = new JButton("Output Parameters..."); outputParamButton.setEnabled(!reprojectionModel.preserveResolution); outputParamButton.addActionListener(new OutputParamActionListener()); outputSettingsPanel.add(outputParamButton); outputSettingsPanel.add(new JLabel("No-data value:")); final JTextField noDataField = new JTextField(); outputSettingsPanel.add(noDataField); context.bind(Model.NO_DATA_VALUE, noDataField); JCheckBox addDeltaBandsChecker = new JCheckBox("Add delta lat/lon bands"); outputSettingsPanel.add(addDeltaBandsChecker); context.bind(Model.ADD_DELTA_BANDS, addDeltaBandsChecker); outputSettingsPanel.add(new JLabel("Resampling method:")); JComboBox<String> resampleComboBox = new JComboBox<>(RESAMPLING_IDENTIFIER); resampleComboBox.setPrototypeDisplayValue(RESAMPLING_IDENTIFIER[0]); context.bind(Model.RESAMPLING_NAME, resampleComboBox); outputSettingsPanel.add(resampleComboBox); reprojectionContainer.addPropertyChangeListener(Model.PRESERVE_RESOLUTION, evt -> updateOutputParameterState()); return outputSettingsPanel; } private void updateOutputParameterState() { outputParamButton.setEnabled(!reprojectionModel.preserveResolution && (crs != null)); updateProductSize(); } private JPanel createSourceProductPanel() { final JPanel panel = sourceProductSelector.createDefaultPanel(); sourceProductSelector.getProductNameLabel().setText("Name:"); sourceProductSelector.getProductNameComboBox().setPrototypeDisplayValue( "MER_RR__1PPBCM20030730_071000_000003972018_00321_07389_0000.N1"); sourceProductSelector.addSelectionChangeListener(new AbstractSelectionChangeListener() { @Override public void selectionChanged(SelectionChangeEvent event) { final Product sourceProduct = getSourceProduct(); updateTargetProductName(sourceProduct); GeoPos centerGeoPos = null; if (sourceProduct != null) { centerGeoPos = ProductUtils.getCenterGeoPos(sourceProduct); } infoForm.setCenterPos(centerGeoPos); if (outputGeometryModel != null) { outputGeometryModel.setSourceProduct(sourceProduct); } updateCRS(); } }); return panel; } private void updateTargetProductName(Product selectedProduct) { final TargetProductSelectorModel selectorModel = targetProductSelector.getModel(); if (selectedProduct != null) { final String productName = MessageFormat.format("{0}_" + targetProductSuffix, selectedProduct.getName()); selectorModel.setProductName(productName); } else if (selectorModel.getProductName() == null) { selectorModel.setProductName(targetProductSuffix); } } private class OutputParamActionListener implements ActionListener { @Override public void actionPerformed(ActionEvent event) { try { final Product sourceProduct = getSourceProduct(); if (sourceProduct == null) { showWarningMessage("Please select a product to reproject.\n"); return; } if (crs == null) { showWarningMessage("Please specify a 'Coordinate Reference System' first.\n"); return; } OutputGeometryFormModel workCopy; if (outputGeometryModel != null) { workCopy = new OutputGeometryFormModel(outputGeometryModel); } else { final Product collocationProduct = collocationCrsUI.getCollocationProduct(); if (collocationCrsUI.getRadioButton().isSelected() && collocationProduct != null) { workCopy = new OutputGeometryFormModel(sourceProduct, collocationProduct); } else { workCopy = new OutputGeometryFormModel(sourceProduct, crs); } } final OutputGeometryForm form = new OutputGeometryForm(workCopy); final ModalDialog outputParametersDialog = new OutputParametersDialog(appContext.getApplicationWindow(), sourceProduct, workCopy); outputParametersDialog.setContent(form); if (outputParametersDialog.show() == ModalDialog.ID_OK) { outputGeometryModel = workCopy; updateProductSize(); } } catch (Exception e) { appContext.handleError("Could not create a 'Coordinate Reference System'.\n" + e.getMessage(), e); } } } private void showWarningMessage(String message) { AbstractDialog.showWarningDialog(getParent(), message, "Reprojection"); } private class OutputParametersDialog extends ModalDialog { private static final String TITLE = "Output Parameters"; private final Product sourceProduct; private final OutputGeometryFormModel outputGeometryFormModel; public OutputParametersDialog(Window parent, Product sourceProduct, OutputGeometryFormModel outputGeometryFormModel) { super(parent, TITLE, ModalDialog.ID_OK_CANCEL | ModalDialog.ID_RESET, null); this.sourceProduct = sourceProduct; this.outputGeometryFormModel = outputGeometryFormModel; } @Override protected void onReset() { final Product collocationProduct = collocationCrsUI.getCollocationProduct(); ImageGeometry imageGeometry; if (collocationCrsUI.getRadioButton().isSelected() && collocationProduct != null) { imageGeometry = ImageGeometry.createCollocationTargetGeometry(sourceProduct, collocationProduct); } else { imageGeometry = ImageGeometry.createTargetGeometry(sourceProduct, crs, null, null, null, null, null, null, null, null, null); } outputGeometryFormModel.resetToDefaults(imageGeometry); } } private static class Model { private static final String PRESERVE_RESOLUTION = "preserveResolution"; private static final String REPROJ_TIEPOINTS = "includeTiePointGrids"; private static final String ADD_DELTA_BANDS = "addDeltaBands"; private static final String NO_DATA_VALUE = "noDataValue"; private static final String RESAMPLING_NAME = "resamplingName"; private boolean preserveResolution = true; private boolean includeTiePointGrids = true; private boolean addDeltaBands = false; private double noDataValue = Double.NaN; private String resamplingName = RESAMPLING_IDENTIFIER[0]; } private static class OrthorectifyProductFilter implements ProductFilter { @Override public boolean accept(Product product) { return product.canBeOrthorectified(); } } private static class GeoCodingProductFilter implements ProductFilter { @Override public boolean accept(Product product) { final GeoCoding geoCoding = product.getSceneGeoCoding(); return geoCoding != null && geoCoding.canGetGeoPos() && geoCoding.canGetPixelPos(); } } }