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, 7 years 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.