[444] | 1 | //License: GPLv2 or later. Copyright 2007 by Raphael Mack and others
|
---|
| 2 |
|
---|
| 3 | package org.openstreetmap.josm.data.gpx;
|
---|
| 4 |
|
---|
[582] | 5 | import java.io.File;
|
---|
[444] | 6 | import java.util.Collection;
|
---|
| 7 | import java.util.LinkedList;
|
---|
| 8 | import java.util.Map;
|
---|
[5715] | 9 | import org.openstreetmap.josm.Main;
|
---|
[582] | 10 |
|
---|
[444] | 11 | import org.openstreetmap.josm.data.Bounds;
|
---|
[5715] | 12 | import org.openstreetmap.josm.data.coor.EastNorth;
|
---|
[444] | 13 |
|
---|
| 14 | /**
|
---|
[655] | 15 | * Objects of this class represent a gpx file with tracks, waypoints and routes.
|
---|
| 16 | * It uses GPX v1.1, see {@link <a href="http://www.topografix.com/GPX/1/1/">the spec</a>}
|
---|
| 17 | * for details.
|
---|
[1169] | 18 | *
|
---|
[444] | 19 | * @author Raphael Mack <ramack@raphael-mack.de>
|
---|
| 20 | */
|
---|
| 21 | public class GpxData extends WithAttributes {
|
---|
[1677] | 22 |
|
---|
[1169] | 23 | public File storageFile;
|
---|
| 24 | public boolean fromServer;
|
---|
[444] | 25 |
|
---|
[5395] | 26 | public String creator;
|
---|
| 27 |
|
---|
[3119] | 28 | public final Collection<GpxTrack> tracks = new LinkedList<GpxTrack>();
|
---|
| 29 | public final Collection<GpxRoute> routes = new LinkedList<GpxRoute>();
|
---|
| 30 | public final Collection<WayPoint> waypoints = new LinkedList<WayPoint>();
|
---|
[444] | 31 |
|
---|
[1911] | 32 | @SuppressWarnings("unchecked")
|
---|
[1169] | 33 | public void mergeFrom(GpxData other) {
|
---|
| 34 | if (storageFile == null && other.storageFile != null) {
|
---|
| 35 | storageFile = other.storageFile;
|
---|
| 36 | }
|
---|
| 37 | fromServer = fromServer && other.fromServer;
|
---|
[444] | 38 |
|
---|
[1169] | 39 | for (Map.Entry<String, Object> ent : other.attr.entrySet()) {
|
---|
| 40 | // TODO: Detect conflicts.
|
---|
| 41 | String k = ent.getKey();
|
---|
[1574] | 42 | if (k.equals(META_LINKS) && attr.containsKey(META_LINKS)) {
|
---|
| 43 | ((Collection<GpxLink>) attr.get(META_LINKS)).addAll(
|
---|
[1911] | 44 | (Collection<GpxLink>) ent.getValue());
|
---|
[1169] | 45 | } else {
|
---|
| 46 | attr.put(k, ent.getValue());
|
---|
| 47 | }
|
---|
| 48 | }
|
---|
| 49 | tracks.addAll(other.tracks);
|
---|
| 50 | routes.addAll(other.routes);
|
---|
| 51 | waypoints.addAll(other.waypoints);
|
---|
| 52 | }
|
---|
[444] | 53 |
|
---|
[1169] | 54 | public boolean hasTrackPoints() {
|
---|
| 55 | for (GpxTrack trk : tracks) {
|
---|
[2907] | 56 | for (GpxTrackSegment trkseg : trk.getSegments()) {
|
---|
| 57 | if (!trkseg.getWayPoints().isEmpty())
|
---|
[1169] | 58 | return true;
|
---|
| 59 | }
|
---|
| 60 | }
|
---|
| 61 | return false;
|
---|
| 62 | }
|
---|
[444] | 63 |
|
---|
[1169] | 64 | public boolean hasRoutePoints() {
|
---|
| 65 | for (GpxRoute rte : routes) {
|
---|
| 66 | if (!rte.routePoints.isEmpty())
|
---|
| 67 | return true;
|
---|
| 68 | }
|
---|
| 69 | return false;
|
---|
| 70 | }
|
---|
[444] | 71 |
|
---|
[2795] | 72 | public boolean isEmpty() {
|
---|
| 73 | return !hasRoutePoints() && !hasTrackPoints() && waypoints.isEmpty();
|
---|
| 74 | }
|
---|
| 75 |
|
---|
[5108] | 76 | /**
|
---|
| 77 | * calculates the bounding box of available data and returns it.
|
---|
| 78 | * The bounds are not stored internally, but recalculated every time
|
---|
| 79 | * this function is called.
|
---|
[6069] | 80 | *
|
---|
[5108] | 81 | * FIXME might perhaps use visitor pattern?
|
---|
| 82 | */
|
---|
[1722] | 83 | public Bounds recalculateBounds() {
|
---|
| 84 | Bounds bounds = null;
|
---|
[1169] | 85 | for (WayPoint wpt : waypoints) {
|
---|
| 86 | if (bounds == null) {
|
---|
[1724] | 87 | bounds = new Bounds(wpt.getCoor());
|
---|
[1169] | 88 | } else {
|
---|
[1724] | 89 | bounds.extend(wpt.getCoor());
|
---|
[1169] | 90 | }
|
---|
| 91 | }
|
---|
| 92 | for (GpxRoute rte : routes) {
|
---|
| 93 | for (WayPoint wpt : rte.routePoints) {
|
---|
| 94 | if (bounds == null) {
|
---|
[1724] | 95 | bounds = new Bounds(wpt.getCoor());
|
---|
[1169] | 96 | } else {
|
---|
[1724] | 97 | bounds.extend(wpt.getCoor());
|
---|
[1169] | 98 | }
|
---|
| 99 | }
|
---|
| 100 | }
|
---|
| 101 | for (GpxTrack trk : tracks) {
|
---|
[2907] | 102 | Bounds trkBounds = trk.getBounds();
|
---|
| 103 | if (trkBounds != null) {
|
---|
| 104 | if (bounds == null) {
|
---|
| 105 | bounds = new Bounds(trkBounds);
|
---|
| 106 | } else {
|
---|
| 107 | bounds.extend(trkBounds);
|
---|
[1169] | 108 | }
|
---|
| 109 | }
|
---|
| 110 | }
|
---|
[1722] | 111 | return bounds;
|
---|
[1169] | 112 | }
|
---|
| 113 |
|
---|
[647] | 114 | /**
|
---|
| 115 | * calculates the sum of the lengths of all track segments
|
---|
| 116 | */
|
---|
| 117 | public double length(){
|
---|
| 118 | double result = 0.0; // in meters
|
---|
[1169] | 119 |
|
---|
[2247] | 120 | for (GpxTrack trk : tracks) {
|
---|
| 121 | result += trk.length();
|
---|
| 122 | }
|
---|
[2151] | 123 |
|
---|
[647] | 124 | return result;
|
---|
| 125 | }
|
---|
[6069] | 126 |
|
---|
[5715] | 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 | }
|
---|
[444] | 240 | }
|
---|