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

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

see #8902 - c-like array definitions changed to java-like (patch by shinigami)

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