001    //License: GPLv2 or later
002    //Copyright 2007 by Raphael Mack and others
003    
004    package org.openstreetmap.josm.data.gpx;
005    
006    import java.awt.Color;
007    import java.util.ArrayList;
008    import java.util.Date;
009    import java.util.List;
010    
011    import org.openstreetmap.josm.Main;
012    import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
013    import org.openstreetmap.josm.data.coor.EastNorth;
014    import org.openstreetmap.josm.data.coor.LatLon;
015    import org.openstreetmap.josm.data.projection.Projections;
016    import org.openstreetmap.josm.tools.PrimaryDateParser;
017    import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider;
018    
019    public class WayPoint extends WithAttributes implements Comparable<WayPoint>, TemplateEngineDataProvider {
020    
021        private static ThreadLocal<PrimaryDateParser> dateParser = new ThreadLocal<PrimaryDateParser>() {
022            @Override protected PrimaryDateParser initialValue() {
023                return new PrimaryDateParser();
024            }
025        };
026    
027        public double time;
028        public Color customColoring;
029        public boolean drawLine;
030        public int dir;
031    
032        public WayPoint(WayPoint p) {
033            attr.putAll(p.attr);
034            lat = p.lat;
035            lon = p.lon;
036            east = p.east;
037            north = p.north;
038            time = p.time;
039            customColoring = p.customColoring;
040            drawLine = p.drawLine;
041            dir = p.dir;
042        }
043    
044        public WayPoint(LatLon ll) {
045            lat = ll.lat();
046            lon = ll.lon();
047        }
048    
049        /*
050         * We "inline" lat/lon, rather than usinga LatLon internally => reduces memory overhead. Relevant
051         * because a lot of GPX waypoints are created when GPS tracks are downloaded from the OSM server.
052         */
053        private double lat = 0;
054        private double lon = 0;
055    
056        /*
057         * internal cache of projected coordinates
058         */
059        private double east = Double.NaN;
060        private double north = Double.NaN;
061    
062        /**
063         * Invalidate the internal cache of east/north coordinates.
064         */
065        public void invalidateEastNorthCache() {
066            this.east = Double.NaN;
067            this.north = Double.NaN;
068        }
069    
070        public final LatLon getCoor() {
071            return new LatLon(lat,lon);
072        }
073    
074        /**
075         * <p>Replies the projected east/north coordinates.</p>
076         *
077         * <p>Uses the {@link Main#getProjection() global projection} to project the lan/lon-coordinates.
078         * Internally caches the projected coordinates.</p>
079         *
080         * <p><strong>Caveat:</strong> doesn't listen to projection changes. Clients must
081         * {@link #reproject() trigger a reprojection} or {@link #invalidateEastNorthCache() invalidate the internal cache}.</p>
082         *
083         * @return the east north coordinates or {@code null}
084         * @see #invalidateEastNorthCache()
085         *
086         */
087        public final EastNorth getEastNorth() {
088            if (Double.isNaN(east) || Double.isNaN(north)) {
089                // projected coordinates haven't been calculated yet,
090                // so fill the cache of the projected waypoint coordinates
091                EastNorth en = Projections.project(new LatLon(lat, lon));
092                this.east = en.east();
093                this.north = en.north();
094            }
095            return new EastNorth(east, north);
096        }
097    
098        @Override
099        public String toString() {
100            return "WayPoint (" + (attr.containsKey("name") ? attr.get("name") + ", " :"") + getCoor().toString() + ", " + attr + ")";
101        }
102    
103        /**
104         * Convert the time stamp of the waypoint into seconds from the epoch
105         */
106        public void setTime() {
107            if(attr.containsKey("time")) {
108                try {
109                    time = dateParser.get().parse(attr.get("time").toString()).getTime() / 1000.; /* ms => seconds */
110                } catch(Exception e) {
111                    time = 0;
112                }
113            }
114        }
115    
116        public int compareTo(WayPoint w) {
117            return Double.compare(time, w.time);
118        }
119    
120        public Date getTime() {
121            return new Date((long) (time * 1000));
122        }
123    
124        @Override
125        public Object getTemplateValue(String name, boolean special) {
126            if (!special)
127                return attr.get(name);
128            else
129                return null;
130        }
131    
132        @Override
133        public boolean evaluateCondition(Match condition) {
134            throw new UnsupportedOperationException();
135        }
136    
137        @Override
138        public List<String> getTemplateKeys() {
139            return new ArrayList<String>(attr.keySet());
140        }
141    }