1 | // License: GPL. For details, see LICENSE file. |
---|
2 | package org.openstreetmap.josm.data.gpx; |
---|
3 | |
---|
4 | import java.awt.Color; |
---|
5 | import java.time.DateTimeException; |
---|
6 | import java.util.ArrayList; |
---|
7 | import java.util.Date; |
---|
8 | import java.util.List; |
---|
9 | import java.util.Objects; |
---|
10 | |
---|
11 | import org.openstreetmap.josm.data.coor.EastNorth; |
---|
12 | import org.openstreetmap.josm.data.coor.ILatLon; |
---|
13 | import org.openstreetmap.josm.data.coor.LatLon; |
---|
14 | import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match; |
---|
15 | import org.openstreetmap.josm.data.projection.Projecting; |
---|
16 | import org.openstreetmap.josm.tools.Logging; |
---|
17 | import org.openstreetmap.josm.tools.UncheckedParseException; |
---|
18 | import org.openstreetmap.josm.tools.date.DateUtils; |
---|
19 | import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider; |
---|
20 | |
---|
21 | /** |
---|
22 | * A point in the GPX data |
---|
23 | * @since 12167 implements ILatLon |
---|
24 | */ |
---|
25 | public class WayPoint extends WithAttributes implements Comparable<WayPoint>, TemplateEngineDataProvider, ILatLon { |
---|
26 | |
---|
27 | /** |
---|
28 | * The seconds (not milliseconds!) since 1970-01-01 00:00 UTC |
---|
29 | */ |
---|
30 | public double time; |
---|
31 | /** |
---|
32 | * The color to draw the segment before this point in |
---|
33 | * @see #drawLine |
---|
34 | */ |
---|
35 | public Color customColoring; |
---|
36 | /** |
---|
37 | * <code>true</code> indicates that the line before this point should be drawn |
---|
38 | */ |
---|
39 | public boolean drawLine; |
---|
40 | /** |
---|
41 | * The direction of the line before this point. Used as cache to speed up drawing. Should not be relied on. |
---|
42 | */ |
---|
43 | public int dir; |
---|
44 | |
---|
45 | /** |
---|
46 | * Constructs a new {@code WayPoint} from an existing one. |
---|
47 | * @param p existing waypoint |
---|
48 | */ |
---|
49 | public WayPoint(WayPoint p) { |
---|
50 | attr.putAll(p.attr); |
---|
51 | lat = p.lat; |
---|
52 | lon = p.lon; |
---|
53 | east = p.east; |
---|
54 | north = p.north; |
---|
55 | eastNorthCacheKey = p.eastNorthCacheKey; |
---|
56 | time = p.time; |
---|
57 | customColoring = p.customColoring; |
---|
58 | drawLine = p.drawLine; |
---|
59 | dir = p.dir; |
---|
60 | } |
---|
61 | |
---|
62 | /** |
---|
63 | * Constructs a new {@code WayPoint} from lat/lon coordinates. |
---|
64 | * @param ll lat/lon coordinates |
---|
65 | */ |
---|
66 | public WayPoint(LatLon ll) { |
---|
67 | lat = ll.lat(); |
---|
68 | lon = ll.lon(); |
---|
69 | } |
---|
70 | |
---|
71 | /* |
---|
72 | * We "inline" lat/lon, rather than usinga LatLon internally => reduces memory overhead. Relevant |
---|
73 | * because a lot of GPX waypoints are created when GPS tracks are downloaded from the OSM server. |
---|
74 | */ |
---|
75 | private final double lat; |
---|
76 | private final double lon; |
---|
77 | |
---|
78 | /* |
---|
79 | * internal cache of projected coordinates |
---|
80 | */ |
---|
81 | private double east = Double.NaN; |
---|
82 | private double north = Double.NaN; |
---|
83 | private Object eastNorthCacheKey; |
---|
84 | |
---|
85 | /** |
---|
86 | * Invalidate the internal cache of east/north coordinates. |
---|
87 | */ |
---|
88 | public void invalidateEastNorthCache() { |
---|
89 | this.east = Double.NaN; |
---|
90 | this.north = Double.NaN; |
---|
91 | } |
---|
92 | |
---|
93 | /** |
---|
94 | * Returns the waypoint coordinates. |
---|
95 | * @return the waypoint coordinates |
---|
96 | */ |
---|
97 | public final LatLon getCoor() { |
---|
98 | return new LatLon(lat, lon); |
---|
99 | } |
---|
100 | |
---|
101 | @Override |
---|
102 | public double lon() { |
---|
103 | return lon; |
---|
104 | } |
---|
105 | |
---|
106 | @Override |
---|
107 | public double lat() { |
---|
108 | return lat; |
---|
109 | } |
---|
110 | |
---|
111 | @Override |
---|
112 | public final EastNorth getEastNorth(Projecting projecting) { |
---|
113 | Object newCacheKey = projecting.getCacheKey(); |
---|
114 | if (Double.isNaN(east) || Double.isNaN(north) || !Objects.equals(newCacheKey, this.eastNorthCacheKey)) { |
---|
115 | // projected coordinates haven't been calculated yet, |
---|
116 | // so fill the cache of the projected waypoint coordinates |
---|
117 | EastNorth en = projecting.latlon2eastNorth(this); |
---|
118 | this.east = en.east(); |
---|
119 | this.north = en.north(); |
---|
120 | this.eastNorthCacheKey = newCacheKey; |
---|
121 | } |
---|
122 | return new EastNorth(east, north); |
---|
123 | } |
---|
124 | |
---|
125 | @Override |
---|
126 | public String toString() { |
---|
127 | return "WayPoint (" + (attr.containsKey(GPX_NAME) ? get(GPX_NAME) + ", " : "") + getCoor() + ", " + attr + ')'; |
---|
128 | } |
---|
129 | |
---|
130 | /** |
---|
131 | * Sets the {@link #time} field as well as the {@link #PT_TIME} attribute to the specified time. |
---|
132 | * |
---|
133 | * @param time the time to set |
---|
134 | * @since 9383 |
---|
135 | */ |
---|
136 | public void setTime(Date time) { |
---|
137 | this.time = time.getTime() / 1000.; |
---|
138 | this.attr.put(PT_TIME, DateUtils.fromDate(time)); |
---|
139 | } |
---|
140 | |
---|
141 | /** |
---|
142 | * Convert the time stamp of the waypoint into seconds from the epoch. |
---|
143 | * |
---|
144 | * @deprecated call {@link #setTimeFromAttribute()} directly if you need this |
---|
145 | */ |
---|
146 | @Deprecated |
---|
147 | public void setTime() { |
---|
148 | setTimeFromAttribute(); |
---|
149 | } |
---|
150 | |
---|
151 | /** |
---|
152 | * Sets the {@link #time} field as well as the {@link #PT_TIME} attribute to the specified time. |
---|
153 | * |
---|
154 | * @param ts seconds from the epoch |
---|
155 | * @since 13210 |
---|
156 | */ |
---|
157 | public void setTime(long ts) { |
---|
158 | this.time = ts; |
---|
159 | this.attr.put(PT_TIME, DateUtils.fromTimestamp(ts)); |
---|
160 | } |
---|
161 | |
---|
162 | /** |
---|
163 | * Sets the {@link #time} field as well as the {@link #PT_TIME} attribute to the specified time. |
---|
164 | * |
---|
165 | * @param ts milliseconds from the epoch |
---|
166 | * @since 14434 |
---|
167 | */ |
---|
168 | public void setTimeInMillis(long ts) { |
---|
169 | this.time = ts / 1000.; |
---|
170 | this.attr.put(PT_TIME, DateUtils.fromTimestampInMillis(ts)); |
---|
171 | } |
---|
172 | |
---|
173 | /** |
---|
174 | * Convert the time stamp of the waypoint into seconds from the epoch |
---|
175 | * @return The parsed time if successful, or {@code null} |
---|
176 | * @since 9383 |
---|
177 | */ |
---|
178 | public Date setTimeFromAttribute() { |
---|
179 | if (attr.containsKey(PT_TIME)) { |
---|
180 | try { |
---|
181 | final Object obj = get(PT_TIME); |
---|
182 | final Date date = obj instanceof Date ? (Date) obj : DateUtils.fromString(obj.toString()); |
---|
183 | time = date.getTime() / 1000.; |
---|
184 | return date; |
---|
185 | } catch (UncheckedParseException | DateTimeException e) { |
---|
186 | Logging.warn(e); |
---|
187 | time = 0; |
---|
188 | } |
---|
189 | } |
---|
190 | return null; |
---|
191 | } |
---|
192 | |
---|
193 | @Override |
---|
194 | public int compareTo(WayPoint w) { |
---|
195 | return Double.compare(time, w.time); |
---|
196 | } |
---|
197 | |
---|
198 | /** |
---|
199 | * Returns the waypoint time. |
---|
200 | * @return the waypoint time |
---|
201 | */ |
---|
202 | public Date getTime() { |
---|
203 | return new Date((long) (time * 1000)); |
---|
204 | } |
---|
205 | |
---|
206 | @Override |
---|
207 | public Object getTemplateValue(String name, boolean special) { |
---|
208 | if (!special) |
---|
209 | return get(name); |
---|
210 | else |
---|
211 | return null; |
---|
212 | } |
---|
213 | |
---|
214 | @Override |
---|
215 | public boolean evaluateCondition(Match condition) { |
---|
216 | throw new UnsupportedOperationException(); |
---|
217 | } |
---|
218 | |
---|
219 | @Override |
---|
220 | public List<String> getTemplateKeys() { |
---|
221 | return new ArrayList<>(attr.keySet()); |
---|
222 | } |
---|
223 | |
---|
224 | @Override |
---|
225 | public int hashCode() { |
---|
226 | final int prime = 31; |
---|
227 | int result = super.hashCode(); |
---|
228 | long temp = Double.doubleToLongBits(lat); |
---|
229 | result = prime * result + (int) (temp ^ (temp >>> 32)); |
---|
230 | temp = Double.doubleToLongBits(lon); |
---|
231 | result = prime * result + (int) (temp ^ (temp >>> 32)); |
---|
232 | temp = Double.doubleToLongBits(time); |
---|
233 | result = prime * result + (int) (temp ^ (temp >>> 32)); |
---|
234 | return result; |
---|
235 | } |
---|
236 | |
---|
237 | @Override |
---|
238 | public boolean equals(Object obj) { |
---|
239 | if (this == obj) |
---|
240 | return true; |
---|
241 | if (obj == null || !super.equals(obj) || getClass() != obj.getClass()) |
---|
242 | return false; |
---|
243 | WayPoint other = (WayPoint) obj; |
---|
244 | return Double.doubleToLongBits(lat) == Double.doubleToLongBits(other.lat) |
---|
245 | && Double.doubleToLongBits(lon) == Double.doubleToLongBits(other.lon) |
---|
246 | && Double.doubleToLongBits(time) == Double.doubleToLongBits(other.time); |
---|
247 | } |
---|
248 | } |
---|