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

Last change on this file since 11374 was 11374, checked in by Don-vip, 2 years ago

sonar - squid:S00112 - Generic exceptions should never be thrown: define JosmRuntimeException

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