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

Last change on this file since 11266 was 11095, checked in by simon04, 8 years ago

Default method implementations of Data interface

  • Property svn:eol-style set to native
File size: 18.2 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.Collections;
7import java.util.Date;
8import java.util.HashSet;
9import java.util.Iterator;
10import java.util.LinkedList;
11import java.util.Map;
12import java.util.NoSuchElementException;
13import java.util.Set;
14
15import org.openstreetmap.josm.Main;
16import org.openstreetmap.josm.data.Bounds;
17import org.openstreetmap.josm.data.Data;
18import org.openstreetmap.josm.data.DataSource;
19import org.openstreetmap.josm.data.coor.EastNorth;
20import org.openstreetmap.josm.tools.Utils;
21
22/**
23 * Objects of this class represent a gpx file with tracks, waypoints and routes.
24 * It uses GPX v1.1, see <a href="http://www.topografix.com/GPX/1/1/">the spec</a>
25 * for details.
26 *
27 * @author Raphael Mack &lt;ramack@raphael-mack.de&gt;
28 */
29public class GpxData extends WithAttributes implements Data {
30
31 public File storageFile;
32 public boolean fromServer;
33
34 /** Creator (usually software) */
35 public String creator;
36
37 /** Tracks */
38 public final Collection<GpxTrack> tracks = new LinkedList<>();
39 /** Routes */
40 public final Collection<GpxRoute> routes = new LinkedList<>();
41 /** Waypoints */
42 public final Collection<WayPoint> waypoints = new LinkedList<>();
43
44 /**
45 * All data sources (bounds of downloaded bounds) of this GpxData.<br>
46 * Not part of GPX standard but rather a JOSM extension, needed by the fact that
47 * OSM API does not provide {@code <bounds>} element in its GPX reply.
48 * @since 7575
49 */
50 public final Set<DataSource> dataSources = new HashSet<>();
51
52 /**
53 * Merges data from another object.
54 * @param other existing GPX data
55 */
56 public void mergeFrom(GpxData other) {
57 if (storageFile == null && other.storageFile != null) {
58 storageFile = other.storageFile;
59 }
60 fromServer = fromServer && other.fromServer;
61
62 for (Map.Entry<String, Object> ent : other.attr.entrySet()) {
63 // TODO: Detect conflicts.
64 String k = ent.getKey();
65 if (META_LINKS.equals(k) && attr.containsKey(META_LINKS)) {
66 Collection<GpxLink> my = super.<GpxLink>getCollection(META_LINKS);
67 @SuppressWarnings("unchecked")
68 Collection<GpxLink> their = (Collection<GpxLink>) ent.getValue();
69 my.addAll(their);
70 } else {
71 put(k, ent.getValue());
72 }
73 }
74 tracks.addAll(other.tracks);
75 routes.addAll(other.routes);
76 waypoints.addAll(other.waypoints);
77 dataSources.addAll(other.dataSources);
78 }
79
80 /**
81 * Determines if this GPX data has one or more track points
82 * @return {@code true} if this GPX data has track points, {@code false} otherwise
83 */
84 public boolean hasTrackPoints() {
85 for (GpxTrack trk : tracks) {
86 for (GpxTrackSegment trkseg : trk.getSegments()) {
87 if (!trkseg.getWayPoints().isEmpty())
88 return true;
89 }
90 }
91 return false;
92 }
93
94 /**
95 * Determines if this GPX data has one or more route points
96 * @return {@code true} if this GPX data has route points, {@code false} otherwise
97 */
98 public boolean hasRoutePoints() {
99 for (GpxRoute rte : routes) {
100 if (!rte.routePoints.isEmpty())
101 return true;
102 }
103 return false;
104 }
105
106 /**
107 * Determines if this GPX data is empty (i.e. does not contain any point)
108 * @return {@code true} if this GPX data is empty, {@code false} otherwise
109 */
110 public boolean isEmpty() {
111 return !hasRoutePoints() && !hasTrackPoints() && waypoints.isEmpty();
112 }
113
114 /**
115 * Returns the bounds defining the extend of this data, as read in metadata, if any.
116 * If no bounds is defined in metadata, {@code null} is returned. There is no guarantee
117 * that data entirely fit in this bounds, as it is not recalculated. To get recalculated bounds,
118 * see {@link #recalculateBounds()}. To get downloaded areas, see {@link #dataSources}.
119 * @return the bounds defining the extend of this data, or {@code null}.
120 * @see #recalculateBounds()
121 * @see #dataSources
122 * @since 7575
123 */
124 public Bounds getMetaBounds() {
125 Object value = get(META_BOUNDS);
126 if (value instanceof Bounds) {
127 return (Bounds) value;
128 }
129 return null;
130 }
131
132 /**
133 * Calculates the bounding box of available data and returns it.
134 * The bounds are not stored internally, but recalculated every time
135 * this function is called.<br>
136 * To get bounds as read from metadata, see {@link #getMetaBounds()}.<br>
137 * To get downloaded areas, see {@link #dataSources}.<br>
138 *
139 * FIXME might perhaps use visitor pattern?
140 * @return the bounds
141 * @see #getMetaBounds()
142 * @see #dataSources
143 */
144 public Bounds recalculateBounds() {
145 Bounds bounds = null;
146 for (WayPoint wpt : waypoints) {
147 if (bounds == null) {
148 bounds = new Bounds(wpt.getCoor());
149 } else {
150 bounds.extend(wpt.getCoor());
151 }
152 }
153 for (GpxRoute rte : routes) {
154 for (WayPoint wpt : rte.routePoints) {
155 if (bounds == null) {
156 bounds = new Bounds(wpt.getCoor());
157 } else {
158 bounds.extend(wpt.getCoor());
159 }
160 }
161 }
162 for (GpxTrack trk : tracks) {
163 Bounds trkBounds = trk.getBounds();
164 if (trkBounds != null) {
165 if (bounds == null) {
166 bounds = new Bounds(trkBounds);
167 } else {
168 bounds.extend(trkBounds);
169 }
170 }
171 }
172 return bounds;
173 }
174
175 /**
176 * calculates the sum of the lengths of all track segments
177 * @return the length in meters
178 */
179 public double length() {
180 double result = 0.0; // in meters
181
182 for (GpxTrack trk : tracks) {
183 result += trk.length();
184 }
185
186 return result;
187 }
188
189 /**
190 * returns minimum and maximum timestamps in the track
191 * @param trk track to analyze
192 * @return minimum and maximum dates in array of 2 elements
193 */
194 public static Date[] getMinMaxTimeForTrack(GpxTrack trk) {
195 WayPoint earliest = null, latest = null;
196
197 for (GpxTrackSegment seg : trk.getSegments()) {
198 for (WayPoint pnt : seg.getWayPoints()) {
199 if (latest == null) {
200 latest = earliest = pnt;
201 } else {
202 if (pnt.compareTo(earliest) < 0) {
203 earliest = pnt;
204 } else if (pnt.compareTo(latest) > 0) {
205 latest = pnt;
206 }
207 }
208 }
209 }
210 if (earliest == null || latest == null) return null;
211 return new Date[]{earliest.getTime(), latest.getTime()};
212 }
213
214 /**
215 * Returns minimum and maximum timestamps for all tracks
216 * Warning: there are lot of track with broken timestamps,
217 * so we just ingore points from future and from year before 1970 in this method
218 * works correctly @since 5815
219 * @return minimum and maximum dates in array of 2 elements
220 */
221 public Date[] getMinMaxTimeForAllTracks() {
222 double min = 1e100;
223 double max = -1e100;
224 double now = System.currentTimeMillis()/1000.0;
225 for (GpxTrack trk: tracks) {
226 for (GpxTrackSegment seg : trk.getSegments()) {
227 for (WayPoint pnt : seg.getWayPoints()) {
228 double t = pnt.time;
229 if (t > 0 && t <= now) {
230 if (t > max) max = t;
231 if (t < min) min = t;
232 }
233 }
234 }
235 }
236 if (Utils.equalsEpsilon(min, 1e100) || Utils.equalsEpsilon(max, -1e100)) return new Date[0];
237 return new Date[]{new Date((long) (min * 1000)), new Date((long) (max * 1000))};
238 }
239
240 /**
241 * Makes a WayPoint at the projection of point p onto the track providing p is less than
242 * tolerance away from the track
243 *
244 * @param p : the point to determine the projection for
245 * @param tolerance : must be no further than this from the track
246 * @return the closest point on the track to p, which may be the first or last point if off the
247 * end of a segment, or may be null if nothing close enough
248 */
249 public WayPoint nearestPointOnTrack(EastNorth p, double tolerance) {
250 /*
251 * assume the coordinates of P are xp,yp, and those of a section of track between two
252 * trackpoints are R=xr,yr and S=xs,ys. Let N be the projected point.
253 *
254 * The equation of RS is Ax + By + C = 0 where A = ys - yr B = xr - xs C = - Axr - Byr
255 *
256 * Also, note that the distance RS^2 is A^2 + B^2
257 *
258 * If RS^2 == 0.0 ignore the degenerate section of track
259 *
260 * PN^2 = (Axp + Byp + C)^2 / RS^2 that is the distance from P to the line
261 *
262 * so if PN^2 is less than PNmin^2 (initialized to tolerance) we can reject the line
263 * otherwise... determine if the projected poijnt lies within the bounds of the line: PR^2 -
264 * PN^2 <= RS^2 and PS^2 - PN^2 <= RS^2
265 *
266 * where PR^2 = (xp - xr)^2 + (yp-yr)^2 and PS^2 = (xp - xs)^2 + (yp-ys)^2
267 *
268 * If so, calculate N as xn = xr + (RN/RS) B yn = y1 + (RN/RS) A
269 *
270 * where RN = sqrt(PR^2 - PN^2)
271 */
272
273 double pnminsq = tolerance * tolerance;
274 EastNorth bestEN = null;
275 double bestTime = 0.0;
276 double px = p.east();
277 double py = p.north();
278 double rx = 0.0, ry = 0.0, sx, sy, x, y;
279 if (tracks == null)
280 return null;
281 for (GpxTrack track : tracks) {
282 for (GpxTrackSegment seg : track.getSegments()) {
283 WayPoint r = null;
284 for (WayPoint S : seg.getWayPoints()) {
285 EastNorth en = S.getEastNorth();
286 if (r == null) {
287 r = S;
288 rx = en.east();
289 ry = en.north();
290 x = px - rx;
291 y = py - ry;
292 double pRsq = x * x + y * y;
293 if (pRsq < pnminsq) {
294 pnminsq = pRsq;
295 bestEN = en;
296 bestTime = r.time;
297 }
298 } else {
299 sx = en.east();
300 sy = en.north();
301 double a = sy - ry;
302 double b = rx - sx;
303 double c = -a * rx - b * ry;
304 double rssq = a * a + b * b;
305 if (rssq == 0) {
306 continue;
307 }
308 double pnsq = a * px + b * py + c;
309 pnsq = pnsq * pnsq / rssq;
310 if (pnsq < pnminsq) {
311 x = px - rx;
312 y = py - ry;
313 double prsq = x * x + y * y;
314 x = px - sx;
315 y = py - sy;
316 double pssq = x * x + y * y;
317 if (prsq - pnsq <= rssq && pssq - pnsq <= rssq) {
318 double rnoverRS = Math.sqrt((prsq - pnsq) / rssq);
319 double nx = rx - rnoverRS * b;
320 double ny = ry + rnoverRS * a;
321 bestEN = new EastNorth(nx, ny);
322 bestTime = r.time + rnoverRS * (S.time - r.time);
323 pnminsq = pnsq;
324 }
325 }
326 r = S;
327 rx = sx;
328 ry = sy;
329 }
330 }
331 if (r != null) {
332 EastNorth c = r.getEastNorth();
333 /* if there is only one point in the seg, it will do this twice, but no matter */
334 rx = c.east();
335 ry = c.north();
336 x = px - rx;
337 y = py - ry;
338 double prsq = x * x + y * y;
339 if (prsq < pnminsq) {
340 pnminsq = prsq;
341 bestEN = c;
342 bestTime = r.time;
343 }
344 }
345 }
346 }
347 if (bestEN == null)
348 return null;
349 WayPoint best = new WayPoint(Main.getProjection().eastNorth2latlon(bestEN));
350 best.time = bestTime;
351 return best;
352 }
353
354 /**
355 * Iterate over all track segments and over all routes.
356 *
357 * @param trackVisibility An array indicating which tracks should be
358 * included in the iteration. Can be null, then all tracks are included.
359 * @return an Iterable object, which iterates over all track segments and
360 * over all routes
361 */
362 public Iterable<Collection<WayPoint>> getLinesIterable(final boolean ... trackVisibility) {
363 return () -> new LinesIterator(this, trackVisibility);
364 }
365
366 /**
367 * Resets the internal caches of east/north coordinates.
368 */
369 public void resetEastNorthCache() {
370 if (waypoints != null) {
371 for (WayPoint wp : waypoints) {
372 wp.invalidateEastNorthCache();
373 }
374 }
375 if (tracks != null) {
376 for (GpxTrack track: tracks) {
377 for (GpxTrackSegment segment: track.getSegments()) {
378 for (WayPoint wp: segment.getWayPoints()) {
379 wp.invalidateEastNorthCache();
380 }
381 }
382 }
383 }
384 if (routes != null) {
385 for (GpxRoute route: routes) {
386 if (route.routePoints == null) {
387 continue;
388 }
389 for (WayPoint wp: route.routePoints) {
390 wp.invalidateEastNorthCache();
391 }
392 }
393 }
394 }
395
396 /**
397 * Iterates over all track segments and then over all routes.
398 */
399 public static class LinesIterator implements Iterator<Collection<WayPoint>> {
400
401 private Iterator<GpxTrack> itTracks;
402 private int idxTracks;
403 private Iterator<GpxTrackSegment> itTrackSegments;
404 private final Iterator<GpxRoute> itRoutes;
405
406 private Collection<WayPoint> next;
407 private final boolean[] trackVisibility;
408
409 /**
410 * Constructs a new {@code LinesIterator}.
411 * @param data GPX data
412 * @param trackVisibility An array indicating which tracks should be
413 * included in the iteration. Can be null, then all tracks are included.
414 */
415 public LinesIterator(GpxData data, boolean ... trackVisibility) {
416 itTracks = data.tracks.iterator();
417 idxTracks = -1;
418 itRoutes = data.routes.iterator();
419 this.trackVisibility = trackVisibility;
420 next = getNext();
421 }
422
423 @Override
424 public boolean hasNext() {
425 return next != null;
426 }
427
428 @Override
429 public Collection<WayPoint> next() {
430 if (!hasNext()) {
431 throw new NoSuchElementException();
432 }
433 Collection<WayPoint> current = next;
434 next = getNext();
435 return current;
436 }
437
438 private Collection<WayPoint> getNext() {
439 if (itTracks != null) {
440 if (itTrackSegments != null && itTrackSegments.hasNext()) {
441 return itTrackSegments.next().getWayPoints();
442 } else {
443 while (itTracks.hasNext()) {
444 GpxTrack nxtTrack = itTracks.next();
445 idxTracks++;
446 if (trackVisibility != null && !trackVisibility[idxTracks])
447 continue;
448 itTrackSegments = nxtTrack.getSegments().iterator();
449 if (itTrackSegments.hasNext()) {
450 return itTrackSegments.next().getWayPoints();
451 }
452 }
453 // if we get here, all the Tracks are finished; Continue with Routes
454 itTracks = null;
455 }
456 }
457 if (itRoutes.hasNext()) {
458 return itRoutes.next().routePoints;
459 }
460 return null;
461 }
462
463 @Override
464 public void remove() {
465 throw new UnsupportedOperationException();
466 }
467 }
468
469 @Override
470 public Collection<DataSource> getDataSources() {
471 return Collections.unmodifiableCollection(dataSources);
472 }
473
474 @Override
475 public int hashCode() {
476 final int prime = 31;
477 int result = 1;
478 result = prime * result + ((dataSources == null) ? 0 : dataSources.hashCode());
479 result = prime * result + ((routes == null) ? 0 : routes.hashCode());
480 result = prime * result + ((tracks == null) ? 0 : tracks.hashCode());
481 result = prime * result + ((waypoints == null) ? 0 : waypoints.hashCode());
482 return result;
483 }
484
485 @Override
486 public boolean equals(Object obj) {
487 if (this == obj)
488 return true;
489 if (obj == null)
490 return false;
491 if (getClass() != obj.getClass())
492 return false;
493 GpxData other = (GpxData) obj;
494 if (dataSources == null) {
495 if (other.dataSources != null)
496 return false;
497 } else if (!dataSources.equals(other.dataSources))
498 return false;
499 if (routes == null) {
500 if (other.routes != null)
501 return false;
502 } else if (!routes.equals(other.routes))
503 return false;
504 if (tracks == null) {
505 if (other.tracks != null)
506 return false;
507 } else if (!tracks.equals(other.tracks))
508 return false;
509 if (waypoints == null) {
510 if (other.waypoints != null)
511 return false;
512 } else if (!waypoints.equals(other.waypoints))
513 return false;
514 return true;
515 }
516}
Note: See TracBrowser for help on using the repository browser.