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.Component; 007import java.awt.GridBagLayout; 008import java.awt.event.ActionListener; 009import java.util.ArrayList; 010import java.util.Collection; 011import java.util.Collections; 012import java.util.Comparator; 013import java.util.HashMap; 014import java.util.List; 015import java.util.Map; 016import java.util.stream.IntStream; 017 018import javax.swing.BorderFactory; 019import javax.swing.JButton; 020import javax.swing.JLabel; 021import javax.swing.JOptionPane; 022import javax.swing.JPanel; 023import javax.swing.JScrollPane; 024import javax.swing.JSeparator; 025 026import org.openstreetmap.josm.actions.ExpertToggleAction; 027import org.openstreetmap.josm.data.Bounds; 028import org.openstreetmap.josm.data.SystemOfMeasurement; 029import org.openstreetmap.josm.data.coor.conversion.CoordinateFormatManager; 030import org.openstreetmap.josm.data.coor.conversion.ICoordinateFormat; 031import org.openstreetmap.josm.data.preferences.ListProperty; 032import org.openstreetmap.josm.data.preferences.StringProperty; 033import org.openstreetmap.josm.data.projection.CustomProjection; 034import org.openstreetmap.josm.data.projection.Projection; 035import org.openstreetmap.josm.data.projection.ProjectionRegistry; 036import org.openstreetmap.josm.data.projection.Projections; 037import org.openstreetmap.josm.gui.ExtendedDialog; 038import org.openstreetmap.josm.gui.MainApplication; 039import org.openstreetmap.josm.gui.help.HelpUtil; 040import org.openstreetmap.josm.gui.preferences.DefaultTabPreferenceSetting; 041import org.openstreetmap.josm.gui.preferences.PreferenceSetting; 042import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory; 043import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane; 044import org.openstreetmap.josm.gui.widgets.JosmComboBox; 045import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel; 046import org.openstreetmap.josm.spi.preferences.Config; 047import org.openstreetmap.josm.tools.GBC; 048import org.openstreetmap.josm.tools.JosmRuntimeException; 049import org.openstreetmap.josm.tools.Logging; 050 051/** 052 * Projection preferences. 053 * 054 * How to add new Projections: 055 * - Find EPSG code for the projection. 056 * - Look up the parameter string for Proj4, e.g. on http://spatialreference.org/ 057 * and add it to the file 'data/projection/epsg' in JOSM trunk 058 * - Search for official references and verify the parameter values. These 059 * documents are often available in the local language only. 060 * - Use {@link #registerProjectionChoice}, to make the entry known to JOSM. 061 * 062 * In case there is no EPSG code: 063 * - override {@link AbstractProjectionChoice#getProjection()} and provide 064 * a manual implementation of the projection. Use {@link CustomProjection} 065 * if possible. 066 */ 067public class ProjectionPreference extends DefaultTabPreferenceSetting { 068 069 /** 070 * Factory used to create a new {@code ProjectionPreference}. 071 */ 072 public static class Factory implements PreferenceSettingFactory { 073 @Override 074 public PreferenceSetting createPreferenceSetting() { 075 return new ProjectionPreference(); 076 } 077 } 078 079 private static final List<ProjectionChoice> projectionChoices = new ArrayList<>(); 080 private static final Map<String, ProjectionChoice> projectionChoicesById = new HashMap<>(); 081 082 /** 083 * WGS84: Directly use latitude / longitude values as x/y. 084 */ 085 public static final ProjectionChoice wgs84 = registerProjectionChoice(tr("WGS84 Geographic"), "core:wgs84", 4326); 086 087 /** 088 * Mercator Projection. 089 * 090 * The center of the mercator projection is always the 0 grad coordinate. 091 * 092 * See also USGS Bulletin 1532 (http://pubs.usgs.gov/bul/1532/report.pdf) 093 * initially EPSG used 3785 but that has been superseded by 3857, see https://www.epsg-registry.org/ 094 */ 095 public static final ProjectionChoice mercator = registerProjectionChoice(tr("Mercator"), "core:mercator", 3857); 096 097 /** 098 * Lambert conic conform 4 zones using the French geodetic system NTF. 099 * 100 * This newer version uses the grid translation NTF<->RGF93 provided by IGN for a submillimetric accuracy. 101 * (RGF93 is the French geodetic system similar to WGS84 but not mathematically equal) 102 * 103 * Source: http://geodesie.ign.fr/contenu/fichiers/Changement_systeme_geodesique.pdf 104 */ 105 public static final ProjectionChoice lambert = new LambertProjectionChoice(); 106 107 /** 108 * French departements in the Caribbean Sea and Indian Ocean. 109 * 110 * Using the UTM transvers Mercator projection and specific geodesic settings. 111 */ 112 public static final ProjectionChoice utm_france_dom = new UTMFranceDOMProjectionChoice(); 113 114 /** 115 * Lambert Conic Conform 9 Zones projection. 116 * 117 * As specified by the IGN in this document 118 * http://geodesie.ign.fr/contenu/fichiers/documentation/rgf93/cc9zones.pdf 119 */ 120 public static final ProjectionChoice lambert_cc9 = new LambertCC9ZonesProjectionChoice(); 121 122 static { 123 124 /************************ 125 * Global projections. 126 */ 127 128 /** 129 * UTM. 130 */ 131 registerProjectionChoice(new UTMProjectionChoice()); 132 133 /************************ 134 * Regional - alphabetical order by country code. 135 */ 136 137 /** 138 * Belgian Lambert 72 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 1972"), "core:belgianLambert1972", 31370); // BE 146 147 /** 148 * Belgian Lambert 2008 projection. 149 * 150 * As specified by the Belgian IGN in this document: 151 * http://www.ngi.be/Common/Lambert2008/Transformation_Geographic_Lambert_FR.pdf 152 * 153 * @author Don-vip 154 */ 155 registerProjectionChoice(tr("Belgian Lambert 2008"), "core:belgianLambert2008", 3812); // BE 156 157 /** 158 * SwissGrid CH1903 / L03, see https://en.wikipedia.org/wiki/Swiss_coordinate_system. 159 * 160 * Actually, what we have here, is CH1903+ (EPSG:2056), but without 161 * the additional false easting of 2000km and false northing 1000 km. 162 * 163 * To get to CH1903, a shift file is required. So currently, there are errors 164 * up to 1.6m (depending on the location). 165 */ 166 registerProjectionChoice(new SwissGridProjectionChoice()); // CH 167 168 registerProjectionChoice(new GaussKruegerProjectionChoice()); // DE 169 170 /** 171 * Estonian Coordinate System of 1997. 172 * 173 * Thanks to Johan Montagnat and its geoconv java converter application 174 * (https://www.i3s.unice.fr/~johan/gps/ , published under GPL license) 175 * from which some code and constants have been reused here. 176 */ 177 registerProjectionChoice(tr("Lambert Zone (Estonia)"), "core:lambertest", 3301); // EE 178 179 /** 180 * Lambert conic conform 4 zones using the French geodetic system NTF. 181 * 182 * This newer version uses the grid translation NTF<->RGF93 provided by IGN for a submillimetric accuracy. 183 * (RGF93 is the French geodetic system similar to WGS84 but not mathematically equal) 184 * 185 * Source: http://geodesie.ign.fr/contenu/fichiers/Changement_systeme_geodesique.pdf 186 * @author Pieren 187 */ 188 registerProjectionChoice(lambert); // FR 189 190 /** 191 * Lambert 93 projection. 192 * 193 * As specified by the IGN in this document 194 * http://geodesie.ign.fr/contenu/fichiers/documentation/rgf93/Lambert-93.pdf 195 * @author Don-vip 196 */ 197 registerProjectionChoice(tr("Lambert 93 (France)"), "core:lambert93", 2154); // FR 198 199 /** 200 * Lambert Conic Conform 9 Zones projection. 201 * 202 * As specified by the IGN in this document 203 * http://geodesie.ign.fr/contenu/fichiers/documentation/rgf93/cc9zones.pdf 204 * @author Pieren 205 */ 206 registerProjectionChoice(lambert_cc9); // FR 207 208 /** 209 * French departements in the Caribbean Sea and Indian Ocean. 210 * 211 * Using the UTM transvers Mercator projection and specific geodesic settings. 212 */ 213 registerProjectionChoice(utm_france_dom); // FR 214 215 /** 216 * LKS-92/ Latvia TM projection. 217 * 218 * Based on data from spatialreference.org. 219 * http://spatialreference.org/ref/epsg/3059/ 220 * 221 * @author Viesturs Zarins 222 */ 223 registerProjectionChoice(tr("LKS-92 (Latvia TM)"), "core:tmerclv", 3059); // LV 224 225 /** 226 * Netherlands RD projection 227 * 228 * @author vholten 229 */ 230 registerProjectionChoice(tr("Rijksdriehoekscoördinaten (Netherlands)"), "core:dutchrd", 28992); // NL 231 232 /** 233 * PUWG 1992 and 2000 are the official cordinate systems in Poland. 234 * 235 * They use the same math as UTM only with different constants. 236 * 237 * @author steelman 238 */ 239 registerProjectionChoice(new PuwgProjectionChoice()); // PL 240 241 /** 242 * SWEREF99 projections. Official coordinate system in Sweden. 243 */ 244 registerProjectionChoice(tr("SWEREF99 TM / EPSG:3006 (Sweden)"), "core:sweref99tm", 3006); // SE 245 registerProjectionChoice(tr("SWEREF99 13 30 / EPSG:3008 (Sweden)"), "core:sweref99", 3008); // SE 246 247 /************************ 248 * Projection by Code. 249 */ 250 registerProjectionChoice(new CodeProjectionChoice()); 251 252 /************************ 253 * Custom projection. 254 */ 255 registerProjectionChoice(new CustomProjectionChoice()); 256 } 257 258 /** 259 * Registers a new projection choice. 260 * @param c projection choice 261 */ 262 public static void registerProjectionChoice(ProjectionChoice c) { 263 projectionChoices.add(c); 264 projectionChoicesById.put(c.getId(), c); 265 for (String code : c.allCodes()) { 266 Projections.registerProjectionSupplier(code, () -> { 267 Collection<String> pref = c.getPreferencesFromCode(code); 268 c.setPreferences(pref); 269 try { 270 return c.getProjection(); 271 } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) { 272 Logging.log(Logging.LEVEL_WARN, "Unable to get projection "+code+" with "+c+':', e); 273 return null; 274 } 275 }); 276 } 277 } 278 279 /** 280 * Registers a new projection choice. 281 * @param name short name of the projection choice as shown in the GUI 282 * @param id short name of the projection choice as shown in the GUI 283 * @param epsg the unique numeric EPSG identifier for the projection 284 * @return the registered {@link ProjectionChoice} 285 */ 286 private static ProjectionChoice registerProjectionChoice(String name, String id, Integer epsg) { 287 ProjectionChoice pc = new SingleProjectionChoice(name, id, "EPSG:"+epsg); 288 registerProjectionChoice(pc); 289 return pc; 290 } 291 292 /** 293 * Returns the list of projection choices. 294 * @return the list of projection choices 295 */ 296 public static List<ProjectionChoice> getProjectionChoices() { 297 return Collections.unmodifiableList(projectionChoices); 298 } 299 300 private static String projectionChoice; 301 302 private static final StringProperty PROP_PROJECTION_DEFAULT = new StringProperty("projection.default", mercator.getId()); 303 private static final StringProperty PROP_COORDINATES = new StringProperty("coordinates", null); 304 private static final ListProperty PROP_SUB_PROJECTION_DEFAULT = new ListProperty("projection.default.sub", null); 305 306 /** 307 * Combobox with all projections available 308 */ 309 private final JosmComboBox<ProjectionChoice> projectionCombo; 310 311 /** 312 * Combobox with all coordinate display possibilities 313 */ 314 private final JosmComboBox<ICoordinateFormat> coordinatesCombo; 315 316 /** 317 * Combobox with all system of measurements 318 */ 319 private final JosmComboBox<SystemOfMeasurement> unitsCombo = new JosmComboBox<>( 320 SystemOfMeasurement.ALL_SYSTEMS.values().stream() 321 .sorted(Comparator.comparing(SystemOfMeasurement::toString)) 322 .toArray(SystemOfMeasurement[]::new)); 323 324 /** 325 * This variable holds the JPanel with the projection's preferences. If the 326 * selected projection does not implement this, it will be set to an empty 327 * Panel. 328 */ 329 private JPanel projSubPrefPanel; 330 private final JPanel projSubPrefPanelWrapper = new JPanel(new GridBagLayout()); 331 332 private final JLabel projectionCodeLabel = new JLabel(tr("Projection code")); 333 private final Component projectionCodeGlue = GBC.glue(5, 0); 334 private final JLabel projectionCode = new JLabel(); 335 private final JLabel projectionNameLabel = new JLabel(tr("Projection name")); 336 private final Component projectionNameGlue = GBC.glue(5, 0); 337 private final JLabel projectionName = new JLabel(); 338 private final JLabel bounds = new JLabel(); 339 340 /** 341 * This is the panel holding all projection preferences 342 */ 343 private final VerticallyScrollablePanel projPanel = new VerticallyScrollablePanel(new GridBagLayout()); 344 345 /** 346 * The GridBagConstraints for the Panel containing the ProjectionSubPrefs. 347 * This is required twice in the code, creating it here keeps both occurrences 348 * in sync 349 */ 350 private static final GBC projSubPrefPanelGBC = GBC.std().fill(GBC.BOTH).weight(1.0, 1.0); 351 352 /** 353 * Constructs a new {@code ProjectionPreference}. 354 */ 355 public ProjectionPreference() { 356 super(/* ICON(preferences/) */ "map", tr("Map Projection"), tr("Map Projection")); 357 this.projectionCombo = new JosmComboBox<>( 358 projectionChoices.toArray(new ProjectionChoice[0])); 359 this.coordinatesCombo = new JosmComboBox<>( 360 CoordinateFormatManager.getCoordinateFormats().toArray(new ICoordinateFormat[0])); 361 } 362 363 @Override 364 public void addGui(PreferenceTabbedPane gui) { 365 final ProjectionChoice pc = setupProjectionCombo(); 366 367 IntStream.range(0, coordinatesCombo.getItemCount()) 368 .filter(i -> coordinatesCombo.getItemAt(i).getId().equals(PROP_COORDINATES.get())).findFirst() 369 .ifPresent(coordinatesCombo::setSelectedIndex); 370 371 unitsCombo.setSelectedItem(SystemOfMeasurement.getSystemOfMeasurement()); 372 373 projPanel.add(new JLabel(tr("Projection method")), GBC.std().insets(5, 5, 0, 5)); 374 projPanel.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL)); 375 projPanel.add(projectionCombo, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5)); 376 projPanel.add(projectionCodeLabel, GBC.std().insets(25, 5, 0, 5)); 377 projPanel.add(projectionCodeGlue, GBC.std().fill(GBC.HORIZONTAL)); 378 projPanel.add(projectionCode, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5)); 379 projPanel.add(projectionNameLabel, GBC.std().insets(25, 5, 0, 5)); 380 projPanel.add(projectionNameGlue, GBC.std().fill(GBC.HORIZONTAL)); 381 projPanel.add(projectionName, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5)); 382 projPanel.add(new JLabel(tr("Bounds")), GBC.std().insets(25, 5, 0, 5)); 383 projPanel.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL)); 384 projPanel.add(bounds, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5)); 385 projPanel.add(projSubPrefPanelWrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(20, 5, 5, 5)); 386 387 projectionCodeLabel.setLabelFor(projectionCode); 388 projectionNameLabel.setLabelFor(projectionName); 389 390 JButton btnSetAsDefault = new JButton(tr("Set as default")); 391 projPanel.add(btnSetAsDefault, GBC.eol().insets(5, 10, 5, 5)); 392 btnSetAsDefault.addActionListener(e -> { 393 ProjectionChoice pc2 = (ProjectionChoice) projectionCombo.getSelectedItem(); 394 String id = pc2.getId(); 395 Collection<String> prefs = pc2.getPreferences(projSubPrefPanel); 396 setProjection(id, prefs, true); 397 pc2.setPreferences(prefs); 398 Projection proj = pc2.getProjection(); 399 new ExtendedDialog(gui, tr("Default projection"), tr("OK")) 400 .setButtonIcons("ok") 401 .setIcon(JOptionPane.INFORMATION_MESSAGE) 402 .setContent(tr("Default projection has been set to ''{0}''", proj.toCode())) 403 .showDialog(); 404 }); 405 ExpertToggleAction.addVisibilitySwitcher(btnSetAsDefault); 406 407 projPanel.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0, 5, 0, 10)); 408 projPanel.add(new JLabel(tr("Display coordinates as")), GBC.std().insets(5, 5, 0, 5)); 409 projPanel.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL)); 410 projPanel.add(coordinatesCombo, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5)); 411 projPanel.add(new JLabel(tr("System of measurement")), GBC.std().insets(5, 5, 0, 5)); 412 projPanel.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL)); 413 projPanel.add(unitsCombo, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5)); 414 projPanel.add(GBC.glue(1, 1), GBC.std().fill(GBC.HORIZONTAL).weight(1.0, 1.0)); 415 416 JScrollPane scrollPane = projPanel.getVerticalScrollPane(); 417 scrollPane.setBorder(BorderFactory.createEmptyBorder()); 418 gui.createPreferenceTab(this).add(scrollPane, GBC.std().fill()); 419 420 selectedProjectionChanged(pc); 421 } 422 423 private void updateMeta(ProjectionChoice pc) { 424 pc.setPreferences(pc.getPreferences(projSubPrefPanel)); 425 Projection proj = pc.getProjection(); 426 projectionCode.setText(proj.toCode()); 427 projectionName.setText(proj.toString()); 428 Bounds b = proj.getWorldBoundsLatLon(); 429 ICoordinateFormat cf = CoordinateFormatManager.getDefaultFormat(); 430 bounds.setText(cf.lonToString(b.getMin()) + ", " + cf.latToString(b.getMin()) + " : " + 431 cf.lonToString(b.getMax()) + ", " + cf.latToString(b.getMax())); 432 boolean showCode = true; 433 boolean showName = false; 434 if (pc instanceof SubPrefsOptions) { 435 showCode = ((SubPrefsOptions) pc).showProjectionCode(); 436 showName = ((SubPrefsOptions) pc).showProjectionName(); 437 } 438 projectionCodeLabel.setVisible(showCode); 439 projectionCodeGlue.setVisible(showCode); 440 projectionCode.setVisible(showCode); 441 projectionNameLabel.setVisible(showName); 442 projectionNameGlue.setVisible(showName); 443 projectionName.setVisible(showName); 444 } 445 446 @Override 447 public boolean ok() { 448 ProjectionChoice pc = (ProjectionChoice) projectionCombo.getSelectedItem(); 449 450 String id = pc.getId(); 451 Collection<String> prefs = pc.getPreferences(projSubPrefPanel); 452 453 setProjection(id, prefs, false); 454 455 ICoordinateFormat selectedItem = (ICoordinateFormat) coordinatesCombo.getSelectedItem(); 456 if (selectedItem != null && PROP_COORDINATES.put(selectedItem.getId())) { 457 CoordinateFormatManager.setCoordinateFormat(selectedItem); 458 } 459 460 SystemOfMeasurement.setSystemOfMeasurement(((SystemOfMeasurement) unitsCombo.getSelectedItem())); 461 462 return false; 463 } 464 465 /** 466 * Set default projection. 467 */ 468 public static void setProjection() { 469 setProjection(PROP_PROJECTION_DEFAULT.get(), PROP_SUB_PROJECTION_DEFAULT.get(), false); 470 } 471 472 /** 473 * Set projection. 474 * @param id id of the selected projection choice 475 * @param pref the configuration for the selected projection choice 476 * @param makeDefault true, if it is to be set as permanent default 477 * false, if it is to be set for the current session 478 * @since 12306 479 */ 480 public static void setProjection(String id, Collection<String> pref, boolean makeDefault) { 481 ProjectionChoice pc = projectionChoicesById.get(id); 482 483 if (pc == null) { 484 JOptionPane.showMessageDialog( 485 MainApplication.getMainFrame(), 486 tr("The projection {0} could not be activated. Using Mercator", id), 487 tr("Error"), 488 JOptionPane.ERROR_MESSAGE 489 ); 490 pref = null; 491 pc = mercator; 492 } 493 id = pc.getId(); 494 Config.getPref().putList("projection.sub."+id, pref == null ? null : new ArrayList<>(pref)); 495 if (makeDefault) { 496 PROP_PROJECTION_DEFAULT.put(id); 497 PROP_SUB_PROJECTION_DEFAULT.put(pref == null ? null : new ArrayList<>(pref)); 498 } else { 499 projectionChoice = id; 500 } 501 pc.setPreferences(pref); 502 Projection proj = pc.getProjection(); 503 ProjectionRegistry.setProjection(proj); 504 } 505 506 /** 507 * Handles all the work related to update the projection-specific 508 * preferences 509 * @param pc the choice class representing user selection 510 */ 511 private void selectedProjectionChanged(final ProjectionChoice pc) { 512 // Don't try to update if we're still starting up 513 int size = projPanel.getComponentCount(); 514 if (size < 1) 515 return; 516 517 final ActionListener listener = e -> updateMeta(pc); 518 519 // Replace old panel with new one 520 projSubPrefPanelWrapper.removeAll(); 521 projSubPrefPanel = pc.getPreferencePanel(listener); 522 projSubPrefPanelWrapper.add(projSubPrefPanel, projSubPrefPanelGBC); 523 projPanel.revalidate(); 524 projSubPrefPanel.repaint(); 525 updateMeta(pc); 526 } 527 528 /** 529 * Sets up projection combobox with default values and action listener 530 * @return the choice class for user selection 531 */ 532 private ProjectionChoice setupProjectionCombo() { 533 String pcId = getCurrentProjectionChoiceId(); 534 ProjectionChoice pc = null; 535 for (int i = 0; i < projectionCombo.getItemCount(); ++i) { 536 ProjectionChoice pc1 = projectionCombo.getItemAt(i); 537 pc1.setPreferences(getSubprojectionPreference(pc1.getId())); 538 if (pc1.getId().equals(pcId)) { 539 projectionCombo.setSelectedIndex(i); 540 selectedProjectionChanged(pc1); 541 pc = pc1; 542 } 543 } 544 // If the ProjectionChoice from the preferences is not available, it 545 // should have been set to Mercator at JOSM start. 546 if (pc == null) 547 throw new JosmRuntimeException("Couldn't find the current projection in the list of available projections!"); 548 549 projectionCombo.addActionListener(e -> { 550 ProjectionChoice pc1 = (ProjectionChoice) projectionCombo.getSelectedItem(); 551 selectedProjectionChanged(pc1); 552 }); 553 return pc; 554 } 555 556 /** 557 * Get the id of the projection choice that is currently set. 558 * @return id of the projection choice that is currently set 559 */ 560 public static String getCurrentProjectionChoiceId() { 561 return projectionChoice != null ? projectionChoice : PROP_PROJECTION_DEFAULT.get(); 562 } 563 564 /** 565 * Get the preferences that have been selected the last time for the given 566 * projection choice. 567 * @param pcId id of the projection choice 568 * @return projection choice parameters that have been selected by the user 569 * the last time; null if user has never selected the given projection choice 570 */ 571 public static Collection<String> getSubprojectionPreference(String pcId) { 572 return Config.getPref().getList("projection.sub."+pcId, null); 573 } 574 575 @Override 576 public boolean isExpert() { 577 return false; 578 } 579 580 /** 581 * Selects the given projection. 582 * @param projection The projection to select. 583 * @since 5604 584 */ 585 public void selectProjection(ProjectionChoice projection) { 586 if (projectionCombo != null && projection != null) { 587 projectionCombo.setSelectedItem(projection); 588 } 589 } 590 591 @Override 592 public String getHelpContext() { 593 return HelpUtil.ht("/Preferences/Map"); 594 } 595}