001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.gpx; 003 004import java.io.File; 005import java.util.Collection; 006import java.util.Iterator; 007import java.util.LinkedList; 008import java.util.Map; 009 010import org.openstreetmap.josm.Main; 011import org.openstreetmap.josm.data.Bounds; 012import org.openstreetmap.josm.data.coor.EastNorth; 013 014/** 015 * Objects of this class represent a gpx file with tracks, waypoints and routes. 016 * It uses GPX v1.1, see {@link <a href="http://www.topografix.com/GPX/1/1/">the spec</a>} 017 * for details. 018 * 019 * @author Raphael Mack <ramack@raphael-mack.de> 020 */ 021public class GpxData extends WithAttributes { 022 023 public File storageFile; 024 public boolean fromServer; 025 026 public String creator; 027 028 public final Collection<GpxTrack> tracks = new LinkedList<GpxTrack>(); 029 public final Collection<GpxRoute> routes = new LinkedList<GpxRoute>(); 030 public final Collection<WayPoint> waypoints = new LinkedList<WayPoint>(); 031 032 @SuppressWarnings("unchecked") 033 public void mergeFrom(GpxData other) { 034 if (storageFile == null && other.storageFile != null) { 035 storageFile = other.storageFile; 036 } 037 fromServer = fromServer && other.fromServer; 038 039 for (Map.Entry<String, Object> ent : other.attr.entrySet()) { 040 // TODO: Detect conflicts. 041 String k = ent.getKey(); 042 if (k.equals(META_LINKS) && attr.containsKey(META_LINKS)) { 043 ((Collection<GpxLink>) attr.get(META_LINKS)).addAll( 044 (Collection<GpxLink>) ent.getValue()); 045 } else { 046 attr.put(k, ent.getValue()); 047 } 048 } 049 tracks.addAll(other.tracks); 050 routes.addAll(other.routes); 051 waypoints.addAll(other.waypoints); 052 } 053 054 public boolean hasTrackPoints() { 055 for (GpxTrack trk : tracks) { 056 for (GpxTrackSegment trkseg : trk.getSegments()) { 057 if (!trkseg.getWayPoints().isEmpty()) 058 return true; 059 } 060 } 061 return false; 062 } 063 064 public boolean hasRoutePoints() { 065 for (GpxRoute rte : routes) { 066 if (!rte.routePoints.isEmpty()) 067 return true; 068 } 069 return false; 070 } 071 072 public boolean isEmpty() { 073 return !hasRoutePoints() && !hasTrackPoints() && waypoints.isEmpty(); 074 } 075 076 /** 077 * calculates the bounding box of available data and returns it. 078 * The bounds are not stored internally, but recalculated every time 079 * this function is called. 080 * 081 * FIXME might perhaps use visitor pattern? 082 */ 083 public Bounds recalculateBounds() { 084 Bounds bounds = null; 085 for (WayPoint wpt : waypoints) { 086 if (bounds == null) { 087 bounds = new Bounds(wpt.getCoor()); 088 } else { 089 bounds.extend(wpt.getCoor()); 090 } 091 } 092 for (GpxRoute rte : routes) { 093 for (WayPoint wpt : rte.routePoints) { 094 if (bounds == null) { 095 bounds = new Bounds(wpt.getCoor()); 096 } else { 097 bounds.extend(wpt.getCoor()); 098 } 099 } 100 } 101 for (GpxTrack trk : tracks) { 102 Bounds trkBounds = trk.getBounds(); 103 if (trkBounds != null) { 104 if (bounds == null) { 105 bounds = new Bounds(trkBounds); 106 } else { 107 bounds.extend(trkBounds); 108 } 109 } 110 } 111 return bounds; 112 } 113 114 /** 115 * calculates the sum of the lengths of all track segments 116 */ 117 public double length(){ 118 double result = 0.0; // in meters 119 120 for (GpxTrack trk : tracks) { 121 result += trk.length(); 122 } 123 124 return result; 125 } 126 127 /** 128 * Makes a WayPoint at the projection of point P onto the track providing P is less than 129 * tolerance away from the track 130 * 131 * @param P : the point to determine the projection for 132 * @param tolerance : must be no further than this from the track 133 * @return the closest point on the track to P, which may be the first or last point if off the 134 * end of a segment, or may be null if nothing close enough 135 */ 136 public WayPoint nearestPointOnTrack(EastNorth P, double tolerance) { 137 /* 138 * assume the coordinates of P are xp,yp, and those of a section of track between two 139 * trackpoints are R=xr,yr and S=xs,ys. Let N be the projected point. 140 * 141 * The equation of RS is Ax + By + C = 0 where A = ys - yr B = xr - xs C = - Axr - Byr 142 * 143 * Also, note that the distance RS^2 is A^2 + B^2 144 * 145 * If RS^2 == 0.0 ignore the degenerate section of track 146 * 147 * PN^2 = (Axp + Byp + C)^2 / RS^2 that is the distance from P to the line 148 * 149 * so if PN^2 is less than PNmin^2 (initialized to tolerance) we can reject the line 150 * otherwise... determine if the projected poijnt lies within the bounds of the line: PR^2 - 151 * PN^2 <= RS^2 and PS^2 - PN^2 <= RS^2 152 * 153 * where PR^2 = (xp - xr)^2 + (yp-yr)^2 and PS^2 = (xp - xs)^2 + (yp-ys)^2 154 * 155 * If so, calculate N as xn = xr + (RN/RS) B yn = y1 + (RN/RS) A 156 * 157 * where RN = sqrt(PR^2 - PN^2) 158 */ 159 160 double PNminsq = tolerance * tolerance; 161 EastNorth bestEN = null; 162 double bestTime = 0.0; 163 double px = P.east(); 164 double py = P.north(); 165 double rx = 0.0, ry = 0.0, sx, sy, x, y; 166 if (tracks == null) 167 return null; 168 for (GpxTrack track : tracks) { 169 for (GpxTrackSegment seg : track.getSegments()) { 170 WayPoint R = null; 171 for (WayPoint S : seg.getWayPoints()) { 172 EastNorth c = S.getEastNorth(); 173 if (R == null) { 174 R = S; 175 rx = c.east(); 176 ry = c.north(); 177 x = px - rx; 178 y = py - ry; 179 double PRsq = x * x + y * y; 180 if (PRsq < PNminsq) { 181 PNminsq = PRsq; 182 bestEN = c; 183 bestTime = R.time; 184 } 185 } else { 186 sx = c.east(); 187 sy = c.north(); 188 double A = sy - ry; 189 double B = rx - sx; 190 double C = -A * rx - B * ry; 191 double RSsq = A * A + B * B; 192 if (RSsq == 0.0) { 193 continue; 194 } 195 double PNsq = A * px + B * py + C; 196 PNsq = PNsq * PNsq / RSsq; 197 if (PNsq < PNminsq) { 198 x = px - rx; 199 y = py - ry; 200 double PRsq = x * x + y * y; 201 x = px - sx; 202 y = py - sy; 203 double PSsq = x * x + y * y; 204 if (PRsq - PNsq <= RSsq && PSsq - PNsq <= RSsq) { 205 double RNoverRS = Math.sqrt((PRsq - PNsq) / RSsq); 206 double nx = rx - RNoverRS * B; 207 double ny = ry + RNoverRS * A; 208 bestEN = new EastNorth(nx, ny); 209 bestTime = R.time + RNoverRS * (S.time - R.time); 210 PNminsq = PNsq; 211 } 212 } 213 R = S; 214 rx = sx; 215 ry = sy; 216 } 217 } 218 if (R != null) { 219 EastNorth c = R.getEastNorth(); 220 /* if there is only one point in the seg, it will do this twice, but no matter */ 221 rx = c.east(); 222 ry = c.north(); 223 x = px - rx; 224 y = py - ry; 225 double PRsq = x * x + y * y; 226 if (PRsq < PNminsq) { 227 PNminsq = PRsq; 228 bestEN = c; 229 bestTime = R.time; 230 } 231 } 232 } 233 } 234 if (bestEN == null) 235 return null; 236 WayPoint best = new WayPoint(Main.getProjection().eastNorth2latlon(bestEN)); 237 best.time = bestTime; 238 return best; 239 } 240 241 /** 242 * Iterate over all track segments and over all routes. 243 * 244 * @param trackVisibility An array indicating which tracks should be 245 * included in the iteration. Can be null, then all tracks are included. 246 * @return an Iterable object, which iterates over all track segments and 247 * over all routes 248 */ 249 public Iterable<Collection<WayPoint>> getLinesIterable(final boolean[] trackVisibility) { 250 return new Iterable<Collection<WayPoint>>() { 251 @Override 252 public Iterator<Collection<WayPoint>> iterator() { 253 return new LinesIterator(GpxData.this, trackVisibility); 254 } 255 }; 256 } 257 258 /** 259 * Iterates over all track segments and then over all routes. 260 */ 261 public static class LinesIterator implements Iterator<Collection<WayPoint>> { 262 263 private Iterator<GpxTrack> itTracks; 264 private int idxTracks; 265 private Iterator<GpxTrackSegment> itTrackSegments; 266 private Iterator<GpxRoute> itRoutes; 267 268 private Collection<WayPoint> next; 269 private boolean[] trackVisibility; 270 271 public LinesIterator(GpxData data, boolean[] trackVisibility) { 272 itTracks = data.tracks.iterator(); 273 idxTracks = -1; 274 itRoutes = data.routes.iterator(); 275 this.trackVisibility = trackVisibility; 276 next = getNext(); 277 } 278 279 @Override 280 public boolean hasNext() { 281 return next != null; 282 } 283 284 @Override 285 public Collection<WayPoint> next() { 286 Collection<WayPoint> current = next; 287 next = getNext(); 288 return current; 289 } 290 291 private Collection<WayPoint> getNext() { 292 if (itTracks != null) { 293 if (itTrackSegments != null && itTrackSegments.hasNext()) { 294 return itTrackSegments.next().getWayPoints(); 295 } else { 296 while (itTracks.hasNext()) { 297 GpxTrack nxtTrack = itTracks.next(); 298 idxTracks++; 299 if (trackVisibility != null && !trackVisibility[idxTracks]) 300 continue; 301 itTrackSegments = nxtTrack.getSegments().iterator(); 302 if (itTrackSegments.hasNext()) { 303 return itTrackSegments.next().getWayPoints(); 304 } 305 } 306 // if we get here, all the Tracks are finished; Continue with 307 // Routes 308 itTracks = null; 309 } 310 } 311 if (itRoutes.hasNext()) { 312 return itRoutes.next().routePoints; 313 } 314 return null; 315 } 316 317 @Override 318 public void remove() { 319 throw new UnsupportedOperationException(); 320 } 321 } 322 323}