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; 008import java.util.Objects; 009 010import org.openstreetmap.josm.data.coor.EastNorth; 011import org.openstreetmap.josm.data.coor.ILatLon; 012import org.openstreetmap.josm.data.coor.LatLon; 013import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match; 014import org.openstreetmap.josm.data.projection.Projecting; 015import org.openstreetmap.josm.tools.Logging; 016import org.openstreetmap.josm.tools.UncheckedParseException; 017import org.openstreetmap.josm.tools.date.DateUtils; 018import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider; 019 020/** 021 * A point in the GPX data 022 * @since 12167 implements ILatLon 023 */ 024public class WayPoint extends WithAttributes implements Comparable<WayPoint>, TemplateEngineDataProvider, ILatLon { 025 026 /** 027 * The seconds (not milliseconds!) since 1970-01-01 00:00 UTC 028 */ 029 public double time; 030 /** 031 * The color to draw the segment before this point in 032 * @see #drawLine 033 */ 034 public Color customColoring; 035 /** 036 * <code>true</code> indicates that the line before this point should be drawn 037 */ 038 public boolean drawLine; 039 /** 040 * The direction of the line before this point. Used as cache to speed up drawing. Should not be relied on. 041 */ 042 public int dir; 043 044 /** 045 * Constructs a new {@code WayPoint} from an existing one. 046 * @param p existing waypoint 047 */ 048 public WayPoint(WayPoint p) { 049 attr.putAll(p.attr); 050 lat = p.lat; 051 lon = p.lon; 052 east = p.east; 053 north = p.north; 054 eastNorthCacheKey = p.eastNorthCacheKey; 055 time = p.time; 056 customColoring = p.customColoring; 057 drawLine = p.drawLine; 058 dir = p.dir; 059 } 060 061 /** 062 * Constructs a new {@code WayPoint} from lat/lon coordinates. 063 * @param ll lat/lon coordinates 064 */ 065 public WayPoint(LatLon ll) { 066 lat = ll.lat(); 067 lon = ll.lon(); 068 } 069 070 /* 071 * We "inline" lat/lon, rather than usinga LatLon internally => reduces memory overhead. Relevant 072 * because a lot of GPX waypoints are created when GPS tracks are downloaded from the OSM server. 073 */ 074 private final double lat; 075 private final double lon; 076 077 /* 078 * internal cache of projected coordinates 079 */ 080 private double east = Double.NaN; 081 private double north = Double.NaN; 082 private Object eastNorthCacheKey; 083 084 /** 085 * Invalidate the internal cache of east/north coordinates. 086 */ 087 public void invalidateEastNorthCache() { 088 this.east = Double.NaN; 089 this.north = Double.NaN; 090 } 091 092 /** 093 * Returns the waypoint coordinates. 094 * @return the waypoint coordinates 095 */ 096 public final LatLon getCoor() { 097 return new LatLon(lat, lon); 098 } 099 100 @Override 101 public double lon() { 102 return lon; 103 } 104 105 @Override 106 public double lat() { 107 return lat; 108 } 109 110 @Override 111 public final EastNorth getEastNorth(Projecting projecting) { 112 Object newCacheKey = projecting.getCacheKey(); 113 if (Double.isNaN(east) || Double.isNaN(north) || !Objects.equals(newCacheKey, this.eastNorthCacheKey)) { 114 // projected coordinates haven't been calculated yet, 115 // so fill the cache of the projected waypoint coordinates 116 EastNorth en = projecting.latlon2eastNorth(this); 117 this.east = en.east(); 118 this.north = en.north(); 119 this.eastNorthCacheKey = newCacheKey; 120 } 121 return new EastNorth(east, north); 122 } 123 124 @Override 125 public String toString() { 126 return "WayPoint (" + (attr.containsKey(GPX_NAME) ? get(GPX_NAME) + ", " : "") + getCoor() + ", " + attr + ')'; 127 } 128 129 /** 130 * Sets the {@link #time} field as well as the {@link #PT_TIME} attribute to the specified time 131 * 132 * @param time the time to set 133 * @since 9383 134 */ 135 public void setTime(Date time) { 136 this.time = time.getTime() / 1000.; 137 this.attr.put(PT_TIME, DateUtils.fromDate(time)); 138 } 139 140 /** 141 * Convert the time stamp of the waypoint into seconds from the epoch 142 */ 143 public void setTime() { 144 setTimeFromAttribute(); 145 } 146 147 /** 148 * Set the the time stamp of the waypoint into seconds from the epoch, 149 * @param time millisecond from the epoch 150 * @since 13210 151 */ 152 public void setTime(long time) { 153 this.time = time / 1000.; 154 } 155 156 /** 157 * Convert the time stamp of the waypoint into seconds from the epoch 158 * @return The parsed time if successful, or {@code null} 159 * @since 9383 160 */ 161 public Date setTimeFromAttribute() { 162 if (attr.containsKey(PT_TIME)) { 163 try { 164 final Object obj = get(PT_TIME); 165 final Date time = obj instanceof Date ? (Date) obj : DateUtils.fromString(obj.toString()); 166 this.time = time.getTime() / 1000.; 167 return time; 168 } catch (UncheckedParseException e) { 169 Logging.warn(e); 170 time = 0; 171 } 172 } 173 return null; 174 } 175 176 @Override 177 public int compareTo(WayPoint w) { 178 return Double.compare(time, w.time); 179 } 180 181 /** 182 * Returns the waypoint time. 183 * @return the waypoint time 184 */ 185 public Date getTime() { 186 return new Date((long) (time * 1000)); 187 } 188 189 @Override 190 public Object getTemplateValue(String name, boolean special) { 191 if (!special) 192 return get(name); 193 else 194 return null; 195 } 196 197 @Override 198 public boolean evaluateCondition(Match condition) { 199 throw new UnsupportedOperationException(); 200 } 201 202 @Override 203 public List<String> getTemplateKeys() { 204 return new ArrayList<>(attr.keySet()); 205 } 206 207 @Override 208 public int hashCode() { 209 final int prime = 31; 210 int result = super.hashCode(); 211 long temp = Double.doubleToLongBits(lat); 212 result = prime * result + (int) (temp ^ (temp >>> 32)); 213 temp = Double.doubleToLongBits(lon); 214 result = prime * result + (int) (temp ^ (temp >>> 32)); 215 temp = Double.doubleToLongBits(time); 216 result = prime * result + (int) (temp ^ (temp >>> 32)); 217 return result; 218 } 219 220 @Override 221 public boolean equals(Object obj) { 222 if (this == obj) 223 return true; 224 if (obj == null || !super.equals(obj) || getClass() != obj.getClass()) 225 return false; 226 WayPoint other = (WayPoint) obj; 227 return Double.doubleToLongBits(lat) == Double.doubleToLongBits(other.lat) 228 && Double.doubleToLongBits(lon) == Double.doubleToLongBits(other.lon) 229 && Double.doubleToLongBits(time) == Double.doubleToLongBits(other.time); 230 } 231}