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

Last change on this file since 11710 was 11288, checked in by simon04, 7 years ago

see #13376 - Use TimeUnit instead of combinations of 1000/60/60/24

  • Property svn:eol-style set to native
File size: 17.8 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.DoubleSummaryStatistics;
9import java.util.HashSet;
10import java.util.Iterator;
11import java.util.LinkedList;
12import java.util.Map;
13import java.util.NoSuchElementException;
14import java.util.Set;
15
16import org.openstreetmap.josm.Main;
17import org.openstreetmap.josm.data.Bounds;
18import org.openstreetmap.josm.data.Data;
19import org.openstreetmap.josm.data.DataSource;
20import org.openstreetmap.josm.data.coor.EastNorth;
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 final DoubleSummaryStatistics statistics = trk.getSegments().stream()
196 .flatMap(seg -> seg.getWayPoints().stream())
197 .mapToDouble(pnt -> pnt.time)
198 .summaryStatistics();
199 return statistics.getCount() == 0
200 ? null
201 : new Date[]{new Date((long) (statistics.getMin() * 1000)), new Date((long) (statistics.getMax() * 1000))};
202 }
203
204 /**
205 * Returns minimum and maximum timestamps for all tracks
206 * Warning: there are lot of track with broken timestamps,
207 * so we just ingore points from future and from year before 1970 in this method
208 * works correctly @since 5815
209 * @return minimum and maximum dates in array of 2 elements
210 */
211 public Date[] getMinMaxTimeForAllTracks() {
212 double now = System.currentTimeMillis() / 1000.0;
213 final DoubleSummaryStatistics statistics = tracks.stream()
214 .flatMap(trk -> trk.getSegments().stream())
215 .flatMap(seg -> seg.getWayPoints().stream())
216 .mapToDouble(pnt -> pnt.time)
217 .filter(t -> t > 0 && t <= now)
218 .summaryStatistics();
219 return statistics.getCount() == 0
220 ? new Date[0]
221 : new Date[]{new Date((long) (statistics.getMin() * 1000)), new Date((long) (statistics.getMax() * 1000))};
222 }
223
224 /**
225 * Makes a WayPoint at the projection of point p onto the track providing p is less than
226 * tolerance away from the track
227 *
228 * @param p : the point to determine the projection for
229 * @param tolerance : must be no further than this from the track
230 * @return the closest point on the track to p, which may be the first or last point if off the
231 * end of a segment, or may be null if nothing close enough
232 */
233 public WayPoint nearestPointOnTrack(EastNorth p, double tolerance) {
234 /*
235 * assume the coordinates of P are xp,yp, and those of a section of track between two
236 * trackpoints are R=xr,yr and S=xs,ys. Let N be the projected point.
237 *
238 * The equation of RS is Ax + By + C = 0 where A = ys - yr B = xr - xs C = - Axr - Byr
239 *
240 * Also, note that the distance RS^2 is A^2 + B^2
241 *
242 * If RS^2 == 0.0 ignore the degenerate section of track
243 *
244 * PN^2 = (Axp + Byp + C)^2 / RS^2 that is the distance from P to the line
245 *
246 * so if PN^2 is less than PNmin^2 (initialized to tolerance) we can reject the line
247 * otherwise... determine if the projected poijnt lies within the bounds of the line: PR^2 -
248 * PN^2 <= RS^2 and PS^2 - PN^2 <= RS^2
249 *
250 * where PR^2 = (xp - xr)^2 + (yp-yr)^2 and PS^2 = (xp - xs)^2 + (yp-ys)^2
251 *
252 * If so, calculate N as xn = xr + (RN/RS) B yn = y1 + (RN/RS) A
253 *
254 * where RN = sqrt(PR^2 - PN^2)
255 */
256
257 double pnminsq = tolerance * tolerance;
258 EastNorth bestEN = null;
259 double bestTime = 0.0;
260 double px = p.east();
261 double py = p.north();
262 double rx = 0.0, ry = 0.0, sx, sy, x, y;
263 if (tracks == null)
264 return null;
265 for (GpxTrack track : tracks) {
266 for (GpxTrackSegment seg : track.getSegments()) {
267 WayPoint r = null;
268 for (WayPoint S : seg.getWayPoints()) {
269 EastNorth en = S.getEastNorth();
270 if (r == null) {
271 r = S;
272 rx = en.east();
273 ry = en.north();
274 x = px - rx;
275 y = py - ry;
276 double pRsq = x * x + y * y;
277 if (pRsq < pnminsq) {
278 pnminsq = pRsq;
279 bestEN = en;
280 bestTime = r.time;
281 }
282 } else {
283 sx = en.east();
284 sy = en.north();
285 double a = sy - ry;
286 double b = rx - sx;
287 double c = -a * rx - b * ry;
288 double rssq = a * a + b * b;
289 if (rssq == 0) {
290 continue;
291 }
292 double pnsq = a * px + b * py + c;
293 pnsq = pnsq * pnsq / rssq;
294 if (pnsq < pnminsq) {
295 x = px - rx;
296 y = py - ry;
297 double prsq = x * x + y * y;
298 x = px - sx;
299 y = py - sy;
300 double pssq = x * x + y * y;
301 if (prsq - pnsq <= rssq && pssq - pnsq <= rssq) {
302 double rnoverRS = Math.sqrt((prsq - pnsq) / rssq);
303 double nx = rx - rnoverRS * b;
304 double ny = ry + rnoverRS * a;
305 bestEN = new EastNorth(nx, ny);
306 bestTime = r.time + rnoverRS * (S.time - r.time);
307 pnminsq = pnsq;
308 }
309 }
310 r = S;
311 rx = sx;
312 ry = sy;
313 }
314 }
315 if (r != null) {
316 EastNorth c = r.getEastNorth();
317 /* if there is only one point in the seg, it will do this twice, but no matter */
318 rx = c.east();
319 ry = c.north();
320 x = px - rx;
321 y = py - ry;
322 double prsq = x * x + y * y;
323 if (prsq < pnminsq) {
324 pnminsq = prsq;
325 bestEN = c;
326 bestTime = r.time;
327 }
328 }
329 }
330 }
331 if (bestEN == null)
332 return null;
333 WayPoint best = new WayPoint(Main.getProjection().eastNorth2latlon(bestEN));
334 best.time = bestTime;
335 return best;
336 }
337
338 /**
339 * Iterate over all track segments and over all routes.
340 *
341 * @param trackVisibility An array indicating which tracks should be
342 * included in the iteration. Can be null, then all tracks are included.
343 * @return an Iterable object, which iterates over all track segments and
344 * over all routes
345 */
346 public Iterable<Collection<WayPoint>> getLinesIterable(final boolean ... trackVisibility) {
347 return () -> new LinesIterator(this, trackVisibility);
348 }
349
350 /**
351 * Resets the internal caches of east/north coordinates.
352 */
353 public void resetEastNorthCache() {
354 if (waypoints != null) {
355 for (WayPoint wp : waypoints) {
356 wp.invalidateEastNorthCache();
357 }
358 }
359 if (tracks != null) {
360 for (GpxTrack track: tracks) {
361 for (GpxTrackSegment segment: track.getSegments()) {
362 for (WayPoint wp: segment.getWayPoints()) {
363 wp.invalidateEastNorthCache();
364 }
365 }
366 }
367 }
368 if (routes != null) {
369 for (GpxRoute route: routes) {
370 if (route.routePoints == null) {
371 continue;
372 }
373 for (WayPoint wp: route.routePoints) {
374 wp.invalidateEastNorthCache();
375 }
376 }
377 }
378 }
379
380 /**
381 * Iterates over all track segments and then over all routes.
382 */
383 public static class LinesIterator implements Iterator<Collection<WayPoint>> {
384
385 private Iterator<GpxTrack> itTracks;
386 private int idxTracks;
387 private Iterator<GpxTrackSegment> itTrackSegments;
388 private final Iterator<GpxRoute> itRoutes;
389
390 private Collection<WayPoint> next;
391 private final boolean[] trackVisibility;
392
393 /**
394 * Constructs a new {@code LinesIterator}.
395 * @param data GPX data
396 * @param trackVisibility An array indicating which tracks should be
397 * included in the iteration. Can be null, then all tracks are included.
398 */
399 public LinesIterator(GpxData data, boolean ... trackVisibility) {
400 itTracks = data.tracks.iterator();
401 idxTracks = -1;
402 itRoutes = data.routes.iterator();
403 this.trackVisibility = trackVisibility;
404 next = getNext();
405 }
406
407 @Override
408 public boolean hasNext() {
409 return next != null;
410 }
411
412 @Override
413 public Collection<WayPoint> next() {
414 if (!hasNext()) {
415 throw new NoSuchElementException();
416 }
417 Collection<WayPoint> current = next;
418 next = getNext();
419 return current;
420 }
421
422 private Collection<WayPoint> getNext() {
423 if (itTracks != null) {
424 if (itTrackSegments != null && itTrackSegments.hasNext()) {
425 return itTrackSegments.next().getWayPoints();
426 } else {
427 while (itTracks.hasNext()) {
428 GpxTrack nxtTrack = itTracks.next();
429 idxTracks++;
430 if (trackVisibility != null && !trackVisibility[idxTracks])
431 continue;
432 itTrackSegments = nxtTrack.getSegments().iterator();
433 if (itTrackSegments.hasNext()) {
434 return itTrackSegments.next().getWayPoints();
435 }
436 }
437 // if we get here, all the Tracks are finished; Continue with Routes
438 itTracks = null;
439 }
440 }
441 if (itRoutes.hasNext()) {
442 return itRoutes.next().routePoints;
443 }
444 return null;
445 }
446
447 @Override
448 public void remove() {
449 throw new UnsupportedOperationException();
450 }
451 }
452
453 @Override
454 public Collection<DataSource> getDataSources() {
455 return Collections.unmodifiableCollection(dataSources);
456 }
457
458 @Override
459 public int hashCode() {
460 final int prime = 31;
461 int result = 1;
462 result = prime * result + ((dataSources == null) ? 0 : dataSources.hashCode());
463 result = prime * result + ((routes == null) ? 0 : routes.hashCode());
464 result = prime * result + ((tracks == null) ? 0 : tracks.hashCode());
465 result = prime * result + ((waypoints == null) ? 0 : waypoints.hashCode());
466 return result;
467 }
468
469 @Override
470 public boolean equals(Object obj) {
471 if (this == obj)
472 return true;
473 if (obj == null)
474 return false;
475 if (getClass() != obj.getClass())
476 return false;
477 GpxData other = (GpxData) obj;
478 if (dataSources == null) {
479 if (other.dataSources != null)
480 return false;
481 } else if (!dataSources.equals(other.dataSources))
482 return false;
483 if (routes == null) {
484 if (other.routes != null)
485 return false;
486 } else if (!routes.equals(other.routes))
487 return false;
488 if (tracks == null) {
489 if (other.tracks != null)
490 return false;
491 } else if (!tracks.equals(other.tracks))
492 return false;
493 if (waypoints == null) {
494 if (other.waypoints != null)
495 return false;
496 } else if (!waypoints.equals(other.waypoints))
497 return false;
498 return true;
499 }
500}
Note: See TracBrowser for help on using the repository browser.