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

Last change on this file since 627 was 627, checked in by framm, 16 years ago
  • Property svn:eol-style set to native
File size: 27.6 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;
28
29import javax.swing.AbstractAction;
30import javax.swing.Box;
31import javax.swing.ButtonGroup;
32import javax.swing.Icon;
33import javax.swing.JCheckBox;
34import javax.swing.JColorChooser;
35import javax.swing.JFileChooser;
36import javax.swing.JLabel;
37import javax.swing.JMenuItem;
38import javax.swing.JOptionPane;
39import javax.swing.JPanel;
40import javax.swing.JRadioButton;
41import javax.swing.JSeparator;
42import javax.swing.JTextField;
43import javax.swing.filechooser.FileFilter;
44
45import org.openstreetmap.josm.Main;
46import org.openstreetmap.josm.actions.RenameLayerAction;
47import org.openstreetmap.josm.actions.SaveAction;
48import org.openstreetmap.josm.actions.SaveAsAction;
49import org.openstreetmap.josm.data.coor.EastNorth;
50import org.openstreetmap.josm.data.gpx.GpxData;
51import org.openstreetmap.josm.data.gpx.GpxRoute;
52import org.openstreetmap.josm.data.gpx.GpxTrack;
53import org.openstreetmap.josm.data.gpx.WayPoint;
54import org.openstreetmap.josm.data.osm.DataSet;
55import org.openstreetmap.josm.data.osm.Node;
56import org.openstreetmap.josm.data.osm.Way;
57import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
58import org.openstreetmap.josm.gui.MapView;
59import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
60import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
61import org.openstreetmap.josm.gui.layer.markerlayer.AudioMarker;
62import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
63import org.openstreetmap.josm.io.GpxWriter;
64import org.openstreetmap.josm.io.MultiPartFormOutputStream;
65import org.openstreetmap.josm.tools.ColorHelper;
66import org.openstreetmap.josm.tools.DontShowAgainInfo;
67import org.openstreetmap.josm.tools.GBC;
68import org.openstreetmap.josm.tools.ImageProvider;
69import org.openstreetmap.josm.tools.UrlLabel;
70
71public class GpxLayer extends Layer {
72 public GpxData data;
73 private final GpxLayer me;
74 protected static final double PHI = Math.toRadians(15);
75
76 public GpxLayer(GpxData d) {
77 super((String) d.attr.get("name"));
78 data = d;
79 me = this;
80 }
81
82 public GpxLayer(GpxData d, String name) {
83 this(d);
84 this.name = name;
85 }
86
87 @Override public Icon getIcon() {
88 return ImageProvider.get("layer", "gpx_small");
89 }
90
91 @Override public Object getInfoComponent() {
92 return getToolTipText();
93 }
94
95 @Override public Component[] getMenuEntries() {
96 JMenuItem line = new JMenuItem(tr("Customize line drawing"), ImageProvider.get("mapmode/addsegment"));
97 line.addActionListener(new ActionListener() {
98 public void actionPerformed(ActionEvent e) {
99 JRadioButton[] r = new JRadioButton[3];
100 r[0] = new JRadioButton(tr("Use global settings."));
101 r[1] = new JRadioButton(tr("Draw lines between points for this layer."));
102 r[2] = new JRadioButton(tr("Do not draw lines between points for this layer."));
103 ButtonGroup group = new ButtonGroup();
104 Box panel = Box.createVerticalBox();
105 for (JRadioButton b : r) {
106 group.add(b);
107 panel.add(b);
108 }
109 String propName = "draw.rawgps.lines.layer "+name;
110 if (Main.pref.hasKey(propName))
111 group.setSelected(r[Main.pref.getBoolean(propName) ? 1:2].getModel(), true);
112 else
113 group.setSelected(r[0].getModel(), true);
114 int answer = JOptionPane.showConfirmDialog(Main.parent, panel, tr("Select line drawing options"), JOptionPane.OK_CANCEL_OPTION);
115 if (answer == JOptionPane.CANCEL_OPTION)
116 return;
117 if (group.getSelection() == r[0].getModel())
118 Main.pref.put(propName, null);
119 else
120 Main.pref.put(propName, group.getSelection() == r[1].getModel());
121 Main.map.repaint();
122 }
123 });
124
125 JMenuItem color = new JMenuItem(tr("Customize Color"), ImageProvider.get("colorchooser"));
126 color.putClientProperty("help", "Action/LayerCustomizeColor");
127 color.addActionListener(new ActionListener() {
128 public void actionPerformed(ActionEvent e) {
129 String col = Main.pref.get("color.layer "+name, Main.pref.get("color.gps point", ColorHelper.color2html(Color.gray)));
130 JColorChooser c = new JColorChooser(ColorHelper.html2color(col));
131 Object[] options = new Object[]{tr("OK"), tr("Cancel"), tr("Default")};
132 int answer = JOptionPane.showOptionDialog(Main.parent, c, tr("Choose a color"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[0]);
133 switch (answer) {
134 case 0:
135 Main.pref.put("color.layer "+name, ColorHelper.color2html(c.getColor()));
136 break;
137 case 1:
138 return;
139 case 2:
140 Main.pref.put("color.layer "+name, null);
141 break;
142 }
143 Main.map.repaint();
144 }
145 });
146
147 JMenuItem markersFromNamedTrackpoints = new JMenuItem(tr("Markers From Named Points"), ImageProvider.get("addmarkers"));
148 markersFromNamedTrackpoints.putClientProperty("help", "Action/MarkersFromNamedPoints");
149 markersFromNamedTrackpoints.addActionListener(new ActionListener() {
150 public void actionPerformed(ActionEvent e) {
151 GpxData namedTrackPoints = new GpxData();
152 for (GpxTrack track : data.tracks)
153 for (Collection<WayPoint> seg : track.trackSegs)
154 for (WayPoint point : seg)
155 if (point.attr.containsKey("name") || point.attr.containsKey("desc"))
156 namedTrackPoints.waypoints.add(point);
157
158 MarkerLayer ml = new MarkerLayer(namedTrackPoints, tr("Named Trackpoints from {0}", name), associatedFile, me);
159 if (ml.data.size() > 0) {
160 Main.main.addLayer(ml);
161 }
162 }
163 });
164
165 JMenuItem importAudio = new JMenuItem(tr("Import Audio"), ImageProvider.get("importaudio"));
166 importAudio.putClientProperty("help", "ImportAudio");
167 importAudio.addActionListener(new ActionListener() {
168 public void actionPerformed(ActionEvent e) {
169 String dir = Main.pref.get("markers.lastaudiodirectory");
170 JFileChooser fc = new JFileChooser(dir);
171 fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
172 fc.setAcceptAllFileFilterUsed(false);
173 fc.setFileFilter(new FileFilter(){
174 @Override public boolean accept(File f) {
175 return f.isDirectory() || f.getName().toLowerCase().endsWith(".wav");
176 }
177 @Override public String getDescription() {
178 return tr("Wave Audio files (*.wav)");
179 }
180 });
181 fc.showOpenDialog(Main.parent);
182 File sel = fc.getSelectedFile();
183 if (!fc.getCurrentDirectory().getAbsolutePath().equals(dir))
184 Main.pref.put("markers.lastaudiodirectory", fc.getCurrentDirectory().getAbsolutePath());
185 if (sel == null)
186 return;
187 importAudio(sel);
188 Main.map.repaint();
189 }
190 });
191
192 JMenuItem tagimage = new JMenuItem(tr("Import images"), ImageProvider.get("tagimages"));
193 tagimage.putClientProperty("help", "Action/ImportImages");
194 tagimage.addActionListener(new ActionListener() {
195 public void actionPerformed(ActionEvent e) {
196 JFileChooser fc = new JFileChooser(Main.pref.get("tagimages.lastdirectory"));
197 fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
198 fc.setMultiSelectionEnabled(true);
199 fc.setAcceptAllFileFilterUsed(false);
200 fc.setFileFilter(new FileFilter() {
201 @Override public boolean accept(File f) {
202 return f.isDirectory() || f.getName().toLowerCase().endsWith(".jpg");
203 }
204 @Override public String getDescription() {
205 return tr("JPEG images (*.jpg)");
206 }
207 });
208 fc.showOpenDialog(Main.parent);
209 File[] sel = fc.getSelectedFiles();
210 if (sel == null || sel.length == 0)
211 return;
212 LinkedList<File> files = new LinkedList<File>();
213 addRecursiveFiles(files, sel);
214 Main.pref.put("tagimages.lastdirectory", fc.getCurrentDirectory().getPath());
215 GeoImageLayer.create(files, GpxLayer.this);
216 }
217
218 private void addRecursiveFiles(LinkedList<File> files, File[] sel) {
219 for (File f : sel) {
220 if (f.isDirectory())
221 addRecursiveFiles(files, f.listFiles());
222 else if (f.getName().toLowerCase().endsWith(".jpg"))
223 files.add(f);
224 }
225 }
226 });
227
228 if (Main.applet)
229 return new Component[] {
230 new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
231 new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
232 new JSeparator(),
233 color,
234 line,
235 new JMenuItem(new ConvertToDataLayerAction()),
236 new JSeparator(),
237 new JMenuItem(new RenameLayerAction(associatedFile, this)),
238 new JSeparator(),
239 new JMenuItem(new LayerListPopup.InfoAction(this))};
240 return new Component[] {
241 new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
242 new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
243 new JSeparator(),
244 new JMenuItem(new SaveAction(this)),
245 new JMenuItem(new SaveAsAction(this)),
246 // new JMenuItem(new UploadTraceAction()),
247 color,
248 line,
249 tagimage,
250 importAudio,
251 markersFromNamedTrackpoints,
252 new JMenuItem(new ConvertToDataLayerAction()),
253 new JSeparator(),
254 new JMenuItem(new RenameLayerAction(associatedFile, this)),
255 new JSeparator(),
256 new JMenuItem(new LayerListPopup.InfoAction(this))};
257 }
258
259 @Override public String getToolTipText() {
260 StringBuilder info = new StringBuilder().append("<html>");
261
262 info.append(trn("{0} track, ", "{0} tracks, ",
263 data.tracks.size(), data.tracks.size()))
264 .append(trn("{0} route, ", "{0} routes, ",
265 data.routes.size(), data.routes.size()))
266 .append(trn("{0} waypoint", "{0} waypoints",
267 data.waypoints.size(), data.waypoints.size()))
268 .append("<br>");
269
270 if (data.attr.containsKey("name"))
271 info.append(tr("Name: {0}", data.attr.get("name")))
272 .append("<br>");
273
274 if (data.attr.containsKey("desc"))
275 info.append(tr("Description: {0}", data.attr.get("desc")))
276 .append("<br>");
277
278 return info.append("</html>").toString();
279 }
280
281 @Override public boolean isMergable(Layer other) {
282 return other instanceof GpxLayer;
283 }
284
285 @Override public void mergeFrom(Layer from) {
286 data.mergeFrom(((GpxLayer)from).data);
287 }
288
289 private static Color[] colors = new Color[256];
290 static {
291 for (int i = 0; i < colors.length; i++) {
292 colors[i] = Color.getHSBColor(i/300.0f, 1, 1);
293 }
294 }
295
296 @Override public void paint(Graphics g, MapView mv) {
297 String gpsCol = Main.pref.get("color.gps point");
298 String gpsColSpecial = Main.pref.get("color.layer "+name);
299 if (!gpsColSpecial.equals("")) {
300 g.setColor(ColorHelper.html2color(gpsColSpecial));
301 } else if (!gpsCol.equals("")) {
302 g.setColor(ColorHelper.html2color(gpsCol));
303 } else{
304 g.setColor(Color.GRAY);
305 }
306
307 boolean forceLines = Main.pref.getBoolean("draw.rawgps.lines.force");
308 boolean direction = Main.pref.getBoolean("draw.rawgps.direction");
309 int maxLineLength = Integer.parseInt(Main.pref.get("draw.rawgps.max-line-length", "-1"));
310 boolean lines = Main.pref.getBoolean("draw.rawgps.lines");
311 String linesKey = "draw.rawgps.lines.layer "+name;
312 if (Main.pref.hasKey(linesKey))
313 lines = Main.pref.getBoolean(linesKey);
314 boolean large = Main.pref.getBoolean("draw.rawgps.large");
315 boolean colored = Main.pref.getBoolean("draw.rawgps.colors");
316
317 Point old = null;
318 WayPoint oldWp = null;
319 for (GpxTrack trk : data.tracks) {
320 if (!forceLines) {
321 old = null;
322 }
323 for (Collection<WayPoint> segment : trk.trackSegs) {
324 for (WayPoint trkPnt : segment) {
325 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
326 continue;
327 Point screen = mv.getPoint(trkPnt.eastNorth);
328 if (lines && old != null) {
329 double dist = trkPnt.latlon.greatCircleDistance(oldWp.latlon);
330 double dtime = trkPnt.time - oldWp.time;
331 double vel = dist/dtime;
332
333 if (colored && dtime > 0) {
334 // scale linearly until 130km/h = 36.1m/s
335 if (vel < 0 || vel > 36) {
336 g.setColor(colors[255]);
337 } else {
338 g.setColor(colors[(int) (7*vel)]);
339 }
340 }
341
342 // draw line, if no maxLineLength is set or the line is shorter.
343 if (maxLineLength == -1 || dist <= maxLineLength){
344 g.drawLine(old.x, old.y, screen.x, screen.y);
345
346 if (direction) {
347 double t = Math.atan2(screen.y-old.y, screen.x-old.x) + Math.PI;
348 g.drawLine(screen.x,screen.y, (int)(screen.x + 10*Math.cos(t-PHI)), (int)(screen.y + 10*Math.sin(t-PHI)));
349 g.drawLine(screen.x,screen.y, (int)(screen.x + 10*Math.cos(t+PHI)), (int)(screen.y + 10*Math.sin(t+PHI)));
350 }
351 }else{
352 g.drawRect(screen.x, screen.y, 0, 0);
353 }
354
355 } else if (!large) {
356 g.drawRect(screen.x, screen.y, 0, 0);
357 }
358 if (large)
359 g.fillRect(screen.x-1, screen.y-1, 3, 3);
360 old = screen;
361 oldWp = trkPnt;
362 }
363 }
364 }
365 }
366
367 @Override public void visitBoundingBox(BoundingXYVisitor v) {
368 for (WayPoint p : data.waypoints)
369 v.visit(p.eastNorth);
370
371 for (GpxRoute rte : data.routes) {
372 Collection<WayPoint> r = rte.routePoints;
373 for (WayPoint p : r) {
374 v.visit(p.eastNorth);
375 }
376 }
377
378 for (GpxTrack trk : data.tracks) {
379 for (Collection<WayPoint> seg : trk.trackSegs) {
380 for (WayPoint p : seg) {
381 v.visit(p.eastNorth);
382 }
383 }
384 }
385 }
386
387 public class UploadTraceAction extends AbstractAction {
388 public UploadTraceAction() {
389 super(tr("Upload this trace..."), ImageProvider.get("uploadtrace"));
390 }
391 public void actionPerformed(ActionEvent e) {
392 JPanel msg = new JPanel(new GridBagLayout());
393 msg.add(new JLabel(tr("<html>This functionality has been added only recently. Please<br>"+
394 "use with care and check if it works as expected.</html>")), GBC.eop());
395 ButtonGroup bg = new ButtonGroup();
396 JRadioButton c1 = null;
397 JRadioButton c2 = null;
398
399 //TODO
400 //check whether data comes from server
401 //check whether data changed sind last save/open
402
403 c1 = new JRadioButton(tr("Upload track filtered by JOSM"), true);
404 c2 = new JRadioButton(tr("Upload raw file: "), false);
405 c2.setEnabled(false);
406 c1.setEnabled(false);
407 bg.add(c1);
408 bg.add(c2);
409
410 msg.add(c1, GBC.eol());
411 msg.add(c2, GBC.eop());
412
413
414 JLabel description = new JLabel((String) data.attr.get("desc"));
415 JTextField tags = new JTextField();
416 tags.setText((String) data.attr.get("keywords"));
417 msg.add(new JLabel(tr("Description:")), GBC.std());
418 msg.add(description, GBC.eol().fill(GBC.HORIZONTAL));
419 msg.add(new JLabel(tr("Tags (keywords in GPX):")), GBC.std());
420 msg.add(tags, GBC.eol().fill(GBC.HORIZONTAL));
421 JCheckBox c3 = new JCheckBox("public");
422 msg.add(c3, GBC.eop());
423 msg.add(new JLabel("Please ensure that you don't upload your traces twice."), GBC.eop());
424
425 int answer = JOptionPane.showConfirmDialog(Main.parent, msg, tr("GPX-Upload"), JOptionPane.OK_CANCEL_OPTION);
426 if (answer == JOptionPane.OK_OPTION)
427 {
428 try {
429 String version = Main.pref.get("osm-server.version", "0.5");
430 URL url = new URL(Main.pref.get("osm-server.url") +
431 "/" + version + "/gpx/create");
432
433 // create a boundary string
434 String boundary = MultiPartFormOutputStream.createBoundary();
435 URLConnection urlConn = MultiPartFormOutputStream.createConnection(url);
436 urlConn.setRequestProperty("Accept", "*/*");
437 urlConn.setRequestProperty("Content-Type",
438 MultiPartFormOutputStream.getContentType(boundary));
439 // set some other request headers...
440 urlConn.setRequestProperty("Connection", "Keep-Alive");
441 urlConn.setRequestProperty("Cache-Control", "no-cache");
442 // no need to connect cuz getOutputStream() does it
443 MultiPartFormOutputStream out =
444 new MultiPartFormOutputStream(urlConn.getOutputStream(), boundary);
445 out.writeField("description", description.getText());
446 out.writeField("tags", tags.getText());
447 out.writeField("public", (c3.getSelectedObjects() != null) ? "1" : "0");
448 // upload a file
449 // out.writeFile("gpx_file", "text/xml", associatedFile);
450 // can also write bytes directly
451 // out.writeFile("myFile", "text/plain", "C:\\test.txt",
452 // "This is some file text.".getBytes("ASCII"));
453 File tmp = File.createTempFile("josm", "tmp.gpx");
454 FileOutputStream outs = new FileOutputStream(tmp);
455 new GpxWriter(outs).write(data);
456 outs.close();
457 FileInputStream ins = new FileInputStream(tmp);
458 new GpxWriter(System.out).write(data);
459 out.writeFile("gpx_file", "text/xml", data.storageFile.getName(), ins);
460 out.close();
461 tmp.delete();
462 // read response from server
463 BufferedReader in = new BufferedReader(
464 new InputStreamReader(urlConn.getInputStream()));
465 String line = "";
466 while((line = in.readLine()) != null) {
467 System.out.println(line);
468 }
469 in.close();
470
471 //TODO check response
472 /* int retCode = urlConn.getResponseCode();
473 System.out.println("got return: " + retCode);
474 String retMsg = urlConn.getResponseMessage();
475 urlConn.disconnect();
476 if (retCode != 200) {
477 // Look for a detailed error message from the server
478 if (urlConn.getHeaderField("Error") != null)
479 retMsg += "\n" + urlConn.getHeaderField("Error");
480
481 // Report our error
482 ByteArrayOutputStream o = new ByteArrayOutputStream();
483 System.out.println(new String(o.toByteArray(), "UTF-8").toString());
484 throw new RuntimeException(retCode+" "+retMsg);
485 }
486 */
487 } catch (UnknownHostException ex) {
488 throw new RuntimeException(tr("Unknown host")+": "+ex.getMessage(), ex);
489 } catch (Exception ex) {
490 //if (cancel)
491 // return; // assume cancel
492 if (ex instanceof RuntimeException)
493 throw (RuntimeException)ex;
494 throw new RuntimeException(ex.getMessage(), ex);
495 }
496 }
497 }
498 }
499
500 public class ConvertToDataLayerAction extends AbstractAction {
501 public ConvertToDataLayerAction() {
502 super(tr("Convert to data layer"), ImageProvider.get("converttoosm"));
503 }
504 public void actionPerformed(ActionEvent e) {
505 JPanel msg = new JPanel(new GridBagLayout());
506 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());
507 msg.add(new UrlLabel(tr("http://www.openstreetmap.org/traces")), GBC.eop());
508 if (!DontShowAgainInfo.show("convert_to_data", msg))
509 return;
510 DataSet ds = new DataSet();
511 for (GpxTrack trk : data.tracks) {
512 for (Collection<WayPoint> segment : trk.trackSegs) {
513 Way w = new Way();
514 for (WayPoint p : segment) {
515 Node n = new Node(p.latlon);
516 ds.nodes.add(n);
517 w.nodes.add(n);
518 }
519 ds.ways.add(w);
520 }
521 }
522 Main.main.addLayer(new OsmDataLayer(ds, tr("Converted from: {0}", GpxLayer.this.name), null));
523 Main.main.removeLayer(GpxLayer.this);
524 }
525 }
526
527 /**
528 * Makes a new marker layer derived from this GpxLayer containing at least one
529 * audio marker which the given audio file is associated with.
530 * Markers are derived from the following
531 * (a) explict waypoints in the GPX layer, or
532 * (b) named trackpoints in the GPX layer, or
533 * (c) (in future) voice recognised markers in the sound recording
534 * (d) a single marker at the beginning of the track
535 * @param wavFile : the file to be associated with the markers in the new marker layer
536 */
537 private void importAudio(File wavFile) {
538 String uri = "file:".concat(wavFile.getAbsolutePath());
539 MarkerLayer ml = new MarkerLayer(new GpxData(), tr("Audio markers from {0}", name), associatedFile, me);
540
541 Collection<WayPoint> waypoints = new ArrayList<WayPoint>();
542 boolean timedMarkersOmitted = false;
543 boolean untimedMarkersOmitted = false;
544
545 // determine time of first point in track
546 double firstTime = -1.0;
547 if (data.tracks != null && ! data.tracks.isEmpty()) {
548 for (GpxTrack track : data.tracks) {
549 if (track.trackSegs == null) continue;
550 for (Collection<WayPoint> seg : track.trackSegs) {
551 for (WayPoint w : seg) {
552 firstTime = w.time;
553 break;
554 }
555 if (firstTime >= 0.0) break;
556 }
557 if (firstTime >= 0.0) break;
558 }
559 }
560 if (firstTime < 0.0) {
561 JOptionPane.showMessageDialog(Main.parent, tr("No GPX track available in layer to associate audio with."));
562 return;
563 }
564
565 // (a) try explicit timestamped waypoints - unless suppressed
566 if (Main.pref.getBoolean("marker.audiofromexplicitwaypoints", true) &&
567 data.waypoints != null && ! data.waypoints.isEmpty())
568 {
569 for (WayPoint w : data.waypoints) {
570 if (w.time > firstTime) {
571 waypoints.add(w);
572 } else if (w.time > 0.0) {
573 timedMarkersOmitted = true;
574 }
575 }
576 }
577
578 // (b) try explicit waypoints without timestamps - unless suppressed
579 if (Main.pref.getBoolean("marker.audiofromuntimedwaypoints", true) &&
580 data.waypoints != null && ! data.waypoints.isEmpty())
581 {
582 for (WayPoint w : data.waypoints) {
583 if (waypoints.contains(w)) { continue; }
584 WayPoint wNear = nearestPointOnTrack(w.eastNorth, 10.0e-7 /* about 25m */);
585 if (wNear != null) {
586 WayPoint wc = new WayPoint(w.latlon);
587 wc.time = wNear.time;
588 if (w.attr.containsKey("name")) wc.attr.put("name", w.getString("name"));
589 waypoints.add(wc);
590 } else {
591 untimedMarkersOmitted = true;
592 }
593 }
594 }
595
596 // (c) use explicitly named track points, again unless suppressed
597 if ((Main.pref.getBoolean("marker.audiofromnamedtrackpoints", Main.pref.getBoolean("marker.namedtrackpoints") /* old name */)) &&
598 data.tracks != null && ! data.tracks.isEmpty())
599 {
600 for (GpxTrack track : data.tracks) {
601 if (track.trackSegs == null) continue;
602 for (Collection<WayPoint> seg : track.trackSegs) {
603 for (WayPoint w : seg) {
604 if (w.attr.containsKey("name") || w.attr.containsKey("desc")) {
605 waypoints.add(w);
606 }
607 }
608 }
609 }
610 }
611
612 // (d) analyse audio for spoken markers here, in due course
613
614 // (e) simply add a single marker at the start of the track
615 if ((Main.pref.getBoolean("marker.audiofromstart") || waypoints.isEmpty()) &&
616 data.tracks != null && ! data.tracks.isEmpty())
617 {
618 boolean gotOne = false;
619 for (GpxTrack track : data.tracks) {
620 if (track.trackSegs == null) continue;
621 for (Collection<WayPoint> seg : track.trackSegs) {
622 for (WayPoint w : seg) {
623 WayPoint wStart = new WayPoint(w.latlon);
624 wStart.attr.put("name", "start");
625 wStart.time = w.time;
626 waypoints.add(wStart);
627 gotOne = true;
628 break;
629 }
630 if (gotOne) break;
631 }
632 if (gotOne) break;
633 }
634 }
635
636 /* we must have got at least one waypoint now */
637
638 Collections.sort((ArrayList<WayPoint>) waypoints, new Comparator<WayPoint>() {
639 public int compare(WayPoint a, WayPoint b) {
640 return a.time <= b.time ? -1 : 1;
641 }
642 });
643
644 firstTime = -1.0; /* this time of the first waypoint, not first trackpoint */
645 for (WayPoint w : waypoints) {
646 if (firstTime < 0.0) firstTime = w.time;
647 double offset = w.time - firstTime;
648 String name;
649 if (w.attr.containsKey("name"))
650 name = w.getString("name");
651 else if (w.attr.containsKey("desc"))
652 name = w.getString("desc");
653 else
654 name = AudioMarker.inventName(offset);
655 AudioMarker am = AudioMarker.create(w.latlon,
656 name, uri, ml, w.time, offset);
657 ml.data.add(am);
658 }
659 Main.main.addLayer(ml);
660
661 if (timedMarkersOmitted) {
662 JOptionPane.showMessageDialog(Main.parent, tr("Some waypoints with timestamps from before the start of the track were omitted."));
663 }
664 if (untimedMarkersOmitted) {
665 JOptionPane.showMessageDialog(Main.parent, tr("Some waypoints which were too far from the track to sensibly estimate their time were omitted."));
666 }
667 }
668
669 /**
670 * Makes a WayPoint at the projection of point P onto the track providing P is
671 * less than tolerance away from the track
672
673 * @param P : the point to determine the projection for
674 * @param tolerance : must be no further than this from the track
675 * @return the closest point on the track to P, which may be the
676 * first or last point if off the end of a segment, or may be null if
677 * nothing close enough
678 */
679 public WayPoint nearestPointOnTrack(EastNorth P, double tolerance) {
680 /*
681 * assume the coordinates of P are xp,yp, and those of a section of track
682 * between two trackpoints are R=xr,yr and S=xs,ys. Let N be the projected point.
683 *
684 * The equation of RS is Ax + By + C = 0 where
685 * A = ys - yr
686 * B = xr - xs
687 * C = - Axr - Byr
688 *
689 * Also, note that the distance RS^2 is A^2 + B^2
690 *
691 * If RS^2 == 0.0 ignore the degenerate section of track
692 *
693 * PN^2 = (Axp + Byp + C)^2 / RS^2
694 * that is the distance from P to the line
695 *
696 * so if PN^2 is less than PNmin^2 (initialized to tolerance) we can reject
697 * the line; otherwise...
698 * determine if the projected poijnt lies within the bounds of the line:
699 * PR^2 - PN^2 <= RS^2 and PS^2 - PN^2 <= RS^2
700 *
701 * where PR^2 = (xp - xr)^2 + (yp-yr)^2
702 * and PS^2 = (xp - xs)^2 + (yp-ys)^2
703 *
704 * If so, calculate N as
705 * xn = xr + (RN/RS) B
706 * yn = y1 + (RN/RS) A
707 *
708 * where RN = sqrt(PR^2 - PN^2)
709 */
710
711 double PNminsq = tolerance * tolerance;
712 EastNorth bestEN = null;
713 double bestTime = 0.0;
714 double px = P.east();
715 double py = P.north();
716 double rx = 0.0, ry = 0.0, sx, sy, x, y;
717 if (data.tracks == null) return null;
718 for (GpxTrack track : data.tracks) {
719 if (track.trackSegs == null) continue;
720 for (Collection<WayPoint> seg : track.trackSegs) {
721 WayPoint R = null;
722 for (WayPoint S : seg) {
723 if (R == null) {
724 R = S;
725 rx = R.eastNorth.east();
726 ry = R.eastNorth.north();
727 x = px - rx;
728 y = py - ry;
729 double PRsq = x * x + y * y;
730 if (PRsq < PNminsq) {
731 PNminsq = PRsq;
732 bestEN = R.eastNorth;
733 bestTime = R.time;
734 }
735 } else {
736 sx = S.eastNorth.east();
737 sy = S.eastNorth.north();
738 double A = sy - ry;
739 double B = rx - sx;
740 double C = - A * rx - B * ry;
741 double RSsq = A * A + B * B;
742 if (RSsq == 0.0) continue;
743 double PNsq = A * px + B * py + C;
744 PNsq = PNsq * PNsq / RSsq;
745 if (PNsq < PNminsq) {
746 x = px - rx;
747 y = py - ry;
748 double PRsq = x * x + y * y;
749 x = px - sx;
750 y = py - sy;
751 double PSsq = x * x + y * y;
752 if (PRsq - PNsq <= RSsq && PSsq - PNsq <= RSsq) {
753 double RNoverRS = Math.sqrt((PRsq - PNsq)/RSsq);
754 double nx = rx - RNoverRS * B;
755 double ny = ry + RNoverRS * A;
756 bestEN = new EastNorth(nx, ny);
757 bestTime = R.time + RNoverRS * (S.time - R.time);
758 PNminsq = PNsq;
759 }
760 }
761 R = S;
762 rx = sx;
763 ry = sy;
764 }
765 }
766 if (R != null) {
767 /* if there is only one point in the seg, it will do this twice, but no matter */
768 rx = R.eastNorth.east();
769 ry = R.eastNorth.north();
770 x = px - rx;
771 y = py - ry;
772 double PRsq = x * x + y * y;
773 if (PRsq < PNminsq) {
774 PNminsq = PRsq;
775 bestEN = R.eastNorth;
776 bestTime = R.time;
777 }
778
779 }
780 }
781 }
782 if (bestEN == null) return null;
783 WayPoint best = new WayPoint(Main.proj.eastNorth2latlon(bestEN));
784 best.time = bestTime;
785 return best;
786 }
787}
Note: See TracBrowser for help on using the repository browser.