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

Last change on this file since 10175 was 10175, checked in by Don-vip, 8 years ago

fix #12813 - adapt GPX color scale bar to system of measurement for HDOP and velocity

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