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