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

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

sonar - pmd:UselessQualifiedThis - Useless qualified this usage in the same class

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