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

Last change on this file since 5717 was 5717, checked in by akks, 11 years ago

see #8416: allow to filter gpx traces by age (layer context menu, filter dialog)

  • Property svn:eol-style set to native
File size: 36.9 KB
Line 
1// License: GPL. See LICENSE file for details.
2
3package org.openstreetmap.josm.gui.layer;
4
5import static org.openstreetmap.josm.tools.I18n.marktr;
6import static org.openstreetmap.josm.tools.I18n.tr;
7import static org.openstreetmap.josm.tools.I18n.trn;
8
9import java.awt.BasicStroke;
10import java.awt.Color;
11import java.awt.Dimension;
12import java.awt.Graphics2D;
13import java.awt.Point;
14import java.awt.RenderingHints;
15import java.awt.Stroke;
16import java.io.File;
17import java.text.DateFormat;
18import java.util.ArrayList;
19import java.util.Date;
20import java.util.LinkedList;
21import java.util.List;
22
23import javax.swing.Action;
24import javax.swing.Icon;
25import javax.swing.JScrollPane;
26import javax.swing.SwingUtilities;
27
28import org.openstreetmap.josm.Main;
29import org.openstreetmap.josm.actions.RenameLayerAction;
30import org.openstreetmap.josm.actions.SaveActionBase;
31import org.openstreetmap.josm.data.Bounds;
32import org.openstreetmap.josm.data.coor.LatLon;
33import org.openstreetmap.josm.data.gpx.GpxConstants;
34import org.openstreetmap.josm.data.gpx.GpxData;
35import org.openstreetmap.josm.data.gpx.GpxRoute;
36import org.openstreetmap.josm.data.gpx.GpxTrack;
37import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
38import org.openstreetmap.josm.data.gpx.WayPoint;
39import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
40import org.openstreetmap.josm.data.projection.Projection;
41import org.openstreetmap.josm.gui.MapView;
42import org.openstreetmap.josm.gui.NavigatableComponent;
43import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
44import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
45import org.openstreetmap.josm.gui.layer.gpx.ChooseTrackVisibilityAction;
46import org.openstreetmap.josm.gui.layer.gpx.ConvertToDataLayerAction;
47import org.openstreetmap.josm.gui.layer.gpx.CustomizeDrawingAction;
48import org.openstreetmap.josm.gui.layer.gpx.DownloadAlongTrackAction;
49import org.openstreetmap.josm.gui.layer.gpx.DownloadWmsAlongTrackAction;
50import org.openstreetmap.josm.gui.layer.gpx.ImportAudioAction;
51import org.openstreetmap.josm.gui.layer.gpx.ImportImagesAction;
52import org.openstreetmap.josm.gui.layer.gpx.MarkersFromNamedPointsAction;
53import org.openstreetmap.josm.gui.widgets.HtmlPanel;
54import org.openstreetmap.josm.io.GpxImporter;
55import org.openstreetmap.josm.tools.ImageProvider;
56import org.openstreetmap.josm.tools.Utils;
57
58public class GpxLayer extends Layer {
59
60 public GpxData data;
61 protected static final double PHI = Math.toRadians(15);
62 private boolean computeCacheInSync;
63 private int computeCacheMaxLineLengthUsed;
64 private Color computeCacheColorUsed;
65 private boolean computeCacheColorDynamic;
66 private colorModes computeCacheColored;
67 private int computeCacheColorTracksTune;
68 private boolean isLocalFile;
69 // used by ChooseTrackVisibilityAction to determine which tracks to show/hide
70 public boolean[] trackVisibility = new boolean[0];
71
72 private final List<GpxTrack> lastTracks = new ArrayList<GpxTrack>(); // List of tracks at last paint
73 private int lastUpdateCount;
74
75 public GpxLayer(GpxData d) {
76 super((String) d.attr.get("name"));
77 data = d;
78 computeCacheInSync = false;
79 ensureTrackVisibilityLength();
80 }
81
82 public GpxLayer(GpxData d, String name) {
83 this(d);
84 this.setName(name);
85 }
86
87 public GpxLayer(GpxData d, String name, boolean isLocal) {
88 this(d);
89 this.setName(name);
90 this.isLocalFile = isLocal;
91 }
92
93 /**
94 * returns minimum and maximum timestamps in the track
95 */
96 public static Date[] getMinMaxTimeForTrack(GpxTrack trk) {
97 WayPoint earliest = null, latest = null;
98
99 for (GpxTrackSegment seg : trk.getSegments()) {
100 for (WayPoint pnt : seg.getWayPoints()) {
101 if (latest == null) {
102 latest = earliest = pnt;
103 } else {
104 if (pnt.compareTo(earliest) < 0) {
105 earliest = pnt;
106 } else {
107 latest = pnt;
108 }
109 }
110 }
111 }
112 if (earliest==null || latest==null) return null;
113 return new Date[]{earliest.getTime(), latest.getTime()};
114 }
115
116 /**
117 * returns minimum and maximum timestamps for all tracks
118 */
119 public Date[] getMinMaxTimeForAllTracks() {
120 double min=Double.MIN_VALUE, max=Double.MAX_VALUE, t;
121 for (GpxTrack trk: data.tracks) {
122 for (GpxTrackSegment seg : trk.getSegments()) {
123 for (WayPoint pnt : seg.getWayPoints()) {
124 t = pnt.time;
125 if (t!=0) {
126 if (t>max) max=t;
127 if (t<min) min=t;
128 }
129 }
130 }
131 }
132 if (min==Double.MIN_VALUE || max==Double.MAX_VALUE) return null;
133 return new Date[]{new Date((long) (min * 1000)), new Date((long) (max * 1000)), };
134 }
135
136
137 /**
138 * returns a human readable string that shows the timespan of the given track
139 */
140 public static String getTimespanForTrack(GpxTrack trk) {
141 Date[] bounds = getMinMaxTimeForTrack(trk);
142 String ts = "";
143 if (bounds != null) {
144 DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);
145 String earliestDate = df.format(bounds[0]);
146 String latestDate = df.format(bounds[1]);
147
148 if (earliestDate.equals(latestDate)) {
149 DateFormat tf = DateFormat.getTimeInstance(DateFormat.SHORT);
150 ts += earliestDate + " ";
151 ts += tf.format(bounds[0]) + " - " + tf.format(bounds[1]);
152 } else {
153 DateFormat dtf = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
154 ts += dtf.format(bounds[0]) + " - " + dtf.format(bounds[1]);
155 }
156
157 int diff = (int) (bounds[1].getTime() - bounds[0].getTime());
158 ts += String.format(" (%d:%02d)", diff / 3600, (diff % 3600) / 60);
159 }
160 return ts;
161 }
162
163 @Override
164 public Icon getIcon() {
165 return ImageProvider.get("layer", "gpx_small");
166 }
167
168 @Override
169 public Object getInfoComponent() {
170 StringBuilder info = new StringBuilder();
171
172 if (data.attr.containsKey("name")) {
173 info.append(tr("Name: {0}", data.attr.get(GpxConstants.META_NAME))).append("<br>");
174 }
175
176 if (data.attr.containsKey("desc")) {
177 info.append(tr("Description: {0}", data.attr.get(GpxConstants.META_DESC))).append("<br>");
178 }
179
180 if (data.tracks.size() > 0) {
181 info.append("<table><thead align='center'><tr><td colspan='5'>"
182 + trn("{0} track", "{0} tracks", data.tracks.size(), data.tracks.size())
183 + "</td></tr><tr align='center'><td>" + tr("Name") + "</td><td>"
184 + tr("Description") + "</td><td>" + tr("Timespan")
185 + "</td><td>" + tr("Length") + "</td><td>" + tr("URL")
186 + "</td></tr></thead>");
187
188 for (GpxTrack trk : data.tracks) {
189 info.append("<tr><td>");
190 if (trk.getAttributes().containsKey("name")) {
191 info.append(trk.getAttributes().get("name"));
192 }
193 info.append("</td><td>");
194 if (trk.getAttributes().containsKey("desc")) {
195 info.append(" ").append(trk.getAttributes().get("desc"));
196 }
197 info.append("</td><td>");
198 info.append(getTimespanForTrack(trk));
199 info.append("</td><td>");
200 info.append(NavigatableComponent.getSystemOfMeasurement().getDistText(trk.length()));
201 info.append("</td><td>");
202 if (trk.getAttributes().containsKey("url")) {
203 info.append(trk.getAttributes().get("url"));
204 }
205 info.append("</td></tr>");
206 }
207
208 info.append("</table><br><br>");
209
210 }
211
212 info.append(tr("Length: {0}", NavigatableComponent.getSystemOfMeasurement().getDistText(data.length()))).append("<br>");
213
214 info.append(trn("{0} route, ", "{0} routes, ", data.routes.size(), data.routes.size())).append(
215 trn("{0} waypoint", "{0} waypoints", data.waypoints.size(), data.waypoints.size())).append("<br>");
216
217 final JScrollPane sp = new JScrollPane(new HtmlPanel(info.toString()), JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
218 sp.setPreferredSize(new Dimension(sp.getPreferredSize().width, 350));
219 SwingUtilities.invokeLater(new Runnable() {
220 @Override
221 public void run() {
222 sp.getVerticalScrollBar().setValue(0);
223 }
224 });
225 return sp;
226 }
227
228 @Override
229 public Color getColor(boolean ignoreCustom) {
230 Color c = Main.pref.getColor(marktr("gps point"), "layer " + getName(), Color.gray);
231
232 return ignoreCustom || getColorMode() == colorModes.none ? c : null;
233 }
234
235 public colorModes getColorMode() {
236 try {
237 int i=Main.pref.getInteger("draw.rawgps.colors", "layer " + getName(), 0);
238 return colorModes.values()[i];
239 } catch (Exception e) {
240 }
241 return colorModes.none;
242 }
243
244 /* for preferences */
245 static public Color getGenericColor() {
246 return Main.pref.getColor(marktr("gps point"), Color.gray);
247 }
248
249 @Override
250 public Action[] getMenuEntries() {
251 if (Main.applet) {
252 return new Action[] {
253 LayerListDialog.getInstance().createShowHideLayerAction(),
254 LayerListDialog.getInstance().createDeleteLayerAction(),
255 SeparatorLayerAction.INSTANCE,
256 new CustomizeColor(this),
257 new CustomizeDrawingAction(this),
258 new ConvertToDataLayerAction(this),
259 SeparatorLayerAction.INSTANCE,
260 new ChooseTrackVisibilityAction(this),
261 new RenameLayerAction(getAssociatedFile(), this),
262 SeparatorLayerAction.INSTANCE,
263 new LayerListPopup.InfoAction(this) };
264 }
265 return new Action[] {
266 LayerListDialog.getInstance().createShowHideLayerAction(),
267 LayerListDialog.getInstance().createDeleteLayerAction(),
268 SeparatorLayerAction.INSTANCE,
269 new LayerSaveAction(this),
270 new LayerSaveAsAction(this),
271 new CustomizeColor(this),
272 new CustomizeDrawingAction(this),
273 new ImportImagesAction(this),
274 new ImportAudioAction(this),
275 new MarkersFromNamedPointsAction(this),
276 new ConvertToDataLayerAction(this),
277 new DownloadAlongTrackAction(data),
278 new DownloadWmsAlongTrackAction(data),
279 SeparatorLayerAction.INSTANCE,
280 new ChooseTrackVisibilityAction(this),
281 new RenameLayerAction(getAssociatedFile(), this),
282 SeparatorLayerAction.INSTANCE,
283 new LayerListPopup.InfoAction(this) };
284 }
285
286 public boolean isLocalFile() {
287 return isLocalFile;
288 }
289
290 @Override
291 public String getToolTipText() {
292 StringBuilder info = new StringBuilder().append("<html>");
293
294 if (data.attr.containsKey("name")) {
295 info.append(tr("Name: {0}", data.attr.get(GpxConstants.META_NAME))).append("<br>");
296 }
297
298 if (data.attr.containsKey("desc")) {
299 info.append(tr("Description: {0}", data.attr.get(GpxConstants.META_DESC))).append("<br>");
300 }
301
302 info.append(trn("{0} track, ", "{0} tracks, ", data.tracks.size(), data.tracks.size()));
303 info.append(trn("{0} route, ", "{0} routes, ", data.routes.size(), data.routes.size()));
304 info.append(trn("{0} waypoint", "{0} waypoints", data.waypoints.size(), data.waypoints.size())).append("<br>");
305
306 info.append(tr("Length: {0}", NavigatableComponent.getSystemOfMeasurement().getDistText(data.length())));
307 info.append("<br>");
308
309 return info.append("</html>").toString();
310 }
311
312 @Override
313 public boolean isMergable(Layer other) {
314 return other instanceof GpxLayer;
315 }
316
317 private int sumUpdateCount() {
318 int updateCount = 0;
319 for (GpxTrack track: data.tracks) {
320 updateCount += track.getUpdateCount();
321 }
322 return updateCount;
323 }
324
325 @Override
326 public boolean isChanged() {
327 if (data.tracks.equals(lastTracks))
328 return sumUpdateCount() != lastUpdateCount;
329 else
330 return true;
331 }
332
333 public void filterTracksByDate(Date fromDate, Date toDate, boolean showWithoutDate) {
334 int i = 0;
335 long from = fromDate.getTime();
336 long to = toDate.getTime();
337 for (GpxTrack trk : data.tracks) {
338 Date[] t = GpxLayer.getMinMaxTimeForTrack(trk);
339
340 if (t==null) continue;
341 long tm = t[1].getTime();
342 trackVisibility[i]= (tm==0 && showWithoutDate) || (from<=tm && tm <= to);
343 i++;
344 }
345 }
346
347 @Override
348 public void mergeFrom(Layer from) {
349 data.mergeFrom(((GpxLayer) from).data);
350 computeCacheInSync = false;
351 }
352
353 private final static Color[] colors = new Color[256];
354 static {
355 for (int i = 0; i < colors.length; i++) {
356 colors[i] = Color.getHSBColor(i / 300.0f, 1, 1);
357 }
358 }
359
360 private final static Color[] colors_cyclic = new Color[256];
361 static {
362 for (int i = 0; i < colors_cyclic.length; i++) {
363 // red yellow green blue red
364 int[] h = new int[] { 0, 59, 127, 244, 360};
365 int[] s = new int[] { 100, 84, 99, 100 };
366 int[] b = new int[] { 90, 93, 74, 83 };
367
368 float angle = 4 - i / 256f * 4;
369 int quadrant = (int) angle;
370 angle -= quadrant;
371 quadrant = Utils.mod(quadrant+1, 4);
372
373 float vh = h[quadrant] * w(angle) + h[quadrant+1] * (1 - w(angle));
374 float vs = s[quadrant] * w(angle) + s[Utils.mod(quadrant+1, 4)] * (1 - w(angle));
375 float vb = b[quadrant] * w(angle) + b[Utils.mod(quadrant+1, 4)] * (1 - w(angle));
376
377 colors_cyclic[i] = Color.getHSBColor(vh/360f, vs/100f, vb/100f);
378 }
379 }
380
381 /**
382 * transition function:
383 * w(0)=1, w(1)=0, 0<=w(x)<=1
384 * @param x number: 0<=x<=1
385 * @return the weighted value
386 */
387 private static float w(float x) {
388 if (x < 0.5)
389 return 1 - 2*x*x;
390 else
391 return 2*(1-x)*(1-x);
392 }
393
394 // lookup array to draw arrows without doing any math
395 private final static int ll0 = 9;
396 private final static int sl4 = 5;
397 private final static int sl9 = 3;
398 private final static int[][] dir = { { +sl4, +ll0, +ll0, +sl4 }, { -sl9, +ll0, +sl9, +ll0 }, { -ll0, +sl4, -sl4, +ll0 },
399 { -ll0, -sl9, -ll0, +sl9 }, { -sl4, -ll0, -ll0, -sl4 }, { +sl9, -ll0, -sl9, -ll0 },
400 { +ll0, -sl4, +sl4, -ll0 }, { +ll0, +sl9, +ll0, -sl9 }, { +sl4, +ll0, +ll0, +sl4 },
401 { -sl9, +ll0, +sl9, +ll0 }, { -ll0, +sl4, -sl4, +ll0 }, { -ll0, -sl9, -ll0, +sl9 } };
402
403 // the different color modes
404 enum colorModes {
405 none, velocity, dilution, direction, time
406 }
407
408 @Override
409 public void paint(Graphics2D g, MapView mv, Bounds box) {
410 lastUpdateCount = sumUpdateCount();
411 lastTracks.clear();
412 lastTracks.addAll(data.tracks);
413
414 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
415 Main.pref.getBoolean("mappaint.gpx.use-antialiasing", false) ?
416 RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
417
418 /****************************************************************
419 ********** STEP 1 - GET CONFIG VALUES **************************
420 ****************************************************************/
421 // Long startTime = System.currentTimeMillis();
422 Color neutralColor = getColor(true);
423 String spec="layer "+getName();
424
425 // also draw lines between points belonging to different segments
426 boolean forceLines = Main.pref.getBoolean("draw.rawgps.lines.force", spec, false);
427 // draw direction arrows on the lines
428 boolean direction = Main.pref.getBoolean("draw.rawgps.direction", spec, false);
429 // don't draw lines if longer than x meters
430 int lineWidth = Main.pref.getInteger("draw.rawgps.linewidth", spec, 0);
431
432 int maxLineLength;
433 boolean lines;
434 if (!this.data.fromServer) {
435 maxLineLength = Main.pref.getInteger("draw.rawgps.max-line-length.local", spec, -1);
436 lines = Main.pref.getBoolean("draw.rawgps.lines.local", spec, true);
437 } else {
438 maxLineLength = Main.pref.getInteger("draw.rawgps.max-line-length", spec, 200);
439 lines = Main.pref.getBoolean("draw.rawgps.lines", spec, true);
440 }
441 // paint large dots for points
442 boolean large = Main.pref.getBoolean("draw.rawgps.large", spec, false);
443 int largesize = Main.pref.getInteger("draw.rawgps.large.size", spec, 3);
444 boolean hdopcircle = Main.pref.getBoolean("draw.rawgps.hdopcircle", spec, false);
445 // color the lines
446 colorModes colored = getColorMode();
447 // paint direction arrow with alternate math. may be faster
448 boolean alternatedirection = Main.pref.getBoolean("draw.rawgps.alternatedirection", spec, false);
449 // don't draw arrows nearer to each other than this
450 int delta = Main.pref.getInteger("draw.rawgps.min-arrow-distance", spec, 40);
451 // allows to tweak line coloring for different speed levels.
452 int colorTracksTune = Main.pref.getInteger("draw.rawgps.colorTracksTune", spec, 45);
453 boolean colorModeDynamic = Main.pref.getBoolean("draw.rawgps.colors.dynamic", spec, false);
454 int hdopfactor = Main.pref.getInteger("hdop.factor", 25);
455
456 Stroke storedStroke = g.getStroke();
457 if(lineWidth != 0)
458 {
459 g.setStroke(new BasicStroke(lineWidth,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND));
460 largesize += lineWidth;
461 }
462
463 /****************************************************************
464 ********** STEP 2a - CHECK CACHE VALIDITY **********************
465 ****************************************************************/
466 if ((computeCacheMaxLineLengthUsed != maxLineLength) || (!neutralColor.equals(computeCacheColorUsed))
467 || (computeCacheColored != colored) || (computeCacheColorTracksTune != colorTracksTune)
468 || (computeCacheColorDynamic != colorModeDynamic)) {
469 computeCacheMaxLineLengthUsed = maxLineLength;
470 computeCacheInSync = false;
471 computeCacheColorUsed = neutralColor;
472 computeCacheColored = colored;
473 computeCacheColorTracksTune = colorTracksTune;
474 computeCacheColorDynamic = colorModeDynamic;
475 }
476
477 /****************************************************************
478 ********** STEP 2b - RE-COMPUTE CACHE DATA *********************
479 ****************************************************************/
480 if (!computeCacheInSync) { // don't compute if the cache is good
481 double minval = +1e10;
482 double maxval = -1e10;
483 WayPoint oldWp = null;
484 if (colorModeDynamic) {
485 if (colored == colorModes.velocity) {
486 for (GpxTrack trk : data.tracks) {
487 for (GpxTrackSegment segment : trk.getSegments()) {
488 if(!forceLines) {
489 oldWp = null;
490 }
491 for (WayPoint trkPnt : segment.getWayPoints()) {
492 LatLon c = trkPnt.getCoor();
493 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
494 continue;
495 }
496 if (oldWp != null && trkPnt.time > oldWp.time) {
497 double vel = c.greatCircleDistance(oldWp.getCoor())
498 / (trkPnt.time - oldWp.time);
499 if(vel > maxval) {
500 maxval = vel;
501 }
502 if(vel < minval) {
503 minval = vel;
504 }
505 }
506 oldWp = trkPnt;
507 }
508 }
509 }
510 } else if (colored == colorModes.dilution) {
511 for (GpxTrack trk : data.tracks) {
512 for (GpxTrackSegment segment : trk.getSegments()) {
513 for (WayPoint trkPnt : segment.getWayPoints()) {
514 Object val = trkPnt.attr.get("hdop");
515 if (val != null) {
516 double hdop = ((Float) val).doubleValue();
517 if(hdop > maxval) {
518 maxval = hdop;
519 }
520 if(hdop < minval) {
521 minval = hdop;
522 }
523 }
524 }
525 }
526 }
527 }
528 oldWp = null;
529 }
530 if (colored == colorModes.time) {
531 for (GpxTrack trk : data.tracks) {
532 for (GpxTrackSegment segment : trk.getSegments()) {
533 for (WayPoint trkPnt : segment.getWayPoints()) {
534 double t=trkPnt.time;
535 if (t==0) {
536 continue; // skip non-dated trackpoints
537 }
538 if(t > maxval) {
539 maxval = t;
540 }
541 if(t < minval) {
542 minval = t;
543 }
544 }
545 }
546 }
547 }
548
549 for (GpxTrack trk : data.tracks) {
550 for (GpxTrackSegment segment : trk.getSegments()) {
551 if (!forceLines) { // don't draw lines between segments, unless forced to
552 oldWp = null;
553 }
554 for (WayPoint trkPnt : segment.getWayPoints()) {
555 LatLon c = trkPnt.getCoor();
556 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
557 continue;
558 }
559 trkPnt.customColoring = neutralColor;
560 if(colored == colorModes.dilution && trkPnt.attr.get("hdop") != null) {
561 float hdop = ((Float) trkPnt.attr.get("hdop")).floatValue();
562 int hdoplvl =(int) Math.round(colorModeDynamic ? ((hdop-minval)*255/(maxval-minval))
563 : (hdop <= 0 ? 0 : hdop * hdopfactor));
564 // High hdop is bad, but high values in colors are green.
565 // Therefore inverse the logic
566 int hdopcolor = 255 - (hdoplvl > 255 ? 255 : hdoplvl);
567 trkPnt.customColoring = colors[hdopcolor];
568 }
569 if (oldWp != null) {
570 double dist = c.greatCircleDistance(oldWp.getCoor());
571 boolean noDraw=false;
572 switch (colored) {
573 case velocity:
574 double dtime = trkPnt.time - oldWp.time;
575 if(dtime > 0) {
576 float vel = (float) (dist / dtime);
577 int velColor =(int) Math.round(colorModeDynamic ? ((vel-minval)*255/(maxval-minval))
578 : (vel <= 0 ? 0 : vel / colorTracksTune * 255));
579 trkPnt.customColoring = colors[Math.max(0, Math.min(velColor, 255))];
580 } else {
581 trkPnt.customColoring = colors[255];
582 }
583 break;
584 case direction:
585 double dirColor = oldWp.getCoor().heading(trkPnt.getCoor()) / (2.0 * Math.PI) * 256;
586 // Bad case first
587 if (dirColor != dirColor || dirColor < 0.0 || dirColor >= 256.0) {
588 trkPnt.customColoring = colors_cyclic[0];
589 } else {
590 trkPnt.customColoring = colors_cyclic[(int) (dirColor)];
591 }
592 break;
593 case time:
594 if (trkPnt.time>0){
595 int tColor = (int) Math.round((trkPnt.time-minval)*255/(maxval-minval));
596 trkPnt.customColoring = colors[tColor];
597 } else {
598 trkPnt.customColoring = neutralColor;
599 }
600 break;
601 }
602
603 if (!noDraw && (maxLineLength == -1 || dist <= maxLineLength)) {
604 trkPnt.drawLine = true;
605 trkPnt.dir = (int) oldWp.getCoor().heading(trkPnt.getCoor());
606 } else {
607 trkPnt.drawLine = false;
608 }
609 } else { // make sure we reset outdated data
610 trkPnt.drawLine = false;
611 }
612 oldWp = trkPnt;
613 }
614 }
615 }
616 computeCacheInSync = true;
617 }
618
619 LinkedList<WayPoint> visibleSegments = new LinkedList<WayPoint>();
620 WayPoint last = null;
621 int i = 0;
622 ensureTrackVisibilityLength();
623 for (GpxTrack trk: data.tracks) {
624 // hide tracks that were de-selected in ChooseTrackVisibilityAction
625 if(!trackVisibility[i++]) {
626 continue;
627 }
628
629 for (GpxTrackSegment trkSeg: trk.getSegments()) {
630 for(WayPoint pt : trkSeg.getWayPoints())
631 {
632 Bounds b = new Bounds(pt.getCoor());
633 // last should never be null when this is true!
634 if(pt.drawLine) {
635 b.extend(last.getCoor());
636 }
637 if(b.intersects(box))
638 {
639 if(last != null && (visibleSegments.isEmpty()
640 || visibleSegments.getLast() != last)) {
641 if(last.drawLine) {
642 WayPoint l = new WayPoint(last);
643 l.drawLine = false;
644 visibleSegments.add(l);
645 } else {
646 visibleSegments.add(last);
647 }
648 }
649 visibleSegments.add(pt);
650 }
651 last = pt;
652 }
653 }
654 }
655 if(visibleSegments.isEmpty())
656 return;
657
658 /****************************************************************
659 ********** STEP 3a - DRAW LINES ********************************
660 ****************************************************************/
661 if (lines) {
662 Point old = null;
663 for (WayPoint trkPnt : visibleSegments) {
664 LatLon c = trkPnt.getCoor();
665 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
666 continue;
667 }
668 Point screen = mv.getPoint(trkPnt.getEastNorth());
669 if (trkPnt.drawLine) {
670 // skip points that are on the same screenposition
671 if (old != null && ((old.x != screen.x) || (old.y != screen.y))) {
672 g.setColor(trkPnt.customColoring);
673 g.drawLine(old.x, old.y, screen.x, screen.y);
674 }
675 }
676 old = screen;
677 } // end for trkpnt
678 } // end if lines
679
680 /****************************************************************
681 ********** STEP 3b - DRAW NICE ARROWS **************************
682 ****************************************************************/
683 if (lines && direction && !alternatedirection) {
684 Point old = null;
685 Point oldA = null; // last arrow painted
686 for (WayPoint trkPnt : visibleSegments) {
687 LatLon c = trkPnt.getCoor();
688 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
689 continue;
690 }
691 if (trkPnt.drawLine) {
692 Point screen = mv.getPoint(trkPnt.getEastNorth());
693 // skip points that are on the same screenposition
694 if (old != null
695 && (oldA == null || screen.x < oldA.x - delta || screen.x > oldA.x + delta
696 || screen.y < oldA.y - delta || screen.y > oldA.y + delta)) {
697 g.setColor(trkPnt.customColoring);
698 double t = Math.atan2(screen.y - old.y, screen.x - old.x) + Math.PI;
699 g.drawLine(screen.x, screen.y, (int) (screen.x + 10 * Math.cos(t - PHI)),
700 (int) (screen.y + 10 * Math.sin(t - PHI)));
701 g.drawLine(screen.x, screen.y, (int) (screen.x + 10 * Math.cos(t + PHI)),
702 (int) (screen.y + 10 * Math.sin(t + PHI)));
703 oldA = screen;
704 }
705 old = screen;
706 }
707 } // end for trkpnt
708 } // end if lines
709
710 /****************************************************************
711 ********** STEP 3c - DRAW FAST ARROWS **************************
712 ****************************************************************/
713 if (lines && direction && alternatedirection) {
714 Point old = null;
715 Point oldA = null; // last arrow painted
716 for (WayPoint trkPnt : visibleSegments) {
717 LatLon c = trkPnt.getCoor();
718 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
719 continue;
720 }
721 if (trkPnt.drawLine) {
722 Point screen = mv.getPoint(trkPnt.getEastNorth());
723 // skip points that are on the same screenposition
724 if (old != null
725 && (oldA == null || screen.x < oldA.x - delta || screen.x > oldA.x + delta
726 || screen.y < oldA.y - delta || screen.y > oldA.y + delta)) {
727 g.setColor(trkPnt.customColoring);
728 g.drawLine(screen.x, screen.y, screen.x + dir[trkPnt.dir][0], screen.y
729 + dir[trkPnt.dir][1]);
730 g.drawLine(screen.x, screen.y, screen.x + dir[trkPnt.dir][2], screen.y
731 + dir[trkPnt.dir][3]);
732 oldA = screen;
733 }
734 old = screen;
735 }
736 } // end for trkpnt
737 } // end if lines
738
739 /****************************************************************
740 ********** STEP 3d - DRAW LARGE POINTS AND HDOP CIRCLE *********
741 ****************************************************************/
742 if (large || hdopcircle) {
743 g.setColor(neutralColor);
744 for (WayPoint trkPnt : visibleSegments) {
745 LatLon c = trkPnt.getCoor();
746 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
747 continue;
748 }
749 Point screen = mv.getPoint(trkPnt.getEastNorth());
750 g.setColor(trkPnt.customColoring);
751 if (hdopcircle && trkPnt.attr.get("hdop") != null) {
752 // hdop value
753 float hdop = ((Float)trkPnt.attr.get("hdop")).floatValue();
754 if (hdop < 0) {
755 hdop = 0;
756 }
757 // hdop pixels
758 int hdopp = mv.getPoint(new LatLon(trkPnt.getCoor().lat(), trkPnt.getCoor().lon() + 2*6*hdop*360/40000000)).x - screen.x;
759 g.drawArc(screen.x-hdopp/2, screen.y-hdopp/2, hdopp, hdopp, 0, 360);
760 }
761 if (large) {
762 g.fillRect(screen.x-1, screen.y-1, largesize, largesize);
763 }
764 } // end for trkpnt
765 } // end if large || hdopcircle
766
767 /****************************************************************
768 ********** STEP 3e - DRAW SMALL POINTS FOR LINES ***************
769 ****************************************************************/
770 if (!large && lines) {
771 g.setColor(neutralColor);
772 for (WayPoint trkPnt : visibleSegments) {
773 LatLon c = trkPnt.getCoor();
774 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
775 continue;
776 }
777 if (!trkPnt.drawLine) {
778 Point screen = mv.getPoint(trkPnt.getEastNorth());
779 g.drawRect(screen.x, screen.y, 0, 0);
780 }
781 } // end for trkpnt
782 } // end if large
783
784 /****************************************************************
785 ********** STEP 3f - DRAW SMALL POINTS INSTEAD OF LINES ********
786 ****************************************************************/
787 if (!large && !lines) {
788 g.setColor(neutralColor);
789 for (WayPoint trkPnt : visibleSegments) {
790 LatLon c = trkPnt.getCoor();
791 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
792 continue;
793 }
794 Point screen = mv.getPoint(trkPnt.getEastNorth());
795 g.setColor(trkPnt.customColoring);
796 g.drawRect(screen.x, screen.y, 0, 0);
797 } // end for trkpnt
798 } // end if large
799
800 if(lineWidth != 0)
801 {
802 g.setStroke(storedStroke);
803 }
804 // Long duration = System.currentTimeMillis() - startTime;
805 // System.out.println(duration);
806 } // end paint
807
808 @Override
809 public void visitBoundingBox(BoundingXYVisitor v) {
810 v.visit(data.recalculateBounds());
811 }
812
813 @Override
814 public File getAssociatedFile() {
815 return data.storageFile;
816 }
817
818 @Override
819 public void setAssociatedFile(File file) {
820 data.storageFile = file;
821 }
822
823 /** ensures the trackVisibility array has the correct length without losing data.
824 * additional entries are initialized to true;
825 */
826 final private void ensureTrackVisibilityLength() {
827 final int l = data.tracks.size();
828 if(l == trackVisibility.length)
829 return;
830 final boolean[] back = trackVisibility.clone();
831 final int m = Math.min(l, back.length);
832 trackVisibility = new boolean[l];
833 for(int i=0; i < m; i++) {
834 trackVisibility[i] = back[i];
835 }
836 for(int i=m; i < l; i++) {
837 trackVisibility[i] = true;
838 }
839 }
840
841 @Override
842 public void projectionChanged(Projection oldValue, Projection newValue) {
843 if (newValue == null) return;
844 if (data.waypoints != null) {
845 for (WayPoint wp : data.waypoints){
846 wp.invalidateEastNorthCache();
847 }
848 }
849 if (data.tracks != null){
850 for (GpxTrack track: data.tracks) {
851 for (GpxTrackSegment segment: track.getSegments()) {
852 for (WayPoint wp: segment.getWayPoints()) {
853 wp.invalidateEastNorthCache();
854 }
855 }
856 }
857 }
858 if (data.routes != null) {
859 for (GpxRoute route: data.routes) {
860 if (route.routePoints == null) {
861 continue;
862 }
863 for (WayPoint wp: route.routePoints) {
864 wp.invalidateEastNorthCache();
865 }
866 }
867 }
868 }
869
870 @Override
871 public boolean isSavable() {
872 return true; // With GpxExporter
873 }
874
875 @Override
876 public boolean checkSaveConditions() {
877 return data != null;
878 }
879
880 @Override
881 public File createAndOpenSaveFileChooser() {
882 return SaveActionBase.createAndOpenSaveFileChooser(tr("Save GPX file"), GpxImporter.FILE_FILTER);
883 }
884
885}
Note: See TracBrowser for help on using the repository browser.