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

Last change on this file since 721 was 721, checked in by ramack, 16 years ago

patch from Henry Loenwind, closes #720 and #966

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