source: josm/trunk/src/org/openstreetmap/josm/data/gpx/GpxData.java@ 7392

Last change on this file since 7392 was 7319, checked in by akks, 10 years ago

Add colorbar for active GPX layer, big GpxLayer class refactoring, see #5662
new classes GpxDrawHelper and ColorScale responsible for GPX drawing and coloring
move data-related methods to GpxData
remove WayPoint.customColoringTransparent from memory, calculate it at paint-time

  • Property svn:eol-style set to native
File size: 14.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.gpx;
3
4import java.io.File;
5import java.util.Collection;
6import java.util.Date;
7import java.util.Iterator;
8import java.util.LinkedList;
9import java.util.Map;
10
11import org.openstreetmap.josm.Main;
12import org.openstreetmap.josm.data.Bounds;
13import org.openstreetmap.josm.data.coor.EastNorth;
14
15/**
16 * Objects of this class represent a gpx file with tracks, waypoints and routes.
17 * It uses GPX v1.1, see <a href="http://www.topografix.com/GPX/1/1/">the spec</a>
18 * for details.
19 *
20 * @author Raphael Mack &lt;ramack@raphael-mack.de&gt;
21 */
22public class GpxData extends WithAttributes {
23
24 public File storageFile;
25 public boolean fromServer;
26
27 public String creator;
28
29 public final Collection<GpxTrack> tracks = new LinkedList<>();
30 public final Collection<GpxRoute> routes = new LinkedList<>();
31 public final Collection<WayPoint> waypoints = new LinkedList<>();
32
33 public void mergeFrom(GpxData other) {
34 if (storageFile == null && other.storageFile != null) {
35 storageFile = other.storageFile;
36 }
37 fromServer = fromServer && other.fromServer;
38
39 for (Map.Entry<String, Object> ent : other.attr.entrySet()) {
40 // TODO: Detect conflicts.
41 String k = ent.getKey();
42 if (k.equals(META_LINKS) && attr.containsKey(META_LINKS)) {
43 @SuppressWarnings("unchecked")
44 Collection<GpxLink> my = (Collection<GpxLink>) attr.get(META_LINKS);
45 @SuppressWarnings("unchecked")
46 Collection<GpxLink> their = (Collection<GpxLink>) ent.getValue();
47 my.addAll(their);
48 } else {
49 attr.put(k, ent.getValue());
50 }
51 }
52 tracks.addAll(other.tracks);
53 routes.addAll(other.routes);
54 waypoints.addAll(other.waypoints);
55 }
56
57 public boolean hasTrackPoints() {
58 for (GpxTrack trk : tracks) {
59 for (GpxTrackSegment trkseg : trk.getSegments()) {
60 if (!trkseg.getWayPoints().isEmpty())
61 return true;
62 }
63 }
64 return false;
65 }
66
67 public boolean hasRoutePoints() {
68 for (GpxRoute rte : routes) {
69 if (!rte.routePoints.isEmpty())
70 return true;
71 }
72 return false;
73 }
74
75 public boolean isEmpty() {
76 return !hasRoutePoints() && !hasTrackPoints() && waypoints.isEmpty();
77 }
78
79 /**
80 * calculates the bounding box of available data and returns it.
81 * The bounds are not stored internally, but recalculated every time
82 * this function is called.
83 *
84 * FIXME might perhaps use visitor pattern?
85 * @return the bounds
86 */
87 public Bounds recalculateBounds() {
88 Bounds bounds = null;
89 for (WayPoint wpt : waypoints) {
90 if (bounds == null) {
91 bounds = new Bounds(wpt.getCoor());
92 } else {
93 bounds.extend(wpt.getCoor());
94 }
95 }
96 for (GpxRoute rte : routes) {
97 for (WayPoint wpt : rte.routePoints) {
98 if (bounds == null) {
99 bounds = new Bounds(wpt.getCoor());
100 } else {
101 bounds.extend(wpt.getCoor());
102 }
103 }
104 }
105 for (GpxTrack trk : tracks) {
106 Bounds trkBounds = trk.getBounds();
107 if (trkBounds != null) {
108 if (bounds == null) {
109 bounds = new Bounds(trkBounds);
110 } else {
111 bounds.extend(trkBounds);
112 }
113 }
114 }
115 return bounds;
116 }
117
118 /**
119 * calculates the sum of the lengths of all track segments
120 * @return the length in meters
121 */
122 public double length(){
123 double result = 0.0; // in meters
124
125 for (GpxTrack trk : tracks) {
126 result += trk.length();
127 }
128
129 return result;
130 }
131
132 /**
133 * returns minimum and maximum timestamps in the track
134 * @param trk track to analyze
135 * @return minimum and maximum dates in array of 2 elements
136 */
137 public static Date[] getMinMaxTimeForTrack(GpxTrack trk) {
138 WayPoint earliest = null, latest = null;
139
140 for (GpxTrackSegment seg : trk.getSegments()) {
141 for (WayPoint pnt : seg.getWayPoints()) {
142 if (latest == null) {
143 latest = earliest = pnt;
144 } else {
145 if (pnt.compareTo(earliest) < 0) {
146 earliest = pnt;
147 } else {
148 latest = pnt;
149 }
150 }
151 }
152 }
153 if (earliest==null || latest==null) return null;
154 return new Date[]{earliest.getTime(), latest.getTime()};
155 }
156
157 /**
158 * Returns minimum and maximum timestamps for all tracks
159 * Warning: there are lot of track with broken timestamps,
160 * so we just ingore points from future and from year before 1970 in this method
161 * works correctly @since 5815
162 * @return minimum and maximum dates in array of 2 elements
163 */
164 public Date[] getMinMaxTimeForAllTracks() {
165 double min=1e100, max=-1e100, t;
166 double now = System.currentTimeMillis()/1000.0;
167 for (GpxTrack trk: tracks) {
168 for (GpxTrackSegment seg : trk.getSegments()) {
169 for (WayPoint pnt : seg.getWayPoints()) {
170 t = pnt.time;
171 if (t>0 && t<=now) {
172 if (t>max) max=t;
173 if (t<min) min=t;
174 }
175 }
176 }
177 }
178 if (min==1e100 || max==-1e100) return null;
179 return new Date[]{new Date((long) (min * 1000)), new Date((long) (max * 1000)), };
180 }
181
182 /**
183 * Makes a WayPoint at the projection of point P onto the track providing P is less than
184 * tolerance away from the track
185 *
186 * @param P : the point to determine the projection for
187 * @param tolerance : must be no further than this from the track
188 * @return the closest point on the track to P, which may be the first or last point if off the
189 * end of a segment, or may be null if nothing close enough
190 */
191 public WayPoint nearestPointOnTrack(EastNorth P, double tolerance) {
192 /*
193 * assume the coordinates of P are xp,yp, and those of a section of track between two
194 * trackpoints are R=xr,yr and S=xs,ys. Let N be the projected point.
195 *
196 * The equation of RS is Ax + By + C = 0 where A = ys - yr B = xr - xs C = - Axr - Byr
197 *
198 * Also, note that the distance RS^2 is A^2 + B^2
199 *
200 * If RS^2 == 0.0 ignore the degenerate section of track
201 *
202 * PN^2 = (Axp + Byp + C)^2 / RS^2 that is the distance from P to the line
203 *
204 * so if PN^2 is less than PNmin^2 (initialized to tolerance) we can reject the line
205 * otherwise... determine if the projected poijnt lies within the bounds of the line: PR^2 -
206 * PN^2 <= RS^2 and PS^2 - PN^2 <= RS^2
207 *
208 * where PR^2 = (xp - xr)^2 + (yp-yr)^2 and PS^2 = (xp - xs)^2 + (yp-ys)^2
209 *
210 * If so, calculate N as xn = xr + (RN/RS) B yn = y1 + (RN/RS) A
211 *
212 * where RN = sqrt(PR^2 - PN^2)
213 */
214
215 double PNminsq = tolerance * tolerance;
216 EastNorth bestEN = null;
217 double bestTime = 0.0;
218 double px = P.east();
219 double py = P.north();
220 double rx = 0.0, ry = 0.0, sx, sy, x, y;
221 if (tracks == null)
222 return null;
223 for (GpxTrack track : tracks) {
224 for (GpxTrackSegment seg : track.getSegments()) {
225 WayPoint R = null;
226 for (WayPoint S : seg.getWayPoints()) {
227 EastNorth c = S.getEastNorth();
228 if (R == null) {
229 R = S;
230 rx = c.east();
231 ry = c.north();
232 x = px - rx;
233 y = py - ry;
234 double PRsq = x * x + y * y;
235 if (PRsq < PNminsq) {
236 PNminsq = PRsq;
237 bestEN = c;
238 bestTime = R.time;
239 }
240 } else {
241 sx = c.east();
242 sy = c.north();
243 double A = sy - ry;
244 double B = rx - sx;
245 double C = -A * rx - B * ry;
246 double RSsq = A * A + B * B;
247 if (RSsq == 0.0) {
248 continue;
249 }
250 double PNsq = A * px + B * py + C;
251 PNsq = PNsq * PNsq / RSsq;
252 if (PNsq < PNminsq) {
253 x = px - rx;
254 y = py - ry;
255 double PRsq = x * x + y * y;
256 x = px - sx;
257 y = py - sy;
258 double PSsq = x * x + y * y;
259 if (PRsq - PNsq <= RSsq && PSsq - PNsq <= RSsq) {
260 double RNoverRS = Math.sqrt((PRsq - PNsq) / RSsq);
261 double nx = rx - RNoverRS * B;
262 double ny = ry + RNoverRS * A;
263 bestEN = new EastNorth(nx, ny);
264 bestTime = R.time + RNoverRS * (S.time - R.time);
265 PNminsq = PNsq;
266 }
267 }
268 R = S;
269 rx = sx;
270 ry = sy;
271 }
272 }
273 if (R != null) {
274 EastNorth c = R.getEastNorth();
275 /* if there is only one point in the seg, it will do this twice, but no matter */
276 rx = c.east();
277 ry = c.north();
278 x = px - rx;
279 y = py - ry;
280 double PRsq = x * x + y * y;
281 if (PRsq < PNminsq) {
282 PNminsq = PRsq;
283 bestEN = c;
284 bestTime = R.time;
285 }
286 }
287 }
288 }
289 if (bestEN == null)
290 return null;
291 WayPoint best = new WayPoint(Main.getProjection().eastNorth2latlon(bestEN));
292 best.time = bestTime;
293 return best;
294 }
295
296 /**
297 * Iterate over all track segments and over all routes.
298 *
299 * @param trackVisibility An array indicating which tracks should be
300 * included in the iteration. Can be null, then all tracks are included.
301 * @return an Iterable object, which iterates over all track segments and
302 * over all routes
303 */
304 public Iterable<Collection<WayPoint>> getLinesIterable(final boolean[] trackVisibility) {
305 return new Iterable<Collection<WayPoint>>() {
306 @Override
307 public Iterator<Collection<WayPoint>> iterator() {
308 return new LinesIterator(GpxData.this, trackVisibility);
309 }
310 };
311 }
312
313 public void resetEastNorthCache() {
314 if (waypoints != null) {
315 for (WayPoint wp : waypoints){
316 wp.invalidateEastNorthCache();
317 }
318 }
319 if (tracks != null){
320 for (GpxTrack track: tracks) {
321 for (GpxTrackSegment segment: track.getSegments()) {
322 for (WayPoint wp: segment.getWayPoints()) {
323 wp.invalidateEastNorthCache();
324 }
325 }
326 }
327 }
328 if (routes != null) {
329 for (GpxRoute route: routes) {
330 if (route.routePoints == null) {
331 continue;
332 }
333 for (WayPoint wp: route.routePoints) {
334 wp.invalidateEastNorthCache();
335 }
336 }
337 }
338 }
339
340 /**
341 * Iterates over all track segments and then over all routes.
342 */
343 public static class LinesIterator implements Iterator<Collection<WayPoint>> {
344
345 private Iterator<GpxTrack> itTracks;
346 private int idxTracks;
347 private Iterator<GpxTrackSegment> itTrackSegments;
348 private final Iterator<GpxRoute> itRoutes;
349
350 private Collection<WayPoint> next;
351 private final boolean[] trackVisibility;
352
353 public LinesIterator(GpxData data, boolean[] trackVisibility) {
354 itTracks = data.tracks.iterator();
355 idxTracks = -1;
356 itRoutes = data.routes.iterator();
357 this.trackVisibility = trackVisibility;
358 next = getNext();
359 }
360
361 @Override
362 public boolean hasNext() {
363 return next != null;
364 }
365
366 @Override
367 public Collection<WayPoint> next() {
368 Collection<WayPoint> current = next;
369 next = getNext();
370 return current;
371 }
372
373 private Collection<WayPoint> getNext() {
374 if (itTracks != null) {
375 if (itTrackSegments != null && itTrackSegments.hasNext()) {
376 return itTrackSegments.next().getWayPoints();
377 } else {
378 while (itTracks.hasNext()) {
379 GpxTrack nxtTrack = itTracks.next();
380 idxTracks++;
381 if (trackVisibility != null && !trackVisibility[idxTracks])
382 continue;
383 itTrackSegments = nxtTrack.getSegments().iterator();
384 if (itTrackSegments.hasNext()) {
385 return itTrackSegments.next().getWayPoints();
386 }
387 }
388 // if we get here, all the Tracks are finished; Continue with
389 // Routes
390 itTracks = null;
391 }
392 }
393 if (itRoutes.hasNext()) {
394 return itRoutes.next().routePoints;
395 }
396 return null;
397 }
398
399 @Override
400 public void remove() {
401 throw new UnsupportedOperationException();
402 }
403 }
404
405}
Note: See TracBrowser for help on using the repository browser.