/* * 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.ui.crs.projdef; import com.bc.ceres.binding.Property; import com.bc.ceres.binding.PropertyAccessor; import com.bc.ceres.binding.PropertyContainer; import com.bc.ceres.binding.PropertyDescriptor; import com.bc.ceres.binding.ValueSet; import com.bc.ceres.swing.TableLayout; import com.bc.ceres.swing.binding.BindingContext; import com.bc.ceres.swing.binding.PropertyPane; import com.jidesoft.swing.ComboBoxSearchable; import com.jidesoft.swing.SearchableUtils; import org.esa.snap.core.datamodel.GeoPos; import org.esa.snap.ui.AbstractDialog; import org.esa.snap.ui.ModalDialog; import org.geotools.referencing.AbstractIdentifiedObject; import org.geotools.referencing.ReferencingFactoryFinder; import org.geotools.referencing.datum.DefaultGeodeticDatum; import org.opengis.parameter.GeneralParameterDescriptor; import org.opengis.parameter.GeneralParameterValue; import org.opengis.parameter.ParameterDescriptor; import org.opengis.parameter.ParameterValue; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.AuthorityFactory; import org.opengis.referencing.FactoryException; import org.opengis.referencing.IdentifiedObject; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.datum.DatumAuthorityFactory; import org.opengis.referencing.datum.Ellipsoid; import org.opengis.referencing.datum.GeodeticDatum; import org.opengis.referencing.operation.MathTransformFactory; import org.opengis.referencing.operation.OperationMethod; import org.opengis.referencing.operation.Projection; import javax.swing.DefaultListCellRenderer; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.SwingUtilities; import javax.swing.WindowConstants; import java.awt.Component; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.TreeSet; /** * @author Marco Peters * @author Marco Zühlke * @since BEAM 4.7 */ public class CustomCrsPanel extends JPanel { private static final String OPERATION_WRAPPER = "operationWrapper"; private static final String DATUM = "datum"; private static final String PARAMETERS = "parameters"; private final Set<GeodeticDatum> datumSet; private final Set<AbstractCrsProvider> crsProviderSet; private final CustomCrsPanel.Model model; private final PropertyContainer vc; private final Window parent; private JComboBox<AbstractCrsProvider> projectionComboBox; private JComboBox<GeodeticDatum> datumComboBox; private JButton paramButton; private static final String SEMI_MAJOR_PARAM_NAME = "semi_major"; private static final String SEMI_MINOR_PARAM_NAME = "semi_minor"; /** * @deprecated since BEAM 5.0, use {@link #CustomCrsPanel(java.awt.Window, java.util.Set, java.util.Set)} instead */ @Deprecated public CustomCrsPanel(Window parent) { this(parent, CustomCrsPanel.createDatumSet(), CustomCrsPanel.createCrsProviderSet()); } public CustomCrsPanel(Window parent, Set<GeodeticDatum> datumSet, Set<AbstractCrsProvider> crsProviderSet) { this.parent = parent; this.datumSet = datumSet; this.crsProviderSet = crsProviderSet; GeodeticDatum wgs84Datum = null; // This is necessary because DefaultGeodeticDatum.WGS84 is // not equal to the geodetic WGS84 datum from the database for (GeodeticDatum geodeticDatum : datumSet) { if (DefaultGeodeticDatum.isWGS84(geodeticDatum)) { wgs84Datum = geodeticDatum; break; } } AbstractCrsProvider defaultMethod = new WGS84CrsProvider(wgs84Datum); crsProviderSet.add(defaultMethod); crsProviderSet.add(new UTMZonesCrsProvider(wgs84Datum)); crsProviderSet.add(new UTMAutomaticCrsProvider(wgs84Datum)); model = new Model(); model.operationWrapper = defaultMethod; model.datum = wgs84Datum; vc = PropertyContainer.createObjectBacked(model); vc.addPropertyChangeListener(new UpdateListener()); createUI(); updateModel(OPERATION_WRAPPER); } public CoordinateReferenceSystem getCRS(GeoPos referencePos) throws FactoryException { return model.operationWrapper.getCRS(referencePos, model.parameters, model.datum); } private void createUI() { final TableLayout tableLayout = new TableLayout(2); setLayout(tableLayout); tableLayout.setTableFill(TableLayout.Fill.HORIZONTAL); tableLayout.setTablePadding(4, 4); tableLayout.setTableAnchor(TableLayout.Anchor.WEST); tableLayout.setColumnWeightX(0, 0.0); tableLayout.setColumnWeightX(1, 1.0); tableLayout.setCellColspan(2, 0, 2); tableLayout.setCellAnchor(2, 0, TableLayout.Anchor.EAST); tableLayout.setCellFill(2, 0, TableLayout.Fill.NONE); final JLabel datumLabel = new JLabel("Geodetic datum:"); final JLabel projectionLabel = new JLabel("Projection:"); projectionComboBox = new JComboBox<>(crsProviderSet.toArray(new AbstractCrsProvider[crsProviderSet.size()])); projectionComboBox.setEditable(false); // combobox searchable only works when combobox is not editable. final ComboBoxSearchable methodSearchable = new CrsProviderSearchable(projectionComboBox); methodSearchable.installListeners(); projectionComboBox.setRenderer(new CrsProviderCellRenderer()); datumComboBox = new JComboBox<>(datumSet.toArray(new GeodeticDatum[datumSet.size()])); datumComboBox.setEditable(false); // combobox searchable only works when combobox is not editable. SearchableUtils.installSearchable(datumComboBox); datumComboBox.setRenderer(new IdentifiedObjectCellRenderer()); final ComboBoxSearchable datumSearchable = new IdentifiedObjectSearchable(datumComboBox); datumSearchable.installListeners(); paramButton = new JButton("Projection Parameters..."); paramButton.addActionListener(new ParameterButtonListener()); add(datumLabel); add(datumComboBox); add(projectionLabel); add(projectionComboBox); add(paramButton); addPropertyChangeListener("enabled", new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { updateEnableState((Boolean) evt.getNewValue()); } }); final BindingContext context = new BindingContext(vc); context.bind(OPERATION_WRAPPER, projectionComboBox); context.bind(DATUM, datumComboBox); } private void updateEnableState(boolean componentEnabled) { projectionComboBox.setEnabled(componentEnabled); datumComboBox.setEnabled(model.operationWrapper.isDatumChangable() && componentEnabled); paramButton.setEnabled(model.operationWrapper.hasParameters() && componentEnabled); } private void updateModel(String propertyName) { if (OPERATION_WRAPPER.equals(propertyName)) { GeodeticDatum defaultDatum = model.operationWrapper.getDefaultDatum(); if (defaultDatum != null) { vc.setValue(DATUM, defaultDatum); } Object oldParameterGroup; if (model.operationWrapper.hasParameters()) { oldParameterGroup = vc.getValue(PARAMETERS); ParameterValueGroup newParameters = model.operationWrapper.getParameter(); if (oldParameterGroup instanceof ParameterValueGroup) { ParameterValueGroup oldParameters = (ParameterValueGroup) oldParameterGroup; List<GeneralParameterDescriptor> generalParameterDescriptors = newParameters.getDescriptor().descriptors(); List<GeneralParameterValue> oldValues = oldParameters.values(); for (GeneralParameterDescriptor newDescriptor : generalParameterDescriptors) { String parameterName = newDescriptor.getName().getCode(); for (GeneralParameterValue oldParameterValue : oldValues) { if (AbstractIdentifiedObject.nameMatches(oldParameterValue.getDescriptor(), newDescriptor)) { Object old = ((ParameterValue)oldParameterValue).getValue(); newParameters.parameter(parameterName).setValue(old); } } } } if (hasParameter(newParameters, SEMI_MAJOR_PARAM_NAME) && hasParameter(newParameters, SEMI_MINOR_PARAM_NAME)) { Ellipsoid ellipsoid = model.datum.getEllipsoid(); ParameterValue<?> semiMajorParam = newParameters.parameter(SEMI_MAJOR_PARAM_NAME); if (semiMajorParam.getValue() == null) { semiMajorParam.setValue(ellipsoid.getSemiMajorAxis()); } ParameterValue<?> semiMinorParam = newParameters.parameter(SEMI_MINOR_PARAM_NAME); if (semiMinorParam.getValue() == null) { semiMinorParam.setValue(ellipsoid.getSemiMinorAxis()); } } vc.setValue(PARAMETERS, newParameters); } } if (DATUM.equals(propertyName)) { if (model.datum != null && model.parameters != null && hasParameter(model.parameters, SEMI_MAJOR_PARAM_NAME) && hasParameter(model.parameters, SEMI_MINOR_PARAM_NAME)) { Ellipsoid ellipsoid = model.datum.getEllipsoid(); model.parameters.parameter(SEMI_MAJOR_PARAM_NAME).setValue(ellipsoid.getSemiMajorAxis()); model.parameters.parameter(SEMI_MINOR_PARAM_NAME).setValue(ellipsoid.getSemiMinorAxis()); } } updateEnableState(true); firePropertyChange("crs", null, null); } private static boolean hasParameter(ParameterValueGroup parameterValueGroup, String name) { List<GeneralParameterDescriptor> generalParameterDescriptors = parameterValueGroup.getDescriptor().descriptors(); for (GeneralParameterDescriptor descriptor : generalParameterDescriptors) { if (AbstractIdentifiedObject.nameMatches(descriptor, name)) { return true; } } return false; } public void setCustom(GeodeticDatum geodeticDatum, OperationMethod operationMethod, ParameterValueGroup parameterValues) { String geodeticDatumName = geodeticDatum.getName().getCode(); for (GeodeticDatum datum : datumSet) { if (datum.getName().getCode().equals(geodeticDatumName)) { vc.setValue(DATUM, datum); break; } } for (AbstractCrsProvider abstractCrsProvider : crsProviderSet) { if (abstractCrsProvider instanceof OperationMethodCrsProvider) { OperationMethodCrsProvider operationMethodCrsProvider = (OperationMethodCrsProvider) abstractCrsProvider; String operationMethodName = operationMethod.getName().getCode(); if (operationMethodCrsProvider.delegate.getName().getCode().equals(operationMethodName)) { vc.setValue(OPERATION_WRAPPER, abstractCrsProvider); break; } } } vc.setValue(PARAMETERS, parameterValues); } private class UpdateListener implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { updateModel(evt.getPropertyName()); } } public static void main(String[] args) { final JFrame frame = new JFrame("Projection Method Form Test"); final CustomCrsPanel customCrsForm = new CustomCrsPanel(frame, CustomCrsPanel.createDatumSet(), CustomCrsPanel.createCrsProviderSet()); frame.setContentPane(customCrsForm); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { frame.pack(); frame.setVisible(true); } }); } private static Set<AbstractCrsProvider> createCrsProviderSet() { MathTransformFactory factory = ReferencingFactoryFinder.getMathTransformFactory(null); Set<OperationMethod> methods = factory.getAvailableMethods(Projection.class); TreeSet<AbstractCrsProvider> crsProviderSet = new TreeSet<>(new CrsProviderComparator()); for (OperationMethod method : methods) { crsProviderSet.add(new OperationMethodCrsProvider(method)); } return crsProviderSet; } private static Set<GeodeticDatum> createDatumSet() { DatumAuthorityFactory factory = ReferencingFactoryFinder.getDatumAuthorityFactory("EPSG", null); List<String> datumCodes = retrieveCodes(GeodeticDatum.class, factory); Set<GeodeticDatum> datumSet = new TreeSet<>(AbstractIdentifiedObject.NAME_COMPARATOR); for (String datumCode : datumCodes) { try { DefaultGeodeticDatum geodeticDatum = (DefaultGeodeticDatum) factory.createGeodeticDatum(datumCode); if (geodeticDatum.getBursaWolfParameters().length != 0 || DefaultGeodeticDatum.isWGS84(geodeticDatum)) { datumSet.add(geodeticDatum); } } catch (FactoryException ignored) { } } return datumSet; } private static List<String> retrieveCodes(Class<? extends GeodeticDatum> crsType, AuthorityFactory factory) { try { Set<String> localCodes = factory.getAuthorityCodes(crsType); return new ArrayList<>(localCodes); } catch (FactoryException ignore) { return Collections.emptyList(); } } private static PropertyContainer createValueContainer(ParameterValueGroup valueGroup) { final PropertyContainer vc = new PropertyContainer(); final List<GeneralParameterValue> values = valueGroup.values(); for (GeneralParameterValue value : values) { final GeneralParameterDescriptor descriptor = value.getDescriptor(); final Class valueType; Set validValues = null; if (descriptor instanceof ParameterDescriptor) { ParameterDescriptor parameterDescriptor = (ParameterDescriptor) descriptor; valueType = parameterDescriptor.getValueClass(); validValues = parameterDescriptor.getValidValues(); } else { valueType = Double.TYPE; } final String paramName = descriptor.getName().getCode(); final PropertyDescriptor vd = new PropertyDescriptor(paramName, valueType); final ParameterValue<?> parameterValue = valueGroup.parameter(paramName); if (parameterValue.getUnit() != null) { vd.setUnit(String.valueOf(parameterValue.getUnit())); } if (validValues != null) { vd.setValueSet(new ValueSet(validValues.toArray())); } vd.setDefaultConverter(); final Property property = new Property(vd, new PropertyAccessor() { @Override public Object getValue() { return parameterValue.getValue(); } @Override public void setValue(Object value) { parameterValue.setValue(value); } }); vc.addProperty(property); } return vc; } private static class Model { private AbstractCrsProvider operationWrapper; private GeodeticDatum datum; @SuppressWarnings("UnusedDeclaration") private ParameterValueGroup parameters; } private class ParameterButtonListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { final String operationName = model.operationWrapper.getName(); final ModalDialog modalDialog = new ModalDialog(parent, operationName + " - Parameters", ModalDialog.ID_OK_CANCEL, null); final ParameterValueGroup workCopy = model.parameters.clone(); final PropertyContainer propertyContainer = createValueContainer(workCopy); modalDialog.setContent(new PropertyPane(propertyContainer).createPanel()); if (modalDialog.show() == AbstractDialog.ID_OK) { vc.setValue(PARAMETERS, workCopy); } } } private static class CrsProviderComparator implements Comparator<AbstractCrsProvider> { @Override public int compare(AbstractCrsProvider o1, AbstractCrsProvider o2) { final String name1 = o1.getName(); final String name2 = o2.getName(); return name1.compareTo(name2); } } private static class IdentifiedObjectSearchable extends ComboBoxSearchable { private IdentifiedObjectSearchable(JComboBox<GeodeticDatum> operationComboBox) { super(operationComboBox); } @Override protected String convertElementToString(Object o) { if (o instanceof IdentifiedObject) { IdentifiedObject identifiedObject = (IdentifiedObject) o; return identifiedObject.getName().getCode(); } else { return super.convertElementToString(o); } } } private static class CrsProviderSearchable extends ComboBoxSearchable { private CrsProviderSearchable(JComboBox<AbstractCrsProvider> operationComboBox) { super(operationComboBox); } @Override protected String convertElementToString(Object o) { if (o instanceof AbstractCrsProvider) { AbstractCrsProvider wrapper = (AbstractCrsProvider) o; return wrapper.getName(); } else { return super.convertElementToString(o); } } } private static class CrsProviderCellRenderer extends DefaultListCellRenderer { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { final Component component = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); JLabel label = (JLabel) component; if (value != null) { AbstractCrsProvider wrapper = (AbstractCrsProvider) value; label.setText(wrapper.getName()); } return label; } } private static class IdentifiedObjectCellRenderer extends DefaultListCellRenderer { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { final Component component = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); JLabel label = (JLabel) component; if (value != null) { IdentifiedObject identifiedObject = (IdentifiedObject) value; label.setText(identifiedObject.getName().getCode()); } return label; } } }