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

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

see #13309 - Caching and notifying preferences (patch by michael2402) - gsoc-core

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