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

Last change on this file since 12486 was 12486, checked in by bastiK, 7 years ago

see #15013, see #14877 - remember projection when session is saved to a .jos/.joz file

  • Property svn:eol-style set to native
File size: 22.8 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
276 private static final StringProperty PROP_PROJECTION_DEFAULT = new StringProperty("projection.default", mercator.getId());
277 private static final StringProperty PROP_COORDINATES = new StringProperty("coordinates", null);
278 private static final CollectionProperty PROP_SUB_PROJECTION_DEFAULT = new CollectionProperty("projection.default.sub", null);
279 public static final StringProperty PROP_SYSTEM_OF_MEASUREMENT = new StringProperty("system_of_measurement", "Metric");
280 private static final String[] unitsValues = ALL_SYSTEMS.keySet().toArray(new String[ALL_SYSTEMS.size()]);
281 private static final String[] unitsValuesTr = new String[unitsValues.length];
282 static {
283 for (int i = 0; i < unitsValues.length; ++i) {
284 unitsValuesTr[i] = tr(unitsValues[i]);
285 }
286 }
287
288 /**
289 * Combobox with all projections available
290 */
291 private final JosmComboBox<ProjectionChoice> projectionCombo = new JosmComboBox<>(
292 projectionChoices.toArray(new ProjectionChoice[projectionChoices.size()]));
293
294 /**
295 * Combobox with all coordinate display possibilities
296 */
297 private final JosmComboBox<CoordinateFormat> coordinatesCombo = new JosmComboBox<>(CoordinateFormat.values());
298
299 private final JosmComboBox<String> unitsCombo = new JosmComboBox<>(unitsValuesTr);
300
301 /**
302 * This variable holds the JPanel with the projection's preferences. If the
303 * selected projection does not implement this, it will be set to an empty
304 * Panel.
305 */
306 private JPanel projSubPrefPanel;
307 private final JPanel projSubPrefPanelWrapper = new JPanel(new GridBagLayout());
308
309 private final JLabel projectionCodeLabel = new JLabel(tr("Projection code"));
310 private final Component projectionCodeGlue = GBC.glue(5, 0);
311 private final JLabel projectionCode = new JLabel();
312 private final JLabel projectionNameLabel = new JLabel(tr("Projection name"));
313 private final Component projectionNameGlue = GBC.glue(5, 0);
314 private final JLabel projectionName = new JLabel();
315 private final JLabel bounds = new JLabel();
316
317 /**
318 * This is the panel holding all projection preferences
319 */
320 private final VerticallyScrollablePanel projPanel = new VerticallyScrollablePanel(new GridBagLayout());
321
322 /**
323 * The GridBagConstraints for the Panel containing the ProjectionSubPrefs.
324 * This is required twice in the code, creating it here keeps both occurrences
325 * in sync
326 */
327 private static final GBC projSubPrefPanelGBC = GBC.std().fill(GBC.BOTH).weight(1.0, 1.0);
328
329 @Override
330 public void addGui(PreferenceTabbedPane gui) {
331 final ProjectionChoice pc = setupProjectionCombo();
332
333 for (int i = 0; i < coordinatesCombo.getItemCount(); ++i) {
334 if (coordinatesCombo.getItemAt(i).name().equals(PROP_COORDINATES.get())) {
335 coordinatesCombo.setSelectedIndex(i);
336 break;
337 }
338 }
339
340 for (int i = 0; i < unitsValues.length; ++i) {
341 if (unitsValues[i].equals(PROP_SYSTEM_OF_MEASUREMENT.get())) {
342 unitsCombo.setSelectedIndex(i);
343 break;
344 }
345 }
346
347 projPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
348 projPanel.add(new JLabel(tr("Projection method")), GBC.std().insets(5, 5, 0, 5));
349 projPanel.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL));
350 projPanel.add(projectionCombo, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5));
351 projPanel.add(projectionCodeLabel, GBC.std().insets(25, 5, 0, 5));
352 projPanel.add(projectionCodeGlue, GBC.std().fill(GBC.HORIZONTAL));
353 projPanel.add(projectionCode, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5));
354 projPanel.add(projectionNameLabel, GBC.std().insets(25, 5, 0, 5));
355 projPanel.add(projectionNameGlue, GBC.std().fill(GBC.HORIZONTAL));
356 projPanel.add(projectionName, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5));
357 projPanel.add(new JLabel(tr("Bounds")), GBC.std().insets(25, 5, 0, 5));
358 projPanel.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL));
359 projPanel.add(bounds, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5));
360 projPanel.add(projSubPrefPanelWrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(20, 5, 5, 5));
361
362 projectionCodeLabel.setLabelFor(projectionCode);
363 projectionNameLabel.setLabelFor(projectionName);
364
365 JButton btnSetAsDefault = new JButton(tr("Set as default"));
366 projPanel.add(btnSetAsDefault, GBC.eol().insets(5, 10, 5, 5));
367 btnSetAsDefault.addActionListener(e -> {
368 ProjectionChoice pc2 = (ProjectionChoice) projectionCombo.getSelectedItem();
369 String id = pc2.getId();
370 Collection<String> prefs = pc2.getPreferences(projSubPrefPanel);
371 setProjection(id, prefs, true);
372 pc2.setPreferences(prefs);
373 Projection proj = pc2.getProjection();
374 new ExtendedDialog(gui, tr("Default projection"), tr("OK"))
375 .setButtonIcons("ok")
376 .setIcon(JOptionPane.INFORMATION_MESSAGE)
377 .setContent(tr("Default projection has been set to ''{0}''", proj.toCode()))
378 .showDialog();
379 });
380 ExpertToggleAction.addVisibilitySwitcher(btnSetAsDefault);
381
382 projPanel.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0, 5, 0, 10));
383 projPanel.add(new JLabel(tr("Display coordinates as")), GBC.std().insets(5, 5, 0, 5));
384 projPanel.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL));
385 projPanel.add(coordinatesCombo, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5));
386 projPanel.add(new JLabel(tr("System of measurement")), GBC.std().insets(5, 5, 0, 5));
387 projPanel.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL));
388 projPanel.add(unitsCombo, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5));
389 projPanel.add(GBC.glue(1, 1), GBC.std().fill(GBC.HORIZONTAL).weight(1.0, 1.0));
390
391 gui.getMapPreference().addSubTab(this, tr("Map Projection"), projPanel.getVerticalScrollPane());
392
393 selectedProjectionChanged(pc);
394 }
395
396 private void updateMeta(ProjectionChoice pc) {
397 pc.setPreferences(pc.getPreferences(projSubPrefPanel));
398 Projection proj = pc.getProjection();
399 projectionCode.setText(proj.toCode());
400 projectionName.setText(proj.toString());
401 Bounds b = proj.getWorldBoundsLatLon();
402 CoordinateFormat cf = CoordinateFormat.getDefaultFormat();
403 bounds.setText(b.getMin().lonToString(cf) + ", " + b.getMin().latToString(cf) + " : " +
404 b.getMax().lonToString(cf) + ", " + b.getMax().latToString(cf));
405 boolean showCode = true;
406 boolean showName = false;
407 if (pc instanceof SubPrefsOptions) {
408 showCode = ((SubPrefsOptions) pc).showProjectionCode();
409 showName = ((SubPrefsOptions) pc).showProjectionName();
410 }
411 projectionCodeLabel.setVisible(showCode);
412 projectionCodeGlue.setVisible(showCode);
413 projectionCode.setVisible(showCode);
414 projectionNameLabel.setVisible(showName);
415 projectionNameGlue.setVisible(showName);
416 projectionName.setVisible(showName);
417 }
418
419 @Override
420 public boolean ok() {
421 ProjectionChoice pc = (ProjectionChoice) projectionCombo.getSelectedItem();
422
423 String id = pc.getId();
424 Collection<String> prefs = pc.getPreferences(projSubPrefPanel);
425
426 setProjection(id, prefs, false);
427
428 if (PROP_COORDINATES.put(((CoordinateFormat) coordinatesCombo.getSelectedItem()).name())) {
429 CoordinateFormat.setCoordinateFormat((CoordinateFormat) coordinatesCombo.getSelectedItem());
430 }
431
432 int i = unitsCombo.getSelectedIndex();
433 SystemOfMeasurement.setSystemOfMeasurement(unitsValues[i]);
434
435 return false;
436 }
437
438 public static void setProjection() {
439 setProjection(PROP_PROJECTION_DEFAULT.get(), PROP_SUB_PROJECTION_DEFAULT.get(), false);
440 }
441
442 /**
443 * Set projection.
444 * @param id id of the selected projection choice
445 * @param pref the configuration for the selected projection choice
446 * @param makeDefault true, if it is to be set as permanent default
447 * false, if it is to be set for the current session
448 * @since 12306
449 */
450 public static void setProjection(String id, Collection<String> pref, boolean makeDefault) {
451 ProjectionChoice pc = projectionChoicesById.get(id);
452
453 if (pc == null) {
454 JOptionPane.showMessageDialog(
455 Main.parent,
456 tr("The projection {0} could not be activated. Using Mercator", id),
457 tr("Error"),
458 JOptionPane.ERROR_MESSAGE
459 );
460 pref = null;
461 pc = mercator;
462 }
463 id = pc.getId();
464 Main.pref.putCollection("projection.sub."+id, pref);
465 if (makeDefault) {
466 PROP_PROJECTION_DEFAULT.put(id);
467 PROP_SUB_PROJECTION_DEFAULT.put(pref);
468 } else {
469 projectionChoice = id;
470 }
471 pc.setPreferences(pref);
472 Projection proj = pc.getProjection();
473 Main.setProjection(proj);
474 }
475
476 /**
477 * Handles all the work related to update the projection-specific
478 * preferences
479 * @param pc the choice class representing user selection
480 */
481 private void selectedProjectionChanged(final ProjectionChoice pc) {
482 // Don't try to update if we're still starting up
483 int size = projPanel.getComponentCount();
484 if (size < 1)
485 return;
486
487 final ActionListener listener = e -> updateMeta(pc);
488
489 // Replace old panel with new one
490 projSubPrefPanelWrapper.removeAll();
491 projSubPrefPanel = pc.getPreferencePanel(listener);
492 projSubPrefPanelWrapper.add(projSubPrefPanel, projSubPrefPanelGBC);
493 projPanel.revalidate();
494 projSubPrefPanel.repaint();
495 updateMeta(pc);
496 }
497
498 /**
499 * Sets up projection combobox with default values and action listener
500 * @return the choice class for user selection
501 */
502 private ProjectionChoice setupProjectionCombo() {
503 String pcId = getCurrentProjectionChoiceId();
504 ProjectionChoice pc = null;
505 for (int i = 0; i < projectionCombo.getItemCount(); ++i) {
506 ProjectionChoice pc1 = projectionCombo.getItemAt(i);
507 pc1.setPreferences(getSubprojectionPreference(pc1.getId()));
508 if (pc1.getId().equals(pcId)) {
509 projectionCombo.setSelectedIndex(i);
510 selectedProjectionChanged(pc1);
511 pc = pc1;
512 }
513 }
514 // If the ProjectionChoice from the preferences is not available, it
515 // should have been set to Mercator at JOSM start.
516 if (pc == null)
517 throw new JosmRuntimeException("Couldn't find the current projection in the list of available projections!");
518
519 projectionCombo.addActionListener(e -> {
520 ProjectionChoice pc1 = (ProjectionChoice) projectionCombo.getSelectedItem();
521 selectedProjectionChanged(pc1);
522 });
523 return pc;
524 }
525
526 /**
527 * Get the id of the projection choice that is currently set.
528 * @return id of the projection choice that is currently set
529 */
530 public static String getCurrentProjectionChoiceId() {
531 return projectionChoice != null ? projectionChoice : PROP_PROJECTION_DEFAULT.get();
532 }
533
534 /**
535 * Get the preferences that have been selected the last time for the given
536 * projection choice.
537 * @param pcId id of the projection choice
538 * @return projection choice parameters that have been selected by the user
539 * the last time; null if user has never selected the given projection choice
540 */
541 public static Collection<String> getSubprojectionPreference(String pcId) {
542 return Main.pref.getCollection("projection.sub."+pcId, null);
543 }
544
545 @Override
546 public boolean isExpert() {
547 return false;
548 }
549
550 @Override
551 public TabPreferenceSetting getTabPreferenceSetting(final PreferenceTabbedPane gui) {
552 return gui.getMapPreference();
553 }
554
555 /**
556 * Selects the given projection.
557 * @param projection The projection to select.
558 * @since 5604
559 */
560 public void selectProjection(ProjectionChoice projection) {
561 if (projectionCombo != null && projection != null) {
562 projectionCombo.setSelectedItem(projection);
563 }
564 }
565}
Note: See TracBrowser for help on using the repository browser.