source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/LatLonDialog.java@ 6084

Last change on this file since 6084 was 6084, checked in by bastiK, 11 years ago

see #8902 - add missing @Override annotations (patch by shinigami)

  • Property svn:eol-style set to native
File size: 18.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Color;
7import java.awt.Component;
8import java.awt.GridBagLayout;
9import java.awt.event.FocusEvent;
10import java.awt.event.FocusListener;
11import java.text.NumberFormat;
12import java.text.ParsePosition;
13import java.util.ArrayList;
14import java.util.List;
15import java.util.Locale;
16import java.util.regex.Matcher;
17import java.util.regex.Pattern;
18
19import javax.swing.BorderFactory;
20import javax.swing.JLabel;
21import javax.swing.JPanel;
22import javax.swing.JSeparator;
23import javax.swing.JTabbedPane;
24import javax.swing.UIManager;
25import javax.swing.event.ChangeEvent;
26import javax.swing.event.ChangeListener;
27import javax.swing.event.DocumentEvent;
28import javax.swing.event.DocumentListener;
29
30import org.openstreetmap.josm.Main;
31import org.openstreetmap.josm.data.coor.CoordinateFormat;
32import org.openstreetmap.josm.data.coor.EastNorth;
33import org.openstreetmap.josm.data.coor.LatLon;
34import org.openstreetmap.josm.gui.ExtendedDialog;
35import org.openstreetmap.josm.gui.widgets.HtmlPanel;
36import org.openstreetmap.josm.gui.widgets.JosmTextField;
37import org.openstreetmap.josm.tools.GBC;
38import org.openstreetmap.josm.tools.WindowGeometry;
39
40public class LatLonDialog extends ExtendedDialog {
41 private static final Color BG_COLOR_ERROR = new Color(255,224,224);
42
43 public JTabbedPane tabs;
44 private JosmTextField tfLatLon, tfEastNorth;
45 private LatLon latLonCoordinates;
46 private EastNorth eastNorthCoordinates;
47
48 private static final double ZERO = 0.0;
49 private static final String DEG = "\u00B0";
50 private static final String MIN = "\u2032";
51 private static final String SEC = "\u2033";
52
53 private static final char N_TR = LatLon.NORTH.charAt(0);
54 private static final char S_TR = LatLon.SOUTH.charAt(0);
55 private static final char E_TR = LatLon.EAST.charAt(0);
56 private static final char W_TR = LatLon.WEST.charAt(0);
57
58 private static final Pattern p = Pattern.compile(
59 "([+|-]?\\d+[.,]\\d+)|" // (1)
60 + "([+|-]?\\d+)|" // (2)
61 + "("+DEG+"|o|deg)|" // (3)
62 + "('|"+MIN+"|min)|" // (4)
63 + "(\"|"+SEC+"|sec)|" // (5)
64 + "(,|;)|" // (6)
65 + "([NSEW"+N_TR+S_TR+E_TR+W_TR+"])|"// (7)
66 + "\\s+|"
67 + "(.+)");
68
69 protected JPanel buildLatLon() {
70 JPanel pnl = new JPanel(new GridBagLayout());
71 pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
72
73 pnl.add(new JLabel(tr("Coordinates:")), GBC.std().insets(0,10,5,0));
74 tfLatLon = new JosmTextField(24);
75 pnl.add(tfLatLon, GBC.eol().insets(0,10,0,0).fill(GBC.HORIZONTAL).weight(1.0, 0.0));
76
77 pnl.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0,5,0,5));
78
79 pnl.add(new HtmlPanel(
80 tr("Enter the coordinates for the new node.<br/>You can separate longitude and latitude with space, comma or semicolon.<br/>" +
81 "Use positive numbers or N, E characters to indicate North or East cardinal direction.<br/>" +
82 "For South and West cardinal directions you can use either negative numbers or S, W characters.<br/>" +
83 "Coordinate value can be in one of three formats:<ul>" +
84 "<li><i>degrees</i><tt>&deg;</tt></li>" +
85 "<li><i>degrees</i><tt>&deg;</tt> <i>minutes</i><tt>&#39;</tt></li>" +
86 "<li><i>degrees</i><tt>&deg;</tt> <i>minutes</i><tt>&#39;</tt> <i>seconds</i><tt>&quot</tt></li>" +
87 "</ul>" +
88 "Symbols <tt>&deg;</tt>, <tt>&#39;</tt>, <tt>&prime;</tt>, <tt>&quot;</tt>, <tt>&Prime;</tt> are optional.<br/><br/>" +
89 "Some examples:<ul>" +
90 "<li>49.29918&deg; 19.24788&deg;</li>" +
91 "<li>N 49.29918 E 19.24788</li>" +
92 "<li>W 49&deg;29.918&#39; S 19&deg;24.788&#39;</li>" +
93 "<li>N 49&deg;29&#39;04&quot; E 19&deg;24&#39;43&quot;</li>" +
94 "<li>49.29918 N, 19.24788 E</li>" +
95 "<li>49&deg;29&#39;21&quot; N 19&deg;24&#39;38&quot; E</li>" +
96 "<li>49 29 51, 19 24 18</li>" +
97 "<li>49 29, 19 24</li>" +
98 "<li>E 49 29, N 19 24</li>" +
99 "<li>49&deg; 29; 19&deg; 24</li>" +
100 "<li>N 49&deg; 29, W 19&deg; 24</li>" +
101 "<li>49&deg; 29.5 S, 19&deg; 24.6 E</li>" +
102 "<li>N 49 29.918 E 19 15.88</li>" +
103 "<li>49 29.4 19 24.5</li>" +
104 "<li>-49 29.4 N -19 24.5 W</li></ul>" +
105 "<li>48 deg 42&#39; 52.13\" N, 21 deg 11&#39; 47.60\" E</li></ul>"
106 )),
107 GBC.eol().fill().weight(1.0, 1.0));
108
109 // parse and verify input on the fly
110 //
111 LatLonInputVerifier inputVerifier = new LatLonInputVerifier();
112 tfLatLon.getDocument().addDocumentListener(inputVerifier);
113
114 // select the text in the field on focus
115 //
116 TextFieldFocusHandler focusHandler = new TextFieldFocusHandler();
117 tfLatLon.addFocusListener(focusHandler);
118 return pnl;
119 }
120
121 private JPanel buildEastNorth() {
122 JPanel pnl = new JPanel(new GridBagLayout());
123 pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
124
125 pnl.add(new JLabel(tr("Projected coordinates:")), GBC.std().insets(0,10,5,0));
126 tfEastNorth = new JosmTextField(24);
127
128 pnl.add(tfEastNorth, GBC.eol().insets(0,10,0,0).fill(GBC.HORIZONTAL).weight(1.0, 0.0));
129
130 pnl.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0,5,0,5));
131
132 pnl.add(new HtmlPanel(
133 tr("Enter easting and northing (x and y) separated by space, comma or semicolon.")),
134 GBC.eol().fill(GBC.HORIZONTAL));
135
136 pnl.add(GBC.glue(1, 1), GBC.eol().fill().weight(1.0, 1.0));
137
138 EastNorthInputVerifier inputVerifier = new EastNorthInputVerifier();
139 tfEastNorth.getDocument().addDocumentListener(inputVerifier);
140
141 TextFieldFocusHandler focusHandler = new TextFieldFocusHandler();
142 tfEastNorth.addFocusListener(focusHandler);
143
144 return pnl;
145 }
146
147 protected void build() {
148 tabs = new JTabbedPane();
149 tabs.addTab(tr("Lat/Lon"), buildLatLon());
150 tabs.addTab(tr("East/North"), buildEastNorth());
151 tabs.getModel().addChangeListener(new ChangeListener() {
152 @Override
153 public void stateChanged(ChangeEvent e) {
154 switch (tabs.getModel().getSelectedIndex()) {
155 case 0: parseLatLonUserInput(); break;
156 case 1: parseEastNorthUserInput(); break;
157 default: throw new AssertionError();
158 }
159 }
160 });
161 setContent(tabs, false);
162 }
163
164 public LatLonDialog(Component parent, String title, String help) {
165 super(Main.parent, tr("Add Node..."), new String[] { tr("Ok"), tr("Cancel") });
166 setButtonIcons(new String[] { "ok", "cancel" });
167 configureContextsensitiveHelp("/Action/AddNode", true);
168
169 build();
170 setCoordinates(null);
171 }
172
173 public boolean isLatLon() {
174 return tabs.getModel().getSelectedIndex() == 0;
175 }
176
177 public void setCoordinates(LatLon ll) {
178 if (ll == null) {
179 ll = new LatLon(0,0);
180 }
181 this.latLonCoordinates = ll;
182 tfLatLon.setText(ll.latToString(CoordinateFormat.getDefaultFormat()) + " " + ll.lonToString(CoordinateFormat.getDefaultFormat()));
183 EastNorth en = Main.getProjection().latlon2eastNorth(ll);
184 tfEastNorth.setText(en.east()+" "+en.north());
185 setOkEnabled(true);
186 }
187
188 public LatLon getCoordinates() {
189 if (isLatLon()) {
190 return latLonCoordinates;
191 } else {
192 if (eastNorthCoordinates == null) return null;
193 return Main.getProjection().eastNorth2latlon(eastNorthCoordinates);
194 }
195 }
196
197 public LatLon getLatLonCoordinates() {
198 return latLonCoordinates;
199 }
200
201 public EastNorth getEastNorthCoordinates() {
202 return eastNorthCoordinates;
203 }
204
205 protected void setErrorFeedback(JosmTextField tf, String message) {
206 tf.setBorder(BorderFactory.createLineBorder(Color.RED, 1));
207 tf.setToolTipText(message);
208 tf.setBackground(BG_COLOR_ERROR);
209 }
210
211 protected void clearErrorFeedback(JosmTextField tf, String message) {
212 tf.setBorder(UIManager.getBorder("TextField.border"));
213 tf.setToolTipText(message);
214 tf.setBackground(UIManager.getColor("TextField.background"));
215 }
216
217 protected Double parseDoubleFromUserInput(String input) {
218 if (input == null) return null;
219 // remove white space and an optional degree symbol
220 //
221 input = input.trim();
222 input = input.replaceAll(DEG, "");
223
224 // try to parse using the current locale
225 //
226 NumberFormat f = NumberFormat.getNumberInstance();
227 Number n=null;
228 ParsePosition pp = new ParsePosition(0);
229 n = f.parse(input,pp);
230 if (pp.getErrorIndex() >= 0 || pp.getIndex()<input.length()) {
231 // fall back - try to parse with the english locale
232 //
233 pp = new ParsePosition(0);
234 f = NumberFormat.getNumberInstance(Locale.ENGLISH);
235 n = f.parse(input, pp);
236 if (pp.getErrorIndex() >= 0 || pp.getIndex()<input.length())
237 return null;
238 }
239 return n== null ? null : n.doubleValue();
240 }
241
242 protected void parseLatLonUserInput() {
243 LatLon latLon;
244 try {
245 latLon = parseLatLon(tfLatLon.getText());
246 if (!LatLon.isValidLat(latLon.lat()) || !LatLon.isValidLon(latLon.lon())) {
247 latLon = null;
248 }
249 } catch (IllegalArgumentException e) {
250 latLon = null;
251 }
252 if (latLon == null) {
253 setErrorFeedback(tfLatLon, tr("Please enter a GPS coordinates"));
254 latLonCoordinates = null;
255 setOkEnabled(false);
256 } else {
257 clearErrorFeedback(tfLatLon,tr("Please enter a GPS coordinates"));
258 latLonCoordinates = latLon;
259 setOkEnabled(true);
260 }
261 }
262
263 protected void parseEastNorthUserInput() {
264 EastNorth en;
265 try {
266 en = parseEastNorth(tfEastNorth.getText());
267 } catch (IllegalArgumentException e) {
268 en = null;
269 }
270 if (en == null) {
271 setErrorFeedback(tfEastNorth, tr("Please enter a Easting and Northing"));
272 latLonCoordinates = null;
273 setOkEnabled(false);
274 } else {
275 clearErrorFeedback(tfEastNorth,tr("Please enter a Easting and Northing"));
276 eastNorthCoordinates = en;
277 setOkEnabled(true);
278 }
279 }
280
281 private void setOkEnabled(boolean b) {
282 if (buttons != null && buttons.size() > 0) {
283 buttons.get(0).setEnabled(b);
284 }
285 }
286
287 @Override
288 public void setVisible(boolean visible) {
289 if (visible) {
290 WindowGeometry.centerInWindow(Main.parent, getSize()).applySafe(this);
291 }
292 super.setVisible(visible);
293 }
294
295 class LatLonInputVerifier implements DocumentListener {
296 @Override
297 public void changedUpdate(DocumentEvent e) {
298 parseLatLonUserInput();
299 }
300
301 @Override
302 public void insertUpdate(DocumentEvent e) {
303 parseLatLonUserInput();
304 }
305
306 @Override
307 public void removeUpdate(DocumentEvent e) {
308 parseLatLonUserInput();
309 }
310 }
311
312 class EastNorthInputVerifier implements DocumentListener {
313 @Override
314 public void changedUpdate(DocumentEvent e) {
315 parseEastNorthUserInput();
316 }
317
318 @Override
319 public void insertUpdate(DocumentEvent e) {
320 parseEastNorthUserInput();
321 }
322
323 @Override
324 public void removeUpdate(DocumentEvent e) {
325 parseEastNorthUserInput();
326 }
327 }
328
329 static class TextFieldFocusHandler implements FocusListener {
330 @Override
331 public void focusGained(FocusEvent e) {
332 Component c = e.getComponent();
333 if (c instanceof JosmTextField) {
334 JosmTextField tf = (JosmTextField)c;
335 tf.selectAll();
336 }
337 }
338 @Override
339 public void focusLost(FocusEvent e) {}
340 }
341
342 private static LatLon parseLatLon(final String coord) {
343 final Matcher m = p.matcher(coord);
344
345 final StringBuilder sb = new StringBuilder();
346 final List<Object> list = new ArrayList<Object>();
347
348 while (m.find()) {
349 if (m.group(1) != null) {
350 sb.append('R'); // floating point number
351 list.add(Double.parseDouble(m.group(1).replace(',', '.')));
352 } else if (m.group(2) != null) {
353 sb.append('Z'); // integer number
354 list.add(Double.parseDouble(m.group(2)));
355 } else if (m.group(3) != null) {
356 sb.append('o'); // degree sign
357 } else if (m.group(4) != null) {
358 sb.append('\''); // seconds sign
359 } else if (m.group(5) != null) {
360 sb.append('"'); // minutes sign
361 } else if (m.group(6) != null) {
362 sb.append(','); // separator
363 } else if (m.group(7) != null) {
364 sb.append("x"); // cardinal direction
365 String c = m.group(7).toUpperCase();
366 if (c.equals("N") || c.equals("S") || c.equals("E") || c.equals("W")) {
367 list.add(c);
368 } else {
369 list.add(c.replace(N_TR, 'N').replace(S_TR, 'S')
370 .replace(E_TR, 'E').replace(W_TR, 'W'));
371 }
372 } else if (m.group(8) != null) {
373 throw new IllegalArgumentException("invalid token: " + m.group(8));
374 }
375 }
376
377 final String pattern = sb.toString();
378
379 final Object[] params = list.toArray();
380 final LatLonHolder latLon = new LatLonHolder();
381
382 if (pattern.matches("Ro?,?Ro?")) {
383 setLatLonObj(latLon,
384 params[0], ZERO, ZERO, "N",
385 params[1], ZERO, ZERO, "E");
386 } else if (pattern.matches("xRo?,?xRo?")) {
387 setLatLonObj(latLon,
388 params[1], ZERO, ZERO, params[0],
389 params[3], ZERO, ZERO, params[2]);
390 } else if (pattern.matches("Ro?x,?Ro?x")) {
391 setLatLonObj(latLon,
392 params[0], ZERO, ZERO, params[1],
393 params[2], ZERO, ZERO, params[3]);
394 } else if (pattern.matches("Zo[RZ]'?,?Zo[RZ]'?|Z[RZ],?Z[RZ]")) {
395 setLatLonObj(latLon,
396 params[0], params[1], ZERO, "N",
397 params[2], params[3], ZERO, "E");
398 } else if (pattern.matches("xZo[RZ]'?,?xZo[RZ]'?|xZo?[RZ],?xZo?[RZ]")) {
399 setLatLonObj(latLon,
400 params[1], params[2], ZERO, params[0],
401 params[4], params[5], ZERO, params[3]);
402 } else if (pattern.matches("Zo[RZ]'?x,?Zo[RZ]'?x|Zo?[RZ]x,?Zo?[RZ]x")) {
403 setLatLonObj(latLon,
404 params[0], params[1], ZERO, params[2],
405 params[3], params[4], ZERO, params[5]);
406 } else if (pattern.matches("ZoZ'[RZ]\"?x,?ZoZ'[RZ]\"?x|ZZ[RZ]x,?ZZ[RZ]x")) {
407 setLatLonObj(latLon,
408 params[0], params[1], params[2], params[3],
409 params[4], params[5], params[6], params[7]);
410 } else if (pattern.matches("xZoZ'[RZ]\"?,?xZoZ'[RZ]\"?|xZZ[RZ],?xZZ[RZ]")) {
411 setLatLonObj(latLon,
412 params[1], params[2], params[3], params[0],
413 params[5], params[6], params[7], params[4]);
414 } else if (pattern.matches("ZZ[RZ],?ZZ[RZ]")) {
415 setLatLonObj(latLon,
416 params[0], params[1], params[2], "N",
417 params[3], params[4], params[5], "E");
418 } else {
419 throw new IllegalArgumentException("invalid format: " + pattern);
420 }
421
422 return new LatLon(latLon.lat, latLon.lon);
423 }
424
425 private static EastNorth parseEastNorth(String s) {
426 String[] en = s.split("[;, ]+");
427 if (en.length != 2) return null;
428 try {
429 double east = Double.parseDouble(en[0]);
430 double north = Double.parseDouble(en[1]);
431 return new EastNorth(east, north);
432 } catch (NumberFormatException nfe) {
433 return null;
434 }
435 }
436
437 private static class LatLonHolder {
438 double lat, lon;
439 }
440
441 private static void setLatLonObj(final LatLonHolder latLon,
442 final Object coord1deg, final Object coord1min, final Object coord1sec, final Object card1,
443 final Object coord2deg, final Object coord2min, final Object coord2sec, final Object card2) {
444
445 setLatLon(latLon,
446 (Double) coord1deg, (Double) coord1min, (Double) coord1sec, (String) card1,
447 (Double) coord2deg, (Double) coord2min, (Double) coord2sec, (String) card2);
448 }
449
450 private static void setLatLon(final LatLonHolder latLon,
451 final double coord1deg, final double coord1min, final double coord1sec, final String card1,
452 final double coord2deg, final double coord2min, final double coord2sec, final String card2) {
453
454 setLatLon(latLon, coord1deg, coord1min, coord1sec, card1);
455 setLatLon(latLon, coord2deg, coord2min, coord2sec, card2);
456 }
457
458 private static void setLatLon(final LatLonHolder latLon, final double coordDeg, final double coordMin, final double coordSec, final String card) {
459 if (coordDeg < -180 || coordDeg > 180 || coordMin < 0 || coordMin >= 60 || coordSec < 0 || coordSec > 60) {
460 throw new IllegalArgumentException("out of range");
461 }
462
463 double coord = (coordDeg < 0 ? -1 : 1) * (Math.abs(coordDeg) + coordMin / 60 + coordSec / 3600);
464 coord = card.equals("N") || card.equals("E") ? coord : -coord;
465 if (card.equals("N") || card.equals("S")) {
466 latLon.lat = coord;
467 } else {
468 latLon.lon = coord;
469 }
470 }
471
472 public String getLatLonText() {
473 return tfLatLon.getText();
474 }
475
476 public void setLatLonText(String text) {
477 tfLatLon.setText(text);
478 }
479
480 public String getEastNorthText() {
481 return tfEastNorth.getText();
482 }
483
484 public void setEastNorthText(String text) {
485 tfEastNorth.setText(text);
486 }
487
488}
Note: See TracBrowser for help on using the repository browser.