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

Last change on this file since 7182 was 7182, checked in by akks, 10 years ago

see #10072: color large GPS points like the track segments
use draw.rawgps.large.alpha parameter for transparency
updated Netbeans project (JAXB)

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