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

Last change on this file since 10465 was 10300, checked in by Don-vip, 8 years ago

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