source: josm/trunk/src/org/openstreetmap/josm/data/gpx/GpxTrack.java@ 16643

Last change on this file since 16643 was 16436, checked in by simon04, 4 years ago

see #19251 - Java 8: use Stream

  • Property svn:eol-style set to native
File size: 8.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.gpx;
3
4import java.awt.Color;
5import java.util.Collection;
6import java.util.Collections;
7import java.util.HashMap;
8import java.util.List;
9import java.util.Map;
10import java.util.Map.Entry;
11import java.util.Optional;
12
13import org.openstreetmap.josm.data.Bounds;
14import org.openstreetmap.josm.tools.ListenerList;
15import org.openstreetmap.josm.tools.Logging;
16import org.openstreetmap.josm.tools.StreamUtils;
17
18/**
19 * GPX track.
20 * Note that the color attributes are not immutable and may be modified by the user.
21 * @since 15496
22 */
23public class GpxTrack extends WithAttributes implements IGpxTrack {
24
25 private final List<IGpxTrackSegment> segments;
26 private final double length;
27 private final Bounds bounds;
28 private Color colorCache;
29 private final ListenerList<IGpxTrack.GpxTrackChangeListener> listeners = ListenerList.create();
30 private static final HashMap<Color, String> closestGarminColorCache = new HashMap<>();
31 private ColorFormat colorFormat;
32
33 /**
34 * Constructs a new {@code GpxTrack}.
35 * @param trackSegs track segments
36 * @param attributes track attributes
37 */
38 public GpxTrack(Collection<Collection<WayPoint>> trackSegs, Map<String, Object> attributes) {
39 this.segments = trackSegs.stream()
40 .filter(trackSeg -> trackSeg != null && !trackSeg.isEmpty())
41 .map(GpxTrackSegment::new)
42 .collect(StreamUtils.toUnmodifiableList());
43 this.length = calculateLength();
44 this.bounds = calculateBounds();
45 this.attr = new HashMap<>(attributes);
46 }
47
48 /**
49 * Constructs a new {@code GpxTrack} from {@code GpxTrackSegment} objects.
50 * @param trackSegs The segments to build the track from. Input is not deep-copied,
51 * which means the caller may reuse the same segments to build
52 * multiple GpxTrack instances from. This should not be
53 * a problem, since this object cannot modify {@code this.segments}.
54 * @param attributes Attributes for the GpxTrack, the input map is copied.
55 */
56 public GpxTrack(List<IGpxTrackSegment> trackSegs, Map<String, Object> attributes) {
57 this.attr = new HashMap<>(attributes);
58 this.segments = Collections.unmodifiableList(trackSegs);
59 this.length = calculateLength();
60 this.bounds = calculateBounds();
61 }
62
63 private double calculateLength() {
64 return segments.stream().mapToDouble(IGpxTrackSegment::length).sum();
65 }
66
67 private Bounds calculateBounds() {
68 Bounds result = null;
69 for (IGpxTrackSegment segment: segments) {
70 Bounds segBounds = segment.getBounds();
71 if (segBounds != null) {
72 if (result == null) {
73 result = new Bounds(segBounds);
74 } else {
75 result.extend(segBounds);
76 }
77 }
78 }
79 return result;
80 }
81
82 @Override
83 public void setColor(Color color) {
84 setColorExtension(color);
85 colorCache = color;
86 }
87
88 private void setColorExtension(Color color) {
89 getExtensions().findAndRemove("gpxx", "DisplayColor");
90 if (color == null) {
91 getExtensions().findAndRemove("gpxd", "color");
92 } else {
93 getExtensions().addOrUpdate("gpxd", "color", String.format("#%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue()));
94 }
95 fireInvalidate();
96 }
97
98 @Override
99 public Color getColor() {
100 if (colorCache == null) {
101 colorCache = getColorFromExtension();
102 }
103 return colorCache;
104 }
105
106 private Color getColorFromExtension() {
107 GpxExtension gpxd = getExtensions().find("gpxd", "color");
108 if (gpxd != null) {
109 colorFormat = ColorFormat.GPXD;
110 String cs = gpxd.getValue();
111 try {
112 return Color.decode(cs);
113 } catch (NumberFormatException ex) {
114 Logging.warn("Could not read gpxd color: " + cs);
115 }
116 } else {
117 GpxExtension gpxx = getExtensions().find("gpxx", "DisplayColor");
118 if (gpxx != null) {
119 colorFormat = ColorFormat.GPXX;
120 String cs = gpxx.getValue();
121 if (cs != null) {
122 Color cc = GARMIN_COLORS.get(cs);
123 if (cc != null) {
124 return cc;
125 }
126 }
127 Logging.warn("Could not read garmin color: " + cs);
128 }
129 }
130 return null;
131 }
132
133 /**
134 * Converts the color to the given format, if present.
135 * @param cFormat can be a {@link GpxConstants.ColorFormat}
136 */
137 public void convertColor(ColorFormat cFormat) {
138 Color c = getColor();
139 if (c == null) return;
140
141 if (cFormat != this.colorFormat) {
142 if (cFormat == null) {
143 // just hide the extensions, don't actually remove them
144 Optional.ofNullable(getExtensions().find("gpxx", "DisplayColor")).ifPresent(GpxExtension::hide);
145 Optional.ofNullable(getExtensions().find("gpxd", "color")).ifPresent(GpxExtension::hide);
146 } else if (cFormat == ColorFormat.GPXX) {
147 getExtensions().findAndRemove("gpxd", "color");
148 String colorString = null;
149 if (closestGarminColorCache.containsKey(c)) {
150 colorString = closestGarminColorCache.get(c);
151 } else {
152 //find closest garmin color
153 double closestDiff = -1;
154 for (Entry<String, Color> e : GARMIN_COLORS.entrySet()) {
155 double diff = colorDist(e.getValue(), c);
156 if (closestDiff < 0 || diff < closestDiff) {
157 colorString = e.getKey();
158 closestDiff = diff;
159 if (closestDiff == 0) break;
160 }
161 }
162 }
163 closestGarminColorCache.put(c, colorString);
164 getExtensions().addIfNotPresent("gpxx", "TrackExtension").getExtensions().addOrUpdate("gpxx", "DisplayColor", colorString);
165 } else if (cFormat == ColorFormat.GPXD) {
166 setColor(c);
167 }
168 colorFormat = cFormat;
169 }
170 }
171
172 private double colorDist(Color c1, Color c2) {
173 // Simple Euclidean distance between two colors
174 return Math.sqrt(Math.pow(c1.getRed() - c2.getRed(), 2)
175 + Math.pow(c1.getGreen() - c2.getGreen(), 2)
176 + Math.pow(c1.getBlue() - c2.getBlue(), 2));
177 }
178
179 @Override
180 public void put(String key, Object value) {
181 super.put(key, value);
182 fireInvalidate();
183 }
184
185 private void fireInvalidate() {
186 listeners.fireEvent(l -> l.gpxDataChanged(new IGpxTrack.GpxTrackChangeEvent(this)));
187 }
188
189 @Override
190 public Bounds getBounds() {
191 return bounds == null ? null : new Bounds(bounds);
192 }
193
194 @Override
195 public double length() {
196 return length;
197 }
198
199 @Override
200 public Collection<IGpxTrackSegment> getSegments() {
201 return segments;
202 }
203
204 @Override
205 public int hashCode() {
206 return 31 * super.hashCode() + ((segments == null) ? 0 : segments.hashCode());
207 }
208
209 @Override
210 public boolean equals(Object obj) {
211 if (this == obj)
212 return true;
213 if (obj == null)
214 return false;
215 if (!super.equals(obj))
216 return false;
217 if (getClass() != obj.getClass())
218 return false;
219 GpxTrack other = (GpxTrack) obj;
220 if (segments == null) {
221 if (other.segments != null)
222 return false;
223 } else if (!segments.equals(other.segments))
224 return false;
225 return true;
226 }
227
228 @Override
229 public void addListener(IGpxTrack.GpxTrackChangeListener l) {
230 listeners.addListener(l);
231 }
232
233 @Override
234 public void removeListener(IGpxTrack.GpxTrackChangeListener l) {
235 listeners.removeListener(l);
236 }
237
238 /**
239 * Resets the color cache
240 */
241 public void invalidate() {
242 colorCache = null;
243 }
244
245 /**
246 * A listener that listens to GPX track changes.
247 * @deprecated use {@link IGpxTrack.GpxTrackChangeListener} instead
248 */
249 @Deprecated
250 @FunctionalInterface
251 interface GpxTrackChangeListener {
252 void gpxDataChanged(GpxTrackChangeEvent e);
253 }
254
255 /**
256 * A track change event for the current track.
257 * @deprecated use {@link IGpxTrack.GpxTrackChangeEvent} instead
258 */
259 @Deprecated
260 static class GpxTrackChangeEvent extends IGpxTrack.GpxTrackChangeEvent {
261 GpxTrackChangeEvent(IGpxTrack source) {
262 super(source);
263 }
264 }
265
266}
Note: See TracBrowser for help on using the repository browser.