001    //License: GPLv2 or later. Copyright 2007 by Raphael Mack and others
002    
003    package org.openstreetmap.josm.data.gpx;
004    
005    import java.io.File;
006    import java.util.Collection;
007    import java.util.LinkedList;
008    import java.util.Map;
009    
010    import org.openstreetmap.josm.data.Bounds;
011    
012    /**
013     * Objects of this class represent a gpx file with tracks, waypoints and routes.
014     * It uses GPX v1.1, see {@link <a href="http://www.topografix.com/GPX/1/1/">the spec</a>}
015     * for details.
016     *
017     * @author Raphael Mack <ramack@raphael-mack.de>
018     */
019    public class GpxData extends WithAttributes {
020    
021        public static final String META_PREFIX = "meta.";
022        public static final String META_AUTHOR_NAME = META_PREFIX + "author.name";
023        public static final String META_AUTHOR_EMAIL = META_PREFIX + "author.email";
024        public static final String META_AUTHOR_LINK = META_PREFIX + "author.link";
025        public static final String META_COPYRIGHT_AUTHOR = META_PREFIX + "copyright.author";
026        public static final String META_COPYRIGHT_LICENSE = META_PREFIX + "copyright.license";
027        public static final String META_COPYRIGHT_YEAR = META_PREFIX + "copyright.year";
028        public static final String META_DESC = META_PREFIX + "desc";
029        public static final String META_KEYWORDS = META_PREFIX + "keywords";
030        public static final String META_LINKS = META_PREFIX + "links";
031        public static final String META_NAME = META_PREFIX + "name";
032        public static final String META_TIME = META_PREFIX + "time";
033    
034        public File storageFile;
035        public boolean fromServer;
036    
037        public String creator;
038    
039        public final Collection<GpxTrack> tracks = new LinkedList<GpxTrack>();
040        public final Collection<GpxRoute> routes = new LinkedList<GpxRoute>();
041        public final Collection<WayPoint> waypoints = new LinkedList<WayPoint>();
042    
043        @SuppressWarnings("unchecked")
044        public void mergeFrom(GpxData other) {
045            if (storageFile == null && other.storageFile != null) {
046                storageFile = other.storageFile;
047            }
048            fromServer = fromServer && other.fromServer;
049    
050            for (Map.Entry<String, Object> ent : other.attr.entrySet()) {
051                // TODO: Detect conflicts.
052                String k = ent.getKey();
053                if (k.equals(META_LINKS) && attr.containsKey(META_LINKS)) {
054                    ((Collection<GpxLink>) attr.get(META_LINKS)).addAll(
055                            (Collection<GpxLink>) ent.getValue());
056                } else {
057                    attr.put(k, ent.getValue());
058                }
059            }
060            tracks.addAll(other.tracks);
061            routes.addAll(other.routes);
062            waypoints.addAll(other.waypoints);
063        }
064    
065        public boolean hasTrackPoints() {
066            for (GpxTrack trk : tracks) {
067                for (GpxTrackSegment trkseg : trk.getSegments()) {
068                    if (!trkseg.getWayPoints().isEmpty())
069                        return true;
070                }
071            }
072            return false;
073        }
074    
075        public boolean hasRoutePoints() {
076            for (GpxRoute rte : routes) {
077                if (!rte.routePoints.isEmpty())
078                    return true;
079            }
080            return false;
081        }
082    
083        public boolean isEmpty() {
084            return !hasRoutePoints() && !hasTrackPoints() && waypoints.isEmpty();
085        }
086    
087        /**
088         * calculates the bounding box of available data and returns it.
089         * The bounds are not stored internally, but recalculated every time
090         * this function is called.
091         * 
092         * FIXME might perhaps use visitor pattern?
093         */
094        public Bounds recalculateBounds() {
095            Bounds bounds = null;
096            for (WayPoint wpt : waypoints) {
097                if (bounds == null) {
098                    bounds = new Bounds(wpt.getCoor());
099                } else {
100                    bounds.extend(wpt.getCoor());
101                }
102            }
103            for (GpxRoute rte : routes) {
104                for (WayPoint wpt : rte.routePoints) {
105                    if (bounds == null) {
106                        bounds = new Bounds(wpt.getCoor());
107                    } else {
108                        bounds.extend(wpt.getCoor());
109                    }
110                }
111            }
112            for (GpxTrack trk : tracks) {
113                Bounds trkBounds = trk.getBounds();
114                if (trkBounds != null) {
115                    if (bounds == null) {
116                        bounds = new Bounds(trkBounds);
117                    } else {
118                        bounds.extend(trkBounds);
119                    }
120                }
121            }
122            return bounds;
123        }
124    
125        /**
126         * calculates the sum of the lengths of all track segments
127         */
128        public double length(){
129            double result = 0.0; // in meters
130    
131            for (GpxTrack trk : tracks) {
132                result += trk.length();
133            }
134    
135            return result;
136        }
137    }