001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.gpx; 003 004import java.awt.Color; 005import java.time.Instant; 006import java.util.ArrayList; 007import java.util.Date; 008import java.util.HashMap; 009import java.util.List; 010import java.util.Objects; 011 012import org.openstreetmap.josm.data.coor.EastNorth; 013import org.openstreetmap.josm.data.coor.ILatLon; 014import org.openstreetmap.josm.data.coor.LatLon; 015import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match; 016import org.openstreetmap.josm.data.projection.Projecting; 017import org.openstreetmap.josm.tools.Logging; 018import org.openstreetmap.josm.tools.Utils; 019import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider; 020 021/** 022 * A point in the GPX data 023 * @since 12167 implements ILatLon 024 */ 025public class WayPoint extends WithAttributes implements Comparable<WayPoint>, TemplateEngineDataProvider, ILatLon { 026 027 /** 028 * The color to draw the segment before this point in 029 * @see #drawLine 030 */ 031 public Color customColoring; 032 033 /** 034 * <code>true</code> indicates that the line before this point should be drawn 035 */ 036 public boolean drawLine; 037 038 /** 039 * The direction of the line before this point. Used as cache to speed up drawing. Should not be relied on. 040 */ 041 public int dir; 042 043 /* 044 * We "inline" lat/lon, rather than using a LatLon internally => reduces memory overhead. Relevant 045 * because a lot of GPX waypoints are created when GPS tracks are downloaded from the OSM server. 046 */ 047 private final double lat; 048 private final double lon; 049 050 /* 051 * internal cache of projected coordinates 052 */ 053 private double east = Double.NaN; 054 private double north = Double.NaN; 055 private Object eastNorthCacheKey; 056 057 /** 058 * Constructs a new {@code WayPoint} from an existing one. 059 * 060 * Except for PT_TIME attribute, all attribute objects are shallow copied. 061 * This means modification of attr objects will affect original and new {@code WayPoint}. 062 * 063 * @param p existing waypoint 064 */ 065 public WayPoint(WayPoint p) { 066 attr = new HashMap<>(0); 067 attr.putAll(p.attr); 068 lat = p.lat; 069 lon = p.lon; 070 east = p.east; 071 north = p.north; 072 eastNorthCacheKey = p.eastNorthCacheKey; 073 customColoring = p.customColoring; 074 drawLine = p.drawLine; 075 dir = p.dir; 076 } 077 078 /** 079 * Constructs a new {@code WayPoint} from lat/lon coordinates. 080 * @param ll lat/lon coordinates 081 */ 082 public WayPoint(LatLon ll) { 083 attr = new HashMap<>(0); 084 lat = ll.lat(); 085 lon = ll.lon(); 086 } 087 088 /** 089 * Invalidate the internal cache of east/north coordinates. 090 */ 091 public void invalidateEastNorthCache() { 092 this.east = Double.NaN; 093 this.north = Double.NaN; 094 } 095 096 /** 097 * Returns the waypoint coordinates. 098 * @return the waypoint coordinates 099 */ 100 public final LatLon getCoor() { 101 return new LatLon(lat, lon); 102 } 103 104 @Override 105 public double lon() { 106 return lon; 107 } 108 109 @Override 110 public double lat() { 111 return lat; 112 } 113 114 @Override 115 public final EastNorth getEastNorth(Projecting projecting) { 116 Object newCacheKey = projecting.getCacheKey(); 117 if (Double.isNaN(east) || Double.isNaN(north) || !Objects.equals(newCacheKey, this.eastNorthCacheKey)) { 118 // projected coordinates haven't been calculated yet, 119 // so fill the cache of the projected waypoint coordinates 120 EastNorth en = projecting.latlon2eastNorth(this); 121 this.east = en.east(); 122 this.north = en.north(); 123 this.eastNorthCacheKey = newCacheKey; 124 } 125 return new EastNorth(east, north); 126 } 127 128 @Override 129 public String toString() { 130 return "WayPoint (" + (attr.containsKey(GPX_NAME) ? get(GPX_NAME) + ", " : "") + getCoor() + ", " + attr + ')'; 131 } 132 133 /** 134 * Sets the {@link #PT_TIME} attribute to the specified time. 135 * 136 * @param ts seconds from the epoch 137 * @since 13210 138 * @deprecated Use {@link #setInstant(Instant)} 139 */ 140 @Deprecated 141 public void setTime(long ts) { 142 setInstant(Instant.ofEpochSecond(ts)); 143 } 144 145 /** 146 * Sets the {@link #PT_TIME} attribute to the specified time. 147 * 148 * @param ts milliseconds from the epoch 149 * @since 14434 150 */ 151 public void setTimeInMillis(long ts) { 152 setInstant(Instant.ofEpochMilli(ts)); 153 } 154 155 /** 156 * Sets the {@link #PT_TIME} attribute to the specified time. 157 * 158 * @param instant the time to set 159 */ 160 public void setInstant(Instant instant) { 161 attr.put(PT_TIME, instant); 162 } 163 164 @Override 165 public int compareTo(WayPoint w) { 166 return Long.compare(getTimeInMillis(), w.getTimeInMillis()); 167 } 168 169 /** 170 * Returns the waypoint time in seconds since the epoch. 171 * 172 * @return the waypoint time 173 */ 174 public double getTime() { 175 return getTimeInMillis() / 1000.; 176 } 177 178 /** 179 * Returns the waypoint time in milliseconds since the epoch. 180 * 181 * @return the waypoint time 182 * @since 14456 183 */ 184 public long getTimeInMillis() { 185 Instant d = getInstant(); 186 return d == null ? 0 : d.toEpochMilli(); 187 } 188 189 /** 190 * Returns true if this waypoint has a time. 191 * 192 * @return true if a time is set, false otherwise 193 * @since 14456 194 */ 195 public boolean hasDate() { 196 return attr.get(PT_TIME) instanceof Instant; 197 } 198 199 /** 200 * Returns the waypoint time Date object. 201 * 202 * @return a copy of the Date object associated with this waypoint 203 * @since 14456 204 * @deprecated Use {@link #getInstant()} 205 */ 206 @Deprecated 207 public Date getDate() { 208 Instant instant = getInstant(); 209 return instant == null ? null : Date.from(instant); 210 } 211 212 /** 213 * Returns the waypoint instant. 214 * 215 * @return the instant associated with this waypoint 216 * @since 14456 217 */ 218 public Instant getInstant() { 219 if (attr != null) { 220 final Object obj = attr.get(PT_TIME); 221 222 if (obj instanceof Instant) { 223 return (Instant) obj; 224 } else if (obj == null) { 225 Logging.trace("Waypoint {0} value unset", PT_TIME); 226 } else { 227 Logging.warn("Unsupported waypoint {0} value: {1}", PT_TIME, obj); 228 } 229 } 230 231 return null; 232 } 233 234 @Override 235 public Object getTemplateValue(String name, boolean special) { 236 if (special) { 237 return null; 238 } else if ("desc".equals(name)) { 239 final Object value = get(name); 240 return value instanceof String ? Utils.stripHtml(((String) value)) : value; 241 } else { 242 return get(name); 243 } 244 } 245 246 @Override 247 public boolean evaluateCondition(Match condition) { 248 throw new UnsupportedOperationException(); 249 } 250 251 @Override 252 public List<String> getTemplateKeys() { 253 return new ArrayList<>(attr.keySet()); 254 } 255 256 @Override 257 public int hashCode() { 258 return Objects.hash(super.hashCode(), lat, lon, getTimeInMillis()); 259 } 260 261 @Override 262 public boolean equals(Object obj) { 263 if (this == obj) 264 return true; 265 if (obj == null || !super.equals(obj) || getClass() != obj.getClass()) 266 return false; 267 WayPoint other = (WayPoint) obj; 268 return Double.doubleToLongBits(lat) == Double.doubleToLongBits(other.lat) 269 && Double.doubleToLongBits(lon) == Double.doubleToLongBits(other.lon) 270 && getTimeInMillis() == other.getTimeInMillis(); 271 } 272}