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

Last change on this file since 8540 was 8540, checked in by Don-vip, 9 years ago

fix remaining checkstyle issues

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