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

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

When doing a String.toLowerCase()/toUpperCase() call, use a Locale. This avoids problems with certain locales, i.e. Lithuanian or Turkish. See PMD UseLocaleWithCaseConversions rule and String.toLowerCase() javadoc.

  • Property svn:eol-style set to native
File size: 19.3 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 @Override
341 public void focusLost(FocusEvent e) {
342 // Not used
343 }
344 }
345
346 public static LatLon parseLatLon(final String coord) {
347 final LatLonHolder latLon = new LatLonHolder();
348 final Matcher mXml = P_XML.matcher(coord);
349 if (mXml.matches()) {
350 setLatLonObj(latLon,
351 Double.valueOf(mXml.group(1).replace(',', '.')), ZERO, ZERO, "N",
352 Double.valueOf(mXml.group(2).replace(',', '.')), ZERO, ZERO, "E");
353 } else {
354 final Matcher m = P.matcher(coord);
355
356 final StringBuilder sb = new StringBuilder();
357 final List<Object> list = new ArrayList<>();
358
359 while (m.find()) {
360 if (m.group(1) != null) {
361 sb.append('R'); // floating point number
362 list.add(Double.valueOf(m.group(1).replace(',', '.')));
363 } else if (m.group(2) != null) {
364 sb.append('Z'); // integer number
365 list.add(Double.valueOf(m.group(2)));
366 } else if (m.group(3) != null) {
367 sb.append('o'); // degree sign
368 } else if (m.group(4) != null) {
369 sb.append('\''); // seconds sign
370 } else if (m.group(5) != null) {
371 sb.append('"'); // minutes sign
372 } else if (m.group(6) != null) {
373 sb.append(','); // separator
374 } else if (m.group(7) != null) {
375 sb.append('x'); // cardinal direction
376 String c = m.group(7).toUpperCase(Locale.ENGLISH);
377 if ("N".equals(c) || "S".equals(c) || "E".equals(c) || "W".equals(c)) {
378 list.add(c);
379 } else {
380 list.add(c.replace(N_TR, 'N').replace(S_TR, 'S')
381 .replace(E_TR, 'E').replace(W_TR, 'W'));
382 }
383 } else if (m.group(8) != null) {
384 throw new IllegalArgumentException("invalid token: " + m.group(8));
385 }
386 }
387
388 final String pattern = sb.toString();
389
390 final Object[] params = list.toArray();
391
392 if (pattern.matches("Ro?,?Ro?")) {
393 setLatLonObj(latLon,
394 params[0], ZERO, ZERO, "N",
395 params[1], ZERO, ZERO, "E");
396 } else if (pattern.matches("xRo?,?xRo?")) {
397 setLatLonObj(latLon,
398 params[1], ZERO, ZERO, params[0],
399 params[3], ZERO, ZERO, params[2]);
400 } else if (pattern.matches("Ro?x,?Ro?x")) {
401 setLatLonObj(latLon,
402 params[0], ZERO, ZERO, params[1],
403 params[2], ZERO, ZERO, params[3]);
404 } else if (pattern.matches("Zo[RZ]'?,?Zo[RZ]'?|Z[RZ],?Z[RZ]")) {
405 setLatLonObj(latLon,
406 params[0], params[1], ZERO, "N",
407 params[2], params[3], ZERO, "E");
408 } else if (pattern.matches("xZo[RZ]'?,?xZo[RZ]'?|xZo?[RZ],?xZo?[RZ]")) {
409 setLatLonObj(latLon,
410 params[1], params[2], ZERO, params[0],
411 params[4], params[5], ZERO, params[3]);
412 } else if (pattern.matches("Zo[RZ]'?x,?Zo[RZ]'?x|Zo?[RZ]x,?Zo?[RZ]x")) {
413 setLatLonObj(latLon,
414 params[0], params[1], ZERO, params[2],
415 params[3], params[4], ZERO, params[5]);
416 } else if (pattern.matches("ZoZ'[RZ]\"?x,?ZoZ'[RZ]\"?x|ZZ[RZ]x,?ZZ[RZ]x")) {
417 setLatLonObj(latLon,
418 params[0], params[1], params[2], params[3],
419 params[4], params[5], params[6], params[7]);
420 } else if (pattern.matches("xZoZ'[RZ]\"?,?xZoZ'[RZ]\"?|xZZ[RZ],?xZZ[RZ]")) {
421 setLatLonObj(latLon,
422 params[1], params[2], params[3], params[0],
423 params[5], params[6], params[7], params[4]);
424 } else if (pattern.matches("ZZ[RZ],?ZZ[RZ]")) {
425 setLatLonObj(latLon,
426 params[0], params[1], params[2], "N",
427 params[3], params[4], params[5], "E");
428 } else {
429 throw new IllegalArgumentException("invalid format: " + pattern);
430 }
431 }
432
433 return new LatLon(latLon.lat, latLon.lon);
434 }
435
436 public static EastNorth parseEastNorth(String s) {
437 String[] en = s.split("[;, ]+");
438 if (en.length != 2) return null;
439 try {
440 double east = Double.parseDouble(en[0]);
441 double north = Double.parseDouble(en[1]);
442 return new EastNorth(east, north);
443 } catch (NumberFormatException nfe) {
444 return null;
445 }
446 }
447
448 private static class LatLonHolder {
449 private double lat;
450 private double lon;
451 }
452
453 private static void setLatLonObj(final LatLonHolder latLon,
454 final Object coord1deg, final Object coord1min, final Object coord1sec, final Object card1,
455 final Object coord2deg, final Object coord2min, final Object coord2sec, final Object card2) {
456
457 setLatLon(latLon,
458 (Double) coord1deg, (Double) coord1min, (Double) coord1sec, (String) card1,
459 (Double) coord2deg, (Double) coord2min, (Double) coord2sec, (String) card2);
460 }
461
462 private static void setLatLon(final LatLonHolder latLon,
463 final double coord1deg, final double coord1min, final double coord1sec, final String card1,
464 final double coord2deg, final double coord2min, final double coord2sec, final String card2) {
465
466 setLatLon(latLon, coord1deg, coord1min, coord1sec, card1);
467 setLatLon(latLon, coord2deg, coord2min, coord2sec, card2);
468 }
469
470 private static void setLatLon(final LatLonHolder latLon, final double coordDeg, final double coordMin, final double coordSec, final String card) {
471 if (coordDeg < -180 || coordDeg > 180 || coordMin < 0 || coordMin >= 60 || coordSec < 0 || coordSec > 60) {
472 throw new IllegalArgumentException("out of range");
473 }
474
475 double coord = (coordDeg < 0 ? -1 : 1) * (Math.abs(coordDeg) + coordMin / 60 + coordSec / 3600);
476 coord = "N".equals(card) || "E".equals(card) ? coord : -coord;
477 if ("N".equals(card) || "S".equals(card)) {
478 latLon.lat = coord;
479 } else {
480 latLon.lon = coord;
481 }
482 }
483
484 public String getLatLonText() {
485 return tfLatLon.getText();
486 }
487
488 public void setLatLonText(String text) {
489 tfLatLon.setText(text);
490 }
491
492 public String getEastNorthText() {
493 return tfEastNorth.getText();
494 }
495
496 public void setEastNorthText(String text) {
497 tfEastNorth.setText(text);
498 }
499
500}
Note: See TracBrowser for help on using the repository browser.