source: josm/trunk/src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java@ 9857

Last change on this file since 9857 was 9796, checked in by bastiK, 9 years ago

applied #12524 - heading calculation reversed longitudes (patch by kolesar)

  • Property svn:eol-style set to native
File size: 23.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.layer.gpx;
3
4import static org.openstreetmap.josm.tools.I18n.marktr;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.BasicStroke;
8import java.awt.Color;
9import java.awt.Graphics2D;
10import java.awt.Point;
11import java.awt.RenderingHints;
12import java.awt.Stroke;
13import java.util.ArrayList;
14import java.util.Arrays;
15import java.util.Collection;
16import java.util.Collections;
17import java.util.Date;
18import java.util.List;
19
20import org.openstreetmap.josm.Main;
21import org.openstreetmap.josm.data.coor.LatLon;
22import org.openstreetmap.josm.data.gpx.GpxConstants;
23import org.openstreetmap.josm.data.gpx.GpxData;
24import org.openstreetmap.josm.data.gpx.WayPoint;
25import org.openstreetmap.josm.gui.MapView;
26import org.openstreetmap.josm.tools.ColorScale;
27
28/**
29 * Class that helps to draw large set of GPS tracks with different colors and options
30 * @since 7319
31 */
32public class GpxDrawHelper {
33 private final GpxData data;
34
35 // draw lines between points belonging to different segments
36 private boolean forceLines;
37 // draw direction arrows on the lines
38 private boolean direction;
39 /** don't draw lines if longer than x meters **/
40 private int lineWidth;
41 private int maxLineLength;
42 private boolean lines;
43 /** paint large dots for points **/
44 private boolean large;
45 private int largesize;
46 private boolean hdopCircle;
47 /** paint direction arrow with alternate math. may be faster **/
48 private boolean alternateDirection;
49 /** don't draw arrows nearer to each other than this **/
50 private int delta;
51 private double minTrackDurationForTimeColoring;
52
53 private int hdopfactor;
54
55 private static final double PHI = Math.toRadians(15);
56
57 //// Variables used only to check cache validity
58 private boolean computeCacheInSync;
59 private int computeCacheMaxLineLengthUsed;
60 private Color computeCacheColorUsed;
61 private boolean computeCacheColorDynamic;
62 private ColorMode computeCacheColored;
63 private int computeCacheColorTracksTune;
64
65 //// Color-related fields
66 /** Mode of the line coloring **/
67 private ColorMode colored;
68 /** max speed for coloring - allows to tweak line coloring for different speed levels. **/
69 private int colorTracksTune;
70 private boolean colorModeDynamic;
71 private Color neutralColor;
72 private int largePointAlpha;
73
74 // default access is used to allow changing from plugins
75 private ColorScale velocityScale;
76 /** Colors (without custom alpha channel, if given) for HDOP painting. **/
77 private ColorScale hdopScale;
78 private ColorScale dateScale;
79 private ColorScale directionScale;
80
81 /** Opacity for hdop points **/
82 private int hdopAlpha;
83
84 private static final Color DEFAULT_COLOR = Color.magenta;
85
86 // lookup array to draw arrows without doing any math
87 private static final int ll0 = 9;
88 private static final int sl4 = 5;
89 private static final int sl9 = 3;
90 private static final int[][] dir = {
91 {+sl4, +ll0, +ll0, +sl4}, {-sl9, +ll0, +sl9, +ll0},
92 {-ll0, +sl4, -sl4, +ll0}, {-ll0, -sl9, -ll0, +sl9},
93 {-sl4, -ll0, -ll0, -sl4}, {+sl9, -ll0, -sl9, -ll0},
94 {+ll0, -sl4, +sl4, -ll0}, {+ll0, +sl9, +ll0, -sl9}
95 };
96
97 private void setupColors() {
98 hdopAlpha = Main.pref.getInteger("hdop.color.alpha", -1);
99 velocityScale = ColorScale.createHSBScale(256).addTitle(tr("Velocity, km/h"));
100 /** Colors (without custom alpha channel, if given) for HDOP painting. **/
101 hdopScale = ColorScale.createHSBScale(256).makeReversed().addTitle(tr("HDOP, m"));
102 dateScale = ColorScale.createHSBScale(256).addTitle(tr("Time"));
103 directionScale = ColorScale.createCyclicScale(256).setIntervalCount(4).addTitle(tr("Direction"));
104 }
105
106 /**
107 * Different color modes
108 */
109 public enum ColorMode {
110 NONE, VELOCITY, HDOP, DIRECTION, TIME;
111
112 static ColorMode fromIndex(final int index) {
113 return values()[index];
114 }
115
116 int toIndex() {
117 return Arrays.asList(values()).indexOf(this);
118 }
119 }
120
121 /**
122 * Constructs a new {@code GpxDrawHelper}.
123 * @param gpxData GPX data
124 */
125 public GpxDrawHelper(GpxData gpxData) {
126 data = gpxData;
127 setupColors();
128 }
129
130 private static String specName(String layerName) {
131 return "layer " + layerName;
132 }
133
134 /**
135 * Get the default color for gps tracks for specified layer
136 * @param layerName name of the GpxLayer
137 * @param ignoreCustom do not use preferences
138 * @return the color or null if the color is not constant
139 */
140 public Color getColor(String layerName, boolean ignoreCustom) {
141 Color c = Main.pref.getColor(marktr("gps point"), specName(layerName), DEFAULT_COLOR);
142 return ignoreCustom || getColorMode(layerName) == ColorMode.NONE ? c : null;
143 }
144
145 /**
146 * Read coloring mode for specified layer from preferences
147 * @param layerName name of the GpxLayer
148 * @return coloting mode
149 */
150 public ColorMode getColorMode(String layerName) {
151 try {
152 int i = Main.pref.getInteger("draw.rawgps.colors", specName(layerName), 0);
153 return ColorMode.fromIndex(i);
154 } catch (Exception e) {
155 Main.warn(e);
156 }
157 return ColorMode.NONE;
158 }
159
160 /** Reads generic color from preferences (usually gray)
161 * @return the color
162 **/
163 public static Color getGenericColor() {
164 return Main.pref.getColor(marktr("gps point"), DEFAULT_COLOR);
165 }
166
167 /**
168 * Read all drawing-related settings from preferences
169 * @param layerName layer name used to access its specific preferences
170 **/
171 public void readPreferences(String layerName) {
172 String spec = specName(layerName);
173 forceLines = Main.pref.getBoolean("draw.rawgps.lines.force", spec, false);
174 direction = Main.pref.getBoolean("draw.rawgps.direction", spec, false);
175 lineWidth = Main.pref.getInteger("draw.rawgps.linewidth", spec, 0);
176
177 if (!data.fromServer) {
178 maxLineLength = Main.pref.getInteger("draw.rawgps.max-line-length.local", spec, -1);
179 lines = Main.pref.getBoolean("draw.rawgps.lines.local", spec, true);
180 } else {
181 maxLineLength = Main.pref.getInteger("draw.rawgps.max-line-length", spec, 200);
182 lines = Main.pref.getBoolean("draw.rawgps.lines", spec, true);
183 }
184 large = Main.pref.getBoolean("draw.rawgps.large", spec, false);
185 largesize = Main.pref.getInteger("draw.rawgps.large.size", spec, 3);
186 hdopCircle = Main.pref.getBoolean("draw.rawgps.hdopcircle", spec, false);
187 colored = getColorMode(layerName);
188 alternateDirection = Main.pref.getBoolean("draw.rawgps.alternatedirection", spec, false);
189 delta = Main.pref.getInteger("draw.rawgps.min-arrow-distance", spec, 40);
190 colorTracksTune = Main.pref.getInteger("draw.rawgps.colorTracksTune", spec, 45);
191 colorModeDynamic = Main.pref.getBoolean("draw.rawgps.colors.dynamic", spec, false);
192 hdopfactor = Main.pref.getInteger("hdop.factor", 25);
193 minTrackDurationForTimeColoring = Main.pref.getInteger("draw.rawgps.date-coloring-min-dt", 60);
194 largePointAlpha = Main.pref.getInteger("draw.rawgps.large.alpha", -1) & 0xFF;
195
196 neutralColor = getColor(layerName, true);
197 velocityScale.setNoDataColor(neutralColor);
198 dateScale.setNoDataColor(neutralColor);
199 hdopScale.setNoDataColor(neutralColor);
200 directionScale.setNoDataColor(neutralColor);
201
202 largesize += lineWidth;
203 }
204
205 public void drawAll(Graphics2D g, MapView mv, List<WayPoint> visibleSegments) {
206
207 checkCache();
208
209 // STEP 2b - RE-COMPUTE CACHE DATA *********************
210 if (!computeCacheInSync) { // don't compute if the cache is good
211 calculateColors();
212 }
213
214 Stroke storedStroke = g.getStroke();
215
216 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
217 Main.pref.getBoolean("mappaint.gpx.use-antialiasing", false) ?
218 RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
219
220 if (lineWidth != 0) {
221 g.setStroke(new BasicStroke(lineWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
222 }
223 fixColors(visibleSegments);
224 drawLines(g, mv, visibleSegments);
225 drawArrows(g, mv, visibleSegments);
226 drawPoints(g, mv, visibleSegments);
227 if (lineWidth != 0) {
228 g.setStroke(storedStroke);
229 }
230 }
231
232 public void calculateColors() {
233 double minval = +1e10;
234 double maxval = -1e10;
235 WayPoint oldWp = null;
236
237 if (colorModeDynamic) {
238 if (colored == ColorMode.VELOCITY) {
239 final List<Double> velocities = new ArrayList<>();
240 for (Collection<WayPoint> segment : data.getLinesIterable(null)) {
241 if (!forceLines) {
242 oldWp = null;
243 }
244 for (WayPoint trkPnt : segment) {
245 LatLon c = trkPnt.getCoor();
246 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
247 continue;
248 }
249 if (oldWp != null && trkPnt.time > oldWp.time) {
250 double vel = c.greatCircleDistance(oldWp.getCoor())
251 / (trkPnt.time - oldWp.time);
252 velocities.add(vel);
253 }
254 oldWp = trkPnt;
255 }
256 }
257 Collections.sort(velocities);
258 if (velocities.isEmpty()) {
259 velocityScale.setRange(0, 120/3.6);
260 } else {
261 minval = velocities.get(velocities.size() / 20); // 5% percentile to remove outliers
262 maxval = velocities.get(velocities.size() * 19 / 20); // 95% percentile to remove outliers
263 velocityScale.setRange(minval, maxval);
264 }
265 } else if (colored == ColorMode.HDOP) {
266 for (Collection<WayPoint> segment : data.getLinesIterable(null)) {
267 for (WayPoint trkPnt : segment) {
268 Object val = trkPnt.get(GpxConstants.PT_HDOP);
269 if (val != null) {
270 double hdop = ((Float) val).doubleValue();
271 if (hdop > maxval) {
272 maxval = hdop;
273 }
274 if (hdop < minval) {
275 minval = hdop;
276 }
277 }
278 }
279 }
280 if (minval >= maxval) {
281 hdopScale.setRange(0, 100);
282 } else {
283 hdopScale.setRange(minval, maxval);
284 }
285 }
286 oldWp = null;
287 } else { // color mode not dynamic
288 velocityScale.setRange(0, colorTracksTune);
289 hdopScale.setRange(0, 1.0/hdopfactor);
290 }
291 double now = System.currentTimeMillis()/1000.0;
292 if (colored == ColorMode.TIME) {
293 Date[] bounds = data.getMinMaxTimeForAllTracks();
294 if (bounds.length >= 2) {
295 minval = bounds[0].getTime()/1000.0;
296 maxval = bounds[1].getTime()/1000.0;
297 } else {
298 minval = 0;
299 maxval = now;
300 }
301 dateScale.setRange(minval, maxval);
302 }
303
304
305 // Now the colors for all the points will be assigned
306 for (Collection<WayPoint> segment : data.getLinesIterable(null)) {
307 if (!forceLines) { // don't draw lines between segments, unless forced to
308 oldWp = null;
309 }
310 for (WayPoint trkPnt : segment) {
311 LatLon c = trkPnt.getCoor();
312 trkPnt.customColoring = neutralColor;
313 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
314 continue;
315 }
316 // now we are sure some color will be assigned
317 Color color = null;
318
319 if (colored == ColorMode.HDOP) {
320 Float hdop = (Float) trkPnt.get(GpxConstants.PT_HDOP);
321 color = hdopScale.getColor(hdop);
322 }
323 if (oldWp != null) { // other coloring modes need segment for calcuation
324 double dist = c.greatCircleDistance(oldWp.getCoor());
325 boolean noDraw = false;
326 switch (colored) {
327 case VELOCITY:
328 double dtime = trkPnt.time - oldWp.time;
329 if (dtime > 0) {
330 color = velocityScale.getColor(dist / dtime);
331 } else {
332 color = velocityScale.getNoDataColor();
333 }
334 break;
335 case DIRECTION:
336 double dirColor = oldWp.getCoor().bearing(trkPnt.getCoor());
337 color = directionScale.getColor(dirColor);
338 break;
339 case TIME:
340 double t = trkPnt.time;
341 // skip bad timestamps and very short tracks
342 if (t > 0 && t <= now && maxval - minval > minTrackDurationForTimeColoring) {
343 color = dateScale.getColor(t);
344 } else {
345 color = dateScale.getNoDataColor();
346 }
347 break;
348 }
349 if (!noDraw && (maxLineLength == -1 || dist <= maxLineLength)) {
350 trkPnt.drawLine = true;
351 double bearing = oldWp.getCoor().bearing(trkPnt.getCoor());
352 trkPnt.dir = ((int) (bearing / Math.PI * 4 + 1.5)) % 8;
353 } else {
354 trkPnt.drawLine = false;
355 }
356 } else { // make sure we reset outdated data
357 trkPnt.drawLine = false;
358 color = neutralColor;
359 }
360 if (color != null) {
361 trkPnt.customColoring = color;
362 }
363 oldWp = trkPnt;
364 }
365 }
366
367 computeCacheInSync = true;
368 }
369
370 private void drawLines(Graphics2D g, MapView mv, List<WayPoint> visibleSegments) {
371 if (lines) {
372 Point old = null;
373 for (WayPoint trkPnt : visibleSegments) {
374 LatLon c = trkPnt.getCoor();
375 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
376 continue;
377 }
378 Point screen = mv.getPoint(trkPnt.getEastNorth());
379 // skip points that are on the same screenposition
380 if (trkPnt.drawLine && old != null && ((old.x != screen.x) || (old.y != screen.y))) {
381 g.setColor(trkPnt.customColoring);
382 g.drawLine(old.x, old.y, screen.x, screen.y);
383 }
384 old = screen;
385 }
386 }
387 }
388
389 private void drawArrows(Graphics2D g, MapView mv, List<WayPoint> visibleSegments) {
390 /****************************************************************
391 ********** STEP 3b - DRAW NICE ARROWS **************************
392 ****************************************************************/
393 if (lines && direction && !alternateDirection) {
394 Point old = null;
395 Point oldA = null; // last arrow painted
396 for (WayPoint trkPnt : visibleSegments) {
397 LatLon c = trkPnt.getCoor();
398 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
399 continue;
400 }
401 if (trkPnt.drawLine) {
402 Point screen = mv.getPoint(trkPnt.getEastNorth());
403 // skip points that are on the same screenposition
404 if (old != null
405 && (oldA == null || screen.x < oldA.x - delta || screen.x > oldA.x + delta
406 || screen.y < oldA.y - delta || screen.y > oldA.y + delta)) {
407 g.setColor(trkPnt.customColoring);
408 double t = Math.atan2(screen.y - old.y, screen.x - old.x) + Math.PI;
409 g.drawLine(screen.x, screen.y, (int) (screen.x + 10 * Math.cos(t - PHI)),
410 (int) (screen.y + 10 * Math.sin(t - PHI)));
411 g.drawLine(screen.x, screen.y, (int) (screen.x + 10 * Math.cos(t + PHI)),
412 (int) (screen.y + 10 * Math.sin(t + PHI)));
413 oldA = screen;
414 }
415 old = screen;
416 }
417 } // end for trkpnt
418 }
419
420 /****************************************************************
421 ********** STEP 3c - DRAW FAST ARROWS **************************
422 ****************************************************************/
423 if (lines && direction && alternateDirection) {
424 Point old = null;
425 Point oldA = null; // last arrow painted
426 for (WayPoint trkPnt : visibleSegments) {
427 LatLon c = trkPnt.getCoor();
428 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
429 continue;
430 }
431 if (trkPnt.drawLine) {
432 Point screen = mv.getPoint(trkPnt.getEastNorth());
433 // skip points that are on the same screenposition
434 if (old != null
435 && (oldA == null || screen.x < oldA.x - delta || screen.x > oldA.x + delta
436 || screen.y < oldA.y - delta || screen.y > oldA.y + delta)) {
437 g.setColor(trkPnt.customColoring);
438 g.drawLine(screen.x, screen.y, screen.x + dir[trkPnt.dir][0], screen.y
439 + dir[trkPnt.dir][1]);
440 g.drawLine(screen.x, screen.y, screen.x + dir[trkPnt.dir][2], screen.y
441 + dir[trkPnt.dir][3]);
442 oldA = screen;
443 }
444 old = screen;
445 }
446 } // end for trkpnt
447 }
448 }
449
450 private void drawPoints(Graphics2D g, MapView mv, List<WayPoint> visibleSegments) {
451 /****************************************************************
452 ********** STEP 3d - DRAW LARGE POINTS AND HDOP CIRCLE *********
453 ****************************************************************/
454 if (large || hdopCircle) {
455 final int halfSize = largesize/2;
456 for (WayPoint trkPnt : visibleSegments) {
457 LatLon c = trkPnt.getCoor();
458 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
459 continue;
460 }
461 Point screen = mv.getPoint(trkPnt.getEastNorth());
462
463
464 if (hdopCircle && trkPnt.get(GpxConstants.PT_HDOP) != null) {
465 // hdop value
466 float hdop = (Float) trkPnt.get(GpxConstants.PT_HDOP);
467 if (hdop < 0) {
468 hdop = 0;
469 }
470 Color customColoringTransparent = hdopAlpha < 0 ? trkPnt.customColoring :
471 new Color(trkPnt.customColoring.getRGB() & 0x00ffffff | hdopAlpha << 24, true);
472 g.setColor(customColoringTransparent);
473 // hdop cirles
474 int hdopp = mv.getPoint(new LatLon(
475 trkPnt.getCoor().lat(),
476 trkPnt.getCoor().lon() + 2*6*hdop*360/40000000d)).x - screen.x;
477 g.drawArc(screen.x-hdopp/2, screen.y-hdopp/2, hdopp, hdopp, 0, 360);
478 }
479 if (large) {
480 // color the large GPS points like the gps lines
481 if (trkPnt.customColoring != null) {
482 Color customColoringTransparent = largePointAlpha < 0 ? trkPnt.customColoring :
483 new Color(trkPnt.customColoring.getRGB() & 0x00ffffff | largePointAlpha << 24, true);
484
485 g.setColor(customColoringTransparent);
486 }
487 g.fillRect(screen.x-halfSize, screen.y-halfSize, largesize, largesize);
488 }
489 } // end for trkpnt
490 } // end if large || hdopcircle
491
492 /****************************************************************
493 ********** STEP 3e - DRAW SMALL POINTS FOR LINES ***************
494 ****************************************************************/
495 if (!large && lines) {
496 g.setColor(neutralColor);
497 for (WayPoint trkPnt : visibleSegments) {
498 LatLon c = trkPnt.getCoor();
499 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
500 continue;
501 }
502 if (!trkPnt.drawLine) {
503 Point screen = mv.getPoint(trkPnt.getEastNorth());
504 g.drawRect(screen.x, screen.y, 0, 0);
505 }
506 } // end for trkpnt
507 } // end if large
508
509 /****************************************************************
510 ********** STEP 3f - DRAW SMALL POINTS INSTEAD OF LINES ********
511 ****************************************************************/
512 if (!large && !lines) {
513 g.setColor(neutralColor);
514 for (WayPoint trkPnt : visibleSegments) {
515 LatLon c = trkPnt.getCoor();
516 if (Double.isNaN(c.lat()) || Double.isNaN(c.lon())) {
517 continue;
518 }
519 Point screen = mv.getPoint(trkPnt.getEastNorth());
520 g.setColor(trkPnt.customColoring);
521 g.drawRect(screen.x, screen.y, 0, 0);
522 } // end for trkpnt
523 } // end if large
524 }
525
526 private void fixColors(List<WayPoint> visibleSegments) {
527 for (WayPoint trkPnt : visibleSegments) {
528 if (trkPnt.customColoring == null) {
529 trkPnt.customColoring = neutralColor;
530 }
531 }
532 }
533
534 /**
535 * Check cache validity set necessary flags
536 */
537 private void checkCache() {
538 if ((computeCacheMaxLineLengthUsed != maxLineLength) || (!neutralColor.equals(computeCacheColorUsed))
539 || (computeCacheColored != colored) || (computeCacheColorTracksTune != colorTracksTune)
540 || (computeCacheColorDynamic != colorModeDynamic)) {
541 computeCacheMaxLineLengthUsed = maxLineLength;
542 computeCacheInSync = false;
543 computeCacheColorUsed = neutralColor;
544 computeCacheColored = colored;
545 computeCacheColorTracksTune = colorTracksTune;
546 computeCacheColorDynamic = colorModeDynamic;
547 }
548 }
549
550 public void dataChanged() {
551 computeCacheInSync = false;
552 }
553
554 public void drawColorBar(Graphics2D g, MapView mv) {
555 int w = mv.getWidth();
556 if (colored == ColorMode.HDOP) {
557 hdopScale.drawColorBar(g, w-30, 50, 20, 100, 1.0);
558 } else if (colored == ColorMode.VELOCITY) {
559 velocityScale.drawColorBar(g, w-30, 50, 20, 100, 3.6);
560 } else if (colored == ColorMode.DIRECTION) {
561 directionScale.drawColorBar(g, w-30, 50, 20, 100, 180.0/Math.PI);
562 }
563 }
564}
Note: See TracBrowser for help on using the repository browser.