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

Last change on this file since 4475 was 4314, checked in by bastiK, 13 years ago

add east/north input to 'add node' and 'move node' actions

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