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

Last change on this file since 941 was 941, checked in by framm, 16 years ago

+ fixed Osm->GPX conversion to retain timestamps.

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