source: josm/trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java@ 999

Last change on this file since 999 was 999, checked in by stoecker, 16 years ago

close bug #1588, code cleanup by bruce89

  • Property svn:eol-style set to native
File size: 35.7 KB
Line 
1// License: GPL. See LICENSE file for details.
2
3package org.openstreetmap.josm.gui.layer;
4
5import static org.openstreetmap.josm.tools.I18n.marktr;
6import static org.openstreetmap.josm.tools.I18n.tr;
7import static org.openstreetmap.josm.tools.I18n.trn;
8
9import java.awt.Color;
10import java.awt.Component;
11import java.awt.Graphics;
12import java.awt.GridBagLayout;
13import java.awt.Point;
14import java.awt.event.ActionEvent;
15import java.awt.event.ActionListener;
16import java.io.BufferedReader;
17import java.io.File;
18import java.io.FileInputStream;
19import java.io.FileOutputStream;
20import java.io.InputStreamReader;
21import java.net.URL;
22import java.net.URLConnection;
23import java.net.UnknownHostException;
24import java.util.ArrayList;
25import java.util.Collection;
26import java.util.Collections;
27import java.util.Comparator;
28import java.util.LinkedList;
29import java.util.Date;
30import java.text.DateFormat;
31import java.text.DecimalFormat;
32
33import javax.swing.AbstractAction;
34import javax.swing.Box;
35import javax.swing.ButtonGroup;
36import javax.swing.Icon;
37import javax.swing.JCheckBox;
38import javax.swing.JColorChooser;
39import javax.swing.JFileChooser;
40import javax.swing.JLabel;
41import javax.swing.JMenuItem;
42import javax.swing.JOptionPane;
43import javax.swing.JPanel;
44import javax.swing.JRadioButton;
45import javax.swing.JSeparator;
46import javax.swing.JTextField;
47import javax.swing.filechooser.FileFilter;
48
49import org.openstreetmap.josm.Main;
50import org.openstreetmap.josm.actions.RenameLayerAction;
51import org.openstreetmap.josm.actions.SaveAction;
52import org.openstreetmap.josm.actions.SaveAsAction;
53import org.openstreetmap.josm.data.coor.EastNorth;
54import org.openstreetmap.josm.data.gpx.GpxData;
55import org.openstreetmap.josm.data.gpx.GpxRoute;
56import org.openstreetmap.josm.data.gpx.GpxTrack;
57import org.openstreetmap.josm.data.gpx.WayPoint;
58import org.openstreetmap.josm.data.osm.DataSet;
59import org.openstreetmap.josm.data.osm.Node;
60import org.openstreetmap.josm.data.osm.Way;
61import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
62import org.openstreetmap.josm.gui.MapView;
63import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
64import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
65import org.openstreetmap.josm.gui.layer.markerlayer.AudioMarker;
66import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
67import org.openstreetmap.josm.io.GpxWriter;
68import org.openstreetmap.josm.io.MultiPartFormOutputStream;
69import org.openstreetmap.josm.tools.DontShowAgainInfo;
70import org.openstreetmap.josm.tools.GBC;
71import org.openstreetmap.josm.tools.ImageProvider;
72import org.openstreetmap.josm.tools.UrlLabel;
73
74public class GpxLayer extends Layer {
75 public GpxData data;
76 private final GpxLayer me;
77 protected static final double PHI = Math.toRadians(15);
78 private boolean computeCacheInSync;
79 private int computeCacheMaxLineLengthUsed;
80 private Color computeCacheColorUsed;
81 private boolean computeCacheColored;
82
83 public GpxLayer(GpxData d) {
84 super((String) d.attr.get("name"));
85 data = d;
86 me = this;
87 computeCacheInSync = false;
88 }
89
90 public GpxLayer(GpxData d, String name) {
91 this(d);
92 this.name = name;
93 }
94
95 @Override public Icon getIcon() {
96 return ImageProvider.get("layer", "gpx_small");
97 }
98
99 @Override public Object getInfoComponent() {
100 return getToolTipText();
101 }
102
103 @Override public Component[] getMenuEntries() {
104 JMenuItem line = new JMenuItem(tr("Customize line drawing"), ImageProvider.get("mapmode/addsegment"));
105 line.addActionListener(new ActionListener() {
106 public void actionPerformed(ActionEvent e) {
107 JRadioButton[] r = new JRadioButton[3];
108 r[0] = new JRadioButton(tr("Use global settings."));
109 r[1] = new JRadioButton(tr("Draw lines between points for this layer."));
110 r[2] = new JRadioButton(tr("Do not draw lines between points for this layer."));
111 ButtonGroup group = new ButtonGroup();
112 Box panel = Box.createVerticalBox();
113 for (JRadioButton b : r) {
114 group.add(b);
115 panel.add(b);
116 }
117 String propName = "draw.rawgps.lines.layer "+name;
118 if (Main.pref.hasKey(propName))
119 group.setSelected(r[Main.pref.getBoolean(propName) ? 1:2].getModel(), true);
120 else
121 group.setSelected(r[0].getModel(), true);
122 int answer = JOptionPane.showConfirmDialog(Main.parent, panel, tr("Select line drawing options"), JOptionPane.OK_CANCEL_OPTION);
123 if (answer == JOptionPane.CANCEL_OPTION)
124 return;
125 if (group.getSelection() == r[0].getModel())
126 Main.pref.put(propName, null);
127 else
128 Main.pref.put(propName, group.getSelection() == r[1].getModel());
129 Main.map.repaint();
130 }
131 });
132
133 JMenuItem color = new JMenuItem(tr("Customize Color"), ImageProvider.get("colorchooser"));
134 color.putClientProperty("help", "Action/LayerCustomizeColor");
135 color.addActionListener(new ActionListener() {
136 public void actionPerformed(ActionEvent e) {
137 JColorChooser c = new JColorChooser(Main.pref.getColor(marktr("gps point"), "layer "+name, Color.gray));
138 Object[] options = new Object[]{tr("OK"), tr("Cancel"), tr("Default")};
139 int answer = JOptionPane.showOptionDialog(Main.parent, c, tr("Choose a color"), JOptionPane.OK_CANCEL_OPTION,
140 JOptionPane.PLAIN_MESSAGE, null, options, options[0]);
141 switch (answer) {
142 case 0:
143 Main.pref.putColor("layer "+name, c.getColor());
144 break;
145 case 1:
146 return;
147 case 2:
148 Main.pref.putColor("layer "+name, null);
149 break;
150 }
151 Main.map.repaint();
152 }
153 });
154
155 JMenuItem markersFromNamedTrackpoints = new JMenuItem(tr("Markers From Named Points"), ImageProvider.get("addmarkers"));
156 markersFromNamedTrackpoints.putClientProperty("help", "Action/MarkersFromNamedPoints");
157 markersFromNamedTrackpoints.addActionListener(new ActionListener() {
158 public void actionPerformed(ActionEvent e) {
159 GpxData namedTrackPoints = new GpxData();
160 for (GpxTrack track : data.tracks)
161 for (Collection<WayPoint> seg : track.trackSegs)
162 for (WayPoint point : seg)
163 if (point.attr.containsKey("name") || point.attr.containsKey("desc"))
164 namedTrackPoints.waypoints.add(point);
165
166 MarkerLayer ml = new MarkerLayer(namedTrackPoints, tr("Named Trackpoints from {0}", name), associatedFile, me);
167 if (ml.data.size() > 0) {
168 Main.main.addLayer(ml);
169 }
170 }
171 });
172
173 JMenuItem importAudio = new JMenuItem(tr("Import Audio"), ImageProvider.get("importaudio"));
174 importAudio.putClientProperty("help", "ImportAudio");
175 importAudio.addActionListener(new ActionListener() {
176 public void actionPerformed(ActionEvent e) {
177 String dir = Main.pref.get("markers.lastaudiodirectory");
178 JFileChooser fc = new JFileChooser(dir);
179 fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
180 fc.setAcceptAllFileFilterUsed(false);
181 fc.setFileFilter(new FileFilter(){
182 @Override public boolean accept(File f) {
183 return f.isDirectory() || f.getName().toLowerCase().endsWith(".wav");
184 }
185 @Override public String getDescription() {
186 return tr("Wave Audio files (*.wav)");
187 }
188 });
189 fc.setMultiSelectionEnabled(true);
190 if(fc.showOpenDialog(Main.parent) == JFileChooser.APPROVE_OPTION) {
191 if (!fc.getCurrentDirectory().getAbsolutePath().equals(dir))
192 Main.pref.put("markers.lastaudiodirectory", fc.getCurrentDirectory().getAbsolutePath());
193
194 // FIXME: properly support multi-selection here.
195 // Calling importAudio several times just creates N maker layers, which
196 // is sub-optimal.
197 File sel[] = fc.getSelectedFiles();
198 if(sel != null)
199 for (int i = 0; i < sel.length; i++)
200 importAudio(sel[i]);
201
202 Main.map.repaint();
203 }
204 }
205 });
206
207 JMenuItem tagimage = new JMenuItem(tr("Import images"), ImageProvider.get("tagimages"));
208 tagimage.putClientProperty("help", "Action/ImportImages");
209 tagimage.addActionListener(new ActionListener() {
210 public void actionPerformed(ActionEvent e) {
211 JFileChooser fc = new JFileChooser(Main.pref.get("tagimages.lastdirectory"));
212 fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
213 fc.setMultiSelectionEnabled(true);
214 fc.setAcceptAllFileFilterUsed(false);
215 fc.setFileFilter(new FileFilter() {
216 @Override public boolean accept(File f) {
217 return f.isDirectory() || f.getName().toLowerCase().endsWith(".jpg");
218 }
219 @Override public String getDescription() {
220 return tr("JPEG images (*.jpg)");
221 }
222 });
223 fc.showOpenDialog(Main.parent);
224 File[] sel = fc.getSelectedFiles();
225 if (sel == null || sel.length == 0)
226 return;
227 LinkedList<File> files = new LinkedList<File>();
228 addRecursiveFiles(files, sel);
229 Main.pref.put("tagimages.lastdirectory", fc.getCurrentDirectory().getPath());
230 GeoImageLayer.create(files, GpxLayer.this);
231 }
232
233 private void addRecursiveFiles(LinkedList<File> files, File[] sel) {
234 for (File f : sel) {
235 if (f.isDirectory())
236 addRecursiveFiles(files, f.listFiles());
237 else if (f.getName().toLowerCase().endsWith(".jpg"))
238 files.add(f);
239 }
240 }
241 });
242
243 if (Main.applet)
244 return new Component[] {
245 new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
246 new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
247 new JSeparator(),
248 color,
249 line,
250 new JMenuItem(new ConvertToDataLayerAction()),
251 new JSeparator(),
252 new JMenuItem(new RenameLayerAction(associatedFile, this)),
253 new JSeparator(),
254 new JMenuItem(new LayerListPopup.InfoAction(this))};
255 return new Component[] {
256 new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
257 new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
258 new JSeparator(),
259 new JMenuItem(new SaveAction(this)),
260 new JMenuItem(new SaveAsAction(this)),
261 // new JMenuItem(new UploadTraceAction()),
262 color,
263 line,
264 tagimage,
265 importAudio,
266 markersFromNamedTrackpoints,
267 new JMenuItem(new ConvertToDataLayerAction()),
268 new JSeparator(),
269 new JMenuItem(new RenameLayerAction(associatedFile, this)),
270 new JSeparator(),
271 new JMenuItem(new LayerListPopup.InfoAction(this))};
272 }
273
274 @Override public String getToolTipText() {
275 StringBuilder info = new StringBuilder().append("<html>");
276
277 info.append(trn("{0} track, ", "{0} tracks, ",
278 data.tracks.size(), data.tracks.size())).append(trn("{0} route, ", "{0} routes, ",
279 data.routes.size(), data.routes.size())).append(trn("{0} waypoint", "{0} waypoints",
280 data.waypoints.size(), data.waypoints.size())).append("<br>");
281
282 if (data.attr.containsKey("name"))
283 info.append(tr("Name: {0}", data.attr.get("name"))).append("<br>");
284
285 if (data.attr.containsKey("desc"))
286 info.append(tr("Description: {0}", data.attr.get("desc"))).append("<br>");
287
288 if(data.tracks.size() > 0){
289 boolean first = true;
290 WayPoint earliest = null, latest = null;
291
292 for(GpxTrack trk: data.tracks){
293 for(Collection<WayPoint> seg:trk.trackSegs){
294 for(WayPoint pnt:seg){
295 if(first){
296 latest = earliest = pnt;
297 first = false;
298 }else{
299 if(pnt.compareTo(earliest) < 0){
300 earliest = pnt;
301 }else{
302 latest = pnt;
303 }
304 }
305 }
306 }
307 }
308 if (earliest != null && latest != null) {
309 DateFormat df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT);
310 info.append(tr("Timespan: ") + df.format(new Date((long)(earliest.time * 1000))) + " - "
311 + df.format(new Date((long)(latest.time * 1000))));
312 int diff = (int)(latest.time - earliest.time);
313 info.append(" (" + (diff / 3600) + ":" + ((diff % 3600)/60) + ")");
314 info.append("<br>");
315 }
316 }
317 info.append(tr("Length: ") + new DecimalFormat("#0.00").format(data.length() / 1000) + "km");
318 info.append("<br>");
319
320 return info.append("</html>").toString();
321 }
322
323 @Override public boolean isMergable(Layer other) {
324 return other instanceof GpxLayer;
325 }
326
327 @Override public void mergeFrom(Layer from) {
328 data.mergeFrom(((GpxLayer)from).data);
329 computeCacheInSync = false;
330 }
331
332 private static Color[] colors = new Color[256];
333 static {
334 for (int i = 0; i < colors.length; i++) {
335 colors[i] = Color.getHSBColor(i/300.0f, 1, 1);
336 }
337 }
338
339 // lookup array to draw arrows without doing any math
340 private static int ll0 = 9;
341 private static int sl4 = 5;
342 private static int sl9 = 3;
343 private static int[][] dir = {
344 {+sl4,+ll0,+ll0,+sl4},
345 {-sl9,+ll0,+sl9,+ll0},
346 {-ll0,+sl4,-sl4,+ll0},
347 {-ll0,-sl9,-ll0,+sl9},
348 {-sl4,-ll0,-ll0,-sl4},
349 {+sl9,-ll0,-sl9,-ll0},
350 {+ll0,-sl4,+sl4,-ll0},
351 {+ll0,+sl9,+ll0,-sl9},
352 {+sl4,+ll0,+ll0,+sl4},
353 {-sl9,+ll0,+sl9,+ll0},
354 {-ll0,+sl4,-sl4,+ll0},
355 {-ll0,-sl9,-ll0,+sl9}
356 };
357
358 @Override public void paint(Graphics g, MapView mv) {
359
360 /****************************************************************
361 ********** STEP 1 - GET CONFIG VALUES **************************
362 ****************************************************************/
363 // Long startTime = System.currentTimeMillis();
364 Color neutralColor = Main.pref.getColor(marktr("gps point"), "layer "+name, Color.GRAY);
365 boolean forceLines = Main.pref.getBoolean("draw.rawgps.lines.force"); // also draw lines between points belonging to different segments
366 boolean direction = Main.pref.getBoolean("draw.rawgps.direction"); // draw direction arrows on the lines
367 int maxLineLength = -1;
368 try {
369 maxLineLength = Integer.parseInt(Main.pref.get("draw.rawgps.max-line-length", "-1")); // don't draw lines if longer than x meters
370 } catch (java.lang.NumberFormatException e) {
371 Main.pref.put("draw.rawgps.max-line-length", "-1");
372 }
373 boolean lines = Main.pref.getBoolean("draw.rawgps.lines"); // draw line between points, global setting
374 String linesKey = "draw.rawgps.lines.layer "+name;
375 if (Main.pref.hasKey(linesKey))
376 lines = Main.pref.getBoolean(linesKey); // draw lines, per-layer setting
377 boolean large = Main.pref.getBoolean("draw.rawgps.large"); // paint large dots for points
378 boolean colored = Main.pref.getBoolean("draw.rawgps.colors"); // color the lines
379 boolean alternatedirection = Main.pref.getBoolean("draw.rawgps.alternatedirection"); // paint direction arrow with alternate math. may be faster
380
381 /****************************************************************
382 ********** STEP 2a - CHECK CACHE VALIDITY **********************
383 ****************************************************************/
384 if (computeCacheInSync && ((computeCacheMaxLineLengthUsed != maxLineLength) ||
385 (!neutralColor.equals(computeCacheColorUsed)) ||
386 (computeCacheColored != colored))) {
387// System.out.println("(re-)computing gpx line styles, reason: CCIS=" + computeCacheInSync + " CCMLLU=" + (computeCacheMaxLineLengthUsed != maxLineLength) + " CCCU=" + (!neutralColor.equals(computeCacheColorUsed)) + " CCC=" + (computeCacheColored != colored));
388 computeCacheMaxLineLengthUsed = maxLineLength;
389 computeCacheInSync = false;
390 computeCacheColorUsed = neutralColor;
391 computeCacheColored = colored;
392 }
393
394 /****************************************************************
395 ********** STEP 2b - RE-COMPUTE CACHE DATA *********************
396 ****************************************************************/
397 if (!computeCacheInSync) { // don't compute if the cache is good
398 WayPoint oldWp = null;
399 for (GpxTrack trk : data.tracks) {
400 if (!forceLines) { // don't draw lines between segments, unless forced to
401 oldWp = null;
402 }
403 for (Collection<WayPoint> segment : trk.trackSegs) {
404 for (WayPoint trkPnt : segment) {
405 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon())) {
406 continue;
407 }
408 if (oldWp != null) {
409 double dist = trkPnt.latlon.greatCircleDistance(oldWp.latlon);
410 double dtime = trkPnt.time - oldWp.time;
411 double vel = dist/dtime;
412
413 if (!colored) {
414 trkPnt.speedLineColor = neutralColor;
415 } else if (dtime <= 0 || vel < 0 || vel > 36) { // attn: bad case first
416 trkPnt.speedLineColor = colors[255];
417 } else {
418 trkPnt.speedLineColor = colors[(int) (7*vel)];
419 }
420 if (maxLineLength == -1 || dist <= maxLineLength) {
421 trkPnt.drawLine = true;
422 trkPnt.dir = (int)(Math.atan2(-trkPnt.eastNorth.north()+oldWp.eastNorth.north(), trkPnt.eastNorth.east()-oldWp.eastNorth.east()) / Math.PI * 4 + 3.5); // crude but works
423 } else {
424 trkPnt.drawLine = false;
425 }
426 } else { // make sure we reset outdated data
427 trkPnt.speedLineColor = colors[255];
428 trkPnt.drawLine = false;
429 }
430 oldWp = trkPnt;
431 }
432 }
433 }
434 computeCacheInSync = true;
435 }
436
437 /****************************************************************
438 ********** STEP 3a - DRAW LINES ********************************
439 ****************************************************************/
440 if (lines) {
441 Point old = null;
442 for (GpxTrack trk : data.tracks) {
443 for (Collection<WayPoint> segment : trk.trackSegs) {
444 for (WayPoint trkPnt : segment) {
445 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
446 continue;
447 Point screen = mv.getPoint(trkPnt.eastNorth);
448 if (trkPnt.drawLine) {
449 // skip points that are on the same screenposition
450 if (old != null && ((old.x != screen.x) || (old.y != screen.y))) {
451 g.setColor(trkPnt.speedLineColor);
452 g.drawLine(old.x, old.y, screen.x, screen.y);
453 }
454 }
455 old = screen;
456 } // end for trkpnt
457 } // end for segment
458 } // end for trk
459 } // end if lines
460
461 /****************************************************************
462 ********** STEP 3b - DRAW NICE ARROWS **************************
463 ****************************************************************/
464 if (lines && direction && !alternatedirection) {
465 Point old = null;
466 for (GpxTrack trk : data.tracks) {
467 for (Collection<WayPoint> segment : trk.trackSegs) {
468 for (WayPoint trkPnt : segment) {
469 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
470 continue;
471 if (trkPnt.drawLine) {
472 Point screen = mv.getPoint(trkPnt.eastNorth);
473 // skip points that are on the same screenposition
474 if (old != null && ((old.x != screen.x) || (old.y != screen.y))) {
475 g.setColor(trkPnt.speedLineColor);
476 double t = Math.atan2(screen.y-old.y, screen.x-old.x) + Math.PI;
477 g.drawLine(screen.x,screen.y, (int)(screen.x + 10*Math.cos(t-PHI)), (int)(screen.y
478 + 10*Math.sin(t-PHI)));
479 g.drawLine(screen.x,screen.y, (int)(screen.x + 10*Math.cos(t+PHI)), (int)(screen.y
480 + 10*Math.sin(t+PHI)));
481 }
482 old = screen;
483 }
484 } // end for trkpnt
485 } // end for segment
486 } // end for trk
487 } // end if lines
488
489 /****************************************************************
490 ********** STEP 3c - DRAW FAST ARROWS **************************
491 ****************************************************************/
492 if (lines && direction && alternatedirection) {
493 Point old = null;
494 for (GpxTrack trk : data.tracks) {
495 for (Collection<WayPoint> segment : trk.trackSegs) {
496 for (WayPoint trkPnt : segment) {
497 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
498 continue;
499 if (trkPnt.drawLine) {
500 Point screen = mv.getPoint(trkPnt.eastNorth);
501 // skip points that are on the same screenposition
502 if (old != null && ((old.x != screen.x) || (old.y != screen.y))) {
503 g.setColor(trkPnt.speedLineColor);
504 g.drawLine(screen.x, screen.y, screen.x + dir[trkPnt.dir][0], screen.y + dir[trkPnt.dir][1]);
505 g.drawLine(screen.x, screen.y, screen.x + dir[trkPnt.dir][2], screen.y + dir[trkPnt.dir][3]);
506 }
507 old = screen;
508 }
509 } // end for trkpnt
510 } // end for segment
511 } // end for trk
512 } // end if lines
513
514 /****************************************************************
515 ********** STEP 3d - DRAW LARGE POINTS *************************
516 ****************************************************************/
517 if (large) {
518 g.setColor(neutralColor);
519 for (GpxTrack trk : data.tracks) {
520 for (Collection<WayPoint> segment : trk.trackSegs) {
521 for (WayPoint trkPnt : segment) {
522 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
523 continue;
524 Point screen = mv.getPoint(trkPnt.eastNorth);
525 g.fillRect(screen.x-1, screen.y-1, 3, 3);
526 } // end for trkpnt
527 } // end for segment
528 } // end for trk
529 } // end if large
530
531 /****************************************************************
532 ********** STEP 3e - DRAW SMALL POINTS FOR LINES ***************
533 ****************************************************************/
534 if (!large && lines){
535 g.setColor(neutralColor);
536 for (GpxTrack trk : data.tracks) {
537 for (Collection<WayPoint> segment : trk.trackSegs) {
538 for (WayPoint trkPnt : segment) {
539 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
540 continue;
541 if (!trkPnt.drawLine) {
542 Point screen = mv.getPoint(trkPnt.eastNorth);
543 g.drawRect(screen.x, screen.y, 0, 0);
544 }
545 } // end for trkpnt
546 } // end for segment
547 } // end for trk
548 } // end if large
549
550 /****************************************************************
551 ********** STEP 3f - DRAW SMALL POINTS INSTEAD OF LINES ********
552 ****************************************************************/
553 if (!large && !lines){
554 g.setColor(neutralColor);
555 for (GpxTrack trk : data.tracks) {
556 for (Collection<WayPoint> segment : trk.trackSegs) {
557 for (WayPoint trkPnt : segment) {
558 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
559 continue;
560 Point screen = mv.getPoint(trkPnt.eastNorth);
561 g.drawRect(screen.x, screen.y, 0, 0);
562 } // end for trkpnt
563 } // end for segment
564 } // end for trk
565 } // end if large
566
567 //Long duration = System.currentTimeMillis() - startTime;
568 //System.out.println(duration);
569 } // end paint
570
571 @Override public void visitBoundingBox(BoundingXYVisitor v) {
572 for (WayPoint p : data.waypoints)
573 v.visit(p.eastNorth);
574
575 for (GpxRoute rte : data.routes) {
576 Collection<WayPoint> r = rte.routePoints;
577 for (WayPoint p : r) {
578 v.visit(p.eastNorth);
579 }
580 }
581
582 for (GpxTrack trk : data.tracks) {
583 for (Collection<WayPoint> seg : trk.trackSegs) {
584 for (WayPoint p : seg) {
585 v.visit(p.eastNorth);
586 }
587 }
588 }
589 }
590
591 public class UploadTraceAction extends AbstractAction {
592 public UploadTraceAction() {
593 super(tr("Upload this trace..."), ImageProvider.get("uploadtrace"));
594 }
595 public void actionPerformed(ActionEvent e) {
596 JPanel msg = new JPanel(new GridBagLayout());
597 msg.add(new JLabel(tr("<html>This functionality has been added only recently. Please<br>"+
598 "use with care and check if it works as expected.</html>")), GBC.eop());
599 ButtonGroup bg = new ButtonGroup();
600 JRadioButton c1 = null;
601 JRadioButton c2 = null;
602
603 //TODO
604 //check whether data comes from server
605 //check whether data changed sind last save/open
606
607 c1 = new JRadioButton(tr("Upload track filtered by JOSM"), true);
608 c2 = new JRadioButton(tr("Upload raw file: "), false);
609 c2.setEnabled(false);
610 c1.setEnabled(false);
611 bg.add(c1);
612 bg.add(c2);
613
614 msg.add(c1, GBC.eol());
615 msg.add(c2, GBC.eop());
616
617
618 JLabel description = new JLabel((String) data.attr.get("desc"));
619 JTextField tags = new JTextField();
620 tags.setText((String) data.attr.get("keywords"));
621 msg.add(new JLabel(tr("Description:")), GBC.std());
622 msg.add(description, GBC.eol().fill(GBC.HORIZONTAL));
623 msg.add(new JLabel(tr("Tags (keywords in GPX):")), GBC.std());
624 msg.add(tags, GBC.eol().fill(GBC.HORIZONTAL));
625 JCheckBox c3 = new JCheckBox("public");
626 msg.add(c3, GBC.eop());
627 msg.add(new JLabel("Please ensure that you don't upload your traces twice."), GBC.eop());
628
629 int answer = JOptionPane.showConfirmDialog(Main.parent, msg, tr("GPX-Upload"), JOptionPane.OK_CANCEL_OPTION);
630 if (answer == JOptionPane.OK_OPTION)
631 {
632 try {
633 String version = Main.pref.get("osm-server.version", "0.5");
634 URL url = new URL(Main.pref.get("osm-server.url") + "/" + version + "/gpx/create");
635
636 // create a boundary string
637 String boundary = MultiPartFormOutputStream.createBoundary();
638 URLConnection urlConn = MultiPartFormOutputStream.createConnection(url);
639 urlConn.setRequestProperty("Accept", "*/*");
640 urlConn.setRequestProperty("Content-Type", MultiPartFormOutputStream.getContentType(boundary));
641 // set some other request headers...
642 urlConn.setRequestProperty("Connection", "Keep-Alive");
643 urlConn.setRequestProperty("Cache-Control", "no-cache");
644 // no need to connect cuz getOutputStream() does it
645 MultiPartFormOutputStream out = new MultiPartFormOutputStream(urlConn.getOutputStream(), boundary);
646 out.writeField("description", description.getText());
647 out.writeField("tags", tags.getText());
648 out.writeField("public", (c3.getSelectedObjects() != null) ? "1" : "0");
649 // upload a file
650 // out.writeFile("gpx_file", "text/xml", associatedFile);
651 // can also write bytes directly
652 // out.writeFile("myFile", "text/plain", "C:\\test.txt",
653 // "This is some file text.".getBytes("ASCII"));
654 File tmp = File.createTempFile("josm", "tmp.gpx");
655 FileOutputStream outs = new FileOutputStream(tmp);
656 new GpxWriter(outs).write(data);
657 outs.close();
658 FileInputStream ins = new FileInputStream(tmp);
659 new GpxWriter(System.out).write(data);
660 out.writeFile("gpx_file", "text/xml", data.storageFile.getName(), ins);
661 out.close();
662 tmp.delete();
663 // read response from server
664 BufferedReader in = new BufferedReader(new InputStreamReader(urlConn.getInputStream()));
665 String line = "";
666 while((line = in.readLine()) != null) {
667 System.out.println(line);
668 }
669 in.close();
670
671 //TODO check response
672 /* int retCode = urlConn.getResponseCode();
673 System.out.println("got return: " + retCode);
674 String retMsg = urlConn.getResponseMessage();
675 urlConn.disconnect();
676 if (retCode != 200) {
677 // Look for a detailed error message from the server
678 if (urlConn.getHeaderField("Error") != null)
679 retMsg += "\n" + urlConn.getHeaderField("Error");
680
681 // Report our error
682 ByteArrayOutputStream o = new ByteArrayOutputStream();
683 System.out.println(new String(o.toByteArray(), "UTF-8").toString());
684 throw new RuntimeException(retCode+" "+retMsg);
685 }
686 */
687 } catch (UnknownHostException ex) {
688 throw new RuntimeException(tr("Unknown host")+": "+ex.getMessage(), ex);
689 } catch (Exception ex) {
690 //if (cancel)
691 // return; // assume cancel
692 if (ex instanceof RuntimeException)
693 throw (RuntimeException)ex;
694 throw new RuntimeException(ex.getMessage(), ex);
695 }
696 }
697 }
698 }
699
700 public class ConvertToDataLayerAction extends AbstractAction {
701 public ConvertToDataLayerAction() {
702 super(tr("Convert to data layer"), ImageProvider.get("converttoosm"));
703 }
704 public void actionPerformed(ActionEvent e) {
705 JPanel msg = new JPanel(new GridBagLayout());
706 msg.add(new JLabel(tr("<html>Upload of unprocessed GPS data as map data is considered harmful.<br>If you want to upload traces, look here:")), GBC.eol());
707 msg.add(new UrlLabel(tr("http://www.openstreetmap.org/traces")), GBC.eop());
708 if (!DontShowAgainInfo.show("convert_to_data", msg))
709 return;
710 DataSet ds = new DataSet();
711 for (GpxTrack trk : data.tracks) {
712 for (Collection<WayPoint> segment : trk.trackSegs) {
713 Way w = new Way();
714 for (WayPoint p : segment) {
715 Node n = new Node(p.latlon);
716 String timestr = p.getString("time");
717 if(timestr != null)
718 {
719 timestr = timestr.replace("Z","+00:00");
720 n.timestamp = timestr;
721 }
722 ds.nodes.add(n);
723 w.nodes.add(n);
724 }
725 ds.ways.add(w);
726 }
727 }
728 Main.main.addLayer(new OsmDataLayer(ds, tr("Converted from: {0}", GpxLayer.this.name), null));
729 Main.main.removeLayer(GpxLayer.this);
730 }
731 }
732
733 /**
734 * Makes a new marker layer derived from this GpxLayer containing at least one
735 * audio marker which the given audio file is associated with.
736 * Markers are derived from the following
737 * (a) explict waypoints in the GPX layer, or
738 * (b) named trackpoints in the GPX layer, or
739 * (c) (in future) voice recognised markers in the sound recording
740 * (d) a single marker at the beginning of the track
741 * @param wavFile : the file to be associated with the markers in the new marker layer
742 */
743 private void importAudio(File wavFile) {
744 String uri = "file:".concat(wavFile.getAbsolutePath());
745 MarkerLayer ml = new MarkerLayer(new GpxData(), tr("Audio markers from {0}", name), associatedFile, me);
746
747 Collection<WayPoint> waypoints = new ArrayList<WayPoint>();
748 boolean timedMarkersOmitted = false;
749 boolean untimedMarkersOmitted = false;
750 double snapDistance = Main.pref.getDouble("marker.audiofromuntimedwaypoints.distance", 1.0e-3); /* about 25m */
751
752 // determine time of first point in track
753 double firstTime = -1.0;
754 if (data.tracks != null && ! data.tracks.isEmpty()) {
755 for (GpxTrack track : data.tracks) {
756 if (track.trackSegs == null) continue;
757 for (Collection<WayPoint> seg : track.trackSegs) {
758 for (WayPoint w : seg) {
759 firstTime = w.time;
760 break;
761 }
762 if (firstTime >= 0.0) break;
763 }
764 if (firstTime >= 0.0) break;
765 }
766 }
767 if (firstTime < 0.0) {
768 JOptionPane.showMessageDialog(Main.parent, tr("No GPX track available in layer to associate audio with."));
769 return;
770 }
771
772 // (a) try explicit timestamped waypoints - unless suppressed
773 if (Main.pref.getBoolean("marker.audiofromexplicitwaypoints", true) &&
774 data.waypoints != null && ! data.waypoints.isEmpty())
775 {
776 for (WayPoint w : data.waypoints) {
777 if (w.time > firstTime) {
778 waypoints.add(w);
779 } else if (w.time > 0.0) {
780 timedMarkersOmitted = true;
781 }
782 }
783 }
784
785 // (b) try explicit waypoints without timestamps - unless suppressed
786 if (Main.pref.getBoolean("marker.audiofromuntimedwaypoints", true) &&
787 data.waypoints != null && ! data.waypoints.isEmpty())
788 {
789 for (WayPoint w : data.waypoints) {
790 if (waypoints.contains(w)) { continue; }
791 WayPoint wNear = nearestPointOnTrack(w.eastNorth, snapDistance);
792 if (wNear != null) {
793 WayPoint wc = new WayPoint(w.latlon);
794 wc.time = wNear.time;
795 if (w.attr.containsKey("name")) wc.attr.put("name", w.getString("name"));
796 waypoints.add(wc);
797 } else {
798 untimedMarkersOmitted = true;
799 }
800 }
801 }
802
803 // (c) use explicitly named track points, again unless suppressed
804 if ((Main.pref.getBoolean("marker.audiofromnamedtrackpoints", false)) &&
805 data.tracks != null && ! data.tracks.isEmpty())
806 {
807 for (GpxTrack track : data.tracks) {
808 if (track.trackSegs == null) continue;
809 for (Collection<WayPoint> seg : track.trackSegs) {
810 for (WayPoint w : seg) {
811 if (w.attr.containsKey("name") || w.attr.containsKey("desc")) {
812 waypoints.add(w);
813 }
814 }
815 }
816 }
817 }
818
819 // (d) analyse audio for spoken markers here, in due course
820
821 // (e) simply add a single marker at the start of the track
822 if ((Main.pref.getBoolean("marker.audiofromstart") || waypoints.isEmpty()) &&
823 data.tracks != null && ! data.tracks.isEmpty())
824 {
825 boolean gotOne = false;
826 for (GpxTrack track : data.tracks) {
827 if (track.trackSegs == null) continue;
828 for (Collection<WayPoint> seg : track.trackSegs) {
829 for (WayPoint w : seg) {
830 WayPoint wStart = new WayPoint(w.latlon);
831 wStart.attr.put("name", "start");
832 wStart.time = w.time;
833 waypoints.add(wStart);
834 gotOne = true;
835 break;
836 }
837 if (gotOne) break;
838 }
839 if (gotOne) break;
840 }
841 }
842
843 /* we must have got at least one waypoint now */
844
845 Collections.sort((ArrayList<WayPoint>) waypoints, new Comparator<WayPoint>() {
846 public int compare(WayPoint a, WayPoint b) {
847 return a.time <= b.time ? -1 : 1;
848 }
849 });
850
851 firstTime = -1.0; /* this time of the first waypoint, not first trackpoint */
852 for (WayPoint w : waypoints) {
853 if (firstTime < 0.0) firstTime = w.time;
854 double offset = w.time - firstTime;
855 String name;
856 if (w.attr.containsKey("name"))
857 name = w.getString("name");
858 else if (w.attr.containsKey("desc"))
859 name = w.getString("desc");
860 else
861 name = AudioMarker.inventName(offset);
862 AudioMarker am = AudioMarker.create(w.latlon,
863 name, uri, ml, w.time, offset);
864 ml.data.add(am);
865 }
866 Main.main.addLayer(ml);
867
868 if (timedMarkersOmitted) {
869 JOptionPane.showMessageDialog(Main.parent,
870 tr("Some waypoints with timestamps from before the start of the track were omitted."));
871 }
872 if (untimedMarkersOmitted) {
873 JOptionPane.showMessageDialog(Main.parent,
874 tr("Some waypoints which were too far from the track to sensibly estimate their time were omitted."));
875 }
876 }
877
878 /**
879 * Makes a WayPoint at the projection of point P onto the track providing P is
880 * less than tolerance away from the track
881
882 * @param P : the point to determine the projection for
883 * @param tolerance : must be no further than this from the track
884 * @return the closest point on the track to P, which may be the
885 * first or last point if off the end of a segment, or may be null if
886 * nothing close enough
887 */
888 public WayPoint nearestPointOnTrack(EastNorth P, double tolerance) {
889 /*
890 * assume the coordinates of P are xp,yp, and those of a section of track
891 * between two trackpoints are R=xr,yr and S=xs,ys. Let N be the projected point.
892 *
893 * The equation of RS is Ax + By + C = 0 where
894 * A = ys - yr
895 * B = xr - xs
896 * C = - Axr - Byr
897 *
898 * Also, note that the distance RS^2 is A^2 + B^2
899 *
900 * If RS^2 == 0.0 ignore the degenerate section of track
901 *
902 * PN^2 = (Axp + Byp + C)^2 / RS^2
903 * that is the distance from P to the line
904 *
905 * so if PN^2 is less than PNmin^2 (initialized to tolerance) we can reject
906 * the line; otherwise...
907 * determine if the projected poijnt lies within the bounds of the line:
908 * PR^2 - PN^2 <= RS^2 and PS^2 - PN^2 <= RS^2
909 *
910 * where PR^2 = (xp - xr)^2 + (yp-yr)^2
911 * and PS^2 = (xp - xs)^2 + (yp-ys)^2
912 *
913 * If so, calculate N as
914 * xn = xr + (RN/RS) B
915 * yn = y1 + (RN/RS) A
916 *
917 * where RN = sqrt(PR^2 - PN^2)
918 */
919
920 double PNminsq = tolerance * tolerance;
921 EastNorth bestEN = null;
922 double bestTime = 0.0;
923 double px = P.east();
924 double py = P.north();
925 double rx = 0.0, ry = 0.0, sx, sy, x, y;
926 if (data.tracks == null) return null;
927 for (GpxTrack track : data.tracks) {
928 if (track.trackSegs == null) continue;
929 for (Collection<WayPoint> seg : track.trackSegs) {
930 WayPoint R = null;
931 for (WayPoint S : seg) {
932 if (R == null) {
933 R = S;
934 rx = R.eastNorth.east();
935 ry = R.eastNorth.north();
936 x = px - rx;
937 y = py - ry;
938 double PRsq = x * x + y * y;
939 if (PRsq < PNminsq) {
940 PNminsq = PRsq;
941 bestEN = R.eastNorth;
942 bestTime = R.time;
943 }
944 } else {
945 sx = S.eastNorth.east();
946 sy = S.eastNorth.north();
947 double A = sy - ry;
948 double B = rx - sx;
949 double C = - A * rx - B * ry;
950 double RSsq = A * A + B * B;
951 if (RSsq == 0.0) continue;
952 double PNsq = A * px + B * py + C;
953 PNsq = PNsq * PNsq / RSsq;
954 if (PNsq < PNminsq) {
955 x = px - rx;
956 y = py - ry;
957 double PRsq = x * x + y * y;
958 x = px - sx;
959 y = py - sy;
960 double PSsq = x * x + y * y;
961 if (PRsq - PNsq <= RSsq && PSsq - PNsq <= RSsq) {
962 double RNoverRS = Math.sqrt((PRsq - PNsq)/RSsq);
963 double nx = rx - RNoverRS * B;
964 double ny = ry + RNoverRS * A;
965 bestEN = new EastNorth(nx, ny);
966 bestTime = R.time + RNoverRS * (S.time - R.time);
967 PNminsq = PNsq;
968 }
969 }
970 R = S;
971 rx = sx;
972 ry = sy;
973 }
974 }
975 if (R != null) {
976 /* if there is only one point in the seg, it will do this twice, but no matter */
977 rx = R.eastNorth.east();
978 ry = R.eastNorth.north();
979 x = px - rx;
980 y = py - ry;
981 double PRsq = x * x + y * y;
982 if (PRsq < PNminsq) {
983 PNminsq = PRsq;
984 bestEN = R.eastNorth;
985 bestTime = R.time;
986 }
987 }
988 }
989 }
990 if (bestEN == null) return null;
991 WayPoint best = new WayPoint(Main.proj.eastNorth2latlon(bestEN));
992 best.time = bestTime;
993 return best;
994 }
995}
Note: See TracBrowser for help on using the repository browser.