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

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

checkstyle - Comments Indentation

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