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

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

sonar - squid:S2221 - "Exception" should not be caught when not required by called methods

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