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

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

replace tabs by spaces

  • 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.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 public void changedUpdate(DocumentEvent e) {
297 parseLatLonUserInput();
298 }
299
300 public void insertUpdate(DocumentEvent e) {
301 parseLatLonUserInput();
302 }
303
304 public void removeUpdate(DocumentEvent e) {
305 parseLatLonUserInput();
306 }
307 }
308
309 class EastNorthInputVerifier implements DocumentListener {
310 public void changedUpdate(DocumentEvent e) {
311 parseEastNorthUserInput();
312 }
313
314 public void insertUpdate(DocumentEvent e) {
315 parseEastNorthUserInput();
316 }
317
318 public void removeUpdate(DocumentEvent e) {
319 parseEastNorthUserInput();
320 }
321 }
322
323 static class TextFieldFocusHandler implements FocusListener {
324 public void focusGained(FocusEvent e) {
325 Component c = e.getComponent();
326 if (c instanceof JosmTextField) {
327 JosmTextField tf = (JosmTextField)c;
328 tf.selectAll();
329 }
330 }
331 public void focusLost(FocusEvent e) {}
332 }
333
334 private static LatLon parseLatLon(final String coord) {
335 final Matcher m = p.matcher(coord);
336
337 final StringBuilder sb = new StringBuilder();
338 final List<Object> list = new ArrayList<Object>();
339
340 while (m.find()) {
341 if (m.group(1) != null) {
342 sb.append('R'); // floating point number
343 list.add(Double.parseDouble(m.group(1).replace(',', '.')));
344 } else if (m.group(2) != null) {
345 sb.append('Z'); // integer number
346 list.add(Double.parseDouble(m.group(2)));
347 } else if (m.group(3) != null) {
348 sb.append('o'); // degree sign
349 } else if (m.group(4) != null) {
350 sb.append('\''); // seconds sign
351 } else if (m.group(5) != null) {
352 sb.append('"'); // minutes sign
353 } else if (m.group(6) != null) {
354 sb.append(','); // separator
355 } else if (m.group(7) != null) {
356 sb.append("x"); // cardinal direction
357 String c = m.group(7).toUpperCase();
358 if (c.equals("N") || c.equals("S") || c.equals("E") || c.equals("W")) {
359 list.add(c);
360 } else {
361 list.add(c.replace(N_TR, 'N').replace(S_TR, 'S')
362 .replace(E_TR, 'E').replace(W_TR, 'W'));
363 }
364 } else if (m.group(8) != null) {
365 throw new IllegalArgumentException("invalid token: " + m.group(8));
366 }
367 }
368
369 final String pattern = sb.toString();
370
371 final Object[] params = list.toArray();
372 final LatLonHolder latLon = new LatLonHolder();
373
374 if (pattern.matches("Ro?,?Ro?")) {
375 setLatLonObj(latLon,
376 params[0], ZERO, ZERO, "N",
377 params[1], ZERO, ZERO, "E");
378 } else if (pattern.matches("xRo?,?xRo?")) {
379 setLatLonObj(latLon,
380 params[1], ZERO, ZERO, params[0],
381 params[3], ZERO, ZERO, params[2]);
382 } else if (pattern.matches("Ro?x,?Ro?x")) {
383 setLatLonObj(latLon,
384 params[0], ZERO, ZERO, params[1],
385 params[2], ZERO, ZERO, params[3]);
386 } else if (pattern.matches("Zo[RZ]'?,?Zo[RZ]'?|Z[RZ],?Z[RZ]")) {
387 setLatLonObj(latLon,
388 params[0], params[1], ZERO, "N",
389 params[2], params[3], ZERO, "E");
390 } else if (pattern.matches("xZo[RZ]'?,?xZo[RZ]'?|xZo?[RZ],?xZo?[RZ]")) {
391 setLatLonObj(latLon,
392 params[1], params[2], ZERO, params[0],
393 params[4], params[5], ZERO, params[3]);
394 } else if (pattern.matches("Zo[RZ]'?x,?Zo[RZ]'?x|Zo?[RZ]x,?Zo?[RZ]x")) {
395 setLatLonObj(latLon,
396 params[0], params[1], ZERO, params[2],
397 params[3], params[4], ZERO, params[5]);
398 } else if (pattern.matches("ZoZ'[RZ]\"?x,?ZoZ'[RZ]\"?x|ZZ[RZ]x,?ZZ[RZ]x")) {
399 setLatLonObj(latLon,
400 params[0], params[1], params[2], params[3],
401 params[4], params[5], params[6], params[7]);
402 } else if (pattern.matches("xZoZ'[RZ]\"?,?xZoZ'[RZ]\"?|xZZ[RZ],?xZZ[RZ]")) {
403 setLatLonObj(latLon,
404 params[1], params[2], params[3], params[0],
405 params[5], params[6], params[7], params[4]);
406 } else if (pattern.matches("ZZ[RZ],?ZZ[RZ]")) {
407 setLatLonObj(latLon,
408 params[0], params[1], params[2], "N",
409 params[3], params[4], params[5], "E");
410 } else {
411 throw new IllegalArgumentException("invalid format: " + pattern);
412 }
413
414 return new LatLon(latLon.lat, latLon.lon);
415 }
416
417 private static EastNorth parseEastNorth(String s) {
418 String[] en = s.split("[;, ]+");
419 if (en.length != 2) return null;
420 try {
421 double east = Double.parseDouble(en[0]);
422 double north = Double.parseDouble(en[1]);
423 return new EastNorth(east, north);
424 } catch (NumberFormatException nfe) {
425 return null;
426 }
427 }
428
429 private static class LatLonHolder {
430 double lat, lon;
431 }
432
433 private static void setLatLonObj(final LatLonHolder latLon,
434 final Object coord1deg, final Object coord1min, final Object coord1sec, final Object card1,
435 final Object coord2deg, final Object coord2min, final Object coord2sec, final Object card2) {
436
437 setLatLon(latLon,
438 (Double) coord1deg, (Double) coord1min, (Double) coord1sec, (String) card1,
439 (Double) coord2deg, (Double) coord2min, (Double) coord2sec, (String) card2);
440 }
441
442 private static void setLatLon(final LatLonHolder latLon,
443 final double coord1deg, final double coord1min, final double coord1sec, final String card1,
444 final double coord2deg, final double coord2min, final double coord2sec, final String card2) {
445
446 setLatLon(latLon, coord1deg, coord1min, coord1sec, card1);
447 setLatLon(latLon, coord2deg, coord2min, coord2sec, card2);
448 }
449
450 private static void setLatLon(final LatLonHolder latLon, final double coordDeg, final double coordMin, final double coordSec, final String card) {
451 if (coordDeg < -180 || coordDeg > 180 || coordMin < 0 || coordMin >= 60 || coordSec < 0 || coordSec > 60) {
452 throw new IllegalArgumentException("out of range");
453 }
454
455 double coord = (coordDeg < 0 ? -1 : 1) * (Math.abs(coordDeg) + coordMin / 60 + coordSec / 3600);
456 coord = card.equals("N") || card.equals("E") ? coord : -coord;
457 if (card.equals("N") || card.equals("S")) {
458 latLon.lat = coord;
459 } else {
460 latLon.lon = coord;
461 }
462 }
463
464 public String getLatLonText() {
465 return tfLatLon.getText();
466 }
467
468 public void setLatLonText(String text) {
469 tfLatLon.setText(text);
470 }
471
472 public String getEastNorthText() {
473 return tfEastNorth.getText();
474 }
475
476 public void setEastNorthText(String text) {
477 tfEastNorth.setText(text);
478 }
479
480}
Note: See TracBrowser for help on using the repository browser.