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

Last change on this file since 1435 was 1435, checked in by stoecker, 15 years ago

remove dead code. patch by xeen

  • Property svn:eol-style set to native
File size: 49.1 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.awt.geom.Area;
17import java.awt.geom.Rectangle2D;
18import java.io.BufferedReader;
19import java.io.File;
20import java.io.FileInputStream;
21import java.io.FileOutputStream;
22import java.io.InputStreamReader;
23import java.net.URL;
24import java.net.URLConnection;
25import java.net.UnknownHostException;
26import java.util.ArrayList;
27import java.util.Collection;
28import java.util.Collections;
29import java.util.Comparator;
30import java.util.LinkedList;
31import java.util.Date;
32import java.util.List;
33import java.text.DateFormat;
34import java.text.DecimalFormat;
35
36import javax.swing.AbstractAction;
37import javax.swing.Box;
38import javax.swing.ButtonGroup;
39import javax.swing.Icon;
40import javax.swing.JCheckBox;
41import javax.swing.JColorChooser;
42import javax.swing.JFileChooser;
43import javax.swing.JLabel;
44import javax.swing.JList;
45import javax.swing.JMenuItem;
46import javax.swing.JOptionPane;
47import javax.swing.JPanel;
48import javax.swing.JRadioButton;
49import javax.swing.JSeparator;
50import javax.swing.JTextField;
51import javax.swing.filechooser.FileFilter;
52
53import org.openstreetmap.josm.Main;
54import org.openstreetmap.josm.actions.RenameLayerAction;
55import org.openstreetmap.josm.actions.SaveAction;
56import org.openstreetmap.josm.actions.SaveAsAction;
57import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
58import org.openstreetmap.josm.data.coor.EastNorth;
59import org.openstreetmap.josm.data.coor.LatLon;
60import org.openstreetmap.josm.data.gpx.GpxData;
61import org.openstreetmap.josm.data.gpx.GpxRoute;
62import org.openstreetmap.josm.data.gpx.GpxTrack;
63import org.openstreetmap.josm.data.gpx.WayPoint;
64import org.openstreetmap.josm.data.osm.DataSet;
65import org.openstreetmap.josm.data.osm.Node;
66import org.openstreetmap.josm.data.osm.Way;
67import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
68import org.openstreetmap.josm.gui.MapView;
69import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
70import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
71import org.openstreetmap.josm.gui.download.DownloadDialog.DownloadTask;
72import org.openstreetmap.josm.gui.layer.markerlayer.AudioMarker;
73import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
74import org.openstreetmap.josm.io.GpxWriter;
75import org.openstreetmap.josm.io.MultiPartFormOutputStream;
76import org.openstreetmap.josm.tools.DontShowAgainInfo;
77import org.openstreetmap.josm.tools.GBC;
78import org.openstreetmap.josm.tools.ImageProvider;
79import org.openstreetmap.josm.tools.UrlLabel;
80
81public class GpxLayer extends Layer {
82 public GpxData data;
83 private final GpxLayer me;
84 protected static final double PHI = Math.toRadians(15);
85 private boolean computeCacheInSync;
86 private int computeCacheMaxLineLengthUsed;
87 private Color computeCacheColorUsed;
88 private colorModes computeCacheColored;
89 private int computeCacheColorTracksTune;
90
91 public GpxLayer(GpxData d) {
92 super((String) d.attr.get("name"));
93 data = d;
94 me = this;
95 computeCacheInSync = false;
96 }
97
98 public GpxLayer(GpxData d, String name) {
99 this(d);
100 this.name = name;
101 }
102
103 @Override public Icon getIcon() {
104 return ImageProvider.get("layer", "gpx_small");
105 }
106
107 @Override public Object getInfoComponent() {
108 return getToolTipText();
109 }
110
111 static public Color getColor(String name)
112 {
113 return Main.pref.getColor(marktr("gps point"), name != null ? "layer "+name : null, Color.gray);
114 }
115
116 @Override public Component[] getMenuEntries() {
117 JMenuItem line = new JMenuItem(tr("Customize line drawing"), ImageProvider.get("mapmode/addsegment"));
118 line.addActionListener(new ActionListener() {
119 public void actionPerformed(ActionEvent e) {
120 JRadioButton[] r = new JRadioButton[3];
121 r[0] = new JRadioButton(tr("Use global settings."));
122 r[1] = new JRadioButton(tr("Draw lines between points for this layer."));
123 r[2] = new JRadioButton(tr("Do not draw lines between points for this layer."));
124 ButtonGroup group = new ButtonGroup();
125 Box panel = Box.createVerticalBox();
126 for (JRadioButton b : r) {
127 group.add(b);
128 panel.add(b);
129 }
130 String propName = "draw.rawgps.lines.layer "+name;
131 if (Main.pref.hasKey(propName))
132 group.setSelected(r[Main.pref.getBoolean(propName) ? 1:2].getModel(), true);
133 else
134 group.setSelected(r[0].getModel(), true);
135 int answer = JOptionPane.showConfirmDialog(Main.parent, panel, tr("Select line drawing options"), JOptionPane.OK_CANCEL_OPTION);
136 if (answer == JOptionPane.CANCEL_OPTION)
137 return;
138 if (group.getSelection() == r[0].getModel())
139 Main.pref.put(propName, null);
140 else
141 Main.pref.put(propName, group.getSelection() == r[1].getModel());
142 Main.map.repaint();
143 }
144 });
145
146 JMenuItem color = new JMenuItem(tr("Customize Color"), ImageProvider.get("colorchooser"));
147 color.putClientProperty("help", "Action/LayerCustomizeColor");
148 color.addActionListener(new ActionListener() {
149 public void actionPerformed(ActionEvent e) {
150 JColorChooser c = new JColorChooser(getColor(name));
151 Object[] options = new Object[]{tr("OK"), tr("Cancel"), tr("Default")};
152 int answer = JOptionPane.showOptionDialog(Main.parent, c, tr("Choose a color"), JOptionPane.OK_CANCEL_OPTION,
153 JOptionPane.PLAIN_MESSAGE, null, options, options[0]);
154 switch (answer) {
155 case 0:
156 Main.pref.putColor("layer "+name, c.getColor());
157 break;
158 case 1:
159 return;
160 case 2:
161 Main.pref.putColor("layer "+name, null);
162 break;
163 }
164 Main.map.repaint();
165 }
166 });
167
168 JMenuItem markersFromNamedTrackpoints = new JMenuItem(tr("Markers From Named Points"), ImageProvider.get("addmarkers"));
169 markersFromNamedTrackpoints.putClientProperty("help", "Action/MarkersFromNamedPoints");
170 markersFromNamedTrackpoints.addActionListener(new ActionListener() {
171 public void actionPerformed(ActionEvent e) {
172 GpxData namedTrackPoints = new GpxData();
173 for (GpxTrack track : data.tracks)
174 for (Collection<WayPoint> seg : track.trackSegs)
175 for (WayPoint point : seg)
176 if (point.attr.containsKey("name") || point.attr.containsKey("desc"))
177 namedTrackPoints.waypoints.add(point);
178
179 MarkerLayer ml = new MarkerLayer(namedTrackPoints, tr("Named Trackpoints from {0}", name), associatedFile, me);
180 if (ml.data.size() > 0) {
181 Main.main.addLayer(ml);
182 }
183 }
184 });
185
186 JMenuItem importAudio = new JMenuItem(tr("Import Audio"), ImageProvider.get("importaudio"));
187 importAudio.putClientProperty("help", "ImportAudio");
188 importAudio.addActionListener(new ActionListener() {
189 public void actionPerformed(ActionEvent e) {
190 String dir = Main.pref.get("markers.lastaudiodirectory");
191 JFileChooser fc = new JFileChooser(dir);
192 fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
193 fc.setAcceptAllFileFilterUsed(false);
194 fc.setFileFilter(new FileFilter(){
195 @Override public boolean accept(File f) {
196 return f.isDirectory() || f.getName().toLowerCase().endsWith(".wav");
197 }
198 @Override public String getDescription() {
199 return tr("Wave Audio files (*.wav)");
200 }
201 });
202 fc.setMultiSelectionEnabled(true);
203 if(fc.showOpenDialog(Main.parent) == JFileChooser.APPROVE_OPTION) {
204 if (!fc.getCurrentDirectory().getAbsolutePath().equals(dir))
205 Main.pref.put("markers.lastaudiodirectory", fc.getCurrentDirectory().getAbsolutePath());
206
207 // FIXME: properly support multi-selection here.
208 // Calling importAudio several times just creates N maker layers, which
209 // is sub-optimal.
210 File sel[] = fc.getSelectedFiles();
211 if(sel != null)
212 for (int i = 0; i < sel.length; i++)
213 importAudio(sel[i]);
214
215 Main.map.repaint();
216 }
217 }
218 });
219
220 JMenuItem tagimage = new JMenuItem(tr("Import images"), ImageProvider.get("tagimages"));
221 tagimage.putClientProperty("help", "Action/ImportImages");
222 tagimage.addActionListener(new ActionListener() {
223 public void actionPerformed(ActionEvent e) {
224 JFileChooser fc = new JFileChooser(Main.pref.get("tagimages.lastdirectory"));
225 fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
226 fc.setMultiSelectionEnabled(true);
227 fc.setAcceptAllFileFilterUsed(false);
228 fc.setFileFilter(new FileFilter() {
229 @Override public boolean accept(File f) {
230 return f.isDirectory() || f.getName().toLowerCase().endsWith(".jpg");
231 }
232 @Override public String getDescription() {
233 return tr("JPEG images (*.jpg)");
234 }
235 });
236 fc.showOpenDialog(Main.parent);
237 File[] sel = fc.getSelectedFiles();
238 if (sel == null || sel.length == 0)
239 return;
240 LinkedList<File> files = new LinkedList<File>();
241 addRecursiveFiles(files, sel);
242 Main.pref.put("tagimages.lastdirectory", fc.getCurrentDirectory().getPath());
243 GeoImageLayer.create(files, GpxLayer.this);
244 }
245
246 private void addRecursiveFiles(LinkedList<File> files, File[] sel) {
247 for (File f : sel) {
248 if (f.isDirectory())
249 addRecursiveFiles(files, f.listFiles());
250 else if (f.getName().toLowerCase().endsWith(".jpg"))
251 files.add(f);
252 }
253 }
254 });
255
256 if (Main.applet)
257 return new Component[] {
258 new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
259 new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
260 new JSeparator(),
261 color,
262 line,
263 new JMenuItem(new ConvertToDataLayerAction()),
264 new JSeparator(),
265 new JMenuItem(new RenameLayerAction(associatedFile, this)),
266 new JSeparator(),
267 new JMenuItem(new LayerListPopup.InfoAction(this))};
268 return new Component[] {
269 new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
270 new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
271 new JSeparator(),
272 new JMenuItem(new SaveAction(this)),
273 new JMenuItem(new SaveAsAction(this)),
274 color,
275 line,
276 tagimage,
277 importAudio,
278 markersFromNamedTrackpoints,
279 new JMenuItem(new ConvertToDataLayerAction()),
280 new JMenuItem(new DownloadAlongTrackAction()),
281 new JSeparator(),
282 new JMenuItem(new RenameLayerAction(associatedFile, this)),
283 new JSeparator(),
284 new JMenuItem(new LayerListPopup.InfoAction(this))};
285 }
286
287 @Override public String getToolTipText() {
288 StringBuilder info = new StringBuilder().append("<html>");
289
290 info.append(trn("{0} track, ", "{0} tracks, ",
291 data.tracks.size(), data.tracks.size())).append(trn("{0} route, ", "{0} routes, ",
292 data.routes.size(), data.routes.size())).append(trn("{0} waypoint", "{0} waypoints",
293 data.waypoints.size(), data.waypoints.size())).append("<br>");
294
295 if (data.attr.containsKey("name"))
296 info.append(tr("Name: {0}", data.attr.get("name"))).append("<br>");
297
298 if (data.attr.containsKey("desc"))
299 info.append(tr("Description: {0}", data.attr.get("desc"))).append("<br>");
300
301 if(data.tracks.size() > 0){
302 boolean first = true;
303 WayPoint earliest = null, latest = null;
304
305 for(GpxTrack trk: data.tracks){
306 for(Collection<WayPoint> seg:trk.trackSegs){
307 for(WayPoint pnt:seg){
308 if(first){
309 latest = earliest = pnt;
310 first = false;
311 }else{
312 if(pnt.compareTo(earliest) < 0){
313 earliest = pnt;
314 }else{
315 latest = pnt;
316 }
317 }
318 }
319 }
320 }
321 if (earliest != null && latest != null) {
322 DateFormat df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT);
323 info.append(tr("Timespan: ") + df.format(new Date((long)(earliest.time * 1000))) + " - "
324 + df.format(new Date((long)(latest.time * 1000))));
325 int diff = (int)(latest.time - earliest.time);
326 info.append(" (" + (diff / 3600) + ":" + ((diff % 3600)/60) + ")");
327 info.append("<br>");
328 }
329 }
330 info.append(tr("Length: ") + new DecimalFormat("#0.00").format(data.length() / 1000) + "km");
331 info.append("<br>");
332
333 return info.append("</html>").toString();
334 }
335
336 @Override public boolean isMergable(Layer other) {
337 return other instanceof GpxLayer;
338 }
339
340 @Override public void mergeFrom(Layer from) {
341 data.mergeFrom(((GpxLayer)from).data);
342 computeCacheInSync = false;
343 }
344
345 private static Color[] colors = new Color[256];
346 static {
347 for (int i = 0; i < colors.length; i++) {
348 colors[i] = Color.getHSBColor(i/300.0f, 1, 1);
349 }
350 }
351
352 // lookup array to draw arrows without doing any math
353 private static int ll0 = 9;
354 private static int sl4 = 5;
355 private static int sl9 = 3;
356 private static int[][] dir = {
357 {+sl4,+ll0,+ll0,+sl4},
358 {-sl9,+ll0,+sl9,+ll0},
359 {-ll0,+sl4,-sl4,+ll0},
360 {-ll0,-sl9,-ll0,+sl9},
361 {-sl4,-ll0,-ll0,-sl4},
362 {+sl9,-ll0,-sl9,-ll0},
363 {+ll0,-sl4,+sl4,-ll0},
364 {+ll0,+sl9,+ll0,-sl9},
365 {+sl4,+ll0,+ll0,+sl4},
366 {-sl9,+ll0,+sl9,+ll0},
367 {-ll0,+sl4,-sl4,+ll0},
368 {-ll0,-sl9,-ll0,+sl9}
369 };
370
371 // the different color modes
372 enum colorModes { none, velocity, dilution }
373
374 @Override public void paint(Graphics g, MapView mv) {
375
376 /****************************************************************
377 ********** STEP 1 - GET CONFIG VALUES **************************
378 ****************************************************************/
379 // Long startTime = System.currentTimeMillis();
380 Color neutralColor = getColor(name);
381 // also draw lines between points belonging to different segments
382 boolean forceLines = Main.pref.getBoolean("draw.rawgps.lines.force");
383 // draw direction arrows on the lines
384 boolean direction = Main.pref.getBoolean("draw.rawgps.direction");
385 // don't draw lines if longer than x meters
386 int maxLineLength = Main.pref.getInteger("draw.rawgps.max-line-length", -1);
387 // draw line between points, global setting
388 boolean lines = Main.pref.getBoolean("draw.rawgps.lines");
389 String linesKey = "draw.rawgps.lines.layer "+name;
390 // draw lines, per-layer setting
391 if (Main.pref.hasKey(linesKey))
392 lines = Main.pref.getBoolean(linesKey);
393 // paint large dots for points
394 boolean large = Main.pref.getBoolean("draw.rawgps.large");
395 // color the lines
396 colorModes colored = colorModes.none;
397 try {
398 colored = colorModes.values()[Main.pref.getInteger("draw.rawgps.colors", 0)];
399 } catch(Exception e) { }
400 // paint direction arrow with alternate math. may be faster
401 boolean alternatedirection = Main.pref.getBoolean("draw.rawgps.alternatedirection");
402 // don't draw arrows nearer to each other than this
403 int delta = Main.pref.getInteger("draw.rawgps.min-arrow-distance", 0);
404 // allows to tweak line coloring for different speed levels.
405 int colorTracksTune = Main.pref.getInteger("draw.rawgps.colorTracksTune", 45);
406 /****************************************************************
407 ********** STEP 2a - CHECK CACHE VALIDITY **********************
408 ****************************************************************/
409 if (computeCacheInSync && ((computeCacheMaxLineLengthUsed != maxLineLength) ||
410 (!neutralColor.equals(computeCacheColorUsed)) ||
411 (computeCacheColored != colored) ||
412 (computeCacheColorTracksTune != colorTracksTune))) {
413// System.out.println("(re-)computing gpx line styles, reason: CCIS=" + computeCacheInSync + " CCMLLU=" + (computeCacheMaxLineLengthUsed != maxLineLength) + " CCCU=" + (!neutralColor.equals(computeCacheColorUsed)) + " CCC=" + (computeCacheColored != colored));
414 computeCacheMaxLineLengthUsed = maxLineLength;
415 computeCacheInSync = false;
416 computeCacheColorUsed = neutralColor;
417 computeCacheColored = colored;
418 computeCacheColorTracksTune = colorTracksTune;
419 }
420
421 /****************************************************************
422 ********** STEP 2b - RE-COMPUTE CACHE DATA *********************
423 ****************************************************************/
424 if (!computeCacheInSync) { // don't compute if the cache is good
425 WayPoint oldWp = null;
426 for (GpxTrack trk : data.tracks) {
427 if (!forceLines) { // don't draw lines between segments, unless forced to
428 oldWp = null;
429 }
430 for (Collection<WayPoint> segment : trk.trackSegs) {
431 for (WayPoint trkPnt : segment) {
432 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon())) {
433 continue;
434 }
435 trkPnt.customColoring = neutralColor;
436 if (oldWp != null) {
437 double dist = trkPnt.latlon.greatCircleDistance(oldWp.latlon);
438
439 switch(colored) {
440 case velocity:
441 double dtime = trkPnt.time - oldWp.time;
442 double vel = dist/dtime;
443 double velColor = vel/colorTracksTune*255;
444 // Bad case first
445 if (dtime <= 0 || vel < 0 || velColor > 255)
446 trkPnt.customColoring = colors[255];
447 else
448 trkPnt.customColoring = colors[(int) (velColor)];
449 break;
450
451 case dilution:
452 if(trkPnt.attr.get("hdop") != null) {
453 float hdop = ((Float)trkPnt.attr.get("hdop")).floatValue();
454 int hdoplvl = Math.round(hdop * 25);
455 // High hdop is bad, but high values in colors are green.
456 // Therefore inverse the logic
457 int hdopcolor = 255 - (hdoplvl > 255 ? 255 : hdoplvl);
458 trkPnt.customColoring = colors[hdopcolor];
459 }
460 break;
461 }
462
463 if (maxLineLength == -1 || dist <= maxLineLength) {
464 trkPnt.drawLine = true;
465 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
466 } else {
467 trkPnt.drawLine = false;
468 }
469 } else { // make sure we reset outdated data
470 trkPnt.drawLine = false;
471 }
472 oldWp = trkPnt;
473 }
474 }
475 }
476 computeCacheInSync = true;
477 }
478
479 /****************************************************************
480 ********** STEP 3a - DRAW LINES ********************************
481 ****************************************************************/
482 if (lines) {
483 Point old = null;
484 for (GpxTrack trk : data.tracks) {
485 for (Collection<WayPoint> segment : trk.trackSegs) {
486 for (WayPoint trkPnt : segment) {
487 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
488 continue;
489 Point screen = mv.getPoint(trkPnt.eastNorth);
490 if (trkPnt.drawLine) {
491 // skip points that are on the same screenposition
492 if (old != null && ((old.x != screen.x) || (old.y != screen.y))) {
493 g.setColor(trkPnt.customColoring);
494 g.drawLine(old.x, old.y, screen.x, screen.y);
495 }
496 }
497 old = screen;
498 } // end for trkpnt
499 } // end for segment
500 } // end for trk
501 } // end if lines
502
503 /****************************************************************
504 ********** STEP 3b - DRAW NICE ARROWS **************************
505 ****************************************************************/
506 if (lines && direction && !alternatedirection) {
507 Point old = null;
508 Point oldA = null; // last arrow painted
509 for (GpxTrack trk : data.tracks) {
510 for (Collection<WayPoint> segment : trk.trackSegs) {
511 for (WayPoint trkPnt : segment) {
512 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
513 continue;
514 if (trkPnt.drawLine) {
515 Point screen = mv.getPoint(trkPnt.eastNorth);
516 // skip points that are on the same screenposition
517 if (old != null && (oldA == null || screen.x < oldA.x-delta || screen.x > oldA.x+delta || screen.y < oldA.y-delta || screen.y > oldA.y+delta)) {
518 g.setColor(trkPnt.customColoring);
519 double t = Math.atan2(screen.y-old.y, screen.x-old.x) + Math.PI;
520 g.drawLine(screen.x,screen.y, (int)(screen.x + 10*Math.cos(t-PHI)), (int)(screen.y
521 + 10*Math.sin(t-PHI)));
522 g.drawLine(screen.x,screen.y, (int)(screen.x + 10*Math.cos(t+PHI)), (int)(screen.y
523 + 10*Math.sin(t+PHI)));
524 oldA = screen;
525 }
526 old = screen;
527 }
528 } // end for trkpnt
529 } // end for segment
530 } // end for trk
531 } // end if lines
532
533 /****************************************************************
534 ********** STEP 3c - DRAW FAST ARROWS **************************
535 ****************************************************************/
536 if (lines && direction && alternatedirection) {
537 Point old = null;
538 Point oldA = null; // last arrow painted
539 for (GpxTrack trk : data.tracks) {
540 for (Collection<WayPoint> segment : trk.trackSegs) {
541 for (WayPoint trkPnt : segment) {
542 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
543 continue;
544 if (trkPnt.drawLine) {
545 Point screen = mv.getPoint(trkPnt.eastNorth);
546 // skip points that are on the same screenposition
547 if (old != null && (oldA == null || screen.x < oldA.x-delta || screen.x > oldA.x+delta || screen.y < oldA.y-delta || screen.y > oldA.y+delta)) {
548 g.setColor(trkPnt.customColoring);
549 g.drawLine(screen.x, screen.y, screen.x + dir[trkPnt.dir][0], screen.y + dir[trkPnt.dir][1]);
550 g.drawLine(screen.x, screen.y, screen.x + dir[trkPnt.dir][2], screen.y + dir[trkPnt.dir][3]);
551 oldA = screen;
552 }
553 old = screen;
554 }
555 } // end for trkpnt
556 } // end for segment
557 } // end for trk
558 } // end if lines
559
560 /****************************************************************
561 ********** STEP 3d - DRAW LARGE POINTS *************************
562 ****************************************************************/
563 if (large) {
564 g.setColor(neutralColor);
565 for (GpxTrack trk : data.tracks) {
566 for (Collection<WayPoint> segment : trk.trackSegs) {
567 for (WayPoint trkPnt : segment) {
568 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
569 continue;
570 Point screen = mv.getPoint(trkPnt.eastNorth);
571 g.setColor(trkPnt.customColoring);
572 g.fillRect(screen.x-1, screen.y-1, 3, 3);
573 } // end for trkpnt
574 } // end for segment
575 } // end for trk
576 } // end if large
577
578 /****************************************************************
579 ********** STEP 3e - DRAW SMALL POINTS FOR LINES ***************
580 ****************************************************************/
581 if (!large && lines){
582 g.setColor(neutralColor);
583 for (GpxTrack trk : data.tracks) {
584 for (Collection<WayPoint> segment : trk.trackSegs) {
585 for (WayPoint trkPnt : segment) {
586 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
587 continue;
588 if (!trkPnt.drawLine) {
589 Point screen = mv.getPoint(trkPnt.eastNorth);
590 g.drawRect(screen.x, screen.y, 0, 0);
591 }
592 } // end for trkpnt
593 } // end for segment
594 } // end for trk
595 } // end if large
596
597 /****************************************************************
598 ********** STEP 3f - DRAW SMALL POINTS INSTEAD OF LINES ********
599 ****************************************************************/
600 if (!large && !lines){
601 g.setColor(neutralColor);
602 for (GpxTrack trk : data.tracks) {
603 for (Collection<WayPoint> segment : trk.trackSegs) {
604 for (WayPoint trkPnt : segment) {
605 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
606 continue;
607 Point screen = mv.getPoint(trkPnt.eastNorth);
608 g.setColor(trkPnt.customColoring);
609 g.drawRect(screen.x, screen.y, 0, 0);
610 } // end for trkpnt
611 } // end for segment
612 } // end for trk
613 } // end if large
614
615 //Long duration = System.currentTimeMillis() - startTime;
616 //System.out.println(duration);
617 } // end paint
618
619 @Override public void visitBoundingBox(BoundingXYVisitor v) {
620 for (WayPoint p : data.waypoints)
621 v.visit(p.eastNorth);
622
623 for (GpxRoute rte : data.routes) {
624 Collection<WayPoint> r = rte.routePoints;
625 for (WayPoint p : r) {
626 v.visit(p.eastNorth);
627 }
628 }
629
630 for (GpxTrack trk : data.tracks) {
631 for (Collection<WayPoint> seg : trk.trackSegs) {
632 for (WayPoint p : seg) {
633 v.visit(p.eastNorth);
634 }
635 }
636 }
637 }
638
639 public class ConvertToDataLayerAction extends AbstractAction {
640 public ConvertToDataLayerAction() {
641 super(tr("Convert to data layer"), ImageProvider.get("converttoosm"));
642 }
643 public void actionPerformed(ActionEvent e) {
644 JPanel msg = new JPanel(new GridBagLayout());
645 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());
646 msg.add(new UrlLabel(tr("http://www.openstreetmap.org/traces")), GBC.eop());
647 if (!DontShowAgainInfo.show("convert_to_data", msg))
648 return;
649 DataSet ds = new DataSet();
650 for (GpxTrack trk : data.tracks) {
651 for (Collection<WayPoint> segment : trk.trackSegs) {
652 Way w = new Way();
653 for (WayPoint p : segment) {
654 Node n = new Node(p.latlon);
655 String timestr = p.getString("time");
656 if(timestr != null)
657 {
658 timestr = timestr.replace("Z","+00:00");
659 n.timestamp = timestr;
660 }
661 ds.nodes.add(n);
662 w.nodes.add(n);
663 }
664 ds.ways.add(w);
665 }
666 }
667 Main.main.addLayer(new OsmDataLayer(ds, tr("Converted from: {0}", GpxLayer.this.name), null));
668 Main.main.removeLayer(GpxLayer.this);
669 }
670 }
671
672 /**
673 * Action that issues a series of download requests to the API, following the GPX track.
674 *
675 * @author fred
676 */
677 public class DownloadAlongTrackAction extends AbstractAction {
678 public DownloadAlongTrackAction() {
679 super(tr("Download from OSM along this track"), ImageProvider.get("downloadalongtrack"));
680 }
681 public void actionPerformed(ActionEvent e) {
682 JPanel msg = new JPanel(new GridBagLayout());
683 Integer dist[] = {5000, 500, 50};
684 Integer area[] = {20, 10, 5, 1};
685
686 msg.add(new JLabel(tr("Download everything within:")), GBC.eol());
687 String s[] = new String[dist.length];
688 for(int i = 0; i < dist.length; ++i)
689 s[i] = tr("{0} meters", dist[i]);
690 JList buffer = new JList(s);
691 msg.add(buffer, GBC.eol());
692 msg.add(new JLabel(tr("Maximum area per request:")), GBC.eol());
693 s = new String[area.length];
694 for(int i = 0; i < area.length; ++i)
695 s[i] = tr("{0} sq km", area[i]);
696 JList maxRect = new JList(s);
697 msg.add(maxRect, GBC.eol());
698
699 if (JOptionPane.showConfirmDialog(Main.parent, msg,
700 tr("Download from OSM along this track"),
701 JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) {
702 return;
703 }
704
705 /*
706 * Find the average latitude for the data we're contemplating, so we can
707 * know how many metres per degree of longitude we have.
708 */
709 double latsum = 0;
710 int latcnt = 0;
711
712 for (GpxTrack trk : data.tracks) {
713 for (Collection<WayPoint> segment : trk.trackSegs) {
714 for (WayPoint p : segment) {
715 latsum += p.latlon.lat();
716 latcnt ++;
717 }
718 }
719 }
720
721 double avglat = latsum / latcnt;
722 double scale = Math.cos(Math.toRadians(avglat));
723
724 /*
725 * Compute buffer zone extents and maximum bounding box size. Note that the
726 * maximum we ever offer is a bbox area of 0.002, while the API theoretically
727 * supports 0.25, but as soon as you touch any built-up area, that kind of
728 * bounding box will download forever and then stop because it has more than
729 * 50k nodes.
730 */
731 Integer i = buffer.getSelectedIndex();
732 int buffer_dist = dist[i < 0 ? 0 : i];
733 double buffer_y = buffer_dist / 100000.0;
734 double buffer_x = buffer_y / scale;
735 i = maxRect.getSelectedIndex();
736 double max_area = area[i < 0 ? 0 : i] / 10000.0 / scale;
737 Area a = new Area();
738 Rectangle2D r = new Rectangle2D.Double();
739
740 /*
741 * Collect the combined area of all gpx points plus buffer zones around them.
742 * We ignore points that lie closer to the previous point than the given buffer
743 * size because otherwise this operation takes ages.
744 */
745 LatLon previous = null;
746 for (GpxTrack trk : data.tracks) {
747 for (Collection<WayPoint> segment : trk.trackSegs) {
748 for (WayPoint p : segment) {
749 if (previous == null || p.latlon.greatCircleDistance(previous) > buffer_dist) {
750 // we add a buffer around the point.
751 r.setRect(p.latlon.lon()-buffer_x, p.latlon.lat()-buffer_y, 2*buffer_x, 2*buffer_y);
752 a.add(new Area(r));
753 previous = p.latlon;
754 }
755 }
756 }
757 }
758
759 /*
760 * Area "a" now contains the hull that we would like to download data for.
761 * however we can only download rectangles, so the following is an attempt at
762 * finding a number of rectangles to download.
763 *
764 * The idea is simply: Start out with the full bounding box. If it is too large,
765 * then split it in half and repeat recursively for each half until you arrive
766 * at something small enough to download. The algorithm is improved
767 * by always using the intersection between the rectangle and the actual desired
768 * area. For example, if you have a track that goes like this:
769 * +----+
770 * | /|
771 * | / |
772 * | / |
773 * |/ |
774 * +----+
775 * then we would first look at downloading the whole rectangle (assume it's too big),
776 * after that we split it in half (upper and lower half), but we do *not* request the
777 * full upper and lower rectangle, only the part of the upper/lower rectangle that
778 * actually has something in it.
779 */
780
781 List<Rectangle2D> toDownload = new ArrayList<Rectangle2D>();
782
783 addToDownload(a, a.getBounds(), toDownload, max_area);
784
785 msg = new JPanel(new GridBagLayout());
786
787 msg.add(new JLabel(tr("<html>This action will require {0} individual<br>download requests. Do you wish<br>to continue?</html>",
788 toDownload.size())), GBC.eol());
789
790 if (JOptionPane.showConfirmDialog(Main.parent, msg,
791 tr("Download from OSM along this track"),
792 JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) {
793 return;
794 }
795
796 // FIXME: DownloadTask's "please wait" dialog should display the number of
797 // downloads left, and "cancel" needs to be honoured. An error along the way
798 // should abort the whole process.
799 DownloadTask osmTask = new DownloadOsmTask();
800 for (Rectangle2D td : toDownload) {
801 osmTask.download(null, td.getMinY(), td.getMinX(), td.getMaxY(), td.getMaxX());
802 }
803 }
804 }
805
806 private static void addToDownload(Area a, Rectangle2D r, Collection<Rectangle2D> results, double max_area) {
807 Area tmp = new Area(r);
808 // intersect with sought-after area
809 tmp.intersect(a);
810 if (tmp.isEmpty()) return;
811 Rectangle2D bounds = tmp.getBounds2D();
812 if (bounds.getWidth() * bounds.getHeight() > max_area) {
813 // the rectangle gets too large; split it and make recursive call.
814 Rectangle2D r1;
815 Rectangle2D r2;
816 if (bounds.getWidth() > bounds.getHeight()) {
817 // rectangles that are wider than high are split into a left and right half,
818 r1 = new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth()/2, bounds.getHeight());
819 r2 = new Rectangle2D.Double(bounds.getX()+bounds.getWidth()/2, bounds.getY(), bounds.getWidth()/2, bounds.getHeight());
820 } else {
821 // others into a top and bottom half.
822 r1 = new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()/2);
823 r2 = new Rectangle2D.Double(bounds.getX(), bounds.getY()+bounds.getHeight()/2, bounds.getWidth(), bounds.getHeight()/2);
824 }
825 addToDownload(a, r1, results, max_area);
826 addToDownload(a, r2, results, max_area);
827 } else {
828 results.add(bounds);
829 }
830 }
831
832 /**
833 * Makes a new marker layer derived from this GpxLayer containing at least one
834 * audio marker which the given audio file is associated with.
835 * Markers are derived from the following
836 * (a) explict waypoints in the GPX layer, or
837 * (b) named trackpoints in the GPX layer, or
838 * (c) (in future) voice recognised markers in the sound recording
839 * (d) a single marker at the beginning of the track
840 * @param wavFile : the file to be associated with the markers in the new marker layer
841 */
842 private void importAudio(File wavFile) {
843 String uri = "file:".concat(wavFile.getAbsolutePath());
844 MarkerLayer ml = new MarkerLayer(new GpxData(), tr("Audio markers from {0}", name), associatedFile, me);
845
846 Collection<WayPoint> waypoints = new ArrayList<WayPoint>();
847 boolean timedMarkersOmitted = false;
848 boolean untimedMarkersOmitted = false;
849 double snapDistance = Main.pref.getDouble("marker.audiofromuntimedwaypoints.distance", 1.0e-3); /* about 25m */
850
851 // determine time of first point in track
852 double firstTime = -1.0;
853 if (data.tracks != null && ! data.tracks.isEmpty()) {
854 for (GpxTrack track : data.tracks) {
855 if (track.trackSegs == null) continue;
856 for (Collection<WayPoint> seg : track.trackSegs) {
857 for (WayPoint w : seg) {
858 firstTime = w.time;
859 break;
860 }
861 if (firstTime >= 0.0) break;
862 }
863 if (firstTime >= 0.0) break;
864 }
865 }
866 if (firstTime < 0.0) {
867 JOptionPane.showMessageDialog(Main.parent, tr("No GPX track available in layer to associate audio with."));
868 return;
869 }
870
871 // (a) try explicit timestamped waypoints - unless suppressed
872 if (Main.pref.getBoolean("marker.audiofromexplicitwaypoints", true) &&
873 data.waypoints != null && ! data.waypoints.isEmpty())
874 {
875 for (WayPoint w : data.waypoints) {
876 if (w.time > firstTime) {
877 waypoints.add(w);
878 } else if (w.time > 0.0) {
879 timedMarkersOmitted = true;
880 }
881 }
882 }
883
884 // (b) try explicit waypoints without timestamps - unless suppressed
885 if (Main.pref.getBoolean("marker.audiofromuntimedwaypoints", true) &&
886 data.waypoints != null && ! data.waypoints.isEmpty())
887 {
888 for (WayPoint w : data.waypoints) {
889 if (waypoints.contains(w)) { continue; }
890 WayPoint wNear = nearestPointOnTrack(w.eastNorth, snapDistance);
891 if (wNear != null) {
892 WayPoint wc = new WayPoint(w.latlon);
893 wc.time = wNear.time;
894 if (w.attr.containsKey("name")) wc.attr.put("name", w.getString("name"));
895 waypoints.add(wc);
896 } else {
897 untimedMarkersOmitted = true;
898 }
899 }
900 }
901
902 // (c) use explicitly named track points, again unless suppressed
903 if ((Main.pref.getBoolean("marker.audiofromnamedtrackpoints", false)) &&
904 data.tracks != null && ! data.tracks.isEmpty())
905 {
906 for (GpxTrack track : data.tracks) {
907 if (track.trackSegs == null) continue;
908 for (Collection<WayPoint> seg : track.trackSegs) {
909 for (WayPoint w : seg) {
910 if (w.attr.containsKey("name") || w.attr.containsKey("desc")) {
911 waypoints.add(w);
912 }
913 }
914 }
915 }
916 }
917
918 // (d) analyse audio for spoken markers here, in due course
919
920 // (e) simply add a single marker at the start of the track
921 if ((Main.pref.getBoolean("marker.audiofromstart") || waypoints.isEmpty()) &&
922 data.tracks != null && ! data.tracks.isEmpty())
923 {
924 boolean gotOne = false;
925 for (GpxTrack track : data.tracks) {
926 if (track.trackSegs == null) continue;
927 for (Collection<WayPoint> seg : track.trackSegs) {
928 for (WayPoint w : seg) {
929 WayPoint wStart = new WayPoint(w.latlon);
930 wStart.attr.put("name", "start");
931 wStart.time = w.time;
932 waypoints.add(wStart);
933 gotOne = true;
934 break;
935 }
936 if (gotOne) break;
937 }
938 if (gotOne) break;
939 }
940 }
941
942 /* we must have got at least one waypoint now */
943
944 Collections.sort((ArrayList<WayPoint>) waypoints, new Comparator<WayPoint>() {
945 public int compare(WayPoint a, WayPoint b) {
946 return a.time <= b.time ? -1 : 1;
947 }
948 });
949
950 firstTime = -1.0; /* this time of the first waypoint, not first trackpoint */
951 for (WayPoint w : waypoints) {
952 if (firstTime < 0.0) firstTime = w.time;
953 double offset = w.time - firstTime;
954 String name;
955 if (w.attr.containsKey("name"))
956 name = w.getString("name");
957 else if (w.attr.containsKey("desc"))
958 name = w.getString("desc");
959 else
960 name = AudioMarker.inventName(offset);
961 AudioMarker am = AudioMarker.create(w.latlon,
962 name, uri, ml, w.time, offset);
963 ml.data.add(am);
964 }
965 Main.main.addLayer(ml);
966
967 if (timedMarkersOmitted) {
968 JOptionPane.showMessageDialog(Main.parent,
969 tr("Some waypoints with timestamps from before the start of the track were omitted."));
970 }
971 if (untimedMarkersOmitted) {
972 JOptionPane.showMessageDialog(Main.parent,
973 tr("Some waypoints which were too far from the track to sensibly estimate their time were omitted."));
974 }
975 }
976
977 /**
978 * Makes a WayPoint at the projection of point P onto the track providing P is
979 * less than tolerance away from the track
980
981 * @param P : the point to determine the projection for
982 * @param tolerance : must be no further than this from the track
983 * @return the closest point on the track to P, which may be the
984 * first or last point if off the end of a segment, or may be null if
985 * nothing close enough
986 */
987 public WayPoint nearestPointOnTrack(EastNorth P, double tolerance) {
988 /*
989 * assume the coordinates of P are xp,yp, and those of a section of track
990 * between two trackpoints are R=xr,yr and S=xs,ys. Let N be the projected point.
991 *
992 * The equation of RS is Ax + By + C = 0 where
993 * A = ys - yr
994 * B = xr - xs
995 * C = - Axr - Byr
996 *
997 * Also, note that the distance RS^2 is A^2 + B^2
998 *
999 * If RS^2 == 0.0 ignore the degenerate section of track
1000 *
1001 * PN^2 = (Axp + Byp + C)^2 / RS^2
1002 * that is the distance from P to the line
1003 *
1004 * so if PN^2 is less than PNmin^2 (initialized to tolerance) we can reject
1005 * the line; otherwise...
1006 * determine if the projected poijnt lies within the bounds of the line:
1007 * PR^2 - PN^2 <= RS^2 and PS^2 - PN^2 <= RS^2
1008 *
1009 * where PR^2 = (xp - xr)^2 + (yp-yr)^2
1010 * and PS^2 = (xp - xs)^2 + (yp-ys)^2
1011 *
1012 * If so, calculate N as
1013 * xn = xr + (RN/RS) B
1014 * yn = y1 + (RN/RS) A
1015 *
1016 * where RN = sqrt(PR^2 - PN^2)
1017 */
1018
1019 double PNminsq = tolerance * tolerance;
1020 EastNorth bestEN = null;
1021 double bestTime = 0.0;
1022 double px = P.east();
1023 double py = P.north();
1024 double rx = 0.0, ry = 0.0, sx, sy, x, y;
1025 if (data.tracks == null) return null;
1026 for (GpxTrack track : data.tracks) {
1027 if (track.trackSegs == null) continue;
1028 for (Collection<WayPoint> seg : track.trackSegs) {
1029 WayPoint R = null;
1030 for (WayPoint S : seg) {
1031 if (R == null) {
1032 R = S;
1033 rx = R.eastNorth.east();
1034 ry = R.eastNorth.north();
1035 x = px - rx;
1036 y = py - ry;
1037 double PRsq = x * x + y * y;
1038 if (PRsq < PNminsq) {
1039 PNminsq = PRsq;
1040 bestEN = R.eastNorth;
1041 bestTime = R.time;
1042 }
1043 } else {
1044 sx = S.eastNorth.east();
1045 sy = S.eastNorth.north();
1046 double A = sy - ry;
1047 double B = rx - sx;
1048 double C = - A * rx - B * ry;
1049 double RSsq = A * A + B * B;
1050 if (RSsq == 0.0) continue;
1051 double PNsq = A * px + B * py + C;
1052 PNsq = PNsq * PNsq / RSsq;
1053 if (PNsq < PNminsq) {
1054 x = px - rx;
1055 y = py - ry;
1056 double PRsq = x * x + y * y;
1057 x = px - sx;
1058 y = py - sy;
1059 double PSsq = x * x + y * y;
1060 if (PRsq - PNsq <= RSsq && PSsq - PNsq <= RSsq) {
1061 double RNoverRS = Math.sqrt((PRsq - PNsq)/RSsq);
1062 double nx = rx - RNoverRS * B;
1063 double ny = ry + RNoverRS * A;
1064 bestEN = new EastNorth(nx, ny);
1065 bestTime = R.time + RNoverRS * (S.time - R.time);
1066 PNminsq = PNsq;
1067 }
1068 }
1069 R = S;
1070 rx = sx;
1071 ry = sy;
1072 }
1073 }
1074 if (R != null) {
1075 /* if there is only one point in the seg, it will do this twice, but no matter */
1076 rx = R.eastNorth.east();
1077 ry = R.eastNorth.north();
1078 x = px - rx;
1079 y = py - ry;
1080 double PRsq = x * x + y * y;
1081 if (PRsq < PNminsq) {
1082 PNminsq = PRsq;
1083 bestEN = R.eastNorth;
1084 bestTime = R.time;
1085 }
1086 }
1087 }
1088 }
1089 if (bestEN == null) return null;
1090 WayPoint best = new WayPoint(Main.proj.eastNorth2latlon(bestEN));
1091 best.time = bestTime;
1092 return best;
1093 }
1094}
Note: See TracBrowser for help on using the repository browser.