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

Last change on this file since 4745 was 4745, checked in by jttt, 12 years ago

Add precache wms tiles action to gpx layers (it will download wms tiles along track to cache for faster work afterwards)

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