001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.preferences.projection;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.BorderLayout;
007import java.awt.GridBagLayout;
008import java.awt.Insets;
009import java.awt.event.ActionListener;
010import java.util.Arrays;
011import java.util.Collection;
012import java.util.Collections;
013import java.util.List;
014
015import javax.swing.JButton;
016import javax.swing.JComponent;
017import javax.swing.JLabel;
018import javax.swing.JPanel;
019import javax.swing.plaf.basic.BasicComboBoxEditor;
020
021import org.openstreetmap.josm.data.projection.CustomProjection;
022import org.openstreetmap.josm.data.projection.Projection;
023import org.openstreetmap.josm.data.projection.ProjectionConfigurationException;
024import org.openstreetmap.josm.data.projection.Projections;
025import org.openstreetmap.josm.gui.ExtendedDialog;
026import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
027import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
028import org.openstreetmap.josm.gui.widgets.HtmlPanel;
029import org.openstreetmap.josm.gui.widgets.JosmTextField;
030import org.openstreetmap.josm.tools.GBC;
031import org.openstreetmap.josm.tools.ImageProvider;
032import org.openstreetmap.josm.tools.Logging;
033import org.openstreetmap.josm.tools.Utils;
034
035/**
036 * ProjectionChoice where a CRS can be defined using various parameters.
037 * <p>
038 * The configuration string mimics the syntax of the PROJ.4 project and should
039 * be mostly compatible.
040 * @see CustomProjection
041 */
042public class CustomProjectionChoice extends AbstractProjectionChoice implements SubPrefsOptions {
043
044    private String pref;
045
046    /**
047     * Constructs a new {@code CustomProjectionChoice}.
048     */
049    public CustomProjectionChoice() {
050        super(tr("Custom Projection"), /* NO-ICON */ "core:custom");
051    }
052
053    private static class PreferencePanel extends JPanel {
054
055        public JosmTextField input;
056        private HistoryComboBox cbInput;
057
058        PreferencePanel(String initialText, ActionListener listener) {
059            build(initialText, listener);
060        }
061
062        private void build(String initialText, final ActionListener listener) {
063            input = new JosmTextField(30);
064            cbInput = new HistoryComboBox();
065            cbInput.setEditor(new BasicComboBoxEditor() {
066                @Override
067                protected JosmTextField createEditorComponent() {
068                    return input;
069                }
070            });
071            List<String> samples = Arrays.asList(
072                    "+proj=lonlat +ellps=WGS84 +datum=WGS84 +bounds=-180,-90,180,90",
073                    "+proj=tmerc +lat_0=0 +lon_0=9 +k_0=1 +x_0=3500000 +y_0=0 +ellps=bessel +nadgrids=BETA2007.gsb");
074            cbInput.getModel().prefs().load("projection.custom.value.history", samples);
075            cbInput.setText(initialText == null ? "" : initialText);
076
077            final HtmlPanel errorsPanel = new HtmlPanel();
078            errorsPanel.setVisible(false);
079            final JLabel valStatus = new JLabel();
080            valStatus.setVisible(false);
081
082            final AbstractTextComponentValidator val = new AbstractTextComponentValidator(input, false, false, false) {
083
084                private String error;
085
086                @Override
087                public void validate() {
088                    if (!isValid()) {
089                        feedbackInvalid(tr("Invalid projection configuration: {0}", error));
090                    } else {
091                        feedbackValid(tr("Projection configuration is valid."));
092                    }
093                    listener.actionPerformed(null);
094                }
095
096                @Override
097                public boolean isValid() {
098                    try {
099                        CustomProjection test = new CustomProjection();
100                        test.update(input.getText());
101                    } catch (ProjectionConfigurationException ex) {
102                        Logging.warn(ex);
103                        error = ex.getMessage();
104                        valStatus.setIcon(ImageProvider.get("data", "error"));
105                        valStatus.setVisible(true);
106                        errorsPanel.setText(error);
107                        errorsPanel.setVisible(true);
108                        return false;
109                    }
110                    errorsPanel.setVisible(false);
111                    valStatus.setIcon(ImageProvider.get("misc", "green_check"));
112                    valStatus.setVisible(true);
113                    return true;
114                }
115            };
116
117            JButton btnCheck = new JButton(tr("Validate"));
118            btnCheck.addActionListener(e -> val.validate());
119            btnCheck.setLayout(new BorderLayout());
120            btnCheck.setMargin(new Insets(-1, 0, -1, 0));
121
122            JButton btnInfo = new JButton(tr("Parameter information..."));
123            btnInfo.addActionListener(e -> {
124                CustomProjectionChoice.ParameterInfoDialog dlg = new CustomProjectionChoice.ParameterInfoDialog();
125                dlg.showDialog();
126                dlg.toFront();
127            });
128
129            this.setLayout(new GridBagLayout());
130            JPanel p2 = new JPanel(new GridBagLayout());
131            p2.add(cbInput, GBC.std().fill(GBC.HORIZONTAL).insets(0, 20, 5, 5));
132            p2.add(btnCheck, GBC.eol().insets(0, 20, 0, 5));
133            this.add(p2, GBC.eol().fill(GBC.HORIZONTAL));
134            p2 = new JPanel(new GridBagLayout());
135            p2.add(valStatus, GBC.std().anchor(GBC.WEST).weight(0.0001, 0));
136            p2.add(errorsPanel, GBC.eol().fill(GBC.HORIZONTAL));
137            this.add(p2, GBC.eol().fill(GBC.HORIZONTAL));
138            p2 = new JPanel(new GridBagLayout());
139            p2.add(btnInfo, GBC.std().insets(0, 20, 0, 0));
140            p2.add(GBC.glue(1, 0), GBC.eol().fill(GBC.HORIZONTAL));
141            this.add(p2, GBC.eol().fill(GBC.HORIZONTAL));
142        }
143
144        public void rememberHistory() {
145            cbInput.addCurrentItemToHistory();
146            cbInput.getModel().prefs().save("projection.custom.value.history");
147        }
148    }
149
150    public static class ParameterInfoDialog extends ExtendedDialog {
151
152        /**
153         * Constructs a new {@code ParameterInfoDialog}.
154         */
155        public ParameterInfoDialog() {
156            super(null, tr("Parameter information"), new String[] {tr("Close")}, false);
157            setContent(build());
158        }
159
160        private static JComponent build() {
161            StringBuilder s = new StringBuilder(1024);
162            s.append("<b>+proj=...</b> - <i>").append(tr("Projection name"))
163             .append("</i><br>&nbsp;&nbsp;&nbsp;&nbsp;").append(tr("Supported values:")).append(' ')
164             .append(Projections.listProjs())
165             .append("<br><b>+lat_0=..., +lat_1=..., +lat_2=...</b> - <i>").append(tr("Projection parameters"))
166             .append("</i><br><b>+x_0=..., +y_0=...</b> - <i>").append(tr("False easting and false northing"))
167             .append("</i><br><b>+lon_0=...</b> - <i>").append(tr("Central meridian"))
168             .append("</i><br><b>+k_0=...</b> - <i>").append(tr("Scaling factor"))
169             .append("</i><br><b>+ellps=...</b> - <i>").append(tr("Ellipsoid name"))
170             .append("</i><br>&nbsp;&nbsp;&nbsp;&nbsp;").append(tr("Supported values:")).append(' ')
171             .append(Projections.listEllipsoids())
172             .append("<br><b>+a=..., +b=..., +rf=..., +f=..., +es=...</b> - <i>").append(tr("Ellipsoid parameters"))
173             .append("</i><br><b>+datum=...</b> - <i>").append(tr("Datum name"))
174             .append("</i><br>&nbsp;&nbsp;&nbsp;&nbsp;").append(tr("Supported values:")).append(' ')
175             .append(Projections.listDatums())
176             .append("<br><b>+towgs84=...</b> - <i>").append(tr("3 or 7 term datum transform parameters"))
177             .append("</i><br><b>+nadgrids=...</b> - <i>").append(tr("NTv2 grid file"))
178             .append("</i><br>&nbsp;&nbsp;&nbsp;&nbsp;").append(tr("Built-in:")).append(' ')
179             .append(Projections.listNadgrids())
180             .append("<br><b>+bounds=</b>minlon,minlat,maxlon,maxlat - <i>").append(tr("Projection bounds (in degrees)"))
181             .append("</i><br><b>+wmssrs=</b>EPSG:123456 - <i>").append(tr("Sets the SRS=... parameter in the WMS request"))
182             .append("</i><br>");
183
184            return new HtmlPanel(s.toString());
185        }
186    }
187
188    @Override
189    public void setPreferences(Collection<String> args) {
190        if (!Utils.isEmpty(args)) {
191            pref = args.iterator().next();
192        }
193    }
194
195    @Override
196    public Projection getProjection() {
197        return new CustomProjection(pref);
198    }
199
200    @Override
201    public String getCurrentCode() {
202        // not needed - getProjection() is overridden
203        throw new UnsupportedOperationException();
204    }
205
206    @Override
207    public String getProjectionName() {
208        // not needed - getProjection() is overridden
209        throw new UnsupportedOperationException();
210    }
211
212    @Override
213    public JPanel getPreferencePanel(ActionListener listener) {
214        return new PreferencePanel(pref, listener);
215    }
216
217    @Override
218    public Collection<String> getPreferences(JPanel panel) {
219        if (!(panel instanceof PreferencePanel)) {
220            throw new IllegalArgumentException("Unsupported panel: "+panel);
221        }
222        PreferencePanel prefPanel = (PreferencePanel) panel;
223        String pref = prefPanel.input.getText();
224        prefPanel.rememberHistory();
225        return Collections.singleton(pref);
226    }
227
228    @Override
229    public String[] allCodes() {
230        return new String[0];
231    }
232
233    @Override
234    public Collection<String> getPreferencesFromCode(String code) {
235        return null;
236    }
237
238    @Override
239    public boolean showProjectionCode() {
240        return false;
241    }
242
243    @Override
244    public boolean showProjectionName() {
245        return false;
246    }
247}