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.UncheckedParseException;
015import org.openstreetmap.josm.tools.date.DateUtils;
016import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider;
017
018public class WayPoint extends WithAttributes implements Comparable<WayPoint>, TemplateEngineDataProvider {
019
020    /**
021     * The seconds (not milliseconds!) since 1970-01-01 00:00 UTC
022     */
023    public double time;
024    public Color customColoring;
025    public boolean drawLine;
026    public int dir;
027
028    public WayPoint(WayPoint p) {
029        attr.putAll(p.attr);
030        lat = p.lat;
031        lon = p.lon;
032        east = p.east;
033        north = p.north;
034        time = p.time;
035        customColoring = p.customColoring;
036        drawLine = p.drawLine;
037        dir = p.dir;
038    }
039
040    public WayPoint(LatLon ll) {
041        lat = ll.lat();
042        lon = ll.lon();
043    }
044
045    /*
046     * We "inline" lat/lon, rather than usinga LatLon internally => reduces memory overhead. Relevant
047     * because a lot of GPX waypoints are created when GPS tracks are downloaded from the OSM server.
048     */
049    private final double lat;
050    private final double lon;
051
052    /*
053     * internal cache of projected coordinates
054     */
055    private double east = Double.NaN;
056    private double north = Double.NaN;
057
058    /**
059     * Invalidate the internal cache of east/north coordinates.
060     */
061    public void invalidateEastNorthCache() {
062        this.east = Double.NaN;
063        this.north = Double.NaN;
064    }
065
066    public final LatLon getCoor() {
067        return new LatLon(lat, lon);
068    }
069
070    /**
071     * <p>Replies the projected east/north coordinates.</p>
072     *
073     * <p>Uses the {@link Main#getProjection() global projection} to project the lan/lon-coordinates.
074     * Internally caches the projected coordinates.</p>
075     *
076     * <p><strong>Caveat:</strong> doesn't listen to projection changes. Clients must
077     * {@link #invalidateEastNorthCache() invalidate the internal cache}.</p>
078     *
079     * @return the east north coordinates or {@code null}
080     * @see #invalidateEastNorthCache()
081     */
082    public final EastNorth getEastNorth() {
083        if (Double.isNaN(east) || Double.isNaN(north)) {
084            // projected coordinates haven't been calculated yet,
085            // so fill the cache of the projected waypoint coordinates
086            EastNorth en = Projections.project(new LatLon(lat, lon));
087            this.east = en.east();
088            this.north = en.north();
089        }
090        return new EastNorth(east, north);
091    }
092
093    @Override
094    public String toString() {
095        return "WayPoint (" + (attr.containsKey(GPX_NAME) ? get(GPX_NAME) + ", " : "") + getCoor() + ", " + attr + ')';
096    }
097
098    /**
099     * Sets the {@link #time} field as well as the {@link #PT_TIME} attribute to the specified time
100     *
101     * @param time the time to set
102     * @since 9383
103     */
104    public void setTime(Date time) {
105        this.time = time.getTime() / 1000.;
106        this.attr.put(PT_TIME, DateUtils.fromDate(time));
107    }
108
109    /**
110     * Convert the time stamp of the waypoint into seconds from the epoch
111     */
112    public void setTime() {
113        setTimeFromAttribute();
114    }
115
116    /**
117     * Convert the time stamp of the waypoint into seconds from the epoch
118     * @return The parsed time if successful, or {@code null}
119     * @since 9383
120     */
121    public Date setTimeFromAttribute() {
122        if (attr.containsKey(PT_TIME)) {
123            try {
124                final Date time = DateUtils.fromString(get(PT_TIME).toString());
125                this.time = time.getTime() / 1000.;
126                return time;
127            } catch (UncheckedParseException e) {
128                Main.warn(e);
129                time = 0;
130            }
131        }
132        return null;
133    }
134
135    @Override
136    public int compareTo(WayPoint w) {
137        return Double.compare(time, w.time);
138    }
139
140    public Date getTime() {
141        return new Date((long) (time * 1000));
142    }
143
144    @Override
145    public Object getTemplateValue(String name, boolean special) {
146        if (!special)
147            return get(name);
148        else
149            return null;
150    }
151
152    @Override
153    public boolean evaluateCondition(Match condition) {
154        throw new UnsupportedOperationException();
155    }
156
157    @Override
158    public List<String> getTemplateKeys() {
159        return new ArrayList<>(attr.keySet());
160    }
161
162    @Override
163    public int hashCode() {
164        final int prime = 31;
165        int result = super.hashCode();
166        long temp = Double.doubleToLongBits(lat);
167        result = prime * result + (int) (temp ^ (temp >>> 32));
168        temp = Double.doubleToLongBits(lon);
169        result = prime * result + (int) (temp ^ (temp >>> 32));
170        temp = Double.doubleToLongBits(time);
171        result = prime * result + (int) (temp ^ (temp >>> 32));
172        return result;
173    }
174
175    @Override
176    public boolean equals(Object obj) {
177        if (this == obj)
178            return true;
179        if (!super.equals(obj))
180            return false;
181        if (getClass() != obj.getClass())
182            return false;
183        WayPoint other = (WayPoint) obj;
184        if (Double.doubleToLongBits(lat) != Double.doubleToLongBits(other.lat))
185            return false;
186        if (Double.doubleToLongBits(lon) != Double.doubleToLongBits(other.lon))
187            return false;
188        if (Double.doubleToLongBits(time) != Double.doubleToLongBits(other.time))
189            return false;
190        return true;
191    }
192}