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

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