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

Last change on this file since 5395 was 5395, checked in by bastiK, 12 years ago

gpxreader: minor refactoring, read version & creator (see #7927)

  • Property svn:eol-style set to native
File size: 86.4 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.Component;
13import java.awt.Dimension;
14import java.awt.Graphics2D;
15import java.awt.GridBagLayout;
16import java.awt.Point;
17import java.awt.RenderingHints;
18import java.awt.Stroke;
19import java.awt.Toolkit;
20import java.awt.event.ActionEvent;
21import java.awt.event.MouseAdapter;
22import java.awt.event.MouseEvent;
23import java.awt.event.MouseListener;
24import java.awt.geom.Area;
25import java.awt.geom.Rectangle2D;
26import java.io.File;
27import java.io.IOException;
28import java.net.MalformedURLException;
29import java.net.URL;
30import java.text.DateFormat;
31import java.util.ArrayList;
32import java.util.Arrays;
33import java.util.Collection;
34import java.util.Collections;
35import java.util.Comparator;
36import java.util.LinkedList;
37import java.util.List;
38import java.util.Map;
39import java.util.concurrent.Future;
40
41import javax.swing.AbstractAction;
42import javax.swing.Action;
43import javax.swing.BorderFactory;
44import javax.swing.DefaultComboBoxModel;
45import javax.swing.Icon;
46import javax.swing.JComboBox;
47import javax.swing.JComponent;
48import javax.swing.JFileChooser;
49import javax.swing.JLabel;
50import javax.swing.JList;
51import javax.swing.JMenuItem;
52import javax.swing.JOptionPane;
53import javax.swing.JPanel;
54import javax.swing.JScrollPane;
55import javax.swing.JTable;
56import javax.swing.ListSelectionModel;
57import javax.swing.SwingUtilities;
58import javax.swing.event.ListSelectionEvent;
59import javax.swing.event.ListSelectionListener;
60import javax.swing.filechooser.FileFilter;
61import javax.swing.table.TableCellRenderer;
62
63import org.openstreetmap.josm.Main;
64import org.openstreetmap.josm.actions.AbstractMergeAction.LayerListCellRenderer;
65import org.openstreetmap.josm.actions.RenameLayerAction;
66import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTaskList;
67import org.openstreetmap.josm.data.Bounds;
68import org.openstreetmap.josm.data.coor.EastNorth;
69import org.openstreetmap.josm.data.coor.LatLon;
70import org.openstreetmap.josm.data.gpx.GpxData;
71import org.openstreetmap.josm.data.gpx.GpxRoute;
72import org.openstreetmap.josm.data.gpx.GpxTrack;
73import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
74import org.openstreetmap.josm.data.gpx.WayPoint;
75import org.openstreetmap.josm.data.osm.DataSet;
76import org.openstreetmap.josm.data.osm.Node;
77import org.openstreetmap.josm.data.osm.Way;
78import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
79import org.openstreetmap.josm.data.projection.Projection;
80import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
81import org.openstreetmap.josm.gui.ExtendedDialog;
82import org.openstreetmap.josm.gui.HelpAwareOptionPane;
83import org.openstreetmap.josm.gui.MapView;
84import org.openstreetmap.josm.gui.NavigatableComponent;
85import org.openstreetmap.josm.gui.PleaseWaitRunnable;
86import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
87import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
88import org.openstreetmap.josm.gui.layer.WMSLayer.PrecacheTask;
89import org.openstreetmap.josm.gui.layer.markerlayer.AudioMarker;
90import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
91import org.openstreetmap.josm.gui.preferences.display.GPXSettingsPanel;
92import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
93import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
94import org.openstreetmap.josm.gui.progress.ProgressMonitor;
95import org.openstreetmap.josm.gui.progress.ProgressTaskId;
96import org.openstreetmap.josm.gui.progress.ProgressTaskIds;
97import org.openstreetmap.josm.gui.widgets.HtmlPanel;
98import org.openstreetmap.josm.io.JpgImporter;
99import org.openstreetmap.josm.io.OsmTransferException;
100import org.openstreetmap.josm.tools.AudioUtil;
101import org.openstreetmap.josm.tools.DateUtils;
102import org.openstreetmap.josm.tools.GBC;
103import org.openstreetmap.josm.tools.ImageProvider;
104import org.openstreetmap.josm.tools.OpenBrowser;
105import org.openstreetmap.josm.tools.UrlLabel;
106import org.openstreetmap.josm.tools.Utils;
107import org.openstreetmap.josm.tools.WindowGeometry;
108import org.xml.sax.SAXException;
109
110public class GpxLayer extends Layer {
111
112 private static final String PREF_DOWNLOAD_ALONG_TRACK_DISTANCE = "gpxLayer.downloadAlongTrack.distance";
113 private static final String PREF_DOWNLOAD_ALONG_TRACK_AREA = "gpxLayer.downloadAlongTrack.area";
114 private static final String PREF_DOWNLOAD_ALONG_TRACK_NEAR = "gpxLayer.downloadAlongTrack.near";
115
116 public GpxData data;
117 protected static final double PHI = Math.toRadians(15);
118 private boolean computeCacheInSync;
119 private int computeCacheMaxLineLengthUsed;
120 private Color computeCacheColorUsed;
121 private boolean computeCacheColorDynamic;
122 private colorModes computeCacheColored;
123 private int computeCacheColorTracksTune;
124 private boolean isLocalFile;
125 // used by ChooseTrackVisibilityAction to determine which tracks to show/hide
126 private boolean[] trackVisibility = new boolean[0];
127
128 private final List<GpxTrack> lastTracks = new ArrayList<GpxTrack>(); // List of tracks at last paint
129 private int lastUpdateCount;
130
131 private static class Markers {
132 public boolean timedMarkersOmitted = false;
133 public boolean untimedMarkersOmitted = false;
134 }
135
136 public GpxLayer(GpxData d) {
137 super((String) d.attr.get("name"));
138 data = d;
139 computeCacheInSync = false;
140 ensureTrackVisibilityLength();
141 }
142
143 public GpxLayer(GpxData d, String name) {
144 this(d);
145 this.setName(name);
146 }
147
148 public GpxLayer(GpxData d, String name, boolean isLocal) {
149 this(d);
150 this.setName(name);
151 this.isLocalFile = isLocal;
152 }
153
154 /**
155 * returns a human readable string that shows the timespan of the given track
156 */
157 private static String getTimespanForTrack(GpxTrack trk) {
158 WayPoint earliest = null, latest = null;
159
160 for (GpxTrackSegment seg : trk.getSegments()) {
161 for (WayPoint pnt : seg.getWayPoints()) {
162 if (latest == null) {
163 latest = earliest = pnt;
164 } else {
165 if (pnt.compareTo(earliest) < 0) {
166 earliest = pnt;
167 } else {
168 latest = pnt;
169 }
170 }
171 }
172 }
173
174 String ts = "";
175
176 if (earliest != null && latest != null) {
177 DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);
178 String earliestDate = df.format(earliest.getTime());
179 String latestDate = df.format(latest.getTime());
180
181 if (earliestDate.equals(latestDate)) {
182 DateFormat tf = DateFormat.getTimeInstance(DateFormat.SHORT);
183 ts += earliestDate + " ";
184 ts += tf.format(earliest.getTime()) + " - " + tf.format(latest.getTime());
185 } else {
186 DateFormat dtf = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
187 ts += dtf.format(earliest.getTime()) + " - " + dtf.format(latest.getTime());
188 }
189
190 int diff = (int) (latest.time - earliest.time);
191 ts += String.format(" (%d:%02d)", diff / 3600, (diff % 3600) / 60);
192 }
193 return ts;
194 }
195
196 @Override
197 public Icon getIcon() {
198 return ImageProvider.get("layer", "gpx_small");
199 }
200
201 @Override
202 public Object getInfoComponent() {
203 StringBuilder info = new StringBuilder();
204
205 if (data.attr.containsKey("name")) {
206 info.append(tr("Name: {0}", data.attr.get(GpxData.META_NAME))).append("<br>");
207 }
208
209 if (data.attr.containsKey("desc")) {
210 info.append(tr("Description: {0}", data.attr.get(GpxData.META_DESC))).append("<br>");
211 }
212
213 if (data.tracks.size() > 0) {
214 info.append("<table><thead align='center'><tr><td colspan='5'>"
215 + trn("{0} track", "{0} tracks", data.tracks.size(), data.tracks.size())
216 + "</td></tr><tr align='center'><td>" + tr("Name") + "</td><td>"
217 + tr("Description") + "</td><td>" + tr("Timespan")
218 + "</td><td>" + tr("Length") + "</td><td>" + tr("URL")
219 + "</td></tr></thead>");
220
221 for (GpxTrack trk : data.tracks) {
222 info.append("<tr><td>");
223 if (trk.getAttributes().containsKey("name")) {
224 info.append(trk.getAttributes().get("name"));
225 }
226 info.append("</td><td>");
227 if (trk.getAttributes().containsKey("desc")) {
228 info.append(" ").append(trk.getAttributes().get("desc"));
229 }
230 info.append("</td><td>");
231 info.append(getTimespanForTrack(trk));
232 info.append("</td><td>");
233 info.append(NavigatableComponent.getSystemOfMeasurement().getDistText(trk.length()));
234 info.append("</td><td>");
235 if (trk.getAttributes().containsKey("url")) {
236 info.append(trk.getAttributes().get("url"));
237 }
238 info.append("</td></tr>");
239 }
240
241 info.append("</table><br><br>");
242
243 }
244
245 info.append(tr("Length: {0}", NavigatableComponent.getSystemOfMeasurement().getDistText(data.length()))).append("<br>");
246
247 info.append(trn("{0} route, ", "{0} routes, ", data.routes.size(), data.routes.size())).append(
248 trn("{0} waypoint", "{0} waypoints", data.waypoints.size(), data.waypoints.size())).append("<br>");
249
250 final JScrollPane sp = new JScrollPane(new HtmlPanel(info.toString()), JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
251 sp.setPreferredSize(new Dimension(sp.getPreferredSize().width, 350));
252 SwingUtilities.invokeLater(new Runnable() {
253 @Override
254 public void run() {
255 sp.getVerticalScrollBar().setValue(0);
256 }
257 });
258 return sp;
259 }
260
261 @Override
262 public Color getColor(boolean ignoreCustom) {
263 Color c = Main.pref.getColor(marktr("gps point"), "layer " + getName(), Color.gray);
264
265 return ignoreCustom || getColorMode() == colorModes.none ? c : null;
266 }
267
268 public colorModes getColorMode() {
269 try {
270 int i=Main.pref.getInteger("draw.rawgps.colors", "layer " + getName(), 0);
271 return colorModes.values()[i];
272 } catch (Exception e) {
273 }
274 return colorModes.none;
275 }
276
277 /* for preferences */
278 static public Color getGenericColor() {
279 return Main.pref.getColor(marktr("gps point"), Color.gray);
280 }
281
282 @Override
283 public Action[] getMenuEntries() {
284 if (Main.applet)
285 return new Action[] {
286 LayerListDialog.getInstance().createShowHideLayerAction(),
287 LayerListDialog.getInstance().createDeleteLayerAction(),
288 SeparatorLayerAction.INSTANCE,
289 new CustomizeColor(this),
290 new CustomizeDrawing(this),
291 new ConvertToDataLayerAction(),
292 SeparatorLayerAction.INSTANCE,
293 new ChooseTrackVisibilityAction(),
294 new RenameLayerAction(getAssociatedFile(), this),
295 SeparatorLayerAction.INSTANCE,
296 new LayerListPopup.InfoAction(this) };
297 return new Action[] {
298 LayerListDialog.getInstance().createShowHideLayerAction(),
299 LayerListDialog.getInstance().createDeleteLayerAction(),
300 SeparatorLayerAction.INSTANCE,
301 new LayerSaveAction(this),
302 new LayerSaveAsAction(this),
303 new CustomizeColor(this),
304 new CustomizeDrawing(this),
305 new ImportImages(),
306 new ImportAudio(),
307 new MarkersFromNamedPoins(),
308 new ConvertToDataLayerAction(),
309 new DownloadAlongTrackAction(),
310 new DownloadWmsAlongTrackAction(),
311 SeparatorLayerAction.INSTANCE,
312 new ChooseTrackVisibilityAction(),
313 new RenameLayerAction(getAssociatedFile(), this),
314 SeparatorLayerAction.INSTANCE,
315 new LayerListPopup.InfoAction(this) };
316 }
317
318 @Override
319 public String getToolTipText() {
320 StringBuilder info = new StringBuilder().append("<html>");
321
322 if (data.attr.containsKey("name")) {
323 info.append(tr("Name: {0}", data.attr.get(GpxData.META_NAME))).append("<br>");
324 }
325
326 if (data.attr.containsKey("desc")) {
327 info.append(tr("Description: {0}", data.attr.get(GpxData.META_DESC))).append("<br>");
328 }
329
330 info.append(trn("{0} track, ", "{0} tracks, ", data.tracks.size(), data.tracks.size()));
331 info.append(trn("{0} route, ", "{0} routes, ", data.routes.size(), data.routes.size()));
332 info.append(trn("{0} waypoint", "{0} waypoints", data.waypoints.size(), data.waypoints.size())).append("<br>");
333
334 info.append(tr("Length: {0}", NavigatableComponent.getSystemOfMeasurement().getDistText(data.length())));
335 info.append("<br>");
336
337 return info.append("</html>").toString();
338 }
339
340 @Override
341 public boolean isMergable(Layer other) {
342 return other instanceof GpxLayer;
343 }
344
345 private int sumUpdateCount() {
346 int updateCount = 0;
347 for (GpxTrack track: data.tracks) {
348 updateCount += track.getUpdateCount();
349 }
350 return updateCount;
351 }
352
353 @Override
354 public boolean isChanged() {
355 if (data.tracks.equals(lastTracks))
356 return sumUpdateCount() != lastUpdateCount;
357 else
358 return true;
359 }
360
361 @Override
362 public void mergeFrom(Layer from) {
363 data.mergeFrom(((GpxLayer) from).data);
364 computeCacheInSync = false;
365 }
366
367 private final static Color[] colors = new Color[256];
368 static {
369 for (int i = 0; i < colors.length; i++) {
370 colors[i] = Color.getHSBColor(i / 300.0f, 1, 1);
371 }
372 }
373
374 private final static Color[] colors_cyclic = new Color[256];
375 static {
376 for (int i = 0; i < colors_cyclic.length; i++) {
377 // red yellow green blue red
378 int[] h = new int[] { 0, 59, 127, 244, 360};
379 int[] s = new int[] { 100, 84, 99, 100 };
380 int[] b = new int[] { 90, 93, 74, 83 };
381
382 float angle = 4 - i / 256f * 4;
383 int quadrant = (int) angle;
384 angle -= quadrant;
385 quadrant = Utils.mod(quadrant+1, 4);
386
387 float vh = h[quadrant] * w(angle) + h[quadrant+1] * (1 - w(angle));
388 float vs = s[quadrant] * w(angle) + s[Utils.mod(quadrant+1, 4)] * (1 - w(angle));
389 float vb = b[quadrant] * w(angle) + b[Utils.mod(quadrant+1, 4)] * (1 - w(angle));
390
391 colors_cyclic[i] = Color.getHSBColor(vh/360f, vs/100f, vb/100f);
392 }
393 }
394
395 /**
396 * transition function:
397 * w(0)=1, w(1)=0, 0<=w(x)<=1
398 * @param x number: 0<=x<=1
399 * @return the weighted value
400 */
401 private static float w(float x) {
402 if (x < 0.5)
403 return 1 - 2*x*x;
404 else
405 return 2*(1-x)*(1-x);
406 }
407
408 // lookup array to draw arrows without doing any math
409 private final static int ll0 = 9;
410 private final static int sl4 = 5;
411 private final static int sl9 = 3;
412 private final static int[][] dir = { { +sl4, +ll0, +ll0, +sl4 }, { -sl9, +ll0, +sl9, +ll0 }, { -ll0, +sl4, -sl4, +ll0 },
413 { -ll0, -sl9, -ll0, +sl9 }, { -sl4, -ll0, -ll0, -sl4 }, { +sl9, -ll0, -sl9, -ll0 },
414 { +ll0, -sl4, +sl4, -ll0 }, { +ll0, +sl9, +ll0, -sl9 }, { +sl4, +ll0, +ll0, +sl4 },
415 { -sl9, +ll0, +sl9, +ll0 }, { -ll0, +sl4, -sl4, +ll0 }, { -ll0, -sl9, -ll0, +sl9 } };
416
417 // the different color modes
418 enum colorModes {
419 none, velocity, dilution, direction, time
420 }
421
422 @Override
423 public void paint(Graphics2D g, MapView mv, Bounds box) {
424 lastUpdateCount = sumUpdateCount();
425 lastTracks.clear();
426 lastTracks.addAll(data.tracks);
427
428 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
429 Main.pref.getBoolean("mappaint.gpx.use-antialiasing", false) ?
430 RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
431
432 /****************************************************************
433 ********** STEP 1 - GET CONFIG VALUES **************************
434 ****************************************************************/
435 // Long startTime = System.currentTimeMillis();
436 Color neutralColor = getColor(true);
437 String spec="layer "+getName();
438
439 // also draw lines between points belonging to different segments
440 boolean forceLines = Main.pref.getBoolean("draw.rawgps.lines.force", spec, false);
441 // draw direction arrows on the lines
442 boolean direction = Main.pref.getBoolean("draw.rawgps.direction", spec, false);
443 // don't draw lines if longer than x meters
444 int lineWidth = Main.pref.getInteger("draw.rawgps.linewidth", spec, 0);
445
446 int maxLineLength;
447 boolean lines;
448 if (!this.data.fromServer) {
449 maxLineLength = Main.pref.getInteger("draw.rawgps.max-line-length.local", spec, -1);
450 lines = Main.pref.getBoolean("draw.rawgps.lines.local", spec, true);
451 } else {
452 maxLineLength = Main.pref.getInteger("draw.rawgps.max-line-length", spec, 200);
453 lines = Main.pref.getBoolean("draw.rawgps.lines", spec, true);
454 }
455 // paint large dots for points
456 boolean large = Main.pref.getBoolean("draw.rawgps.large", spec, false);
457 int largesize = Main.pref.getInteger("draw.rawgps.large.size", spec, 3);
458 boolean hdopcircle = Main.pref.getBoolean("draw.rawgps.hdopcircle", spec, false);
459 // color the lines
460 colorModes colored = getColorMode();
461 // paint direction arrow with alternate math. may be faster
462 boolean alternatedirection = Main.pref.getBoolean("draw.rawgps.alternatedirection", spec, false);
463 // don't draw arrows nearer to each other than this
464 int delta = Main.pref.getInteger("draw.rawgps.min-arrow-distance", spec, 40);
465 // allows to tweak line coloring for different speed levels.
466 int colorTracksTune = Main.pref.getInteger("draw.rawgps.colorTracksTune", spec, 45);
467 boolean colorModeDynamic = Main.pref.getBoolean("draw.rawgps.colors.dynamic", spec, false);
468 int hdopfactor = Main.pref.getInteger("hdop.factor", 25);
469
470 Stroke storedStroke = g.getStroke();
471 if(lineWidth != 0)
472 {
473 g.setStroke(new BasicStroke(lineWidth,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND));
474 largesize += lineWidth;
475 }
476
477 /****************************************************************
478 ********** STEP 2a - CHECK CACHE VALIDITY **********************
479 ****************************************************************/
480 if ((computeCacheMaxLineLengthUsed != maxLineLength) || (!neutralColor.equals(computeCacheColorUsed))
481 || (computeCacheColored != colored) || (computeCacheColorTracksTune != colorTracksTune)
482 || (computeCacheColorDynamic != colorModeDynamic)) {
483 computeCacheMaxLineLengthUsed = maxLineLength;
484 computeCacheInSync = false;
485 computeCacheColorUsed = neutralColor;
486 computeCacheColored = colored;
487 computeCacheColorTracksTune = colorTracksTune;
488 computeCacheColorDynamic = colorModeDynamic;
489 }
490
491 /****************************************************************
492 ********** STEP 2b - RE-COMPUTE CACHE DATA *********************
493 ****************************************************************/
494 if (!computeCacheInSync) { // don't compute if the cache is good
495 double minval = +1e10;
496 double maxval = -1e10;
497 WayPoint oldWp = null;
498 if (colorModeDynamic) {
499 if (colored == colorModes.velocity) {
500 for (GpxTrack trk : data.tracks) {
501 for (GpxTrackSegment segment : trk.getSegments()) {
502 if(!forceLines) {
503 oldWp = null;
504 }
505 for (WayPoint trkPnt : segment.getWayPoints()) {
506 LatLon c = trkPnt.getCoor();
507 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
508 continue;
509 }
510 if (oldWp != null && trkPnt.time > oldWp.time) {
511 double vel = c.greatCircleDistance(oldWp.getCoor())
512 / (trkPnt.time - oldWp.time);
513 if(vel > maxval) {
514 maxval = vel;
515 }
516 if(vel < minval) {
517 minval = vel;
518 }
519 }
520 oldWp = trkPnt;
521 }
522 }
523 }
524 } else if (colored == colorModes.dilution) {
525 for (GpxTrack trk : data.tracks) {
526 for (GpxTrackSegment segment : trk.getSegments()) {
527 for (WayPoint trkPnt : segment.getWayPoints()) {
528 Object val = trkPnt.attr.get("hdop");
529 if (val != null) {
530 double hdop = ((Float) val).doubleValue();
531 if(hdop > maxval) {
532 maxval = hdop;
533 }
534 if(hdop < minval) {
535 minval = hdop;
536 }
537 }
538 }
539 }
540 }
541 }
542 oldWp = null;
543 }
544 if (colored == colorModes.time) {
545 for (GpxTrack trk : data.tracks) {
546 for (GpxTrackSegment segment : trk.getSegments()) {
547 for (WayPoint trkPnt : segment.getWayPoints()) {
548 double t=trkPnt.time;
549 if (t==0) {
550 continue; // skip non-dated trackpoints
551 }
552 if(t > maxval) {
553 maxval = t;
554 }
555 if(t < minval) {
556 minval = t;
557 }
558 }
559 }
560 }
561 }
562
563 for (GpxTrack trk : data.tracks) {
564 for (GpxTrackSegment segment : trk.getSegments()) {
565 if (!forceLines) { // don't draw lines between segments, unless forced to
566 oldWp = null;
567 }
568 for (WayPoint trkPnt : segment.getWayPoints()) {
569 LatLon c = trkPnt.getCoor();
570 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
571 continue;
572 }
573 trkPnt.customColoring = neutralColor;
574 if(colored == colorModes.dilution && trkPnt.attr.get("hdop") != null) {
575 float hdop = ((Float) trkPnt.attr.get("hdop")).floatValue();
576 int hdoplvl =(int) Math.round(colorModeDynamic ? ((hdop-minval)*255/(maxval-minval))
577 : (hdop <= 0 ? 0 : hdop * hdopfactor));
578 // High hdop is bad, but high values in colors are green.
579 // Therefore inverse the logic
580 int hdopcolor = 255 - (hdoplvl > 255 ? 255 : hdoplvl);
581 trkPnt.customColoring = colors[hdopcolor];
582 }
583 if (oldWp != null) {
584 double dist = c.greatCircleDistance(oldWp.getCoor());
585 boolean noDraw=false;
586 switch (colored) {
587 case velocity:
588 double dtime = trkPnt.time - oldWp.time;
589 if(dtime > 0) {
590 float vel = (float) (dist / dtime);
591 int velColor =(int) Math.round(colorModeDynamic ? ((vel-minval)*255/(maxval-minval))
592 : (vel <= 0 ? 0 : vel / colorTracksTune * 255));
593 trkPnt.customColoring = colors[Math.max(0, Math.min(velColor, 255))];
594 } else {
595 trkPnt.customColoring = colors[255];
596 }
597 break;
598 case direction:
599 double dirColor = oldWp.getCoor().heading(trkPnt.getCoor()) / (2.0 * Math.PI) * 256;
600 // Bad case first
601 if (dirColor != dirColor || dirColor < 0.0 || dirColor >= 256.0) {
602 trkPnt.customColoring = colors_cyclic[0];
603 } else {
604 trkPnt.customColoring = colors_cyclic[(int) (dirColor)];
605 }
606 break;
607 case time:
608 if (trkPnt.time>0){
609 int tColor = (int) Math.round((trkPnt.time-minval)*255/(maxval-minval));
610 trkPnt.customColoring = colors[tColor];
611 } else {
612 trkPnt.customColoring = neutralColor;
613 }
614 break;
615 }
616
617 if (!noDraw && (maxLineLength == -1 || dist <= maxLineLength)) {
618 trkPnt.drawLine = true;
619 trkPnt.dir = (int) oldWp.getCoor().heading(trkPnt.getCoor());
620 } else {
621 trkPnt.drawLine = false;
622 }
623 } else { // make sure we reset outdated data
624 trkPnt.drawLine = false;
625 }
626 oldWp = trkPnt;
627 }
628 }
629 }
630 computeCacheInSync = true;
631 }
632
633 LinkedList<WayPoint> visibleSegments = new LinkedList<WayPoint>();
634 WayPoint last = null;
635 int i = 0;
636 ensureTrackVisibilityLength();
637 for (GpxTrack trk: data.tracks) {
638 // hide tracks that were de-selected in ChooseTrackVisibilityAction
639 if(!trackVisibility[i++]) {
640 continue;
641 }
642
643 for (GpxTrackSegment trkSeg: trk.getSegments()) {
644 for(WayPoint pt : trkSeg.getWayPoints())
645 {
646 Bounds b = new Bounds(pt.getCoor());
647 // last should never be null when this is true!
648 if(pt.drawLine) {
649 b.extend(last.getCoor());
650 }
651 if(b.intersects(box))
652 {
653 if(last != null && (visibleSegments.isEmpty()
654 || visibleSegments.getLast() != last)) {
655 if(last.drawLine) {
656 WayPoint l = new WayPoint(last);
657 l.drawLine = false;
658 visibleSegments.add(l);
659 } else {
660 visibleSegments.add(last);
661 }
662 }
663 visibleSegments.add(pt);
664 }
665 last = pt;
666 }
667 }
668 }
669 if(visibleSegments.isEmpty())
670 return;
671
672 /****************************************************************
673 ********** STEP 3a - DRAW LINES ********************************
674 ****************************************************************/
675 if (lines) {
676 Point old = null;
677 for (WayPoint trkPnt : visibleSegments) {
678 LatLon c = trkPnt.getCoor();
679 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
680 continue;
681 }
682 Point screen = mv.getPoint(trkPnt.getEastNorth());
683 if (trkPnt.drawLine) {
684 // skip points that are on the same screenposition
685 if (old != null && ((old.x != screen.x) || (old.y != screen.y))) {
686 g.setColor(trkPnt.customColoring);
687 g.drawLine(old.x, old.y, screen.x, screen.y);
688 }
689 }
690 old = screen;
691 } // end for trkpnt
692 } // end if lines
693
694 /****************************************************************
695 ********** STEP 3b - DRAW NICE ARROWS **************************
696 ****************************************************************/
697 if (lines && direction && !alternatedirection) {
698 Point old = null;
699 Point oldA = null; // last arrow painted
700 for (WayPoint trkPnt : visibleSegments) {
701 LatLon c = trkPnt.getCoor();
702 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
703 continue;
704 }
705 if (trkPnt.drawLine) {
706 Point screen = mv.getPoint(trkPnt.getEastNorth());
707 // skip points that are on the same screenposition
708 if (old != null
709 && (oldA == null || screen.x < oldA.x - delta || screen.x > oldA.x + delta
710 || screen.y < oldA.y - delta || screen.y > oldA.y + delta)) {
711 g.setColor(trkPnt.customColoring);
712 double t = Math.atan2(screen.y - old.y, screen.x - old.x) + Math.PI;
713 g.drawLine(screen.x, screen.y, (int) (screen.x + 10 * Math.cos(t - PHI)),
714 (int) (screen.y + 10 * Math.sin(t - PHI)));
715 g.drawLine(screen.x, screen.y, (int) (screen.x + 10 * Math.cos(t + PHI)),
716 (int) (screen.y + 10 * Math.sin(t + PHI)));
717 oldA = screen;
718 }
719 old = screen;
720 }
721 } // end for trkpnt
722 } // end if lines
723
724 /****************************************************************
725 ********** STEP 3c - DRAW FAST ARROWS **************************
726 ****************************************************************/
727 if (lines && direction && alternatedirection) {
728 Point old = null;
729 Point oldA = null; // last arrow painted
730 for (WayPoint trkPnt : visibleSegments) {
731 LatLon c = trkPnt.getCoor();
732 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
733 continue;
734 }
735 if (trkPnt.drawLine) {
736 Point screen = mv.getPoint(trkPnt.getEastNorth());
737 // skip points that are on the same screenposition
738 if (old != null
739 && (oldA == null || screen.x < oldA.x - delta || screen.x > oldA.x + delta
740 || screen.y < oldA.y - delta || screen.y > oldA.y + delta)) {
741 g.setColor(trkPnt.customColoring);
742 g.drawLine(screen.x, screen.y, screen.x + dir[trkPnt.dir][0], screen.y
743 + dir[trkPnt.dir][1]);
744 g.drawLine(screen.x, screen.y, screen.x + dir[trkPnt.dir][2], screen.y
745 + dir[trkPnt.dir][3]);
746 oldA = screen;
747 }
748 old = screen;
749 }
750 } // end for trkpnt
751 } // end if lines
752
753 /****************************************************************
754 ********** STEP 3d - DRAW LARGE POINTS AND HDOP CIRCLE *********
755 ****************************************************************/
756 if (large || hdopcircle) {
757 g.setColor(neutralColor);
758 for (WayPoint trkPnt : visibleSegments) {
759 LatLon c = trkPnt.getCoor();
760 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
761 continue;
762 }
763 Point screen = mv.getPoint(trkPnt.getEastNorth());
764 g.setColor(trkPnt.customColoring);
765 if (hdopcircle && trkPnt.attr.get("hdop") != null) {
766 // hdop value
767 float hdop = ((Float)trkPnt.attr.get("hdop")).floatValue();
768 if (hdop < 0) {
769 hdop = 0;
770 }
771 // hdop pixels
772 int hdopp = mv.getPoint(new LatLon(trkPnt.getCoor().lat(), trkPnt.getCoor().lon() + 2*6*hdop*360/40000000)).x - screen.x;
773 g.drawArc(screen.x-hdopp/2, screen.y-hdopp/2, hdopp, hdopp, 0, 360);
774 }
775 if (large) {
776 g.fillRect(screen.x-1, screen.y-1, largesize, largesize);
777 }
778 } // end for trkpnt
779 } // end if large || hdopcircle
780
781 /****************************************************************
782 ********** STEP 3e - DRAW SMALL POINTS FOR LINES ***************
783 ****************************************************************/
784 if (!large && lines) {
785 g.setColor(neutralColor);
786 for (WayPoint trkPnt : visibleSegments) {
787 LatLon c = trkPnt.getCoor();
788 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
789 continue;
790 }
791 if (!trkPnt.drawLine) {
792 Point screen = mv.getPoint(trkPnt.getEastNorth());
793 g.drawRect(screen.x, screen.y, 0, 0);
794 }
795 } // end for trkpnt
796 } // end if large
797
798 /****************************************************************
799 ********** STEP 3f - DRAW SMALL POINTS INSTEAD OF LINES ********
800 ****************************************************************/
801 if (!large && !lines) {
802 g.setColor(neutralColor);
803 for (WayPoint trkPnt : visibleSegments) {
804 LatLon c = trkPnt.getCoor();
805 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
806 continue;
807 }
808 Point screen = mv.getPoint(trkPnt.getEastNorth());
809 g.setColor(trkPnt.customColoring);
810 g.drawRect(screen.x, screen.y, 0, 0);
811 } // end for trkpnt
812 } // end if large
813
814 if(lineWidth != 0)
815 {
816 g.setStroke(storedStroke);
817 }
818 // Long duration = System.currentTimeMillis() - startTime;
819 // System.out.println(duration);
820 } // end paint
821
822 @Override
823 public void visitBoundingBox(BoundingXYVisitor v) {
824 v.visit(data.recalculateBounds());
825 }
826
827 public class ConvertToDataLayerAction extends AbstractAction {
828 public ConvertToDataLayerAction() {
829 super(tr("Convert to data layer"), ImageProvider.get("converttoosm"));
830 putValue("help", ht("/Action/ConvertToDataLayer"));
831 }
832
833 @Override
834 public void actionPerformed(ActionEvent e) {
835 JPanel msg = new JPanel(new GridBagLayout());
836 msg
837 .add(
838 new JLabel(
839 tr("<html>Upload of unprocessed GPS data as map data is considered harmful.<br>If you want to upload traces, look here:</html>")),
840 GBC.eol());
841 msg.add(new UrlLabel(tr("http://www.openstreetmap.org/traces"),2), GBC.eop());
842 if (!ConditionalOptionPaneUtil.showConfirmationDialog("convert_to_data", Main.parent, msg, tr("Warning"),
843 JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, JOptionPane.OK_OPTION))
844 return;
845 DataSet ds = new DataSet();
846 for (GpxTrack trk : data.tracks) {
847 for (GpxTrackSegment segment : trk.getSegments()) {
848 List<Node> nodes = new ArrayList<Node>();
849 for (WayPoint p : segment.getWayPoints()) {
850 Node n = new Node(p.getCoor());
851 String timestr = p.getString("time");
852 if (timestr != null) {
853 n.setTimestamp(DateUtils.fromString(timestr));
854 }
855 ds.addPrimitive(n);
856 nodes.add(n);
857 }
858 Way w = new Way();
859 w.setNodes(nodes);
860 ds.addPrimitive(w);
861 }
862 }
863 Main.main
864 .addLayer(new OsmDataLayer(ds, tr("Converted from: {0}", GpxLayer.this.getName()), getAssociatedFile()));
865 Main.main.removeLayer(GpxLayer.this);
866 }
867 }
868
869 @Override
870 public File getAssociatedFile() {
871 return data.storageFile;
872 }
873
874 @Override
875 public void setAssociatedFile(File file) {
876 data.storageFile = file;
877 }
878
879 /** ensures the trackVisibility array has the correct length without losing data.
880 * additional entries are initialized to true;
881 */
882 final private void ensureTrackVisibilityLength() {
883 final int l = data.tracks.size();
884 if(l == trackVisibility.length)
885 return;
886 final boolean[] back = trackVisibility.clone();
887 final int m = Math.min(l, back.length);
888 trackVisibility = new boolean[l];
889 for(int i=0; i < m; i++) {
890 trackVisibility[i] = back[i];
891 }
892 for(int i=m; i < l; i++) {
893 trackVisibility[i] = true;
894 }
895 }
896
897 /**
898 * allows the user to choose which of the downloaded tracks should be displayed.
899 * they can be chosen from the gpx layer context menu.
900 */
901 public class ChooseTrackVisibilityAction extends AbstractAction {
902 public ChooseTrackVisibilityAction() {
903 super(tr("Choose visible tracks"), ImageProvider.get("dialogs/filter"));
904 putValue("help", ht("/Action/ChooseTrackVisibility"));
905 }
906
907 /**
908 * gathers all available data for the tracks and returns them as array of arrays
909 * in the expected column order */
910 private Object[][] buildTableContents() {
911 Object[][] tracks = new Object[data.tracks.size()][5];
912 int i = 0;
913 for (GpxTrack trk : data.tracks) {
914 Map<String, Object> attr = trk.getAttributes();
915 String name = (String) (attr.containsKey("name") ? attr.get("name") : "");
916 String desc = (String) (attr.containsKey("desc") ? attr.get("desc") : "");
917 String time = getTimespanForTrack(trk);
918 String length = NavigatableComponent.getSystemOfMeasurement().getDistText(trk.length());
919 String url = (String) (attr.containsKey("url") ? attr.get("url") : "");
920 tracks[i] = new String[] {name, desc, time, length, url};
921 i++;
922 }
923 return tracks;
924 }
925
926 /**
927 * Builds an non-editable table whose 5th column will open a browser when double clicked.
928 * The table will fill its parent. */
929 private JTable buildTable(String[] headers, Object[][] content) {
930 final JTable t = new JTable(content, headers) {
931 @Override
932 public Component prepareRenderer(TableCellRenderer renderer, int row, int col) {
933 Component c = super.prepareRenderer(renderer, row, col);
934 if (c instanceof JComponent) {
935 JComponent jc = (JComponent)c;
936 jc.setToolTipText((String)getValueAt(row, col));
937 }
938 return c;
939 }
940
941 @Override
942 public boolean isCellEditable(int rowIndex, int colIndex) {
943 return false;
944 }
945 };
946 // default column widths
947 t.getColumnModel().getColumn(0).setPreferredWidth(220);
948 t.getColumnModel().getColumn(1).setPreferredWidth(300);
949 t.getColumnModel().getColumn(2).setPreferredWidth(200);
950 t.getColumnModel().getColumn(3).setPreferredWidth(50);
951 t.getColumnModel().getColumn(4).setPreferredWidth(100);
952 // make the link clickable
953 final MouseListener urlOpener = new MouseAdapter() {
954 @Override
955 public void mouseClicked(MouseEvent e) {
956 if (e.getClickCount() != 2)
957 return;
958 JTable t = (JTable)e.getSource();
959 int col = t.convertColumnIndexToModel(t.columnAtPoint(e.getPoint()));
960 if(col != 4) // only accept clicks on the URL column
961 return;
962 int row = t.rowAtPoint(e.getPoint());
963 String url = (String) t.getValueAt(row, col);
964 if (url == null || url.isEmpty())
965 return;
966 OpenBrowser.displayUrl(url);
967 }
968 };
969 t.addMouseListener(urlOpener);
970 t.setFillsViewportHeight(true);
971 return t;
972 }
973
974 /** selects all rows (=tracks) in the table that are currently visible */
975 private void selectVisibleTracksInTable(JTable table) {
976 // don't select any tracks if the layer is not visible
977 if(!isVisible())
978 return;
979 ListSelectionModel s = table.getSelectionModel();
980 s.clearSelection();
981 for(int i=0; i < trackVisibility.length; i++)
982 if(trackVisibility[i]) {
983 s.addSelectionInterval(i, i);
984 }
985 }
986
987 /** listens to selection changes in the table and redraws the map */
988 private void listenToSelectionChanges(JTable table) {
989 table.getSelectionModel().addListSelectionListener(new ListSelectionListener(){
990 public void valueChanged(ListSelectionEvent e) {
991 if(!(e.getSource() instanceof ListSelectionModel))
992 return;
993
994 ListSelectionModel s = (ListSelectionModel) e.getSource();
995 for(int i = 0; i < data.tracks.size(); i++) {
996 trackVisibility[i] = s.isSelectedIndex(i);
997 }
998 Main.map.mapView.preferenceChanged(null);
999 Main.map.repaint(100);
1000 }
1001 });
1002 }
1003
1004 @Override
1005 public void actionPerformed(ActionEvent arg0) {
1006 final JPanel msg = new JPanel(new GridBagLayout());
1007 msg.add(new JLabel(tr("<html>Select all tracks that you want to be displayed. You can drag select a "
1008 + "range of tracks or use CTRL+Click to select specific ones. The map is updated live in the "
1009 + "background. Open the URLs by double clicking them.</html>")),
1010 GBC.eol().fill(GBC.HORIZONTAL));
1011
1012 // build table
1013 final boolean[] trackVisibilityBackup = trackVisibility.clone();
1014 final String[] headers = {tr("Name"), tr("Description"), tr("Timespan"), tr("Length"), tr("URL")};
1015 final JTable table = buildTable(headers, buildTableContents());
1016 selectVisibleTracksInTable(table);
1017 listenToSelectionChanges(table);
1018
1019 // make the table scrollable
1020 JScrollPane scrollPane = new JScrollPane(table);
1021 msg.add(scrollPane, GBC.eol().fill(GBC.BOTH));
1022
1023 // build dialog
1024 ExtendedDialog ed = new ExtendedDialog(
1025 Main.parent, tr("Set track visibility for {0}", getName()),
1026 new String[] {tr("Show all"), tr("Show selected only"), tr("Cancel")});
1027 ed.setButtonIcons(new String[] {"dialogs/layerlist/eye", "dialogs/filter", "cancel"});
1028 ed.setContent(msg, false);
1029 ed.setDefaultButton(2);
1030 ed.setCancelButton(3);
1031 ed.configureContextsensitiveHelp("/Action/ChooseTrackVisibility", true);
1032 ed.setRememberWindowGeometry(
1033 getClass().getName() + ".geometry",
1034 WindowGeometry.centerInWindow(Main.parent, new Dimension(1000, 500))
1035 );
1036 ed.showDialog();
1037 int v = ed.getValue();
1038 // cancel for unknown buttons and copy back original settings
1039 if(v != 1 && v != 2) {
1040 for(int i = 0; i < data.tracks.size(); i++) {
1041 trackVisibility[i] = trackVisibilityBackup[i];
1042 }
1043 Main.map.repaint();
1044 return;
1045 }
1046
1047 // set visibility (1 = show all, 2 = filter). If no tracks are selected
1048 // set all of them visible and...
1049 ListSelectionModel s = table.getSelectionModel();
1050 final boolean all = v == 1 || s.isSelectionEmpty();
1051 for(int i = 0; i < data.tracks.size(); i++) {
1052 trackVisibility[i] = all || s.isSelectedIndex(i);
1053 }
1054 // ...sync with layer visibility instead to avoid having two ways to hide everything
1055 setVisible(v == 1 || !s.isSelectionEmpty());
1056 Main.map.repaint();
1057 }
1058 }
1059
1060 /**
1061 * Action that issues a series of download requests to the API, following the GPX track.
1062 *
1063 * @author fred
1064 */
1065 public class DownloadAlongTrackAction extends AbstractAction {
1066 final static int NEAR_TRACK=0;
1067 final static int NEAR_WAYPOINTS=1;
1068 final static int NEAR_BOTH=2;
1069 final Integer dist[] = { 5000, 500, 50 };
1070 final Integer area[] = { 20, 10, 5, 1 };
1071
1072 public DownloadAlongTrackAction() {
1073 super(tr("Download from OSM along this track"), ImageProvider.get("downloadalongtrack"));
1074 }
1075
1076 @Override
1077 public void actionPerformed(ActionEvent e) {
1078 /*
1079 * build selection dialog
1080 */
1081 JPanel msg = new JPanel(new GridBagLayout());
1082
1083 msg.add(new JLabel(tr("Download everything within:")), GBC.eol());
1084 String s[] = new String[dist.length];
1085 for (int i = 0; i < dist.length; ++i) {
1086 s[i] = tr("{0} meters", dist[i]);
1087 }
1088 JList buffer = new JList(s);
1089 buffer.setSelectedIndex(Main.pref.getInteger(PREF_DOWNLOAD_ALONG_TRACK_DISTANCE, 0));
1090 msg.add(buffer, GBC.eol());
1091
1092 msg.add(new JLabel(tr("Maximum area per request:")), GBC.eol());
1093 s = new String[area.length];
1094 for (int i = 0; i < area.length; ++i) {
1095 s[i] = tr("{0} sq km", area[i]);
1096 }
1097 JList maxRect = new JList(s);
1098 maxRect.setSelectedIndex(Main.pref.getInteger(PREF_DOWNLOAD_ALONG_TRACK_AREA, 0));
1099 msg.add(maxRect, GBC.eol());
1100
1101 msg.add(new JLabel(tr("Download near:")), GBC.eol());
1102 JList downloadNear = new JList(new String[] { tr("track only"), tr("waypoints only"), tr("track and waypoints") });
1103
1104 downloadNear.setSelectedIndex(Main.pref.getInteger(PREF_DOWNLOAD_ALONG_TRACK_NEAR, 0));
1105 msg.add(downloadNear, GBC.eol());
1106
1107 int ret = JOptionPane.showConfirmDialog(
1108 Main.parent,
1109 msg,
1110 tr("Download from OSM along this track"),
1111 JOptionPane.OK_CANCEL_OPTION,
1112 JOptionPane.QUESTION_MESSAGE
1113 );
1114 switch(ret) {
1115 case JOptionPane.CANCEL_OPTION:
1116 case JOptionPane.CLOSED_OPTION:
1117 return;
1118 default:
1119 // continue
1120 }
1121
1122 Main.pref.putInteger(PREF_DOWNLOAD_ALONG_TRACK_DISTANCE, buffer.getSelectedIndex());
1123 Main.pref.putInteger(PREF_DOWNLOAD_ALONG_TRACK_AREA, maxRect.getSelectedIndex());
1124 final int near = downloadNear.getSelectedIndex();
1125 Main.pref.putInteger(PREF_DOWNLOAD_ALONG_TRACK_NEAR, near);
1126
1127 /*
1128 * Find the average latitude for the data we're contemplating, so we can know how many
1129 * metres per degree of longitude we have.
1130 */
1131 double latsum = 0;
1132 int latcnt = 0;
1133
1134 if (near == NEAR_TRACK || near == NEAR_BOTH) {
1135 for (GpxTrack trk : data.tracks) {
1136 for (GpxTrackSegment segment : trk.getSegments()) {
1137 for (WayPoint p : segment.getWayPoints()) {
1138 latsum += p.getCoor().lat();
1139 latcnt++;
1140 }
1141 }
1142 }
1143 }
1144
1145 if (near == NEAR_WAYPOINTS || near == NEAR_BOTH) {
1146 for (WayPoint p : data.waypoints) {
1147 latsum += p.getCoor().lat();
1148 latcnt++;
1149 }
1150 }
1151
1152 double avglat = latsum / latcnt;
1153 double scale = Math.cos(Math.toRadians(avglat));
1154
1155 /*
1156 * Compute buffer zone extents and maximum bounding box size. Note that the maximum we
1157 * ever offer is a bbox area of 0.002, while the API theoretically supports 0.25, but as
1158 * soon as you touch any built-up area, that kind of bounding box will download forever
1159 * and then stop because it has more than 50k nodes.
1160 */
1161 Integer i = buffer.getSelectedIndex();
1162 final int buffer_dist = dist[i < 0 ? 0 : i];
1163 i = maxRect.getSelectedIndex();
1164 final double max_area = area[i < 0 ? 0 : i] / 10000.0 / scale;
1165 final double buffer_y = buffer_dist / 100000.0;
1166 final double buffer_x = buffer_y / scale;
1167
1168 final int totalTicks = latcnt;
1169 // guess if a progress bar might be useful.
1170 final boolean displayProgress = totalTicks > 2000 && buffer_y < 0.01;
1171
1172 class CalculateDownloadArea extends PleaseWaitRunnable {
1173 private Area a = new Area();
1174 private boolean cancel = false;
1175 private int ticks = 0;
1176 private Rectangle2D r = new Rectangle2D.Double();
1177
1178 public CalculateDownloadArea() {
1179 super(tr("Calculating Download Area"),
1180 (displayProgress ? null : NullProgressMonitor.INSTANCE),
1181 false);
1182 }
1183
1184 @Override
1185 protected void cancel() {
1186 cancel = true;
1187 }
1188
1189 @Override
1190 protected void finish() {
1191 }
1192
1193 @Override
1194 protected void afterFinish() {
1195 if(cancel)
1196 return;
1197 confirmAndDownloadAreas(a, max_area, progressMonitor);
1198 }
1199
1200 /**
1201 * increase tick count by one, report progress every 100 ticks
1202 */
1203 private void tick() {
1204 ticks++;
1205 if(ticks % 100 == 0) {
1206 progressMonitor.worked(100);
1207 }
1208 }
1209
1210 /**
1211 * calculate area for single, given way point and return new LatLon if the
1212 * way point has been used to modify the area.
1213 */
1214 private LatLon calcAreaForWayPoint(WayPoint p, LatLon previous) {
1215 tick();
1216 LatLon c = p.getCoor();
1217 if (previous == null || c.greatCircleDistance(previous) > buffer_dist) {
1218 // we add a buffer around the point.
1219 r.setRect(c.lon() - buffer_x, c.lat() - buffer_y, 2 * buffer_x, 2 * buffer_y);
1220 a.add(new Area(r));
1221 return c;
1222 }
1223 return previous;
1224 }
1225
1226 @Override
1227 protected void realRun() {
1228 progressMonitor.setTicksCount(totalTicks);
1229 /*
1230 * Collect the combined area of all gpx points plus buffer zones around them. We ignore
1231 * points that lie closer to the previous point than the given buffer size because
1232 * otherwise this operation takes ages.
1233 */
1234 LatLon previous = null;
1235 if (near == NEAR_TRACK || near == NEAR_BOTH) {
1236 for (GpxTrack trk : data.tracks) {
1237 for (GpxTrackSegment segment : trk.getSegments()) {
1238 for (WayPoint p : segment.getWayPoints()) {
1239 if(cancel)
1240 return;
1241 previous = calcAreaForWayPoint(p, previous);
1242 }
1243 }
1244 }
1245 }
1246 if (near == NEAR_WAYPOINTS || near == NEAR_BOTH) {
1247 for (WayPoint p : data.waypoints) {
1248 if(cancel)
1249 return;
1250 previous = calcAreaForWayPoint(p, previous);
1251 }
1252 }
1253 }
1254 }
1255
1256 Main.worker.submit(new CalculateDownloadArea());
1257 }
1258
1259
1260 /**
1261 * Area "a" contains the hull that we would like to download data for. however we
1262 * can only download rectangles, so the following is an attempt at finding a number of
1263 * rectangles to download.
1264 *
1265 * The idea is simply: Start out with the full bounding box. If it is too large, then
1266 * split it in half and repeat recursively for each half until you arrive at something
1267 * small enough to download. The algorithm is improved by always using the intersection
1268 * between the rectangle and the actual desired area. For example, if you have a track
1269 * that goes like this: +----+ | /| | / | | / | |/ | +----+ then we would first look at
1270 * downloading the whole rectangle (assume it's too big), after that we split it in half
1271 * (upper and lower half), but we donot request the full upper and lower rectangle, only
1272 * the part of the upper/lower rectangle that actually has something in it.
1273 *
1274 * This functions calculates the rectangles, asks the user to continue and downloads
1275 * the areas if applicable.
1276 */
1277 private void confirmAndDownloadAreas(Area a, double max_area, ProgressMonitor progressMonitor) {
1278 List<Rectangle2D> toDownload = new ArrayList<Rectangle2D>();
1279
1280 addToDownload(a, a.getBounds(), toDownload, max_area);
1281
1282 if(toDownload.size() == 0)
1283 return;
1284
1285 JPanel msg = new JPanel(new GridBagLayout());
1286
1287 msg.add(new JLabel(
1288 tr("<html>This action will require {0} individual<br>"
1289 + "download requests. Do you wish<br>to continue?</html>",
1290 toDownload.size())), GBC.eol());
1291
1292 if (toDownload.size() > 1) {
1293 int ret = JOptionPane.showConfirmDialog(
1294 Main.parent,
1295 msg,
1296 tr("Download from OSM along this track"),
1297 JOptionPane.OK_CANCEL_OPTION,
1298 JOptionPane.PLAIN_MESSAGE
1299 );
1300 switch(ret) {
1301 case JOptionPane.CANCEL_OPTION:
1302 case JOptionPane.CLOSED_OPTION:
1303 return;
1304 default:
1305 // continue
1306 }
1307 }
1308 final PleaseWaitProgressMonitor monitor = new PleaseWaitProgressMonitor(tr("Download data"));
1309 final Future<?> future = new DownloadOsmTaskList().download(false, toDownload, monitor);
1310 Main.worker.submit(
1311 new Runnable() {
1312 @Override
1313 public void run() {
1314 try {
1315 future.get();
1316 } catch(Exception e) {
1317 e.printStackTrace();
1318 return;
1319 }
1320 monitor.close();
1321 }
1322 }
1323 );
1324 }
1325 }
1326
1327
1328 public class DownloadWmsAlongTrackAction extends AbstractAction {
1329 public DownloadWmsAlongTrackAction() {
1330 super(tr("Precache imagery tiles along this track"), ImageProvider.get("downloadalongtrack"));
1331 }
1332
1333 public void actionPerformed(ActionEvent e) {
1334
1335 final List<LatLon> points = new ArrayList<LatLon>();
1336
1337 for (GpxTrack trk : data.tracks) {
1338 for (GpxTrackSegment segment : trk.getSegments()) {
1339 for (WayPoint p : segment.getWayPoints()) {
1340 points.add(p.getCoor());
1341 }
1342 }
1343 }
1344 for (WayPoint p : data.waypoints) {
1345 points.add(p.getCoor());
1346 }
1347
1348
1349 final WMSLayer layer = askWMSLayer();
1350 if (layer != null) {
1351 PleaseWaitRunnable task = new PleaseWaitRunnable(tr("Precaching WMS")) {
1352
1353 private PrecacheTask precacheTask;
1354
1355 @Override
1356 protected void realRun() throws SAXException, IOException, OsmTransferException {
1357 precacheTask = new PrecacheTask(progressMonitor);
1358 layer.downloadAreaToCache(precacheTask, points, 0, 0);
1359 while (!precacheTask.isFinished() && !progressMonitor.isCanceled()) {
1360 synchronized (this) {
1361 try {
1362 wait(200);
1363 } catch (InterruptedException e) {
1364 e.printStackTrace();
1365 }
1366 }
1367 }
1368 }
1369
1370 @Override
1371 protected void finish() {
1372 }
1373
1374 @Override
1375 protected void cancel() {
1376 precacheTask.cancel();
1377 }
1378
1379 @Override
1380 public ProgressTaskId canRunInBackground() {
1381 return ProgressTaskIds.PRECACHE_WMS;
1382 }
1383 };
1384 Main.worker.execute(task);
1385 }
1386
1387
1388 }
1389
1390 protected WMSLayer askWMSLayer() {
1391 List<WMSLayer> targetLayers = Main.map.mapView.getLayersOfType(WMSLayer.class);
1392
1393 if (targetLayers.isEmpty()) {
1394 warnNoImageryLayers();
1395 return null;
1396 }
1397
1398 JComboBox layerList = new JComboBox();
1399 layerList.setRenderer(new LayerListCellRenderer());
1400 layerList.setModel(new DefaultComboBoxModel(targetLayers.toArray()));
1401 layerList.setSelectedIndex(0);
1402
1403 JPanel pnl = new JPanel();
1404 pnl.setLayout(new GridBagLayout());
1405 pnl.add(new JLabel(tr("Please select the imagery layer.")), GBC.eol());
1406 pnl.add(layerList, GBC.eol());
1407
1408 ExtendedDialog ed = new ExtendedDialog(Main.parent,
1409 tr("Select imagery layer"),
1410 new String[] { tr("Download"), tr("Cancel") });
1411 ed.setButtonIcons(new String[] { "dialogs/down", "cancel" });
1412 ed.setContent(pnl);
1413 ed.showDialog();
1414 if (ed.getValue() != 1)
1415 return null;
1416
1417 WMSLayer targetLayer = (WMSLayer) layerList.getSelectedItem();
1418 return targetLayer;
1419 }
1420
1421 protected void warnNoImageryLayers() {
1422 JOptionPane.showMessageDialog(Main.parent,
1423 tr("There are no imagery layers."),
1424 tr("No imagery layers"), JOptionPane.WARNING_MESSAGE);
1425 }
1426 }
1427
1428 private static void addToDownload(Area a, Rectangle2D r, Collection<Rectangle2D> results, double max_area) {
1429 Area tmp = new Area(r);
1430 // intersect with sought-after area
1431 tmp.intersect(a);
1432 if (tmp.isEmpty())
1433 return;
1434 Rectangle2D bounds = tmp.getBounds2D();
1435 if (bounds.getWidth() * bounds.getHeight() > max_area) {
1436 // the rectangle gets too large; split it and make recursive call.
1437 Rectangle2D r1;
1438 Rectangle2D r2;
1439 if (bounds.getWidth() > bounds.getHeight()) {
1440 // rectangles that are wider than high are split into a left and right half,
1441 r1 = new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth() / 2, bounds.getHeight());
1442 r2 = new Rectangle2D.Double(bounds.getX() + bounds.getWidth() / 2, bounds.getY(),
1443 bounds.getWidth() / 2, bounds.getHeight());
1444 } else {
1445 // others into a top and bottom half.
1446 r1 = new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight() / 2);
1447 r2 = new Rectangle2D.Double(bounds.getX(), bounds.getY() + bounds.getHeight() / 2, bounds.getWidth(),
1448 bounds.getHeight() / 2);
1449 }
1450 addToDownload(a, r1, results, max_area);
1451 addToDownload(a, r2, results, max_area);
1452 } else {
1453 results.add(bounds);
1454 }
1455 }
1456
1457 /**
1458 * Makes a new marker layer derived from this GpxLayer containing at least one audio marker
1459 * which the given audio file is associated with. Markers are derived from the following (a)
1460 * explict waypoints in the GPX layer, or (b) named trackpoints in the GPX layer, or (d)
1461 * timestamp on the wav file (e) (in future) voice recognised markers in the sound recording (f)
1462 * a single marker at the beginning of the track
1463 * @param wavFile : the file to be associated with the markers in the new marker layer
1464 * @param markers : keeps track of warning messages to avoid repeated warnings
1465 */
1466 private void importAudio(File wavFile, MarkerLayer ml, double firstStartTime, Markers markers) {
1467 URL url = null;
1468 try {
1469 url = wavFile.toURI().toURL();
1470 } catch (MalformedURLException e) {
1471 System.err.println("Unable to convert filename " + wavFile.getAbsolutePath() + " to URL");
1472 }
1473 Collection<WayPoint> waypoints = new ArrayList<WayPoint>();
1474 boolean timedMarkersOmitted = false;
1475 boolean untimedMarkersOmitted = false;
1476 double snapDistance = Main.pref.getDouble("marker.audiofromuntimedwaypoints.distance", 1.0e-3); /*
1477 * about
1478 * 25
1479 * m
1480 */
1481 WayPoint wayPointFromTimeStamp = null;
1482
1483 // determine time of first point in track
1484 double firstTime = -1.0;
1485 if (data.tracks != null && !data.tracks.isEmpty()) {
1486 for (GpxTrack track : data.tracks) {
1487 for (GpxTrackSegment seg : track.getSegments()) {
1488 for (WayPoint w : seg.getWayPoints()) {
1489 firstTime = w.time;
1490 break;
1491 }
1492 if (firstTime >= 0.0) {
1493 break;
1494 }
1495 }
1496 if (firstTime >= 0.0) {
1497 break;
1498 }
1499 }
1500 }
1501 if (firstTime < 0.0) {
1502 JOptionPane.showMessageDialog(
1503 Main.parent,
1504 tr("No GPX track available in layer to associate audio with."),
1505 tr("Error"),
1506 JOptionPane.ERROR_MESSAGE
1507 );
1508 return;
1509 }
1510
1511 // (a) try explicit timestamped waypoints - unless suppressed
1512 if (Main.pref.getBoolean("marker.audiofromexplicitwaypoints", true) && data.waypoints != null
1513 && !data.waypoints.isEmpty()) {
1514 for (WayPoint w : data.waypoints) {
1515 if (w.time > firstTime) {
1516 waypoints.add(w);
1517 } else if (w.time > 0.0) {
1518 timedMarkersOmitted = true;
1519 }
1520 }
1521 }
1522
1523 // (b) try explicit waypoints without timestamps - unless suppressed
1524 if (Main.pref.getBoolean("marker.audiofromuntimedwaypoints", true) && data.waypoints != null
1525 && !data.waypoints.isEmpty()) {
1526 for (WayPoint w : data.waypoints) {
1527 if (waypoints.contains(w)) {
1528 continue;
1529 }
1530 WayPoint wNear = nearestPointOnTrack(w.getEastNorth(), snapDistance);
1531 if (wNear != null) {
1532 WayPoint wc = new WayPoint(w.getCoor());
1533 wc.time = wNear.time;
1534 if (w.attr.containsKey("name")) {
1535 wc.attr.put("name", w.getString("name"));
1536 }
1537 waypoints.add(wc);
1538 } else {
1539 untimedMarkersOmitted = true;
1540 }
1541 }
1542 }
1543
1544 // (c) use explicitly named track points, again unless suppressed
1545 if ((Main.pref.getBoolean("marker.audiofromnamedtrackpoints", false)) && data.tracks != null
1546 && !data.tracks.isEmpty()) {
1547 for (GpxTrack track : data.tracks) {
1548 for (GpxTrackSegment seg : track.getSegments()) {
1549 for (WayPoint w : seg.getWayPoints()) {
1550 if (w.attr.containsKey("name") || w.attr.containsKey("desc")) {
1551 waypoints.add(w);
1552 }
1553 }
1554 }
1555 }
1556 }
1557
1558 // (d) use timestamp of file as location on track
1559 if ((Main.pref.getBoolean("marker.audiofromwavtimestamps", false)) && data.tracks != null
1560 && !data.tracks.isEmpty()) {
1561 double lastModified = wavFile.lastModified() / 1000.0; // lastModified is in
1562 // milliseconds
1563 double duration = AudioUtil.getCalibratedDuration(wavFile);
1564 double startTime = lastModified - duration;
1565 startTime = firstStartTime + (startTime - firstStartTime)
1566 / Main.pref.getDouble("audio.calibration", "1.0" /* default, ratio */);
1567 WayPoint w1 = null;
1568 WayPoint w2 = null;
1569
1570 for (GpxTrack track : data.tracks) {
1571 for (GpxTrackSegment seg : track.getSegments()) {
1572 for (WayPoint w : seg.getWayPoints()) {
1573 if (startTime < w.time) {
1574 w2 = w;
1575 break;
1576 }
1577 w1 = w;
1578 }
1579 if (w2 != null) {
1580 break;
1581 }
1582 }
1583 }
1584
1585 if (w1 == null || w2 == null) {
1586 timedMarkersOmitted = true;
1587 } else {
1588 wayPointFromTimeStamp = new WayPoint(w1.getCoor().interpolate(w2.getCoor(),
1589 (startTime - w1.time) / (w2.time - w1.time)));
1590 wayPointFromTimeStamp.time = startTime;
1591 String name = wavFile.getName();
1592 int dot = name.lastIndexOf(".");
1593 if (dot > 0) {
1594 name = name.substring(0, dot);
1595 }
1596 wayPointFromTimeStamp.attr.put("name", name);
1597 waypoints.add(wayPointFromTimeStamp);
1598 }
1599 }
1600
1601 // (e) analyse audio for spoken markers here, in due course
1602
1603 // (f) simply add a single marker at the start of the track
1604 if ((Main.pref.getBoolean("marker.audiofromstart") || waypoints.isEmpty()) && data.tracks != null
1605 && !data.tracks.isEmpty()) {
1606 boolean gotOne = false;
1607 for (GpxTrack track : data.tracks) {
1608 for (GpxTrackSegment seg : track.getSegments()) {
1609 for (WayPoint w : seg.getWayPoints()) {
1610 WayPoint wStart = new WayPoint(w.getCoor());
1611 wStart.attr.put("name", "start");
1612 wStart.time = w.time;
1613 waypoints.add(wStart);
1614 gotOne = true;
1615 break;
1616 }
1617 if (gotOne) {
1618 break;
1619 }
1620 }
1621 if (gotOne) {
1622 break;
1623 }
1624 }
1625 }
1626
1627 /* we must have got at least one waypoint now */
1628
1629 Collections.sort((ArrayList<WayPoint>) waypoints, new Comparator<WayPoint>() {
1630 @Override
1631 public int compare(WayPoint a, WayPoint b) {
1632 return a.time <= b.time ? -1 : 1;
1633 }
1634 });
1635
1636 firstTime = -1.0; /* this time of the first waypoint, not first trackpoint */
1637 for (WayPoint w : waypoints) {
1638 if (firstTime < 0.0) {
1639 firstTime = w.time;
1640 }
1641 double offset = w.time - firstTime;
1642 AudioMarker am = new AudioMarker(w.getCoor(), w, url, ml, w.time, offset);
1643 /*
1644 * timeFromAudio intended for future use to shift markers of this type on
1645 * synchronization
1646 */
1647 if (w == wayPointFromTimeStamp) {
1648 am.timeFromAudio = true;
1649 }
1650 ml.data.add(am);
1651 }
1652
1653 if (timedMarkersOmitted && !markers.timedMarkersOmitted) {
1654 JOptionPane
1655 .showMessageDialog(
1656 Main.parent,
1657 tr("Some waypoints with timestamps from before the start of the track or after the end were omitted or moved to the start."));
1658 markers.timedMarkersOmitted = timedMarkersOmitted;
1659 }
1660 if (untimedMarkersOmitted && !markers.untimedMarkersOmitted) {
1661 JOptionPane
1662 .showMessageDialog(
1663 Main.parent,
1664 tr("Some waypoints which were too far from the track to sensibly estimate their time were omitted."));
1665 markers.untimedMarkersOmitted = untimedMarkersOmitted;
1666 }
1667 }
1668
1669 /**
1670 * Makes a WayPoint at the projection of point P onto the track providing P is less than
1671 * tolerance away from the track
1672 *
1673 * @param P : the point to determine the projection for
1674 * @param tolerance : must be no further than this from the track
1675 * @return the closest point on the track to P, which may be the first or last point if off the
1676 * end of a segment, or may be null if nothing close enough
1677 */
1678 public WayPoint nearestPointOnTrack(EastNorth P, double tolerance) {
1679 /*
1680 * assume the coordinates of P are xp,yp, and those of a section of track between two
1681 * trackpoints are R=xr,yr and S=xs,ys. Let N be the projected point.
1682 *
1683 * The equation of RS is Ax + By + C = 0 where A = ys - yr B = xr - xs C = - Axr - Byr
1684 *
1685 * Also, note that the distance RS^2 is A^2 + B^2
1686 *
1687 * If RS^2 == 0.0 ignore the degenerate section of track
1688 *
1689 * PN^2 = (Axp + Byp + C)^2 / RS^2 that is the distance from P to the line
1690 *
1691 * so if PN^2 is less than PNmin^2 (initialized to tolerance) we can reject the line;
1692 * otherwise... determine if the projected poijnt lies within the bounds of the line: PR^2 -
1693 * PN^2 <= RS^2 and PS^2 - PN^2 <= RS^2
1694 *
1695 * where PR^2 = (xp - xr)^2 + (yp-yr)^2 and PS^2 = (xp - xs)^2 + (yp-ys)^2
1696 *
1697 * If so, calculate N as xn = xr + (RN/RS) B yn = y1 + (RN/RS) A
1698 *
1699 * where RN = sqrt(PR^2 - PN^2)
1700 */
1701
1702 double PNminsq = tolerance * tolerance;
1703 EastNorth bestEN = null;
1704 double bestTime = 0.0;
1705 double px = P.east();
1706 double py = P.north();
1707 double rx = 0.0, ry = 0.0, sx, sy, x, y;
1708 if (data.tracks == null)
1709 return null;
1710 for (GpxTrack track : data.tracks) {
1711 for (GpxTrackSegment seg : track.getSegments()) {
1712 WayPoint R = null;
1713 for (WayPoint S : seg.getWayPoints()) {
1714 EastNorth c = S.getEastNorth();
1715 if (R == null) {
1716 R = S;
1717 rx = c.east();
1718 ry = c.north();
1719 x = px - rx;
1720 y = py - ry;
1721 double PRsq = x * x + y * y;
1722 if (PRsq < PNminsq) {
1723 PNminsq = PRsq;
1724 bestEN = c;
1725 bestTime = R.time;
1726 }
1727 } else {
1728 sx = c.east();
1729 sy = c.north();
1730 double A = sy - ry;
1731 double B = rx - sx;
1732 double C = -A * rx - B * ry;
1733 double RSsq = A * A + B * B;
1734 if (RSsq == 0.0) {
1735 continue;
1736 }
1737 double PNsq = A * px + B * py + C;
1738 PNsq = PNsq * PNsq / RSsq;
1739 if (PNsq < PNminsq) {
1740 x = px - rx;
1741 y = py - ry;
1742 double PRsq = x * x + y * y;
1743 x = px - sx;
1744 y = py - sy;
1745 double PSsq = x * x + y * y;
1746 if (PRsq - PNsq <= RSsq && PSsq - PNsq <= RSsq) {
1747 double RNoverRS = Math.sqrt((PRsq - PNsq) / RSsq);
1748 double nx = rx - RNoverRS * B;
1749 double ny = ry + RNoverRS * A;
1750 bestEN = new EastNorth(nx, ny);
1751 bestTime = R.time + RNoverRS * (S.time - R.time);
1752 PNminsq = PNsq;
1753 }
1754 }
1755 R = S;
1756 rx = sx;
1757 ry = sy;
1758 }
1759 }
1760 if (R != null) {
1761 EastNorth c = R.getEastNorth();
1762 /* if there is only one point in the seg, it will do this twice, but no matter */
1763 rx = c.east();
1764 ry = c.north();
1765 x = px - rx;
1766 y = py - ry;
1767 double PRsq = x * x + y * y;
1768 if (PRsq < PNminsq) {
1769 PNminsq = PRsq;
1770 bestEN = c;
1771 bestTime = R.time;
1772 }
1773 }
1774 }
1775 }
1776 if (bestEN == null)
1777 return null;
1778 WayPoint best = new WayPoint(Main.getProjection().eastNorth2latlon(bestEN));
1779 best.time = bestTime;
1780 return best;
1781 }
1782
1783 private class CustomizeDrawing extends AbstractAction implements LayerAction, MultiLayerAction {
1784 List<Layer> layers;
1785
1786 public CustomizeDrawing(List<Layer> l) {
1787 this();
1788 layers = l;
1789 }
1790
1791 public CustomizeDrawing(Layer l) {
1792 this();
1793 layers = new LinkedList<Layer>();
1794 layers.add(l);
1795 }
1796
1797 private CustomizeDrawing() {
1798 super(tr("Customize track drawing"), ImageProvider.get("mapmode/addsegment"));
1799 putValue("help", ht("/Action/GPXLayerCustomizeLineDrawing"));
1800 }
1801
1802 @Override
1803 public boolean supportLayers(List<Layer> layers) {
1804 for(Layer layer: layers) {
1805 if(!(layer instanceof GpxLayer))
1806 return false;
1807 }
1808 return true;
1809 }
1810
1811 @Override
1812 public Component createMenuComponent() {
1813 return new JMenuItem(this);
1814 }
1815
1816 @Override
1817 public Action getMultiLayerAction(List<Layer> layers) {
1818 return new CustomizeDrawing(layers);
1819 }
1820
1821 @Override
1822 public void actionPerformed(ActionEvent e) {
1823 boolean hasLocal = false, hasNonlocal = false;
1824 for (Layer layer : layers) {
1825 if (layer instanceof GpxLayer) {
1826 if (((GpxLayer) layer).isLocalFile) {
1827 hasLocal = true;
1828 } else {
1829 hasNonlocal = true;
1830 }
1831 }
1832 }
1833 GPXSettingsPanel panel=new GPXSettingsPanel(getName(), hasLocal, hasNonlocal);
1834 JScrollPane scrollpane = new JScrollPane(panel,
1835 JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,JScrollPane.HORIZONTAL_SCROLLBAR_NEVER );
1836 scrollpane.setBorder(BorderFactory.createEmptyBorder( 0, 0, 0, 0 ));
1837 int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
1838 if (screenHeight < 700) { // to fit on screen 800x600
1839 scrollpane.setPreferredSize(new Dimension(panel.getPreferredSize().width, Math.min(panel.getPreferredSize().height,450)));
1840 }
1841 int answer = JOptionPane.showConfirmDialog(Main.parent, scrollpane,
1842 tr("Customize track drawing"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
1843 if (answer == JOptionPane.CANCEL_OPTION || answer == JOptionPane.CLOSED_OPTION) return;
1844 for(Layer layer : layers) {
1845 // save preferences for all layers
1846 boolean f=false;
1847 if (layer instanceof GpxLayer) {
1848 f=((GpxLayer)layer).isLocalFile;
1849 }
1850 panel.savePreferences(layer.getName(),f);
1851 }
1852 Main.map.repaint();
1853 }
1854 }
1855
1856 private class MarkersFromNamedPoins extends AbstractAction {
1857
1858 public MarkersFromNamedPoins() {
1859 super(tr("Markers From Named Points"), ImageProvider.get("addmarkers"));
1860 putValue("help", ht("/Action/MarkersFromNamedPoints"));
1861 }
1862
1863 @Override
1864 public void actionPerformed(ActionEvent e) {
1865 GpxData namedTrackPoints = new GpxData();
1866 for (GpxTrack track : data.tracks) {
1867 for (GpxTrackSegment seg : track.getSegments()) {
1868 for (WayPoint point : seg.getWayPoints())
1869 if (point.attr.containsKey("name") || point.attr.containsKey("desc")) {
1870 namedTrackPoints.waypoints.add(point);
1871 }
1872 }
1873 }
1874
1875 MarkerLayer ml = new MarkerLayer(namedTrackPoints, tr("Named Trackpoints from {0}", getName()),
1876 getAssociatedFile(), GpxLayer.this);
1877 if (ml.data.size() > 0) {
1878 Main.main.addLayer(ml);
1879 }
1880
1881 }
1882 }
1883
1884 private class ImportAudio extends AbstractAction {
1885
1886 public ImportAudio() {
1887 super(tr("Import Audio"), ImageProvider.get("importaudio"));
1888 putValue("help", ht("/Action/ImportAudio"));
1889 }
1890
1891 private void warnCantImportIntoServerLayer(GpxLayer layer) {
1892 String msg = tr("<html>The data in the GPX layer ''{0}'' has been downloaded from the server.<br>"
1893 + "Because its way points do not include a timestamp we cannot correlate them with audio data.</html>",
1894 layer.getName()
1895 );
1896 HelpAwareOptionPane.showOptionDialog(
1897 Main.parent,
1898 msg,
1899 tr("Import not possible"),
1900 JOptionPane.WARNING_MESSAGE,
1901 ht("/Action/ImportAudio#CantImportIntoGpxLayerFromServer")
1902 );
1903 }
1904
1905 @Override
1906 public void actionPerformed(ActionEvent e) {
1907 if (GpxLayer.this.data.fromServer) {
1908 warnCantImportIntoServerLayer(GpxLayer.this);
1909 return;
1910 }
1911 String dir = Main.pref.get("markers.lastaudiodirectory");
1912 JFileChooser fc = new JFileChooser(dir);
1913 fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
1914 fc.setAcceptAllFileFilterUsed(false);
1915 fc.setFileFilter(new FileFilter() {
1916 @Override
1917 public boolean accept(File f) {
1918 return f.isDirectory() || f.getName().toLowerCase().endsWith(".wav");
1919 }
1920
1921 @Override
1922 public String getDescription() {
1923 return tr("Wave Audio files (*.wav)");
1924 }
1925 });
1926 fc.setMultiSelectionEnabled(true);
1927 if (fc.showOpenDialog(Main.parent) == JFileChooser.APPROVE_OPTION) {
1928 if (!fc.getCurrentDirectory().getAbsolutePath().equals(dir)) {
1929 Main.pref.put("markers.lastaudiodirectory", fc.getCurrentDirectory().getAbsolutePath());
1930 }
1931
1932 File sel[] = fc.getSelectedFiles();
1933 // sort files in increasing order of timestamp (this is the end time, but so
1934 // long as they don't overlap, that's fine)
1935 if (sel.length > 1) {
1936 Arrays.sort(sel, new Comparator<File>() {
1937 @Override
1938 public int compare(File a, File b) {
1939 return a.lastModified() <= b.lastModified() ? -1 : 1;
1940 }
1941 });
1942 }
1943
1944 String names = null;
1945 for (int i = 0; i < sel.length; i++) {
1946 if (names == null) {
1947 names = " (";
1948 } else {
1949 names += ", ";
1950 }
1951 names += sel[i].getName();
1952 }
1953 if (names != null) {
1954 names += ")";
1955 } else {
1956 names = "";
1957 }
1958 MarkerLayer ml = new MarkerLayer(new GpxData(), tr("Audio markers from {0}", getName()) + names,
1959 getAssociatedFile(), GpxLayer.this);
1960 double firstStartTime = sel[0].lastModified() / 1000.0 /* ms -> seconds */
1961 - AudioUtil.getCalibratedDuration(sel[0]);
1962
1963 Markers m = new Markers();
1964 for (int i = 0; i < sel.length; i++) {
1965 importAudio(sel[i], ml, firstStartTime, m);
1966 }
1967 Main.main.addLayer(ml);
1968 Main.map.repaint();
1969 }
1970
1971 }
1972 }
1973
1974 private class ImportImages extends AbstractAction {
1975
1976 public ImportImages() {
1977 super(tr("Import images"), ImageProvider.get("dialogs/geoimage"));
1978 putValue("help", ht("/Action/ImportImages"));
1979 }
1980
1981 private void warnCantImportIntoServerLayer(GpxLayer layer) {
1982 String msg = tr("<html>The data in the GPX layer ''{0}'' has been downloaded from the server.<br>"
1983 + "Because its way points do not include a timestamp we cannot correlate them with images.</html>",
1984 layer.getName()
1985 );
1986 HelpAwareOptionPane.showOptionDialog(
1987 Main.parent,
1988 msg,
1989 tr("Import not possible"),
1990 JOptionPane.WARNING_MESSAGE,
1991 ht("/Action/ImportImages#CantImportIntoGpxLayerFromServer")
1992 );
1993 }
1994
1995 private void addRecursiveFiles(LinkedList<File> files, File[] sel) {
1996 for (File f : sel) {
1997 if (f.isDirectory()) {
1998 addRecursiveFiles(files, f.listFiles());
1999 } else if (f.getName().toLowerCase().endsWith(".jpg")) {
2000 files.add(f);
2001 }
2002 }
2003 }
2004
2005 @Override
2006 public void actionPerformed(ActionEvent e) {
2007
2008 if (GpxLayer.this.data.fromServer) {
2009 warnCantImportIntoServerLayer(GpxLayer.this);
2010 return;
2011 }
2012 String curDir = Main.pref.get("geoimage.lastdirectory", Main.pref.get("lastDirectory"));
2013 if (curDir.equals("")) {
2014 curDir = ".";
2015 }
2016 JFileChooser fc = new JFileChooser(new File(curDir));
2017
2018 fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
2019 fc.setMultiSelectionEnabled(true);
2020 fc.setAcceptAllFileFilterUsed(false);
2021 JpgImporter importer = new JpgImporter(GpxLayer.this);
2022 fc.setFileFilter(importer.filter);
2023 fc.showOpenDialog(Main.parent);
2024 LinkedList<File> files = new LinkedList<File>();
2025 File[] sel = fc.getSelectedFiles();
2026 if (sel == null || sel.length == 0)
2027 return;
2028 if (!fc.getCurrentDirectory().getAbsolutePath().equals(curDir)) {
2029 Main.pref.put("geoimage.lastdirectory", fc.getCurrentDirectory().getAbsolutePath());
2030 }
2031 addRecursiveFiles(files, sel);
2032 importer.importDataHandleExceptions(files, NullProgressMonitor.INSTANCE);
2033 }
2034 }
2035
2036 @Override
2037 public void projectionChanged(Projection oldValue, Projection newValue) {
2038 if (newValue == null) return;
2039 if (data.waypoints != null) {
2040 for (WayPoint wp : data.waypoints){
2041 wp.invalidateEastNorthCache();
2042 }
2043 }
2044 if (data.tracks != null){
2045 for (GpxTrack track: data.tracks) {
2046 for (GpxTrackSegment segment: track.getSegments()) {
2047 for (WayPoint wp: segment.getWayPoints()) {
2048 wp.invalidateEastNorthCache();
2049 }
2050 }
2051 }
2052 }
2053 if (data.routes != null) {
2054 for (GpxRoute route: data.routes) {
2055 if (route.routePoints == null) {
2056 continue;
2057 }
2058 for (WayPoint wp: route.routePoints) {
2059 wp.invalidateEastNorthCache();
2060 }
2061 }
2062 }
2063 }
2064}
Note: See TracBrowser for help on using the repository browser.