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

Last change on this file since 7599 was 7575, checked in by Don-vip, 10 years ago

fix #7976 - Get downloaded gpx areas, on the same model as osm data

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