source: josm/trunk/src/org/openstreetmap/josm/gui/preferences/projection/ProjectionPreference.java @ 12306

Last change on this file since 12306 was 12306, checked in by bastiK, 21 months ago

fixed #14877 - make projection setting transient

  • Property svn:eol-style set to native
File size: 22.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.preferences.projection;
3
4import static org.openstreetmap.josm.data.SystemOfMeasurement.ALL_SYSTEMS;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.Component;
8import java.awt.GridBagLayout;
9import java.awt.event.ActionListener;
10import java.util.ArrayList;
11import java.util.Collection;
12import java.util.Collections;
13import java.util.HashMap;
14import java.util.List;
15import java.util.Map;
16
17import javax.swing.BorderFactory;
18import javax.swing.JButton;
19import javax.swing.JLabel;
20import javax.swing.JOptionPane;
21import javax.swing.JPanel;
22import javax.swing.JSeparator;
23
24import org.openstreetmap.josm.Main;
25import org.openstreetmap.josm.actions.ExpertToggleAction;
26import org.openstreetmap.josm.data.Bounds;
27import org.openstreetmap.josm.data.SystemOfMeasurement;
28import org.openstreetmap.josm.data.coor.CoordinateFormat;
29import org.openstreetmap.josm.data.preferences.CollectionProperty;
30import org.openstreetmap.josm.data.preferences.StringProperty;
31import org.openstreetmap.josm.data.projection.CustomProjection;
32import org.openstreetmap.josm.data.projection.Projection;
33import org.openstreetmap.josm.gui.ExtendedDialog;
34import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
35import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
36import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
37import org.openstreetmap.josm.gui.preferences.SubPreferenceSetting;
38import org.openstreetmap.josm.gui.preferences.TabPreferenceSetting;
39import org.openstreetmap.josm.gui.widgets.JosmComboBox;
40import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel;
41import org.openstreetmap.josm.tools.GBC;
42import org.openstreetmap.josm.tools.JosmRuntimeException;
43
44/**
45 * Projection preferences.
46 *
47 * How to add new Projections:
48 *  - Find EPSG code for the projection.
49 *  - Look up the parameter string for Proj4, e.g. on http://spatialreference.org/
50 *      and add it to the file 'data/projection/epsg' in JOSM trunk
51 *  - Search for official references and verify the parameter values. These
52 *      documents are often available in the local language only.
53 *  - Use {@link #registerProjectionChoice}, to make the entry known to JOSM.
54 *
55 * In case there is no EPSG code:
56 *  - override {@link AbstractProjectionChoice#getProjection()} and provide
57 *    a manual implementation of the projection. Use {@link CustomProjection}
58 *    if possible.
59 */
60public class ProjectionPreference implements SubPreferenceSetting {
61
62    /**
63     * Factory used to create a new {@code ProjectionPreference}.
64     */
65    public static class Factory implements PreferenceSettingFactory {
66        @Override
67        public PreferenceSetting createPreferenceSetting() {
68            return new ProjectionPreference();
69        }
70    }
71
72    private static final List<ProjectionChoice> projectionChoices = new ArrayList<>();
73    private static final Map<String, ProjectionChoice> projectionChoicesById = new HashMap<>();
74
75    /**
76     * WGS84: Directly use latitude / longitude values as x/y.
77     */
78    public static final ProjectionChoice wgs84 = registerProjectionChoice(tr("WGS84 Geographic"), "core:wgs84", 4326, "epsg4326");
79
80    /**
81     * Mercator Projection.
82     *
83     * The center of the mercator projection is always the 0 grad coordinate.
84     *
85     * See also USGS Bulletin 1532 (http://pubs.usgs.gov/bul/1532/report.pdf)
86     * initially EPSG used 3785 but that has been superseded by 3857, see https://www.epsg-registry.org/
87     */
88    public static final ProjectionChoice mercator = registerProjectionChoice(tr("Mercator"), "core:mercator", 3857);
89
90    /**
91     * Lambert conic conform 4 zones using the French geodetic system NTF.
92     *
93     * This newer version uses the grid translation NTF&lt;-&gt;RGF93 provided by IGN for a submillimetric accuracy.
94     * (RGF93 is the French geodetic system similar to WGS84 but not mathematically equal)
95     *
96     * Source: http://geodesie.ign.fr/contenu/fichiers/Changement_systeme_geodesique.pdf
97     */
98    public static final ProjectionChoice lambert = new LambertProjectionChoice();
99
100    /**
101     * French departements in the Caribbean Sea and Indian Ocean.
102     *
103     * Using the UTM transvers Mercator projection and specific geodesic settings.
104     */
105    public static final ProjectionChoice utm_france_dom = new UTMFranceDOMProjectionChoice();
106
107    /**
108     * Lambert Conic Conform 9 Zones projection.
109     *
110     * As specified by the IGN in this document
111     * http://geodesie.ign.fr/contenu/fichiers/documentation/rgf93/cc9zones.pdf
112     */
113    public static final ProjectionChoice lambert_cc9 = new LambertCC9ZonesProjectionChoice();
114
115    static {
116
117        /************************
118         * Global projections.
119         */
120
121        /**
122         * UTM.
123         */
124        registerProjectionChoice(new UTMProjectionChoice());
125
126        /************************
127         * Regional - alphabetical order by country code.
128         */
129
130        /**
131         * Belgian Lambert 72 projection.
132         *
133         * As specified by the Belgian IGN in this document:
134         * http://www.ngi.be/Common/Lambert2008/Transformation_Geographic_Lambert_FR.pdf
135         *
136         * @author Don-vip
137         */
138        registerProjectionChoice(tr("Belgian Lambert 1972"), "core:belgianLambert1972", 31370);     // BE
139
140        /**
141         * Belgian Lambert 2008 projection.
142         *
143         * As specified by the Belgian IGN in this document:
144         * http://www.ngi.be/Common/Lambert2008/Transformation_Geographic_Lambert_FR.pdf
145         *
146         * @author Don-vip
147         */
148        registerProjectionChoice(tr("Belgian Lambert 2008"), "core:belgianLambert2008", 3812);      // BE
149
150        /**
151         * SwissGrid CH1903 / L03, see https://en.wikipedia.org/wiki/Swiss_coordinate_system.
152         *
153         * Actually, what we have here, is CH1903+ (EPSG:2056), but without
154         * the additional false easting of 2000km and false northing 1000 km.
155         *
156         * To get to CH1903, a shift file is required. So currently, there are errors
157         * up to 1.6m (depending on the location).
158         */
159        registerProjectionChoice(new SwissGridProjectionChoice());                                  // CH
160
161        registerProjectionChoice(new GaussKruegerProjectionChoice());                               // DE
162
163        /**
164         * Estonian Coordinate System of 1997.
165         *
166         * Thanks to Johan Montagnat and its geoconv java converter application
167         * (https://www.i3s.unice.fr/~johan/gps/ , published under GPL license)
168         * from which some code and constants have been reused here.
169         */
170        registerProjectionChoice(tr("Lambert Zone (Estonia)"), "core:lambertest", 3301);            // EE
171
172        /**
173         * Lambert conic conform 4 zones using the French geodetic system NTF.
174         *
175         * This newer version uses the grid translation NTF<->RGF93 provided by IGN for a submillimetric accuracy.
176         * (RGF93 is the French geodetic system similar to WGS84 but not mathematically equal)
177         *
178         * Source: http://geodesie.ign.fr/contenu/fichiers/Changement_systeme_geodesique.pdf
179         * @author Pieren
180         */
181        registerProjectionChoice(lambert);                                                          // FR
182
183        /**
184         * Lambert 93 projection.
185         *
186         * As specified by the IGN in this document
187         * http://geodesie.ign.fr/contenu/fichiers/documentation/rgf93/Lambert-93.pdf
188         * @author Don-vip
189         */
190        registerProjectionChoice(tr("Lambert 93 (France)"), "core:lambert93", 2154);                // FR
191
192        /**
193         * Lambert Conic Conform 9 Zones projection.
194         *
195         * As specified by the IGN in this document
196         * http://geodesie.ign.fr/contenu/fichiers/documentation/rgf93/cc9zones.pdf
197         * @author Pieren
198         */
199        registerProjectionChoice(lambert_cc9);                                                      // FR
200
201        /**
202         * French departements in the Caribbean Sea and Indian Ocean.
203         *
204         * Using the UTM transvers Mercator projection and specific geodesic settings.
205         */
206        registerProjectionChoice(utm_france_dom);                                                   // FR
207
208        /**
209         * LKS-92/ Latvia TM projection.
210         *
211         * Based on data from spatialreference.org.
212         * http://spatialreference.org/ref/epsg/3059/
213         *
214         * @author Viesturs Zarins
215         */
216        registerProjectionChoice(tr("LKS-92 (Latvia TM)"), "core:tmerclv", 3059);                   // LV
217
218        /**
219         * Netherlands RD projection
220         *
221         * @author vholten
222         */
223        registerProjectionChoice(tr("Rijksdriehoekscoördinaten (Netherlands)"), "core:dutchrd", 28992); // NL
224
225        /**
226         * PUWG 1992 and 2000 are the official cordinate systems in Poland.
227         *
228         * They use the same math as UTM only with different constants.
229         *
230         * @author steelman
231         */
232        registerProjectionChoice(new PuwgProjectionChoice());                                       // PL
233
234        /**
235         * SWEREF99 13 30 projection. Based on data from spatialreference.org.
236         * http://spatialreference.org/ref/epsg/3008/
237         *
238         * @author Hanno Hecker
239         */
240        registerProjectionChoice(tr("SWEREF99 13 30 / EPSG:3008 (Sweden)"), "core:sweref99", 3008); // SE
241
242        /************************
243         * Projection by Code.
244         */
245        registerProjectionChoice(new CodeProjectionChoice());
246
247        /************************
248         * Custom projection.
249         */
250        registerProjectionChoice(new CustomProjectionChoice());
251    }
252
253    public static void registerProjectionChoice(ProjectionChoice c) {
254        projectionChoices.add(c);
255        projectionChoicesById.put(c.getId(), c);
256    }
257
258    public static ProjectionChoice registerProjectionChoice(String name, String id, Integer epsg, String cacheDir) {
259        ProjectionChoice pc = new SingleProjectionChoice(name, id, "EPSG:"+epsg, cacheDir);
260        registerProjectionChoice(pc);
261        return pc;
262    }
263
264    private static ProjectionChoice registerProjectionChoice(String name, String id, Integer epsg) {
265        ProjectionChoice pc = new SingleProjectionChoice(name, id, "EPSG:"+epsg);
266        registerProjectionChoice(pc);
267        return pc;
268    }
269
270    public static List<ProjectionChoice> getProjectionChoices() {
271        return Collections.unmodifiableList(projectionChoices);
272    }
273
274    private static String projectionChoice;
275    private static final Map<String, Collection<String>> projectionChoicesSub = new HashMap<>();
276   
277    private static final StringProperty PROP_PROJECTION_DEFAULT = new StringProperty("projection.default", mercator.getId());
278    private static final StringProperty PROP_COORDINATES = new StringProperty("coordinates", null);
279    private static final CollectionProperty PROP_SUB_PROJECTION_DEFAULT = new CollectionProperty("projection.default.sub", null);
280    public static final StringProperty PROP_SYSTEM_OF_MEASUREMENT = new StringProperty("system_of_measurement", "Metric");
281    private static final String[] unitsValues = ALL_SYSTEMS.keySet().toArray(new String[ALL_SYSTEMS.size()]);
282    private static final String[] unitsValuesTr = new String[unitsValues.length];
283    static {
284        for (int i = 0; i < unitsValues.length; ++i) {
285            unitsValuesTr[i] = tr(unitsValues[i]);
286        }
287    }
288
289    /**
290     * Combobox with all projections available
291     */
292    private final JosmComboBox<ProjectionChoice> projectionCombo = new JosmComboBox<>(
293            projectionChoices.toArray(new ProjectionChoice[projectionChoices.size()]));
294
295    /**
296     * Combobox with all coordinate display possibilities
297     */
298    private final JosmComboBox<CoordinateFormat> coordinatesCombo = new JosmComboBox<>(CoordinateFormat.values());
299
300    private final JosmComboBox<String> unitsCombo = new JosmComboBox<>(unitsValuesTr);
301
302    /**
303     * This variable holds the JPanel with the projection's preferences. If the
304     * selected projection does not implement this, it will be set to an empty
305     * Panel.
306     */
307    private JPanel projSubPrefPanel;
308    private final JPanel projSubPrefPanelWrapper = new JPanel(new GridBagLayout());
309
310    private final JLabel projectionCodeLabel = new JLabel(tr("Projection code"));
311    private final Component projectionCodeGlue = GBC.glue(5, 0);
312    private final JLabel projectionCode = new JLabel();
313    private final JLabel projectionNameLabel = new JLabel(tr("Projection name"));
314    private final Component projectionNameGlue = GBC.glue(5, 0);
315    private final JLabel projectionName = new JLabel();
316    private final JLabel bounds = new JLabel();
317
318    /**
319     * This is the panel holding all projection preferences
320     */
321    private final VerticallyScrollablePanel projPanel = new VerticallyScrollablePanel(new GridBagLayout());
322
323    /**
324     * The GridBagConstraints for the Panel containing the ProjectionSubPrefs.
325     * This is required twice in the code, creating it here keeps both occurrences
326     * in sync
327     */
328    private static final GBC projSubPrefPanelGBC = GBC.std().fill(GBC.BOTH).weight(1.0, 1.0);
329
330    @Override
331    public void addGui(PreferenceTabbedPane gui) {
332        final ProjectionChoice pc = setupProjectionCombo();
333
334        for (int i = 0; i < coordinatesCombo.getItemCount(); ++i) {
335            if (coordinatesCombo.getItemAt(i).name().equals(PROP_COORDINATES.get())) {
336                coordinatesCombo.setSelectedIndex(i);
337                break;
338            }
339        }
340
341        for (int i = 0; i < unitsValues.length; ++i) {
342            if (unitsValues[i].equals(PROP_SYSTEM_OF_MEASUREMENT.get())) {
343                unitsCombo.setSelectedIndex(i);
344                break;
345            }
346        }
347
348        projPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
349        projPanel.add(new JLabel(tr("Projection method")), GBC.std().insets(5, 5, 0, 5));
350        projPanel.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL));
351        projPanel.add(projectionCombo, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5));
352        projPanel.add(projectionCodeLabel, GBC.std().insets(25, 5, 0, 5));
353        projPanel.add(projectionCodeGlue, GBC.std().fill(GBC.HORIZONTAL));
354        projPanel.add(projectionCode, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5));
355        projPanel.add(projectionNameLabel, GBC.std().insets(25, 5, 0, 5));
356        projPanel.add(projectionNameGlue, GBC.std().fill(GBC.HORIZONTAL));
357        projPanel.add(projectionName, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5));
358        projPanel.add(new JLabel(tr("Bounds")), GBC.std().insets(25, 5, 0, 5));
359        projPanel.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL));
360        projPanel.add(bounds, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5));
361        projPanel.add(projSubPrefPanelWrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(20, 5, 5, 5));
362
363        projectionCodeLabel.setLabelFor(projectionCode);
364        projectionNameLabel.setLabelFor(projectionName);
365
366        JButton btnSetAsDefault = new JButton(tr("Set as default"));
367        projPanel.add(btnSetAsDefault, GBC.eol().insets(5, 10, 5, 5));
368        btnSetAsDefault.addActionListener(e -> {
369            ProjectionChoice pc2 = (ProjectionChoice) projectionCombo.getSelectedItem();
370            String id = pc2.getId();
371            Collection<String> prefs = pc2.getPreferences(projSubPrefPanel);
372            setProjection(id, prefs, true);
373            pc2.setPreferences(prefs);
374            Projection proj = pc2.getProjection();
375            new ExtendedDialog(gui, tr("Default projection"), tr("OK"))
376                    .setButtonIcons("ok")
377                    .setIcon(JOptionPane.INFORMATION_MESSAGE)
378                    .setContent(tr("Default projection has been set to ''{0}''", proj.toCode()))
379                    .showDialog();
380        });
381        ExpertToggleAction.addVisibilitySwitcher(btnSetAsDefault);
382
383        projPanel.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0, 5, 0, 10));
384        projPanel.add(new JLabel(tr("Display coordinates as")), GBC.std().insets(5, 5, 0, 5));
385        projPanel.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL));
386        projPanel.add(coordinatesCombo, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5));
387        projPanel.add(new JLabel(tr("System of measurement")), GBC.std().insets(5, 5, 0, 5));
388        projPanel.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL));
389        projPanel.add(unitsCombo, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5));
390        projPanel.add(GBC.glue(1, 1), GBC.std().fill(GBC.HORIZONTAL).weight(1.0, 1.0));
391
392        gui.getMapPreference().addSubTab(this, tr("Map Projection"), projPanel.getVerticalScrollPane());
393
394        selectedProjectionChanged(pc);
395    }
396
397    private void updateMeta(ProjectionChoice pc) {
398        pc.setPreferences(pc.getPreferences(projSubPrefPanel));
399        Projection proj = pc.getProjection();
400        projectionCode.setText(proj.toCode());
401        projectionName.setText(proj.toString());
402        Bounds b = proj.getWorldBoundsLatLon();
403        CoordinateFormat cf = CoordinateFormat.getDefaultFormat();
404        bounds.setText(b.getMin().lonToString(cf) + ", " + b.getMin().latToString(cf) + " : " +
405                b.getMax().lonToString(cf) + ", " + b.getMax().latToString(cf));
406        boolean showCode = true;
407        boolean showName = false;
408        if (pc instanceof SubPrefsOptions) {
409            showCode = ((SubPrefsOptions) pc).showProjectionCode();
410            showName = ((SubPrefsOptions) pc).showProjectionName();
411        }
412        projectionCodeLabel.setVisible(showCode);
413        projectionCodeGlue.setVisible(showCode);
414        projectionCode.setVisible(showCode);
415        projectionNameLabel.setVisible(showName);
416        projectionNameGlue.setVisible(showName);
417        projectionName.setVisible(showName);
418    }
419
420    @Override
421    public boolean ok() {
422        ProjectionChoice pc = (ProjectionChoice) projectionCombo.getSelectedItem();
423
424        String id = pc.getId();
425        Collection<String> prefs = pc.getPreferences(projSubPrefPanel);
426
427        setProjection(id, prefs, false);
428
429        if (PROP_COORDINATES.put(((CoordinateFormat) coordinatesCombo.getSelectedItem()).name())) {
430            CoordinateFormat.setCoordinateFormat((CoordinateFormat) coordinatesCombo.getSelectedItem());
431        }
432
433        int i = unitsCombo.getSelectedIndex();
434        SystemOfMeasurement.setSystemOfMeasurement(unitsValues[i]);
435
436        return false;
437    }
438
439    public static void setProjection() {
440        setProjection(PROP_PROJECTION_DEFAULT.get(), PROP_SUB_PROJECTION_DEFAULT.get(), false);
441    }
442
443    /**
444     * Set projection.
445     * @param id id of the selected projection choice
446     * @param pref the configuration for the selected projection choice
447     * @param makeDefault true, if it is to be set as permanent default
448     * false, if it is to be set for the current session
449     * @since 12306
450     */
451    public static void setProjection(String id, Collection<String> pref, boolean makeDefault) {
452        ProjectionChoice pc = projectionChoicesById.get(id);
453
454        if (pc == null) {
455            JOptionPane.showMessageDialog(
456                    Main.parent,
457                    tr("The projection {0} could not be activated. Using Mercator", id),
458                    tr("Error"),
459                    JOptionPane.ERROR_MESSAGE
460            );
461            pref = null;
462            pc = mercator;
463        }
464        id = pc.getId();
465        if (makeDefault) {
466            PROP_PROJECTION_DEFAULT.put(id);
467            PROP_SUB_PROJECTION_DEFAULT.put(pref);
468            Main.pref.putCollection("projection.default.sub."+id, pref);
469        } else {
470            projectionChoice = id;
471            projectionChoicesSub.put(id, pref);
472        }
473        pc.setPreferences(pref);
474        Projection proj = pc.getProjection();
475        Main.setProjection(proj);
476    }
477
478    /**
479     * Handles all the work related to update the projection-specific
480     * preferences
481     * @param pc the choice class representing user selection
482     */
483    private void selectedProjectionChanged(final ProjectionChoice pc) {
484        // Don't try to update if we're still starting up
485        int size = projPanel.getComponentCount();
486        if (size < 1)
487            return;
488
489        final ActionListener listener = e -> updateMeta(pc);
490
491        // Replace old panel with new one
492        projSubPrefPanelWrapper.removeAll();
493        projSubPrefPanel = pc.getPreferencePanel(listener);
494        projSubPrefPanelWrapper.add(projSubPrefPanel, projSubPrefPanelGBC);
495        projPanel.revalidate();
496        projSubPrefPanel.repaint();
497        updateMeta(pc);
498    }
499
500    /**
501     * Sets up projection combobox with default values and action listener
502     * @return the choice class for user selection
503     */
504    private ProjectionChoice setupProjectionCombo() {
505        String pcId = projectionChoice != null ? projectionChoice : PROP_PROJECTION_DEFAULT.get();
506        ProjectionChoice pc = null;
507        for (int i = 0; i < projectionCombo.getItemCount(); ++i) {
508            ProjectionChoice pc1 = projectionCombo.getItemAt(i);
509            pc1.setPreferences(getSubprojectionPreference(pc1));
510            if (pc1.getId().equals(pcId)) {
511                projectionCombo.setSelectedIndex(i);
512                selectedProjectionChanged(pc1);
513                pc = pc1;
514            }
515        }
516        // If the ProjectionChoice from the preferences is not available, it
517        // should have been set to Mercator at JOSM start.
518        if (pc == null)
519            throw new JosmRuntimeException("Couldn't find the current projection in the list of available projections!");
520
521        projectionCombo.addActionListener(e -> {
522            ProjectionChoice pc1 = (ProjectionChoice) projectionCombo.getSelectedItem();
523            selectedProjectionChanged(pc1);
524        });
525        return pc;
526    }
527
528    private static Collection<String> getSubprojectionPreference(ProjectionChoice pc) {
529        Collection<String> sessionValue = projectionChoicesSub.get(pc.getId());
530        if (sessionValue != null)
531            return sessionValue;
532        return Main.pref.getCollection("projection.default.sub."+pc.getId(), null);
533    }
534
535    @Override
536    public boolean isExpert() {
537        return false;
538    }
539
540    @Override
541    public TabPreferenceSetting getTabPreferenceSetting(final PreferenceTabbedPane gui) {
542        return gui.getMapPreference();
543    }
544
545    /**
546     * Selects the given projection.
547     * @param projection The projection to select.
548     * @since 5604
549     */
550    public void selectProjection(ProjectionChoice projection) {
551        if (projectionCombo != null && projection != null) {
552            projectionCombo.setSelectedItem(projection);
553        }
554    }
555}
Note: See TracBrowser for help on using the repository browser.