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.BelgianLambert1972;
030    import org.openstreetmap.josm.data.projection.BelgianLambert2008;
031    import org.openstreetmap.josm.data.projection.Epsg3008;
032    import org.openstreetmap.josm.data.projection.Epsg4326;
033    import org.openstreetmap.josm.data.projection.Lambert93;
034    import org.openstreetmap.josm.data.projection.LambertEST;
035    import org.openstreetmap.josm.data.projection.Mercator;
036    import org.openstreetmap.josm.data.projection.Projection;
037    import org.openstreetmap.josm.data.projection.TransverseMercatorLV;
038    import org.openstreetmap.josm.gui.NavigatableComponent;
039    import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
040    import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
041    import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
042    import org.openstreetmap.josm.gui.preferences.SubPreferenceSetting;
043    import org.openstreetmap.josm.gui.preferences.TabPreferenceSetting;
044    import org.openstreetmap.josm.gui.widgets.JosmComboBox;
045    import org.openstreetmap.josm.tools.GBC;
046    
047    public class ProjectionPreference implements SubPreferenceSetting {
048    
049        public static class Factory implements PreferenceSettingFactory {
050            public PreferenceSetting createPreferenceSetting() {
051                return new ProjectionPreference();
052            }
053        }
054    
055        private static List<ProjectionChoice> projectionChoices = new ArrayList<ProjectionChoice>();
056        private static Map<String, ProjectionChoice> projectionChoicesById = new HashMap<String, ProjectionChoice>();
057        private static Map<String, String> aliasNormalizer = new HashMap<String, String>();
058    
059        public static ProjectionChoice mercator = new SingleProjectionChoice("core:mercator", new Mercator());
060        static {
061            // global projections
062            registerProjectionChoice("core:wgs84", new Epsg4326());
063            registerProjectionChoice(mercator);
064            registerProjectionChoice(new UTMProjectionChoice());
065            // regional - alphabetical order by country code
066            registerProjectionChoice("core:belambert1972", new BelgianLambert1972());   // BE
067            registerProjectionChoice("core:belambert2008", new BelgianLambert2008());   // BE
068            registerProjectionChoice(new SwissGridProjectionChoice());                  // CH
069            registerProjectionChoice(new GaussKruegerProjectionChoice());               // DE
070            registerProjectionChoice("core:lambertest", new LambertEST());              // EE
071            registerProjectionChoice(new LambertProjectionChoice());                    // FR
072            registerProjectionChoice("core:lambert93", new Lambert93());                // FR
073            registerProjectionChoice(new LambertCC9ZonesProjectionChoice());            // FR
074            registerProjectionChoice(new UTM_France_DOM_ProjectionChoice());            // FR
075            registerProjectionChoice("core:tmerclv", new TransverseMercatorLV());       // LV
076            registerProjectionChoice(new PuwgProjectionChoice());                       // PL
077            registerProjectionChoice("core:sweref99", new Epsg3008());                  // SE
078            registerProjectionChoice(new CustomProjectionChoice());
079        }
080    
081        public static void registerProjectionChoice(ProjectionChoice c) {
082            projectionChoices.add(c);
083            projectionChoicesById.put(c.getId(), c);
084            aliasNormalizer.put(c.getId(), c.getId());
085            if (c instanceof Alias) {
086                String alias = ((Alias) c).getAlias();
087                projectionChoicesById.put(alias, c);
088                aliasNormalizer.put(alias, c.getId());
089            }
090        }
091    
092        public static void registerProjectionChoice(String id, Projection projection) {
093            registerProjectionChoice(new SingleProjectionChoice(id, projection));
094        }
095    
096        public static List<ProjectionChoice> getProjectionChoices() {
097            return Collections.unmodifiableList(projectionChoices);
098        }
099    
100        private static final StringProperty PROP_PROJECTION = new StringProperty("projection", mercator.getId());
101        private static final StringProperty PROP_COORDINATES = new StringProperty("coordinates", null);
102        private static final CollectionProperty PROP_SUB_PROJECTION = new CollectionProperty("projection.sub", null);
103        public static final StringProperty PROP_SYSTEM_OF_MEASUREMENT = new StringProperty("system_of_measurement", "Metric");
104        private static final String[] unitsValues = (new ArrayList<String>(NavigatableComponent.SYSTEMS_OF_MEASUREMENT.keySet())).toArray(new String[0]);
105        private static final String[] unitsValuesTr = new String[unitsValues.length];
106        static {
107            for (int i=0; i<unitsValues.length; ++i) {
108                unitsValuesTr[i] = tr(unitsValues[i]);
109            }
110        }
111    
112        /**
113         * Combobox with all projections available
114         */
115        private JosmComboBox projectionCombo = new JosmComboBox(projectionChoices.toArray());
116    
117        /**
118         * Combobox with all coordinate display possibilities
119         */
120        private JosmComboBox coordinatesCombo = new JosmComboBox(CoordinateFormat.values());
121    
122        private JosmComboBox unitsCombo = new JosmComboBox(unitsValuesTr);
123    
124        /**
125         * This variable holds the JPanel with the projection's preferences. If the
126         * selected projection does not implement this, it will be set to an empty
127         * Panel.
128         */
129        private JPanel projSubPrefPanel;
130        private JPanel projSubPrefPanelWrapper = new JPanel(new GridBagLayout());
131    
132        private JLabel projectionCodeLabel;
133        private Component projectionCodeGlue;
134        private JLabel projectionCode = new JLabel();
135        private JLabel bounds = new JLabel();
136    
137        /**
138         * This is the panel holding all projection preferences
139         */
140        private JPanel projPanel = new JPanel(new GridBagLayout());
141    
142        /**
143         * The GridBagConstraints for the Panel containing the ProjectionSubPrefs.
144         * This is required twice in the code, creating it here keeps both occurrences
145         * in sync
146         */
147        static private GBC projSubPrefPanelGBC = GBC.std().fill(GBC.BOTH).weight(1.0, 1.0);
148    
149        public void addGui(PreferenceTabbedPane gui) {
150            ProjectionChoice pc = setupProjectionCombo();
151    
152            for (int i = 0; i < coordinatesCombo.getItemCount(); ++i) {
153                if (((CoordinateFormat)coordinatesCombo.getItemAt(i)).name().equals(PROP_COORDINATES.get())) {
154                    coordinatesCombo.setSelectedIndex(i);
155                    break;
156                }
157            }
158    
159            for (int i = 0; i < unitsValues.length; ++i) {
160                if (unitsValues[i].equals(PROP_SYSTEM_OF_MEASUREMENT.get())) {
161                    unitsCombo.setSelectedIndex(i);
162                    break;
163                }
164            }
165    
166            projPanel.setBorder(BorderFactory.createEmptyBorder( 0, 0, 0, 0 ));
167            projPanel.setLayout(new GridBagLayout());
168            projPanel.add(new JLabel(tr("Projection method")), GBC.std().insets(5,5,0,5));
169            projPanel.add(GBC.glue(5,0), GBC.std().fill(GBC.HORIZONTAL));
170            projPanel.add(projectionCombo, GBC.eop().fill(GBC.HORIZONTAL).insets(0,5,5,5));
171            projPanel.add(projectionCodeLabel = new JLabel(tr("Projection code")), GBC.std().insets(25,5,0,5));
172            projPanel.add(projectionCodeGlue = GBC.glue(5,0), GBC.std().fill(GBC.HORIZONTAL));
173            projPanel.add(projectionCode, GBC.eop().fill(GBC.HORIZONTAL).insets(0,5,5,5));
174            projPanel.add(new JLabel(tr("Bounds")), GBC.std().insets(25,5,0,5));
175            projPanel.add(GBC.glue(5,0), GBC.std().fill(GBC.HORIZONTAL));
176            projPanel.add(bounds, GBC.eop().fill(GBC.HORIZONTAL).insets(0,5,5,5));
177            projPanel.add(projSubPrefPanelWrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(20,5,5,5));
178    
179            projPanel.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0,5,0,10));
180            projPanel.add(new JLabel(tr("Display coordinates as")), GBC.std().insets(5,5,0,5));
181            projPanel.add(GBC.glue(5,0), GBC.std().fill(GBC.HORIZONTAL));
182            projPanel.add(coordinatesCombo, GBC.eop().fill(GBC.HORIZONTAL).insets(0,5,5,5));
183            projPanel.add(new JLabel(tr("System of measurement")), GBC.std().insets(5,5,0,5));
184            projPanel.add(GBC.glue(5,0), GBC.std().fill(GBC.HORIZONTAL));
185            projPanel.add(unitsCombo, GBC.eop().fill(GBC.HORIZONTAL).insets(0,5,5,5));
186            projPanel.add(GBC.glue(1,1), GBC.std().fill(GBC.HORIZONTAL).weight(1.0, 1.0));
187    
188            JScrollPane scrollpane = new JScrollPane(projPanel);
189            gui.getMapPreference().mapcontent.addTab(tr("Map Projection"), scrollpane);
190    
191            selectedProjectionChanged(pc);
192        }
193    
194        private void updateMeta(ProjectionChoice pc) {
195            pc.setPreferences(pc.getPreferences(projSubPrefPanel));
196            Projection proj = pc.getProjection();
197            projectionCode.setText(proj.toCode());
198            Bounds b = proj.getWorldBoundsLatLon();
199            CoordinateFormat cf = CoordinateFormat.getDefaultFormat();
200            bounds.setText(b.getMin().lonToString(cf)+", "+b.getMin().latToString(cf)+" : "+b.getMax().lonToString(cf)+", "+b.getMax().latToString(cf));
201            boolean showCode = true;
202            if (pc instanceof SubPrefsOptions) {
203                showCode = ((SubPrefsOptions) pc).showProjectionCode();
204            }
205            projectionCodeLabel.setVisible(showCode);
206            projectionCodeGlue.setVisible(showCode);
207            projectionCode.setVisible(showCode);
208        }
209    
210        @Override
211        public boolean ok() {
212            ProjectionChoice pc = (ProjectionChoice) projectionCombo.getSelectedItem();
213    
214            String id = pc.getId();
215            Collection<String> prefs = pc.getPreferences(projSubPrefPanel);
216    
217            setProjection(id, prefs);
218    
219            if(PROP_COORDINATES.put(((CoordinateFormat)coordinatesCombo.getSelectedItem()).name())) {
220                CoordinateFormat.setCoordinateFormat((CoordinateFormat)coordinatesCombo.getSelectedItem());
221            }
222    
223            int i = unitsCombo.getSelectedIndex();
224            PROP_SYSTEM_OF_MEASUREMENT.put(unitsValues[i]);
225    
226            return false;
227        }
228    
229        static public void setProjection() {
230            setProjection(PROP_PROJECTION.get(), PROP_SUB_PROJECTION.get());
231        }
232    
233        static public void setProjection(String id, Collection<String> pref) {
234            ProjectionChoice pc = projectionChoicesById.get(id);
235    
236            if (pc == null) {
237                JOptionPane.showMessageDialog(
238                        Main.parent,
239                        tr("The projection {0} could not be activated. Using Mercator", id),
240                        tr("Error"),
241                        JOptionPane.ERROR_MESSAGE
242                );
243                pref = null;
244                pc = mercator;
245            }
246            id = pc.getId();
247            PROP_PROJECTION.put(id);
248            PROP_SUB_PROJECTION.put(pref);
249            Main.pref.putCollection("projection.sub."+id, pref);
250            pc.setPreferences(pref);
251            Projection proj = pc.getProjection();
252            Main.setProjection(proj);
253        }
254    
255        /**
256         * Handles all the work related to update the projection-specific
257         * preferences
258         * @param proj
259         */
260        private void selectedProjectionChanged(final ProjectionChoice pc) {
261            // Don't try to update if we're still starting up
262            int size = projPanel.getComponentCount();
263            if(size < 1)
264                return;
265    
266            final ActionListener listener = new ActionListener() {
267                @Override
268                public void actionPerformed(ActionEvent e) {
269                    updateMeta(pc);
270                }
271            };
272    
273            // Replace old panel with new one
274            projSubPrefPanelWrapper.removeAll();
275            projSubPrefPanel = pc.getPreferencePanel(listener);
276            projSubPrefPanelWrapper.add(projSubPrefPanel, projSubPrefPanelGBC);
277            projPanel.revalidate();
278            projSubPrefPanel.repaint();
279            updateMeta(pc);
280        }
281    
282        /**
283         * Sets up projection combobox with default values and action listener
284         */
285        private ProjectionChoice setupProjectionCombo() {
286            ProjectionChoice pc = null;
287            for (int i = 0; i < projectionCombo.getItemCount(); ++i) {
288                ProjectionChoice pc1 = (ProjectionChoice) projectionCombo.getItemAt(i);
289                pc1.setPreferences(getSubprojectionPreference(pc1));
290                if (pc1.getId().equals(aliasNormalizer.get(PROP_PROJECTION.get()))) {
291                    projectionCombo.setSelectedIndex(i);
292                    selectedProjectionChanged(pc1);
293                    pc = pc1;
294                }
295            }
296            // If the ProjectionChoice from the preferences is not available, it
297            // should have been set to Mercator at JOSM start.
298            if (pc == null)
299                throw new RuntimeException("Couldn't find the current projection in the list of available projections!");
300    
301            projectionCombo.addActionListener(new ActionListener() {
302                public void actionPerformed(ActionEvent e) {
303                    ProjectionChoice pc = (ProjectionChoice) projectionCombo.getSelectedItem();
304                    selectedProjectionChanged(pc);
305                }
306            });
307            return pc;
308        }
309    
310        private Collection<String> getSubprojectionPreference(ProjectionChoice pc) {
311            Collection<String> c1 = Main.pref.getCollection("projection.sub."+pc.getId(), null);
312            if (c1 != null)
313                return c1;
314            if (pc instanceof Alias) {
315                String alias = ((Alias) pc).getAlias();
316                String sname = alias.substring(alias.lastIndexOf(".")+1);
317                return Main.pref.getCollection("projection.sub."+sname, null);
318            }
319            return null;
320        }
321    
322        @Override
323        public boolean isExpert() {
324            return false;
325        }
326    
327        @Override
328        public TabPreferenceSetting getTabPreferenceSetting(final PreferenceTabbedPane gui) {
329            return gui.getMapPreference();
330        }
331    }