001 // License: GPL. Copyright 2007 by Immanuel Scholz and others 002 package org.openstreetmap.josm.gui.preferences.projection; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 006 import java.awt.Component; 007 import java.awt.GridBagLayout; 008 import java.awt.event.ActionEvent; 009 import java.awt.event.ActionListener; 010 import java.util.ArrayList; 011 import java.util.Collection; 012 import java.util.Collections; 013 import java.util.HashMap; 014 import java.util.List; 015 import java.util.Map; 016 017 import javax.swing.BorderFactory; 018 import javax.swing.JLabel; 019 import javax.swing.JOptionPane; 020 import javax.swing.JPanel; 021 import javax.swing.JScrollPane; 022 import javax.swing.JSeparator; 023 024 import org.openstreetmap.josm.Main; 025 import org.openstreetmap.josm.data.Bounds; 026 import org.openstreetmap.josm.data.coor.CoordinateFormat; 027 import org.openstreetmap.josm.data.preferences.CollectionProperty; 028 import org.openstreetmap.josm.data.preferences.StringProperty; 029 import org.openstreetmap.josm.data.projection.CustomProjection; 030 import org.openstreetmap.josm.data.projection.Projection; 031 import org.openstreetmap.josm.gui.NavigatableComponent; 032 import org.openstreetmap.josm.gui.preferences.PreferenceSetting; 033 import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory; 034 import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane; 035 import org.openstreetmap.josm.gui.preferences.SubPreferenceSetting; 036 import org.openstreetmap.josm.gui.preferences.TabPreferenceSetting; 037 import org.openstreetmap.josm.gui.widgets.JosmComboBox; 038 import org.openstreetmap.josm.tools.GBC; 039 040 /** 041 * Projection preferences. 042 * 043 * How to add new Projections: 044 * - Find EPSG code for the projection. 045 * - Look up the parameter string for Proj4, e.g. on http://spatialreference.org/ 046 * and add it to the file 'data/epsg' in JOSM trunk 047 * - Search for official references and verify the parameter values. These 048 * documents are often available in the local language only. 049 * - Use {@link #registerProjectionChoice()}, to make the entry known to JOSM. 050 * 051 * In case there is no EPSG code: 052 * - override {@link AbstractProjectionChoice#getProjection()} and provide 053 * a manual implementation of the projection. Use {@link CustomProjection} 054 * if possible. 055 */ 056 public class ProjectionPreference implements SubPreferenceSetting { 057 058 public static class Factory implements PreferenceSettingFactory { 059 public PreferenceSetting createPreferenceSetting() { 060 return new ProjectionPreference(); 061 } 062 } 063 064 private static List<ProjectionChoice> projectionChoices = new ArrayList<ProjectionChoice>(); 065 private static Map<String, ProjectionChoice> projectionChoicesById = new HashMap<String, ProjectionChoice>(); 066 067 // some ProjectionChoices that are referenced from other parts of the code 068 public static final ProjectionChoice wgs84, mercator, lambert, utm_france_dom, lambert_cc9; 069 070 static { 071 072 /************************ 073 * Global projections. 074 */ 075 076 /** 077 * WGS84: Directly use latitude / longitude values as x/y. 078 */ 079 wgs84 = registerProjectionChoice(tr("WGS84 Geographic"), "core:wgs84", 4326, "epsg4326"); 080 081 /** 082 * Mercator Projection. 083 * 084 * The center of the mercator projection is always the 0 grad 085 * coordinate. 086 * 087 * See also USGS Bulletin 1532 088 * (http://egsc.usgs.gov/isb/pubs/factsheets/fs08799.html) 089 * initially EPSG used 3785 but that has been superseded by 3857, 090 * see http://www.epsg-registry.org/ 091 */ 092 mercator = registerProjectionChoice(tr("Mercator"), "core:mercator", 093 3857); 094 095 /** 096 * UTM. 097 */ 098 registerProjectionChoice(new UTMProjectionChoice()); 099 100 /************************ 101 * Regional - alphabetical order by country code. 102 */ 103 104 /** 105 * Belgian Lambert 72 projection. 106 * 107 * As specified by the Belgian IGN in this document: 108 * http://www.ngi.be/Common/Lambert2008/Transformation_Geographic_Lambert_FR.pdf 109 * 110 * @author Don-vip 111 */ 112 registerProjectionChoice(tr("Belgian Lambert 1972"), "core:belgianLambert1972", 31370); // BE 113 /** 114 * Belgian Lambert 2008 projection. 115 * 116 * As specified by the Belgian IGN in this document: 117 * http://www.ngi.be/Common/Lambert2008/Transformation_Geographic_Lambert_FR.pdf 118 * 119 * @author Don-vip 120 */ 121 registerProjectionChoice(tr("Belgian Lambert 2008"), "core:belgianLambert2008", 3812); // BE 122 123 /** 124 * SwissGrid CH1903 / L03, see http://de.wikipedia.org/wiki/Swiss_Grid. 125 * 126 * Actually, what we have here, is CH1903+ (EPSG:2056), but without 127 * the additional false easting of 2000km and false northing 1000 km. 128 * 129 * To get to CH1903, a shift file is required. So currently, there are errors 130 * up to 1.6m (depending on the location). 131 */ 132 registerProjectionChoice(new SwissGridProjectionChoice()); // CH 133 134 registerProjectionChoice(new GaussKruegerProjectionChoice()); // DE 135 136 /** 137 * Estonian Coordinate System of 1997. 138 * 139 * Thanks to Johan Montagnat and its geoconv java converter application 140 * (http://www.i3s.unice.fr/~johan/gps/ , published under GPL license) 141 * from which some code and constants have been reused here. 142 */ 143 registerProjectionChoice(tr("Lambert Zone (Estonia)"), "core:lambertest", 3301); // EE 144 145 /** 146 * Lambert conic conform 4 zones using the French geodetic system NTF. 147 * 148 * This newer version uses the grid translation NTF<->RGF93 provided by IGN for a submillimetric accuracy. 149 * (RGF93 is the French geodetic system similar to WGS84 but not mathematically equal) 150 * 151 * Source: http://professionnels.ign.fr/DISPLAY/000/526/700/5267002/transformation.pdf 152 * @author Pieren 153 */ 154 registerProjectionChoice(lambert = new LambertProjectionChoice()); // FR 155 /** 156 * Lambert 93 projection. 157 * 158 * As specified by the IGN in this document 159 * http://professionnels.ign.fr/DISPLAY/000/526/702/5267026/NTG_87.pdf 160 * @author Don-vip 161 */ 162 registerProjectionChoice(tr("Lambert 93 (France)"), "core:lambert93", 2154); // FR 163 /** 164 * Lambert Conic Conform 9 Zones projection. 165 * 166 * As specified by the IGN in this document 167 * http://professionnels.ign.fr/DISPLAY/000/526/700/5267002/transformation.pdf 168 * @author Pieren 169 */ 170 registerProjectionChoice(lambert_cc9 = new LambertCC9ZonesProjectionChoice()); // FR 171 /** 172 * French departements in the Caribbean Sea and Indian Ocean. 173 * 174 * Using the UTM transvers Mercator projection and specific geodesic settings. 175 */ 176 registerProjectionChoice(utm_france_dom = new UTM_France_DOM_ProjectionChoice()); // FR 177 178 /** 179 * LKS-92/ Latvia TM projection. 180 * 181 * Based on data from spatialreference.org. 182 * http://spatialreference.org/ref/epsg/3059/ 183 * 184 * @author Viesturs Zarins 185 */ 186 registerProjectionChoice(tr("LKS-92 (Latvia TM)"), "core:tmerclv", 3059); // LV 187 188 /** 189 * PUWG 1992 and 2000 are the official cordinate systems in Poland. 190 * 191 * They use the same math as UTM only with different constants. 192 * 193 * @author steelman 194 */ 195 registerProjectionChoice(new PuwgProjectionChoice()); // PL 196 197 /** 198 * SWEREF99 13 30 projection. Based on data from spatialreference.org. 199 * http://spatialreference.org/ref/epsg/3008/ 200 * 201 * @author Hanno Hecker 202 */ 203 registerProjectionChoice(tr("SWEREF99 13 30 / EPSG:3008 (Sweden)"), "core:sweref99", 3008); // SE 204 205 /************************ 206 * Custom projection. 207 */ 208 registerProjectionChoice(new CustomProjectionChoice()); 209 } 210 211 public static void registerProjectionChoice(ProjectionChoice c) { 212 projectionChoices.add(c); 213 projectionChoicesById.put(c.getId(), c); 214 } 215 216 public static ProjectionChoice registerProjectionChoice(String name, String id, Integer epsg, String cacheDir) { 217 ProjectionChoice pc = new SingleProjectionChoice(name, id, "EPSG:"+epsg, cacheDir); 218 registerProjectionChoice(pc); 219 return pc; 220 } 221 222 private static ProjectionChoice registerProjectionChoice(String name, String id, Integer epsg) { 223 ProjectionChoice pc = new SingleProjectionChoice(name, id, "EPSG:"+epsg); 224 registerProjectionChoice(pc); 225 return pc; 226 } 227 228 public static List<ProjectionChoice> getProjectionChoices() { 229 return Collections.unmodifiableList(projectionChoices); 230 } 231 232 private static final StringProperty PROP_PROJECTION = new StringProperty("projection", mercator.getId()); 233 private static final StringProperty PROP_COORDINATES = new StringProperty("coordinates", null); 234 private static final CollectionProperty PROP_SUB_PROJECTION = new CollectionProperty("projection.sub", null); 235 public static final StringProperty PROP_SYSTEM_OF_MEASUREMENT = new StringProperty("system_of_measurement", "Metric"); 236 private static final String[] unitsValues = (new ArrayList<String>(NavigatableComponent.SYSTEMS_OF_MEASUREMENT.keySet())).toArray(new String[0]); 237 private static final String[] unitsValuesTr = new String[unitsValues.length]; 238 static { 239 for (int i=0; i<unitsValues.length; ++i) { 240 unitsValuesTr[i] = tr(unitsValues[i]); 241 } 242 } 243 244 /** 245 * Combobox with all projections available 246 */ 247 private JosmComboBox projectionCombo = new JosmComboBox(projectionChoices.toArray()); 248 249 /** 250 * Combobox with all coordinate display possibilities 251 */ 252 private JosmComboBox coordinatesCombo = new JosmComboBox(CoordinateFormat.values()); 253 254 private JosmComboBox unitsCombo = new JosmComboBox(unitsValuesTr); 255 256 /** 257 * This variable holds the JPanel with the projection's preferences. If the 258 * selected projection does not implement this, it will be set to an empty 259 * Panel. 260 */ 261 private JPanel projSubPrefPanel; 262 private JPanel projSubPrefPanelWrapper = new JPanel(new GridBagLayout()); 263 264 private JLabel projectionCodeLabel; 265 private Component projectionCodeGlue; 266 private JLabel projectionCode = new JLabel(); 267 private JLabel bounds = new JLabel(); 268 269 /** 270 * This is the panel holding all projection preferences 271 */ 272 private JPanel projPanel = new JPanel(new GridBagLayout()); 273 274 /** 275 * The GridBagConstraints for the Panel containing the ProjectionSubPrefs. 276 * This is required twice in the code, creating it here keeps both occurrences 277 * in sync 278 */ 279 static private GBC projSubPrefPanelGBC = GBC.std().fill(GBC.BOTH).weight(1.0, 1.0); 280 281 public void addGui(PreferenceTabbedPane gui) { 282 ProjectionChoice pc = setupProjectionCombo(); 283 284 for (int i = 0; i < coordinatesCombo.getItemCount(); ++i) { 285 if (((CoordinateFormat)coordinatesCombo.getItemAt(i)).name().equals(PROP_COORDINATES.get())) { 286 coordinatesCombo.setSelectedIndex(i); 287 break; 288 } 289 } 290 291 for (int i = 0; i < unitsValues.length; ++i) { 292 if (unitsValues[i].equals(PROP_SYSTEM_OF_MEASUREMENT.get())) { 293 unitsCombo.setSelectedIndex(i); 294 break; 295 } 296 } 297 298 projPanel.setBorder(BorderFactory.createEmptyBorder( 0, 0, 0, 0 )); 299 projPanel.setLayout(new GridBagLayout()); 300 projPanel.add(new JLabel(tr("Projection method")), GBC.std().insets(5,5,0,5)); 301 projPanel.add(GBC.glue(5,0), GBC.std().fill(GBC.HORIZONTAL)); 302 projPanel.add(projectionCombo, GBC.eop().fill(GBC.HORIZONTAL).insets(0,5,5,5)); 303 projPanel.add(projectionCodeLabel = new JLabel(tr("Projection code")), GBC.std().insets(25,5,0,5)); 304 projPanel.add(projectionCodeGlue = GBC.glue(5,0), GBC.std().fill(GBC.HORIZONTAL)); 305 projPanel.add(projectionCode, GBC.eop().fill(GBC.HORIZONTAL).insets(0,5,5,5)); 306 projPanel.add(new JLabel(tr("Bounds")), GBC.std().insets(25,5,0,5)); 307 projPanel.add(GBC.glue(5,0), GBC.std().fill(GBC.HORIZONTAL)); 308 projPanel.add(bounds, GBC.eop().fill(GBC.HORIZONTAL).insets(0,5,5,5)); 309 projPanel.add(projSubPrefPanelWrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(20,5,5,5)); 310 311 projPanel.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0,5,0,10)); 312 projPanel.add(new JLabel(tr("Display coordinates as")), GBC.std().insets(5,5,0,5)); 313 projPanel.add(GBC.glue(5,0), GBC.std().fill(GBC.HORIZONTAL)); 314 projPanel.add(coordinatesCombo, GBC.eop().fill(GBC.HORIZONTAL).insets(0,5,5,5)); 315 projPanel.add(new JLabel(tr("System of measurement")), GBC.std().insets(5,5,0,5)); 316 projPanel.add(GBC.glue(5,0), GBC.std().fill(GBC.HORIZONTAL)); 317 projPanel.add(unitsCombo, GBC.eop().fill(GBC.HORIZONTAL).insets(0,5,5,5)); 318 projPanel.add(GBC.glue(1,1), GBC.std().fill(GBC.HORIZONTAL).weight(1.0, 1.0)); 319 320 JScrollPane scrollpane = new JScrollPane(projPanel); 321 gui.getMapPreference().mapcontent.addTab(tr("Map Projection"), scrollpane); 322 323 selectedProjectionChanged(pc); 324 } 325 326 private void updateMeta(ProjectionChoice pc) { 327 pc.setPreferences(pc.getPreferences(projSubPrefPanel)); 328 Projection proj = pc.getProjection(); 329 projectionCode.setText(proj.toCode()); 330 Bounds b = proj.getWorldBoundsLatLon(); 331 CoordinateFormat cf = CoordinateFormat.getDefaultFormat(); 332 bounds.setText(b.getMin().lonToString(cf)+", "+b.getMin().latToString(cf)+" : "+b.getMax().lonToString(cf)+", "+b.getMax().latToString(cf)); 333 boolean showCode = true; 334 if (pc instanceof SubPrefsOptions) { 335 showCode = ((SubPrefsOptions) pc).showProjectionCode(); 336 } 337 projectionCodeLabel.setVisible(showCode); 338 projectionCodeGlue.setVisible(showCode); 339 projectionCode.setVisible(showCode); 340 } 341 342 @Override 343 public boolean ok() { 344 ProjectionChoice pc = (ProjectionChoice) projectionCombo.getSelectedItem(); 345 346 String id = pc.getId(); 347 Collection<String> prefs = pc.getPreferences(projSubPrefPanel); 348 349 setProjection(id, prefs); 350 351 if(PROP_COORDINATES.put(((CoordinateFormat)coordinatesCombo.getSelectedItem()).name())) { 352 CoordinateFormat.setCoordinateFormat((CoordinateFormat)coordinatesCombo.getSelectedItem()); 353 } 354 355 int i = unitsCombo.getSelectedIndex(); 356 PROP_SYSTEM_OF_MEASUREMENT.put(unitsValues[i]); 357 358 return false; 359 } 360 361 static public void setProjection() { 362 setProjection(PROP_PROJECTION.get(), PROP_SUB_PROJECTION.get()); 363 } 364 365 static public void setProjection(String id, Collection<String> pref) { 366 ProjectionChoice pc = projectionChoicesById.get(id); 367 368 if (pc == null) { 369 JOptionPane.showMessageDialog( 370 Main.parent, 371 tr("The projection {0} could not be activated. Using Mercator", id), 372 tr("Error"), 373 JOptionPane.ERROR_MESSAGE 374 ); 375 pref = null; 376 pc = mercator; 377 } 378 id = pc.getId(); 379 PROP_PROJECTION.put(id); 380 PROP_SUB_PROJECTION.put(pref); 381 Main.pref.putCollection("projection.sub."+id, pref); 382 pc.setPreferences(pref); 383 Projection proj = pc.getProjection(); 384 Main.setProjection(proj); 385 } 386 387 /** 388 * Handles all the work related to update the projection-specific 389 * preferences 390 * @param proj 391 */ 392 private void selectedProjectionChanged(final ProjectionChoice pc) { 393 // Don't try to update if we're still starting up 394 int size = projPanel.getComponentCount(); 395 if(size < 1) 396 return; 397 398 final ActionListener listener = new ActionListener() { 399 @Override 400 public void actionPerformed(ActionEvent e) { 401 updateMeta(pc); 402 } 403 }; 404 405 // Replace old panel with new one 406 projSubPrefPanelWrapper.removeAll(); 407 projSubPrefPanel = pc.getPreferencePanel(listener); 408 projSubPrefPanelWrapper.add(projSubPrefPanel, projSubPrefPanelGBC); 409 projPanel.revalidate(); 410 projSubPrefPanel.repaint(); 411 updateMeta(pc); 412 } 413 414 /** 415 * Sets up projection combobox with default values and action listener 416 */ 417 private ProjectionChoice setupProjectionCombo() { 418 ProjectionChoice pc = null; 419 for (int i = 0; i < projectionCombo.getItemCount(); ++i) { 420 ProjectionChoice pc1 = (ProjectionChoice) projectionCombo.getItemAt(i); 421 pc1.setPreferences(getSubprojectionPreference(pc1)); 422 if (pc1.getId().equals(PROP_PROJECTION.get())) { 423 projectionCombo.setSelectedIndex(i); 424 selectedProjectionChanged(pc1); 425 pc = pc1; 426 } 427 } 428 // If the ProjectionChoice from the preferences is not available, it 429 // should have been set to Mercator at JOSM start. 430 if (pc == null) 431 throw new RuntimeException("Couldn't find the current projection in the list of available projections!"); 432 433 projectionCombo.addActionListener(new ActionListener() { 434 public void actionPerformed(ActionEvent e) { 435 ProjectionChoice pc = (ProjectionChoice) projectionCombo.getSelectedItem(); 436 selectedProjectionChanged(pc); 437 } 438 }); 439 return pc; 440 } 441 442 private Collection<String> getSubprojectionPreference(ProjectionChoice pc) { 443 return Main.pref.getCollection("projection.sub."+pc.getId(), null); 444 } 445 446 @Override 447 public boolean isExpert() { 448 return false; 449 } 450 451 @Override 452 public TabPreferenceSetting getTabPreferenceSetting(final PreferenceTabbedPane gui) { 453 return gui.getMapPreference(); 454 } 455 }