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

Last change on this file since 3408 was 3408, checked in by jttt, 14 years ago

Show only actions that can work on all selected layers in LayerListDialog popup menu

  • Property svn:eol-style set to native
File size: 62.0 KB
Line 
1// License: GPL. See LICENSE file for details.
2
3package org.openstreetmap.josm.gui.layer;
4
5import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
6import static org.openstreetmap.josm.tools.I18n.marktr;
7import static org.openstreetmap.josm.tools.I18n.tr;
8import static org.openstreetmap.josm.tools.I18n.trn;
9
10import java.awt.BasicStroke;
11import java.awt.Color;
12import java.awt.Graphics2D;
13import java.awt.GridBagLayout;
14import java.awt.Point;
15import java.awt.event.ActionEvent;
16import java.awt.geom.Area;
17import java.awt.geom.Rectangle2D;
18import java.io.File;
19import java.text.DateFormat;
20import java.text.DecimalFormat;
21import java.util.ArrayList;
22import java.util.Arrays;
23import java.util.Collection;
24import java.util.Collections;
25import java.util.Comparator;
26import java.util.LinkedList;
27import java.util.List;
28import java.util.concurrent.Future;
29
30import javax.swing.AbstractAction;
31import javax.swing.Action;
32import javax.swing.Box;
33import javax.swing.ButtonGroup;
34import javax.swing.Icon;
35import javax.swing.JColorChooser;
36import javax.swing.JFileChooser;
37import javax.swing.JLabel;
38import javax.swing.JList;
39import javax.swing.JOptionPane;
40import javax.swing.JPanel;
41import javax.swing.JRadioButton;
42import javax.swing.filechooser.FileFilter;
43
44import org.openstreetmap.josm.Main;
45import org.openstreetmap.josm.actions.RenameLayerAction;
46import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTaskList;
47import org.openstreetmap.josm.data.Bounds;
48import org.openstreetmap.josm.data.coor.EastNorth;
49import org.openstreetmap.josm.data.coor.LatLon;
50import org.openstreetmap.josm.data.gpx.GpxData;
51import org.openstreetmap.josm.data.gpx.GpxTrack;
52import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
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.ConditionalOptionPaneUtil;
59import org.openstreetmap.josm.gui.HelpAwareOptionPane;
60import org.openstreetmap.josm.gui.MapView;
61import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
62import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
63import org.openstreetmap.josm.gui.layer.markerlayer.AudioMarker;
64import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
65import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
66import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
67import org.openstreetmap.josm.io.JpgImporter;
68import org.openstreetmap.josm.tools.AudioUtil;
69import org.openstreetmap.josm.tools.DateUtils;
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
76 private static final String PREF_DOWNLOAD_ALONG_TRACK_DISTANCE = "gpxLayer.downloadAlongTrack.distance";
77 private static final String PREF_DOWNLOAD_ALONG_TRACK_AREA = "gpxLayer.downloadAlongTrack.area";
78 private static final String PREF_DOWNLOAD_ALONG_TRACK_NEAR = "gpxLayer.downloadAlongTrack.near";
79
80 public GpxData data;
81 protected static final double PHI = Math.toRadians(15);
82 private boolean computeCacheInSync;
83 private int computeCacheMaxLineLengthUsed;
84 private Color computeCacheColorUsed;
85 private colorModes computeCacheColored;
86 private int computeCacheColorTracksTune;
87 private boolean isLocalFile;
88
89 private final List<GpxTrack> lastTracks = new ArrayList<GpxTrack>(); // List of tracks at last paint
90 private int lastUpdateCount;
91
92 private static class Markers {
93 public boolean timedMarkersOmitted = false;
94 public boolean untimedMarkersOmitted = false;
95 }
96
97 public GpxLayer(GpxData d) {
98 super((String) d.attr.get("name"));
99 data = d;
100 computeCacheInSync = false;
101 }
102
103 public GpxLayer(GpxData d, String name) {
104 this(d);
105 this.setName(name);
106 }
107
108 public GpxLayer(GpxData d, String name, boolean isLocal) {
109 this(d);
110 this.setName(name);
111 this.isLocalFile = isLocal;
112 }
113
114 @Override
115 public Icon getIcon() {
116 return ImageProvider.get("layer", "gpx_small");
117 }
118
119 @Override
120 public Object getInfoComponent() {
121 return getToolTipText();
122 }
123
124 static public Color getColor(String name) {
125 return Main.pref.getColor(marktr("gps point"), name != null ? "layer " + name : null, Color.gray);
126 }
127
128 @Override
129 public Action[] getMenuEntries() {
130 if (Main.applet)
131 return new Action[] {
132 LayerListDialog.getInstance().createShowHideLayerAction(),
133 LayerListDialog.getInstance().createDeleteLayerAction(),
134 SeparatorLayerAction.INSTANCE,
135 new CustomizeColor(),
136 new CustomizeLineDrawing(),
137 new ConvertToDataLayerAction(),
138 SeparatorLayerAction.INSTANCE,
139 new RenameLayerAction(getAssociatedFile(), this),
140 SeparatorLayerAction.INSTANCE,
141 new LayerListPopup.InfoAction(this) };
142 return new Action[] {
143 LayerListDialog.getInstance().createShowHideLayerAction(),
144 LayerListDialog.getInstance().createDeleteLayerAction(),
145 SeparatorLayerAction.INSTANCE,
146 new LayerSaveAction(this),
147 new LayerSaveAsAction(this),
148 new CustomizeColor(),
149 new CustomizeLineDrawing(),
150 new ImportImages(),
151 new ImportAudio(),
152 new MarkersFromNamedPoins(),
153 new ConvertToDataLayerAction(),
154 new DownloadAlongTrackAction(),
155 SeparatorLayerAction.INSTANCE,
156 new RenameLayerAction(getAssociatedFile(), this),
157 SeparatorLayerAction.INSTANCE,
158 new LayerListPopup.InfoAction(this) };
159 }
160
161 @Override
162 public String getToolTipText() {
163 StringBuilder info = new StringBuilder().append("<html>");
164
165 if (data.attr.containsKey("name")) {
166 info.append(tr("Name: {0}", data.attr.get(GpxData.META_NAME))).append("<br>");
167 }
168
169 if (data.attr.containsKey("desc")) {
170 info.append(tr("Description: {0}", data.attr.get(GpxData.META_DESC))).append("<br>");
171 }
172
173 if (data.tracks.size() > 0) {
174 info.append("<table><thead align='center'><tr><td colspan='5'>"
175 + trn("{0} track", "{0} tracks", data.tracks.size(), data.tracks.size())
176 + "</td></tr><tr align='center'><td>" + tr("Name") + "</td><td>"
177 + tr("Description") + "</td><td>" + tr("Timespan")
178 + "</td><td>" + tr("Length") + "</td><td>" + tr("URL")
179 + "</td></tr></thead>");
180
181 for (GpxTrack trk : data.tracks) {
182 WayPoint earliest = null, latest = null;
183
184 info.append("<tr><td>");
185 if (trk.getAttributes().containsKey("name")) {
186 info.append(trk.getAttributes().get("name"));
187 }
188 info.append("</td><td>");
189 if (trk.getAttributes().containsKey("desc")) {
190 info.append(" ").append(trk.getAttributes().get("desc"));
191 }
192 info.append("</td><td>");
193
194 for (GpxTrackSegment seg : trk.getSegments()) {
195 for (WayPoint pnt : seg.getWayPoints()) {
196 if (latest == null) {
197 latest = earliest = pnt;
198 } else {
199 if (pnt.compareTo(earliest) < 0) {
200 earliest = pnt;
201 } else {
202 latest = pnt;
203 }
204 }
205 }
206 }
207
208 if (earliest != null && latest != null) {
209 DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);
210 String earliestDate = df.format(earliest.getTime());
211 String latestDate = df.format(latest.getTime());
212
213 if (earliestDate.equals(latestDate)) {
214 DateFormat tf = DateFormat.getTimeInstance(DateFormat.SHORT);
215 info.append(earliestDate).append(" ");
216 info.append(tf.format(earliest.getTime())).append(" - ").append(tf.format(latest.getTime()));
217 } else {
218 DateFormat dtf = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
219 info.append(dtf.format(earliest.getTime())).append(" - ").append(dtf.format(latest.getTime()));
220 }
221
222 int diff = (int) (latest.time - earliest.time);
223 info.append(String.format(" (%d:%02d)", diff / 3600, (diff % 3600) / 60));
224 }
225
226 info.append("</td><td>");
227 info.append(new DecimalFormat("#0.00").format(trk.length() / 1000) + "km");
228 info.append("</td><td>");
229 if (trk.getAttributes().containsKey("url")) {
230 info.append(trk.getAttributes().get("url"));
231 }
232 info.append("</td></tr>");
233 }
234
235 info.append("</table><br><br>");
236
237 }
238
239 info.append(tr("Length: ") + new DecimalFormat("#0.00").format(data.length() / 1000) + "km");
240 info.append("<br>");
241
242 info.append(trn("{0} route, ", "{0} routes, ", data.routes.size(), data.routes.size())).append(
243 trn("{0} waypoint", "{0} waypoints", data.waypoints.size(), data.waypoints.size())).append("<br>");
244
245 return info.append("</html>").toString();
246 }
247
248 @Override
249 public boolean isMergable(Layer other) {
250 return other instanceof GpxLayer;
251 }
252
253 private int sumUpdateCount() {
254 int updateCount = 0;
255 for (GpxTrack track: data.tracks) {
256 updateCount += track.getUpdateCount();
257 }
258 return updateCount;
259 }
260
261 @Override
262 public boolean isChanged() {
263 if (data.tracks.equals(lastTracks))
264 return sumUpdateCount() != lastUpdateCount;
265 else
266 return true;
267 }
268
269 @Override
270 public void mergeFrom(Layer from) {
271 data.mergeFrom(((GpxLayer) from).data);
272 computeCacheInSync = false;
273 }
274
275 private static Color[] colors = new Color[256];
276 static {
277 for (int i = 0; i < colors.length; i++) {
278 colors[i] = Color.getHSBColor(i / 300.0f, 1, 1);
279 }
280 }
281
282 // lookup array to draw arrows without doing any math
283 private static int ll0 = 9;
284 private static int sl4 = 5;
285 private static int sl9 = 3;
286 private static int[][] dir = { { +sl4, +ll0, +ll0, +sl4 }, { -sl9, +ll0, +sl9, +ll0 }, { -ll0, +sl4, -sl4, +ll0 },
287 { -ll0, -sl9, -ll0, +sl9 }, { -sl4, -ll0, -ll0, -sl4 }, { +sl9, -ll0, -sl9, -ll0 },
288 { +ll0, -sl4, +sl4, -ll0 }, { +ll0, +sl9, +ll0, -sl9 }, { +sl4, +ll0, +ll0, +sl4 },
289 { -sl9, +ll0, +sl9, +ll0 }, { -ll0, +sl4, -sl4, +ll0 }, { -ll0, -sl9, -ll0, +sl9 } };
290
291 // the different color modes
292 enum colorModes {
293 none, velocity, dilution
294 }
295
296 @Override
297 public void paint(Graphics2D g, MapView mv, Bounds box) {
298 lastUpdateCount = sumUpdateCount();
299 lastTracks.clear();
300 lastTracks.addAll(data.tracks);
301
302 /****************************************************************
303 ********** STEP 1 - GET CONFIG VALUES **************************
304 ****************************************************************/
305 // Long startTime = System.currentTimeMillis();
306 Color neutralColor = getColor(getName());
307 // also draw lines between points belonging to different segments
308 boolean forceLines = Main.pref.getBoolean("draw.rawgps.lines.force");
309 // draw direction arrows on the lines
310 boolean direction = Main.pref.getBoolean("draw.rawgps.direction");
311 // don't draw lines if longer than x meters
312 int lineWidth = Main.pref.getInteger("draw.rawgps.linewidth",0);
313
314 int maxLineLength;
315 if (this.isLocalFile) {
316 maxLineLength = Main.pref.getInteger("draw.rawgps.max-line-length.local", -1);
317 } else {
318 maxLineLength = Main.pref.getInteger("draw.rawgps.max-line-length", 200);
319 }
320 // draw line between points, global setting
321 boolean lines = (Main.pref.getBoolean("draw.rawgps.lines", true) || (Main.pref
322 .getBoolean("draw.rawgps.lines.localfiles") && this.isLocalFile));
323 String linesKey = "draw.rawgps.lines.layer " + getName();
324 // draw lines, per-layer setting
325 if (Main.pref.hasKey(linesKey)) {
326 lines = Main.pref.getBoolean(linesKey);
327 }
328 // paint large dots for points
329 boolean large = Main.pref.getBoolean("draw.rawgps.large");
330 boolean hdopcircle = Main.pref.getBoolean("draw.rawgps.hdopcircle", true);
331 // color the lines
332 colorModes colored = colorModes.none;
333 try {
334 colored = colorModes.values()[Main.pref.getInteger("draw.rawgps.colors", 0)];
335 } catch (Exception e) {
336 }
337 // paint direction arrow with alternate math. may be faster
338 boolean alternatedirection = Main.pref.getBoolean("draw.rawgps.alternatedirection");
339 // don't draw arrows nearer to each other than this
340 int delta = Main.pref.getInteger("draw.rawgps.min-arrow-distance", 0);
341 // allows to tweak line coloring for different speed levels.
342 int colorTracksTune = Main.pref.getInteger("draw.rawgps.colorTracksTune", 45);
343
344 if(lineWidth != 0)
345 {
346 g.setStroke(new BasicStroke(lineWidth,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND));
347 }
348
349 /****************************************************************
350 ********** STEP 2a - CHECK CACHE VALIDITY **********************
351 ****************************************************************/
352 if ((computeCacheMaxLineLengthUsed != maxLineLength) || (!neutralColor.equals(computeCacheColorUsed))
353 || (computeCacheColored != colored) || (computeCacheColorTracksTune != colorTracksTune)) {
354 // System.out.println("(re-)computing gpx line styles, reason: CCIS=" +
355 // computeCacheInSync + " CCMLLU=" + (computeCacheMaxLineLengthUsed != maxLineLength) +
356 // " CCCU=" + (!neutralColor.equals(computeCacheColorUsed)) + " CCC=" +
357 // (computeCacheColored != colored));
358 computeCacheMaxLineLengthUsed = maxLineLength;
359 computeCacheInSync = false;
360 computeCacheColorUsed = neutralColor;
361 computeCacheColored = colored;
362 computeCacheColorTracksTune = colorTracksTune;
363 }
364
365 /****************************************************************
366 ********** STEP 2b - RE-COMPUTE CACHE DATA *********************
367 ****************************************************************/
368 if (!computeCacheInSync) { // don't compute if the cache is good
369 WayPoint oldWp = null;
370 for (GpxTrack trk : data.tracks) {
371 for (GpxTrackSegment segment : trk.getSegments()) {
372 if (!forceLines) { // don't draw lines between segments, unless forced to
373 oldWp = null;
374 }
375 for (WayPoint trkPnt : segment.getWayPoints()) {
376 LatLon c = trkPnt.getCoor();
377 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
378 continue;
379 }
380 trkPnt.customColoring = neutralColor;
381 if (oldWp != null) {
382 double dist = c.greatCircleDistance(oldWp.getCoor());
383
384 switch (colored) {
385 case velocity:
386 double dtime = trkPnt.time - oldWp.time;
387 double vel = dist / dtime;
388 double velColor = vel / colorTracksTune * 255;
389 // Bad case first
390 if (dtime <= 0 || vel < 0 || velColor > 255) {
391 trkPnt.customColoring = colors[255];
392 } else {
393 trkPnt.customColoring = colors[(int) (velColor)];
394 }
395 break;
396
397 case dilution:
398 if (trkPnt.attr.get("hdop") != null) {
399 float hdop = ((Float) trkPnt.attr.get("hdop")).floatValue();
400 if (hdop < 0) {
401 hdop = 0;
402 }
403 int hdoplvl = Math.round(hdop * Main.pref.getInteger("hdop.factor", 25));
404 // High hdop is bad, but high values in colors are green.
405 // Therefore inverse the logic
406 int hdopcolor = 255 - (hdoplvl > 255 ? 255 : hdoplvl);
407 trkPnt.customColoring = colors[hdopcolor];
408 }
409 break;
410 }
411
412 if (maxLineLength == -1 || dist <= maxLineLength) {
413 trkPnt.drawLine = true;
414 trkPnt.dir = (int) oldWp.getCoor().heading(trkPnt.getCoor());
415 } else {
416 trkPnt.drawLine = false;
417 }
418 } else { // make sure we reset outdated data
419 trkPnt.drawLine = false;
420 }
421 oldWp = trkPnt;
422 }
423 }
424 }
425 computeCacheInSync = true;
426 }
427
428 List<Collection<WayPoint>> visibleSegments = new ArrayList<Collection<WayPoint>>();
429 for (GpxTrack trk: data.tracks) {
430 for (GpxTrackSegment trkSeg: trk.getSegments()) {
431 if (trkSeg.getBounds() != null && trkSeg.getBounds().intersects(box)) {
432 visibleSegments.add(trkSeg.getWayPoints());
433 }
434 }
435 }
436
437 /****************************************************************
438 ********** STEP 3a - DRAW LINES ********************************
439 ****************************************************************/
440 if (lines) {
441 Point old = null;
442 for (Collection<WayPoint> segment : visibleSegments) {
443 for (WayPoint trkPnt : segment) {
444 LatLon c = trkPnt.getCoor();
445 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
446 continue;
447 }
448 Point screen = mv.getPoint(trkPnt.getEastNorth());
449 if (trkPnt.drawLine) {
450 // skip points that are on the same screenposition
451 if (old != null && ((old.x != screen.x) || (old.y != screen.y))) {
452 g.setColor(trkPnt.customColoring);
453 g.drawLine(old.x, old.y, screen.x, screen.y);
454 }
455 }
456 old = screen;
457 } // end for trkpnt
458 } // end for segment
459 } // end if lines
460
461 /****************************************************************
462 ********** STEP 3b - DRAW NICE ARROWS **************************
463 ****************************************************************/
464 if (lines && direction && !alternatedirection) {
465 Point old = null;
466 Point oldA = null; // last arrow painted
467 for (Collection<WayPoint> segment : visibleSegments) {
468 for (WayPoint trkPnt : segment) {
469 LatLon c = trkPnt.getCoor();
470 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
471 continue;
472 }
473 if (trkPnt.drawLine) {
474 Point screen = mv.getPoint(trkPnt.getEastNorth());
475 // skip points that are on the same screenposition
476 if (old != null
477 && (oldA == null || screen.x < oldA.x - delta || screen.x > oldA.x + delta
478 || screen.y < oldA.y - delta || screen.y > oldA.y + delta)) {
479 g.setColor(trkPnt.customColoring);
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)),
482 (int) (screen.y + 10 * Math.sin(t - PHI)));
483 g.drawLine(screen.x, screen.y, (int) (screen.x + 10 * Math.cos(t + PHI)),
484 (int) (screen.y + 10 * Math.sin(t + PHI)));
485 oldA = screen;
486 }
487 old = screen;
488 }
489 } // end for trkpnt
490 } // end for segment
491 } // end if lines
492
493 /****************************************************************
494 ********** STEP 3c - DRAW FAST ARROWS **************************
495 ****************************************************************/
496 if (lines && direction && alternatedirection) {
497 Point old = null;
498 Point oldA = null; // last arrow painted
499 for (Collection<WayPoint> segment : visibleSegments) {
500 for (WayPoint trkPnt : segment) {
501 LatLon c = trkPnt.getCoor();
502 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
503 continue;
504 }
505 if (trkPnt.drawLine) {
506 Point screen = mv.getPoint(trkPnt.getEastNorth());
507 // skip points that are on the same screenposition
508 if (old != null
509 && (oldA == null || screen.x < oldA.x - delta || screen.x > oldA.x + delta
510 || screen.y < oldA.y - delta || screen.y > oldA.y + delta)) {
511 g.setColor(trkPnt.customColoring);
512 g.drawLine(screen.x, screen.y, screen.x + dir[trkPnt.dir][0], screen.y
513 + dir[trkPnt.dir][1]);
514 g.drawLine(screen.x, screen.y, screen.x + dir[trkPnt.dir][2], screen.y
515 + dir[trkPnt.dir][3]);
516 oldA = screen;
517 }
518 old = screen;
519 }
520 } // end for trkpnt
521 } // end for segment
522 } // end if lines
523
524 /****************************************************************
525 ********** STEP 3d - DRAW LARGE POINTS AND HDOP CIRCLE *********
526 ****************************************************************/
527 if (large || hdopcircle) {
528 g.setColor(neutralColor);
529 for (Collection<WayPoint> segment : visibleSegments) {
530 for (WayPoint trkPnt : segment) {
531 LatLon c = trkPnt.getCoor();
532 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
533 continue;
534 }
535 Point screen = mv.getPoint(trkPnt.getEastNorth());
536 g.setColor(trkPnt.customColoring);
537 if (hdopcircle && trkPnt.attr.get("hdop") != null) {
538 // hdop value
539 float hdop = ((Float)trkPnt.attr.get("hdop")).floatValue();
540 if (hdop < 0) {
541 hdop = 0;
542 }
543 // hdop pixels
544 int hdopp = mv.getPoint(new LatLon(trkPnt.getCoor().lat(), trkPnt.getCoor().lon() + 2*6*hdop*360/40000000)).x - screen.x;
545 g.drawArc(screen.x-hdopp/2, screen.y-hdopp/2, hdopp, hdopp, 0, 360);
546 }
547 if (large) {
548 g.fillRect(screen.x-1, screen.y-1, 3, 3);
549 }
550 } // end for trkpnt
551 } // end for segment
552 } // end if large || hdopcircle
553
554 /****************************************************************
555 ********** STEP 3e - DRAW SMALL POINTS FOR LINES ***************
556 ****************************************************************/
557 if (!large && lines) {
558 g.setColor(neutralColor);
559 for (Collection<WayPoint> segment : visibleSegments) {
560 for (WayPoint trkPnt : segment) {
561 LatLon c = trkPnt.getCoor();
562 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
563 continue;
564 }
565 if (!trkPnt.drawLine) {
566 Point screen = mv.getPoint(trkPnt.getEastNorth());
567 g.drawRect(screen.x, screen.y, 0, 0);
568 }
569 } // end for trkpnt
570 } // end for segment
571 } // end if large
572
573 /****************************************************************
574 ********** STEP 3f - DRAW SMALL POINTS INSTEAD OF LINES ********
575 ****************************************************************/
576 if (!large && !lines) {
577 g.setColor(neutralColor);
578 for (Collection<WayPoint> segment : visibleSegments) {
579 for (WayPoint trkPnt : segment) {
580 LatLon c = trkPnt.getCoor();
581 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
582 continue;
583 }
584 Point screen = mv.getPoint(trkPnt.getEastNorth());
585 g.setColor(trkPnt.customColoring);
586 g.drawRect(screen.x, screen.y, 0, 0);
587 } // end for trkpnt
588 } // end for segment
589 } // end if large
590
591 // Long duration = System.currentTimeMillis() - startTime;
592 // System.out.println(duration);
593 } // end paint
594
595 @Override
596 public void visitBoundingBox(BoundingXYVisitor v) {
597 v.visit(data.recalculateBounds());
598 }
599
600 public class ConvertToDataLayerAction extends AbstractAction {
601 public ConvertToDataLayerAction() {
602 super(tr("Convert to data layer"), ImageProvider.get("converttoosm"));
603 }
604
605 public void actionPerformed(ActionEvent e) {
606 JPanel msg = new JPanel(new GridBagLayout());
607 msg
608 .add(
609 new JLabel(
610 tr("<html>Upload of unprocessed GPS data as map data is considered harmful.<br>If you want to upload traces, look here:")),
611 GBC.eol());
612 msg.add(new UrlLabel(tr("http://www.openstreetmap.org/traces")), GBC.eop());
613 if (!ConditionalOptionPaneUtil.showConfirmationDialog("convert_to_data", Main.parent, msg, tr("Warning"),
614 JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, JOptionPane.OK_OPTION))
615 return;
616 DataSet ds = new DataSet();
617 for (GpxTrack trk : data.tracks) {
618 for (GpxTrackSegment segment : trk.getSegments()) {
619 List<Node> nodes = new ArrayList<Node>();
620 for (WayPoint p : segment.getWayPoints()) {
621 Node n = new Node(p.getCoor());
622 String timestr = p.getString("time");
623 if (timestr != null) {
624 n.setTimestamp(DateUtils.fromString(timestr));
625 }
626 ds.addPrimitive(n);
627 nodes.add(n);
628 }
629 Way w = new Way();
630 w.setNodes(nodes);
631 ds.addPrimitive(w);
632 }
633 }
634 Main.main
635 .addLayer(new OsmDataLayer(ds, tr("Converted from: {0}", GpxLayer.this.getName()), getAssociatedFile()));
636 Main.main.removeLayer(GpxLayer.this);
637 }
638 }
639
640 @Override
641 public File getAssociatedFile() {
642 return data.storageFile;
643 }
644
645 @Override
646 public void setAssociatedFile(File file) {
647 data.storageFile = file;
648 }
649
650 /**
651 * Action that issues a series of download requests to the API, following the GPX track.
652 *
653 * @author fred
654 */
655 public class DownloadAlongTrackAction extends AbstractAction {
656 public DownloadAlongTrackAction() {
657 super(tr("Download from OSM along this track"), ImageProvider.get("downloadalongtrack"));
658 }
659
660 public void actionPerformed(ActionEvent e) {
661 JPanel msg = new JPanel(new GridBagLayout());
662 Integer dist[] = { 5000, 500, 50 };
663 Integer area[] = { 20, 10, 5, 1 };
664
665 msg.add(new JLabel(tr("Download everything within:")), GBC.eol());
666 String s[] = new String[dist.length];
667 for (int i = 0; i < dist.length; ++i) {
668 s[i] = tr("{0} meters", dist[i]);
669 }
670 JList buffer = new JList(s);
671 buffer.setSelectedIndex(Main.pref.getInteger(PREF_DOWNLOAD_ALONG_TRACK_DISTANCE, 0));
672 msg.add(buffer, GBC.eol());
673
674 msg.add(new JLabel(tr("Maximum area per request:")), GBC.eol());
675 s = new String[area.length];
676 for (int i = 0; i < area.length; ++i) {
677 s[i] = tr("{0} sq km", area[i]);
678 }
679 JList maxRect = new JList(s);
680 maxRect.setSelectedIndex(Main.pref.getInteger(PREF_DOWNLOAD_ALONG_TRACK_AREA, 0));
681 msg.add(maxRect, GBC.eol());
682
683 msg.add(new JLabel(tr("Download near:")), GBC.eol());
684 JList downloadNear = new JList(new String[] { tr("track only"), tr("waypoints only"), tr("track and waypoints") });
685 int NEAR_TRACK=0;
686 int NEAR_WAYPOINTS=1;
687 int NEAR_BOTH=2;
688
689 downloadNear.setSelectedIndex(Main.pref.getInteger(PREF_DOWNLOAD_ALONG_TRACK_NEAR, 0));
690 msg.add(downloadNear, GBC.eol());
691
692 int ret = JOptionPane.showConfirmDialog(
693 Main.parent,
694 msg,
695 tr("Download from OSM along this track"),
696 JOptionPane.OK_CANCEL_OPTION,
697 JOptionPane.QUESTION_MESSAGE
698 );
699 switch(ret) {
700 case JOptionPane.CANCEL_OPTION:
701 case JOptionPane.CLOSED_OPTION:
702 return;
703 default:
704 // continue
705 }
706
707 Main.pref.putInteger(PREF_DOWNLOAD_ALONG_TRACK_DISTANCE, buffer.getSelectedIndex());
708 Main.pref.putInteger(PREF_DOWNLOAD_ALONG_TRACK_AREA, maxRect.getSelectedIndex());
709 int near = downloadNear.getSelectedIndex();
710 Main.pref.putInteger(PREF_DOWNLOAD_ALONG_TRACK_NEAR, near);
711
712 /*
713 * Find the average latitude for the data we're contemplating, so we can know how many
714 * metres per degree of longitude we have.
715 */
716 double latsum = 0;
717 int latcnt = 0;
718
719 if (near == NEAR_TRACK || near == NEAR_BOTH) {
720 for (GpxTrack trk : data.tracks) {
721 for (GpxTrackSegment segment : trk.getSegments()) {
722 for (WayPoint p : segment.getWayPoints()) {
723 latsum += p.getCoor().lat();
724 latcnt++;
725 }
726 }
727 }
728 }
729
730 if (near == NEAR_WAYPOINTS || near == NEAR_BOTH) {
731 for (WayPoint p : data.waypoints) {
732 latsum += p.getCoor().lat();
733 latcnt++;
734 }
735 }
736
737 double avglat = latsum / latcnt;
738 double scale = Math.cos(Math.toRadians(avglat));
739
740 /*
741 * Compute buffer zone extents and maximum bounding box size. Note that the maximum we
742 * ever offer is a bbox area of 0.002, while the API theoretically supports 0.25, but as
743 * soon as you touch any built-up area, that kind of bounding box will download forever
744 * and then stop because it has more than 50k nodes.
745 */
746 Integer i = buffer.getSelectedIndex();
747 int buffer_dist = dist[i < 0 ? 0 : i];
748 double buffer_y = buffer_dist / 100000.0;
749 double buffer_x = buffer_y / scale;
750 i = maxRect.getSelectedIndex();
751 double max_area = area[i < 0 ? 0 : i] / 10000.0 / scale;
752 Area a = new Area();
753 Rectangle2D r = new Rectangle2D.Double();
754
755 /*
756 * Collect the combined area of all gpx points plus buffer zones around them. We ignore
757 * points that lie closer to the previous point than the given buffer size because
758 * otherwise this operation takes ages.
759 */
760 LatLon previous = null;
761 if (near == NEAR_TRACK || near == NEAR_BOTH) {
762 for (GpxTrack trk : data.tracks) {
763 for (GpxTrackSegment segment : trk.getSegments()) {
764 for (WayPoint p : segment.getWayPoints()) {
765 LatLon c = p.getCoor();
766 if (previous == null || c.greatCircleDistance(previous) > buffer_dist) {
767 // we add a buffer around the point.
768 r.setRect(c.lon() - buffer_x, c.lat() - buffer_y, 2 * buffer_x, 2 * buffer_y);
769 a.add(new Area(r));
770 previous = c;
771 }
772 }
773 }
774 }
775 }
776 if (near == NEAR_WAYPOINTS || near == NEAR_BOTH) {
777 for (WayPoint p : data.waypoints) {
778 LatLon c = p.getCoor();
779 if (previous == null || c.greatCircleDistance(previous) > buffer_dist) {
780 // we add a buffer around the point.
781 r.setRect(c.lon() - buffer_x, c.lat() - buffer_y, 2 * buffer_x, 2 * buffer_y);
782 a.add(new Area(r));
783 previous = c;
784 }
785 }
786 }
787
788 /*
789 * Area "a" now contains the hull that we would like to download data for. however we
790 * can only download rectangles, so the following is an attempt at finding a number of
791 * rectangles to download.
792 *
793 * The idea is simply: Start out with the full bounding box. If it is too large, then
794 * split it in half and repeat recursively for each half until you arrive at something
795 * small enough to download. The algorithm is improved by always using the intersection
796 * between the rectangle and the actual desired area. For example, if you have a track
797 * that goes like this: +----+ | /| | / | | / | |/ | +----+ then we would first look at
798 * downloading the whole rectangle (assume it's too big), after that we split it in half
799 * (upper and lower half), but we donot request the full upper and lower rectangle, only
800 * the part of the upper/lower rectangle that actually has something in it.
801 */
802
803 List<Rectangle2D> toDownload = new ArrayList<Rectangle2D>();
804
805 addToDownload(a, a.getBounds(), toDownload, max_area);
806
807 msg = new JPanel(new GridBagLayout());
808
809 msg.add(new JLabel(
810 tr("<html>This action will require {0} individual<br>"
811 + "download requests. Do you wish<br>to continue?</html>",
812 toDownload.size())), GBC.eol());
813
814 if (toDownload.size() > 1) {
815 ret = JOptionPane.showConfirmDialog(
816 Main.parent,
817 msg,
818 tr("Download from OSM along this track"),
819 JOptionPane.OK_CANCEL_OPTION,
820 JOptionPane.PLAIN_MESSAGE
821 );
822 switch(ret) {
823 case JOptionPane.CANCEL_OPTION:
824 case JOptionPane.CLOSED_OPTION:
825 return;
826 default:
827 // continue
828 }
829 }
830 final PleaseWaitProgressMonitor monitor = new PleaseWaitProgressMonitor(tr("Download data"));
831 final Future<?> future = new DownloadOsmTaskList().download(false, toDownload, monitor);
832 Main.worker.submit(
833 new Runnable() {
834 public void run() {
835 try {
836 future.get();
837 } catch(Exception e) {
838 e.printStackTrace();
839 return;
840 }
841 monitor.close();
842 }
843 }
844 );
845 }
846 }
847
848 private static void addToDownload(Area a, Rectangle2D r, Collection<Rectangle2D> results, double max_area) {
849 Area tmp = new Area(r);
850 // intersect with sought-after area
851 tmp.intersect(a);
852 if (tmp.isEmpty())
853 return;
854 Rectangle2D bounds = tmp.getBounds2D();
855 if (bounds.getWidth() * bounds.getHeight() > max_area) {
856 // the rectangle gets too large; split it and make recursive call.
857 Rectangle2D r1;
858 Rectangle2D r2;
859 if (bounds.getWidth() > bounds.getHeight()) {
860 // rectangles that are wider than high are split into a left and right half,
861 r1 = new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth() / 2, bounds.getHeight());
862 r2 = new Rectangle2D.Double(bounds.getX() + bounds.getWidth() / 2, bounds.getY(),
863 bounds.getWidth() / 2, bounds.getHeight());
864 } else {
865 // others into a top and bottom half.
866 r1 = new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight() / 2);
867 r2 = new Rectangle2D.Double(bounds.getX(), bounds.getY() + bounds.getHeight() / 2, bounds.getWidth(),
868 bounds.getHeight() / 2);
869 }
870 addToDownload(a, r1, results, max_area);
871 addToDownload(a, r2, results, max_area);
872 } else {
873 results.add(bounds);
874 }
875 }
876
877 /**
878 * Makes a new marker layer derived from this GpxLayer containing at least one audio marker
879 * which the given audio file is associated with. Markers are derived from the following (a)
880 * explict waypoints in the GPX layer, or (b) named trackpoints in the GPX layer, or (d)
881 * timestamp on the wav file (e) (in future) voice recognised markers in the sound recording (f)
882 * a single marker at the beginning of the track
883 * @param wavFile : the file to be associated with the markers in the new marker layer
884 * @param markers : keeps track of warning messages to avoid repeated warnings
885 */
886 private void importAudio(File wavFile, MarkerLayer ml, double firstStartTime, Markers markers) {
887 String uri = "file:".concat(wavFile.getAbsolutePath());
888 Collection<WayPoint> waypoints = new ArrayList<WayPoint>();
889 boolean timedMarkersOmitted = false;
890 boolean untimedMarkersOmitted = false;
891 double snapDistance = Main.pref.getDouble("marker.audiofromuntimedwaypoints.distance", 1.0e-3); /*
892 * about
893 * 25
894 * m
895 */
896 WayPoint wayPointFromTimeStamp = null;
897
898 // determine time of first point in track
899 double firstTime = -1.0;
900 if (data.tracks != null && !data.tracks.isEmpty()) {
901 for (GpxTrack track : data.tracks) {
902 for (GpxTrackSegment seg : track.getSegments()) {
903 for (WayPoint w : seg.getWayPoints()) {
904 firstTime = w.time;
905 break;
906 }
907 if (firstTime >= 0.0) {
908 break;
909 }
910 }
911 if (firstTime >= 0.0) {
912 break;
913 }
914 }
915 }
916 if (firstTime < 0.0) {
917 JOptionPane.showMessageDialog(
918 Main.parent,
919 tr("No GPX track available in layer to associate audio with."),
920 tr("Error"),
921 JOptionPane.ERROR_MESSAGE
922 );
923 return;
924 }
925
926 // (a) try explicit timestamped waypoints - unless suppressed
927 if (Main.pref.getBoolean("marker.audiofromexplicitwaypoints", true) && data.waypoints != null
928 && !data.waypoints.isEmpty()) {
929 for (WayPoint w : data.waypoints) {
930 if (w.time > firstTime) {
931 waypoints.add(w);
932 } else if (w.time > 0.0) {
933 timedMarkersOmitted = true;
934 }
935 }
936 }
937
938 // (b) try explicit waypoints without timestamps - unless suppressed
939 if (Main.pref.getBoolean("marker.audiofromuntimedwaypoints", true) && data.waypoints != null
940 && !data.waypoints.isEmpty()) {
941 for (WayPoint w : data.waypoints) {
942 if (waypoints.contains(w)) {
943 continue;
944 }
945 WayPoint wNear = nearestPointOnTrack(w.getEastNorth(), snapDistance);
946 if (wNear != null) {
947 WayPoint wc = new WayPoint(w.getCoor());
948 wc.time = wNear.time;
949 if (w.attr.containsKey("name")) {
950 wc.attr.put("name", w.getString("name"));
951 }
952 waypoints.add(wc);
953 } else {
954 untimedMarkersOmitted = true;
955 }
956 }
957 }
958
959 // (c) use explicitly named track points, again unless suppressed
960 if ((Main.pref.getBoolean("marker.audiofromnamedtrackpoints", false)) && data.tracks != null
961 && !data.tracks.isEmpty()) {
962 for (GpxTrack track : data.tracks) {
963 for (GpxTrackSegment seg : track.getSegments()) {
964 for (WayPoint w : seg.getWayPoints()) {
965 if (w.attr.containsKey("name") || w.attr.containsKey("desc")) {
966 waypoints.add(w);
967 }
968 }
969 }
970 }
971 }
972
973 // (d) use timestamp of file as location on track
974 if ((Main.pref.getBoolean("marker.audiofromwavtimestamps", false)) && data.tracks != null
975 && !data.tracks.isEmpty()) {
976 double lastModified = wavFile.lastModified() / 1000.0; // lastModified is in
977 // milliseconds
978 double duration = AudioUtil.getCalibratedDuration(wavFile);
979 double startTime = lastModified - duration;
980 startTime = firstStartTime + (startTime - firstStartTime)
981 / Main.pref.getDouble("audio.calibration", "1.0" /* default, ratio */);
982 WayPoint w1 = null;
983 WayPoint w2 = null;
984
985 for (GpxTrack track : data.tracks) {
986 for (GpxTrackSegment seg : track.getSegments()) {
987 for (WayPoint w : seg.getWayPoints()) {
988 if (startTime < w.time) {
989 w2 = w;
990 break;
991 }
992 w1 = w;
993 }
994 if (w2 != null) {
995 break;
996 }
997 }
998 }
999
1000 if (w1 == null || w2 == null) {
1001 timedMarkersOmitted = true;
1002 } else {
1003 wayPointFromTimeStamp = new WayPoint(w1.getCoor().interpolate(w2.getCoor(),
1004 (startTime - w1.time) / (w2.time - w1.time)));
1005 wayPointFromTimeStamp.time = startTime;
1006 String name = wavFile.getName();
1007 int dot = name.lastIndexOf(".");
1008 if (dot > 0) {
1009 name = name.substring(0, dot);
1010 }
1011 wayPointFromTimeStamp.attr.put("name", name);
1012 waypoints.add(wayPointFromTimeStamp);
1013 }
1014 }
1015
1016 // (e) analyse audio for spoken markers here, in due course
1017
1018 // (f) simply add a single marker at the start of the track
1019 if ((Main.pref.getBoolean("marker.audiofromstart") || waypoints.isEmpty()) && data.tracks != null
1020 && !data.tracks.isEmpty()) {
1021 boolean gotOne = false;
1022 for (GpxTrack track : data.tracks) {
1023 for (GpxTrackSegment seg : track.getSegments()) {
1024 for (WayPoint w : seg.getWayPoints()) {
1025 WayPoint wStart = new WayPoint(w.getCoor());
1026 wStart.attr.put("name", "start");
1027 wStart.time = w.time;
1028 waypoints.add(wStart);
1029 gotOne = true;
1030 break;
1031 }
1032 if (gotOne) {
1033 break;
1034 }
1035 }
1036 if (gotOne) {
1037 break;
1038 }
1039 }
1040 }
1041
1042 /* we must have got at least one waypoint now */
1043
1044 Collections.sort((ArrayList<WayPoint>) waypoints, new Comparator<WayPoint>() {
1045 public int compare(WayPoint a, WayPoint b) {
1046 return a.time <= b.time ? -1 : 1;
1047 }
1048 });
1049
1050 firstTime = -1.0; /* this time of the first waypoint, not first trackpoint */
1051 for (WayPoint w : waypoints) {
1052 if (firstTime < 0.0) {
1053 firstTime = w.time;
1054 }
1055 double offset = w.time - firstTime;
1056 String name;
1057 if (w.attr.containsKey("name")) {
1058 name = w.getString("name");
1059 } else if (w.attr.containsKey("desc")) {
1060 name = w.getString("desc");
1061 } else {
1062 name = AudioMarker.inventName(offset);
1063 }
1064 AudioMarker am = AudioMarker.create(w.getCoor(), name, uri, ml, w.time, offset);
1065 /*
1066 * timeFromAudio intended for future use to shift markers of this type on
1067 * synchronization
1068 */
1069 if (w == wayPointFromTimeStamp) {
1070 am.timeFromAudio = true;
1071 }
1072 ml.data.add(am);
1073 }
1074
1075 if (timedMarkersOmitted && !markers.timedMarkersOmitted) {
1076 JOptionPane
1077 .showMessageDialog(
1078 Main.parent,
1079 tr("Some waypoints with timestamps from before the start of the track or after the end were omitted or moved to the start."));
1080 markers.timedMarkersOmitted = timedMarkersOmitted;
1081 }
1082 if (untimedMarkersOmitted && !markers.untimedMarkersOmitted) {
1083 JOptionPane
1084 .showMessageDialog(
1085 Main.parent,
1086 tr("Some waypoints which were too far from the track to sensibly estimate their time were omitted."));
1087 markers.untimedMarkersOmitted = untimedMarkersOmitted;
1088 }
1089 }
1090
1091 /**
1092 * Makes a WayPoint at the projection of point P onto the track providing P is less than
1093 * tolerance away from the track
1094 *
1095 * @param P : the point to determine the projection for
1096 * @param tolerance : must be no further than this from the track
1097 * @return the closest point on the track to P, which may be the first or last point if off the
1098 * end of a segment, or may be null if nothing close enough
1099 */
1100 public WayPoint nearestPointOnTrack(EastNorth P, double tolerance) {
1101 /*
1102 * assume the coordinates of P are xp,yp, and those of a section of track between two
1103 * trackpoints are R=xr,yr and S=xs,ys. Let N be the projected point.
1104 *
1105 * The equation of RS is Ax + By + C = 0 where A = ys - yr B = xr - xs C = - Axr - Byr
1106 *
1107 * Also, note that the distance RS^2 is A^2 + B^2
1108 *
1109 * If RS^2 == 0.0 ignore the degenerate section of track
1110 *
1111 * PN^2 = (Axp + Byp + C)^2 / RS^2 that is the distance from P to the line
1112 *
1113 * so if PN^2 is less than PNmin^2 (initialized to tolerance) we can reject the line;
1114 * otherwise... determine if the projected poijnt lies within the bounds of the line: PR^2 -
1115 * PN^2 <= RS^2 and PS^2 - PN^2 <= RS^2
1116 *
1117 * where PR^2 = (xp - xr)^2 + (yp-yr)^2 and PS^2 = (xp - xs)^2 + (yp-ys)^2
1118 *
1119 * If so, calculate N as xn = xr + (RN/RS) B yn = y1 + (RN/RS) A
1120 *
1121 * where RN = sqrt(PR^2 - PN^2)
1122 */
1123
1124 double PNminsq = tolerance * tolerance;
1125 EastNorth bestEN = null;
1126 double bestTime = 0.0;
1127 double px = P.east();
1128 double py = P.north();
1129 double rx = 0.0, ry = 0.0, sx, sy, x, y;
1130 if (data.tracks == null)
1131 return null;
1132 for (GpxTrack track : data.tracks) {
1133 for (GpxTrackSegment seg : track.getSegments()) {
1134 WayPoint R = null;
1135 for (WayPoint S : seg.getWayPoints()) {
1136 EastNorth c = S.getEastNorth();
1137 if (R == null) {
1138 R = S;
1139 rx = c.east();
1140 ry = c.north();
1141 x = px - rx;
1142 y = py - ry;
1143 double PRsq = x * x + y * y;
1144 if (PRsq < PNminsq) {
1145 PNminsq = PRsq;
1146 bestEN = c;
1147 bestTime = R.time;
1148 }
1149 } else {
1150 sx = c.east();
1151 sy = c.north();
1152 double A = sy - ry;
1153 double B = rx - sx;
1154 double C = -A * rx - B * ry;
1155 double RSsq = A * A + B * B;
1156 if (RSsq == 0.0) {
1157 continue;
1158 }
1159 double PNsq = A * px + B * py + C;
1160 PNsq = PNsq * PNsq / RSsq;
1161 if (PNsq < PNminsq) {
1162 x = px - rx;
1163 y = py - ry;
1164 double PRsq = x * x + y * y;
1165 x = px - sx;
1166 y = py - sy;
1167 double PSsq = x * x + y * y;
1168 if (PRsq - PNsq <= RSsq && PSsq - PNsq <= RSsq) {
1169 double RNoverRS = Math.sqrt((PRsq - PNsq) / RSsq);
1170 double nx = rx - RNoverRS * B;
1171 double ny = ry + RNoverRS * A;
1172 bestEN = new EastNorth(nx, ny);
1173 bestTime = R.time + RNoverRS * (S.time - R.time);
1174 PNminsq = PNsq;
1175 }
1176 }
1177 R = S;
1178 rx = sx;
1179 ry = sy;
1180 }
1181 }
1182 if (R != null) {
1183 EastNorth c = R.getEastNorth();
1184 /* if there is only one point in the seg, it will do this twice, but no matter */
1185 rx = c.east();
1186 ry = c.north();
1187 x = px - rx;
1188 y = py - ry;
1189 double PRsq = x * x + y * y;
1190 if (PRsq < PNminsq) {
1191 PNminsq = PRsq;
1192 bestEN = c;
1193 bestTime = R.time;
1194 }
1195 }
1196 }
1197 }
1198 if (bestEN == null)
1199 return null;
1200 WayPoint best = new WayPoint(Main.proj.eastNorth2latlon(bestEN));
1201 best.time = bestTime;
1202 return best;
1203 }
1204
1205 private class CustomizeLineDrawing extends AbstractAction {
1206
1207 CustomizeLineDrawing() {
1208 super(tr("Customize line drawing"), ImageProvider.get("mapmode/addsegment"));
1209 }
1210
1211 @Override
1212 public void actionPerformed(ActionEvent e) {
1213 JRadioButton[] r = new JRadioButton[3];
1214 r[0] = new JRadioButton(tr("Use global settings."));
1215 r[1] = new JRadioButton(tr("Draw lines between points for this layer."));
1216 r[2] = new JRadioButton(tr("Do not draw lines between points for this layer."));
1217 ButtonGroup group = new ButtonGroup();
1218 Box panel = Box.createVerticalBox();
1219 for (JRadioButton b : r) {
1220 group.add(b);
1221 panel.add(b);
1222 }
1223 String propName = "draw.rawgps.lines.layer " + getName();
1224 if (Main.pref.hasKey(propName)) {
1225 group.setSelected(r[Main.pref.getBoolean(propName) ? 1 : 2].getModel(), true);
1226 } else {
1227 group.setSelected(r[0].getModel(), true);
1228 }
1229 int answer = JOptionPane.showConfirmDialog(Main.parent, panel,
1230 tr("Select line drawing options"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
1231 switch (answer) {
1232 case JOptionPane.CANCEL_OPTION:
1233 case JOptionPane.CLOSED_OPTION:
1234 return;
1235 default:
1236 // continue
1237 }
1238 if (group.getSelection() == r[0].getModel()) {
1239 Main.pref.put(propName, null);
1240 } else {
1241 Main.pref.put(propName, group.getSelection() == r[1].getModel());
1242 }
1243 Main.map.repaint();
1244 }
1245 }
1246
1247 private class CustomizeColor extends AbstractAction {
1248
1249 public CustomizeColor() {
1250 super(tr("Customize Color"), ImageProvider.get("colorchooser"));
1251 putValue("help", "Action/LayerCustomizeColor");
1252 }
1253
1254 @Override
1255 public void actionPerformed(ActionEvent e) {
1256 JColorChooser c = new JColorChooser(getColor(getName()));
1257 Object[] options = new Object[] { tr("OK"), tr("Cancel"), tr("Default") };
1258 int answer = JOptionPane.showOptionDialog(
1259 Main.parent,
1260 c,
1261 tr("Choose a color"),
1262 JOptionPane.OK_CANCEL_OPTION,
1263 JOptionPane.PLAIN_MESSAGE,
1264 null,
1265 options, options[0]
1266 );
1267 switch (answer) {
1268 case 0:
1269 Main.pref.putColor("layer " + getName(), c.getColor());
1270 break;
1271 case 1:
1272 return;
1273 case 2:
1274 Main.pref.putColor("layer " + getName(), null);
1275 break;
1276 }
1277 Main.map.repaint();
1278 }
1279
1280 }
1281
1282 private class MarkersFromNamedPoins extends AbstractAction {
1283
1284 public MarkersFromNamedPoins() {
1285 super(tr("Markers From Named Points"), ImageProvider.get("addmarkers"));
1286 putValue("help", "Action/MarkersFromNamedPoints");
1287 }
1288
1289 @Override
1290 public void actionPerformed(ActionEvent e) {
1291 GpxData namedTrackPoints = new GpxData();
1292 for (GpxTrack track : data.tracks) {
1293 for (GpxTrackSegment seg : track.getSegments()) {
1294 for (WayPoint point : seg.getWayPoints())
1295 if (point.attr.containsKey("name") || point.attr.containsKey("desc")) {
1296 namedTrackPoints.waypoints.add(point);
1297 }
1298 }
1299 }
1300
1301 MarkerLayer ml = new MarkerLayer(namedTrackPoints, tr("Named Trackpoints from {0}", getName()),
1302 getAssociatedFile(), GpxLayer.this);
1303 if (ml.data.size() > 0) {
1304 Main.main.addLayer(ml);
1305 }
1306
1307 }
1308 }
1309
1310 private class ImportAudio extends AbstractAction {
1311
1312 public ImportAudio() {
1313 super(tr("Import Audio"), ImageProvider.get("importaudio"));
1314 putValue("help", "ImportAudio");
1315 }
1316
1317 private void warnCantImportIntoServerLayer(GpxLayer layer) {
1318 String msg = tr("<html>The data in the GPX layer ''{0}'' has been downloaded from the server.<br>"
1319 + "Because its way points do not include a timestamp we cannot correlate them with audio data.</html>",
1320 layer.getName()
1321 );
1322 HelpAwareOptionPane.showOptionDialog(
1323 Main.parent,
1324 msg,
1325 tr("Import not possible"),
1326 JOptionPane.WARNING_MESSAGE,
1327 ht("/Action/ImportImages#CantImportIntoGpxLayerFromServer")
1328 );
1329 }
1330
1331 @Override
1332 public void actionPerformed(ActionEvent e) {
1333 if (GpxLayer.this.data.fromServer) {
1334 warnCantImportIntoServerLayer(GpxLayer.this);
1335 return;
1336 }
1337 String dir = Main.pref.get("markers.lastaudiodirectory");
1338 JFileChooser fc = new JFileChooser(dir);
1339 fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
1340 fc.setAcceptAllFileFilterUsed(false);
1341 fc.setFileFilter(new FileFilter() {
1342 @Override
1343 public boolean accept(File f) {
1344 return f.isDirectory() || f.getName().toLowerCase().endsWith(".wav");
1345 }
1346
1347 @Override
1348 public String getDescription() {
1349 return tr("Wave Audio files (*.wav)");
1350 }
1351 });
1352 fc.setMultiSelectionEnabled(true);
1353 if (fc.showOpenDialog(Main.parent) == JFileChooser.APPROVE_OPTION) {
1354 if (!fc.getCurrentDirectory().getAbsolutePath().equals(dir)) {
1355 Main.pref.put("markers.lastaudiodirectory", fc.getCurrentDirectory().getAbsolutePath());
1356 }
1357
1358 File sel[] = fc.getSelectedFiles();
1359 // sort files in increasing order of timestamp (this is the end time, but so
1360 // long as they don't overlap, that's fine)
1361 if (sel.length > 1) {
1362 Arrays.sort(sel, new Comparator<File>() {
1363 public int compare(File a, File b) {
1364 return a.lastModified() <= b.lastModified() ? -1 : 1;
1365 }
1366 });
1367 }
1368
1369 String names = null;
1370 for (int i = 0; i < sel.length; i++) {
1371 if (names == null) {
1372 names = " (";
1373 } else {
1374 names += ", ";
1375 }
1376 names += sel[i].getName();
1377 }
1378 if (names != null) {
1379 names += ")";
1380 } else {
1381 names = "";
1382 }
1383 MarkerLayer ml = new MarkerLayer(new GpxData(), tr("Audio markers from {0}", getName()) + names,
1384 getAssociatedFile(), GpxLayer.this);
1385 double firstStartTime = sel[0].lastModified() / 1000.0 /* ms -> seconds */
1386 - AudioUtil.getCalibratedDuration(sel[0]);
1387
1388 Markers m = new Markers();
1389 for (int i = 0; i < sel.length; i++) {
1390 importAudio(sel[i], ml, firstStartTime, m);
1391 }
1392 Main.main.addLayer(ml);
1393 Main.map.repaint();
1394 }
1395
1396 }
1397 }
1398
1399 private class ImportImages extends AbstractAction {
1400
1401 public ImportImages() {
1402 super(tr("Import images"), ImageProvider.get("dialogs/geoimage"));
1403 putValue("help", ht("/Action/ImportImages"));
1404 }
1405
1406 private void warnCantImportIntoServerLayer(GpxLayer layer) {
1407 String msg = tr("<html>The data in the GPX layer ''{0}'' has been downloaded from the server.<br>"
1408 + "Because its way points do not include a timestamp we cannot correlate them with images.</html>",
1409 layer.getName()
1410 );
1411 HelpAwareOptionPane.showOptionDialog(
1412 Main.parent,
1413 msg,
1414 tr("Import not possible"),
1415 JOptionPane.WARNING_MESSAGE,
1416 ht("/Action/ImportImages#CantImportIntoGpxLayerFromServer")
1417 );
1418 }
1419
1420 private void addRecursiveFiles(LinkedList<File> files, File[] sel) {
1421 for (File f : sel) {
1422 if (f.isDirectory()) {
1423 addRecursiveFiles(files, f.listFiles());
1424 } else if (f.getName().toLowerCase().endsWith(".jpg")) {
1425 files.add(f);
1426 }
1427 }
1428 }
1429
1430 @Override
1431 public void actionPerformed(ActionEvent e) {
1432
1433 if (GpxLayer.this.data.fromServer) {
1434 warnCantImportIntoServerLayer(GpxLayer.this);
1435 return;
1436 }
1437 String curDir = Main.pref.get("geoimage.lastdirectory", Main.pref.get("lastDirectory"));
1438 if (curDir.equals("")) {
1439 curDir = ".";
1440 }
1441 JFileChooser fc = new JFileChooser(new File(curDir));
1442
1443 fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
1444 fc.setMultiSelectionEnabled(true);
1445 fc.setAcceptAllFileFilterUsed(false);
1446 JpgImporter importer = new JpgImporter(GpxLayer.this);
1447 fc.setFileFilter(importer.filter);
1448 fc.showOpenDialog(Main.parent);
1449 LinkedList<File> files = new LinkedList<File>();
1450 File[] sel = fc.getSelectedFiles();
1451 if (sel == null || sel.length == 0)
1452 return;
1453 if (!fc.getCurrentDirectory().getAbsolutePath().equals(curDir)) {
1454 Main.pref.put("geoimage.lastdirectory", fc.getCurrentDirectory().getAbsolutePath());
1455 }
1456 addRecursiveFiles(files, sel);
1457 importer.importDataHandleExceptions(files, NullProgressMonitor.INSTANCE);
1458 }
1459
1460 }
1461}
Note: See TracBrowser for help on using the repository browser.