001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.osm; 003 004import org.openstreetmap.josm.Main; 005import org.openstreetmap.josm.data.coor.EastNorth; 006import org.openstreetmap.josm.data.coor.LatLon; 007import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor; 008import org.openstreetmap.josm.data.osm.visitor.Visitor; 009import org.openstreetmap.josm.data.projection.Projections; 010 011/** 012 * One node data, consisting of one world coordinate waypoint. 013 * 014 * @author imi 015 */ 016public final class Node extends OsmPrimitive implements INode { 017 018 /* 019 * We "inline" lat/lon rather than using a LatLon-object => reduces memory footprint 020 */ 021 private double lat = Double.NaN; 022 private double lon = Double.NaN; 023 024 /* 025 * the cached projected coordinates 026 */ 027 private double east = Double.NaN; 028 private double north = Double.NaN; 029 030 private boolean isLatLonKnown() { 031 return !Double.isNaN(lat) && !Double.isNaN(lon); 032 } 033 034 @Override 035 public final void setCoor(LatLon coor) { 036 updateCoor(coor, null); 037 } 038 039 @Override 040 public final void setEastNorth(EastNorth eastNorth) { 041 updateCoor(null, eastNorth); 042 } 043 044 private void updateCoor(LatLon coor, EastNorth eastNorth) { 045 if (getDataSet() != null) { 046 boolean locked = writeLock(); 047 try { 048 getDataSet().fireNodeMoved(this, coor, eastNorth); 049 } finally { 050 writeUnlock(locked); 051 } 052 } else { 053 setCoorInternal(coor, eastNorth); 054 } 055 } 056 057 @Override 058 public final LatLon getCoor() { 059 if (!isLatLonKnown()) return null; 060 return new LatLon(lat,lon); 061 } 062 063 /** 064 * <p>Replies the projected east/north coordinates.</p> 065 * 066 * <p>Uses the {@link Main#getProjection() global projection} to project the lan/lon-coordinates. 067 * Internally caches the projected coordinates.</p> 068 * 069 * <p><strong>Caveat:</strong> doesn't listen to projection changes. Clients must 070 * {@link #invalidateEastNorthCache() invalidate the internal cache}.</p> 071 * 072 * <p>Replies {@code null} if this node doesn't know lat/lon-coordinates, i.e. because it is an incomplete node. 073 * 074 * @return the east north coordinates or {@code null} 075 * @see #invalidateEastNorthCache() 076 * 077 */ 078 @Override 079 public final EastNorth getEastNorth() { 080 if (!isLatLonKnown()) return null; 081 082 if (getDataSet() == null) 083 // there is no dataset that listens for projection changes 084 // and invalidates the cache, so we don't use the cache at all 085 return Projections.project(new LatLon(lat, lon)); 086 087 if (Double.isNaN(east) || Double.isNaN(north)) { 088 // projected coordinates haven't been calculated yet, 089 // so fill the cache of the projected node coordinates 090 EastNorth en = Projections.project(new LatLon(lat, lon)); 091 this.east = en.east(); 092 this.north = en.north(); 093 } 094 return new EastNorth(east, north); 095 } 096 097 /** 098 * To be used only by Dataset.reindexNode 099 */ 100 protected void setCoorInternal(LatLon coor, EastNorth eastNorth) { 101 if (coor != null) { 102 this.lat = coor.lat(); 103 this.lon = coor.lon(); 104 invalidateEastNorthCache(); 105 } else if (eastNorth != null) { 106 LatLon ll = Projections.inverseProject(eastNorth); 107 this.lat = ll.lat(); 108 this.lon = ll.lon(); 109 this.east = eastNorth.east(); 110 this.north = eastNorth.north(); 111 } else { 112 this.lat = Double.NaN; 113 this.lon = Double.NaN; 114 invalidateEastNorthCache(); 115 if (isVisible()) { 116 setIncomplete(true); 117 } 118 } 119 } 120 121 protected Node(long id, boolean allowNegative) { 122 super(id, allowNegative); 123 } 124 125 /** 126 * Constructs a new local {@code Node} with id 0. 127 */ 128 public Node() { 129 this(0, false); 130 } 131 132 /** 133 * Constructs an incomplete {@code Node} object with the given id. 134 * @param id The id. Must be >= 0 135 * @throws IllegalArgumentException if id < 0 136 */ 137 public Node(long id) throws IllegalArgumentException { 138 super(id, false); 139 } 140 141 /** 142 * Constructs a new {@code Node} with the given id and version. 143 * @param id The id. Must be >= 0 144 * @param version The version 145 * @throws IllegalArgumentException if id < 0 146 */ 147 public Node(long id, int version) throws IllegalArgumentException { 148 super(id, version, false); 149 } 150 151 /** 152 * Constructs an identical clone of the argument. 153 * @param clone The node to clone 154 * @param clearMetadata If {@code true}, clears the OSM id and other metadata as defined by {@link #clearOsmMetadata}. If {@code false}, does nothing 155 */ 156 public Node(Node clone, boolean clearMetadata) { 157 super(clone.getUniqueId(), true /* allow negative IDs */); 158 cloneFrom(clone); 159 if (clearMetadata) { 160 clearOsmMetadata(); 161 } 162 } 163 164 /** 165 * Constructs an identical clone of the argument (including the id). 166 * @param clone The node to clone, including its id 167 */ 168 public Node(Node clone) { 169 this(clone, false); 170 } 171 172 /** 173 * Constructs a new {@code Node} with the given lat/lon with id 0. 174 * @param latlon The {@link LatLon} coordinates 175 */ 176 public Node(LatLon latlon) { 177 super(0, false); 178 setCoor(latlon); 179 } 180 181 /** 182 * Constructs a new {@code Node} with the given east/north with id 0. 183 * @param eastNorth The {@link EastNorth} coordinates 184 */ 185 public Node(EastNorth eastNorth) { 186 super(0, false); 187 setEastNorth(eastNorth); 188 } 189 190 @Override 191 void setDataset(DataSet dataSet) { 192 super.setDataset(dataSet); 193 if (!isIncomplete() && isVisible() && (getCoor() == null || getEastNorth() == null)) 194 throw new DataIntegrityProblemException("Complete node with null coordinates: " + toString()); 195 } 196 197 @Override 198 public void accept(Visitor visitor) { 199 visitor.visit(this); 200 } 201 202 @Override 203 public void accept(PrimitiveVisitor visitor) { 204 visitor.visit(this); 205 } 206 207 @Override 208 public void cloneFrom(OsmPrimitive osm) { 209 boolean locked = writeLock(); 210 try { 211 super.cloneFrom(osm); 212 setCoor(((Node)osm).getCoor()); 213 } finally { 214 writeUnlock(locked); 215 } 216 } 217 218 /** 219 * Merges the technical and semantical attributes from <code>other</code> onto this. 220 * 221 * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code> 222 * have an assigend OSM id, the IDs have to be the same. 223 * 224 * @param other the other primitive. Must not be null. 225 * @throws IllegalArgumentException thrown if other is null. 226 * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not 227 * @throws DataIntegrityProblemException thrown if other is new and other.getId() != this.getId() 228 */ 229 @Override 230 public void mergeFrom(OsmPrimitive other) { 231 boolean locked = writeLock(); 232 try { 233 super.mergeFrom(other); 234 if (!other.isIncomplete()) { 235 setCoor(((Node)other).getCoor()); 236 } 237 } finally { 238 writeUnlock(locked); 239 } 240 } 241 242 @Override public void load(PrimitiveData data) { 243 boolean locked = writeLock(); 244 try { 245 super.load(data); 246 setCoor(((NodeData)data).getCoor()); 247 } finally { 248 writeUnlock(locked); 249 } 250 } 251 252 @Override public NodeData save() { 253 NodeData data = new NodeData(); 254 saveCommonAttributes(data); 255 if (!isIncomplete()) { 256 data.setCoor(getCoor()); 257 } 258 return data; 259 } 260 261 @Override 262 public String toString() { 263 String coorDesc = isLatLonKnown() ? "lat="+lat+",lon="+lon : ""; 264 return "{Node id=" + getUniqueId() + " version=" + getVersion() + " " + getFlagsAsString() + " " + coorDesc+"}"; 265 } 266 267 @Override 268 public boolean hasEqualSemanticAttributes(OsmPrimitive other) { 269 if (!(other instanceof Node)) 270 return false; 271 if (! super.hasEqualSemanticAttributes(other)) 272 return false; 273 Node n = (Node)other; 274 LatLon coor = getCoor(); 275 LatLon otherCoor = n.getCoor(); 276 if (coor == null && otherCoor == null) 277 return true; 278 else if (coor != null && otherCoor != null) 279 return coor.equalsEpsilon(otherCoor); 280 else 281 return false; 282 } 283 284 @Override 285 public int compareTo(OsmPrimitive o) { 286 return o instanceof Node ? Long.valueOf(getUniqueId()).compareTo(o.getUniqueId()) : 1; 287 } 288 289 @Override 290 public String getDisplayName(NameFormatter formatter) { 291 return formatter.format(this); 292 } 293 294 @Override 295 public OsmPrimitiveType getType() { 296 return OsmPrimitiveType.NODE; 297 } 298 299 @Override 300 public BBox getBBox() { 301 return new BBox(this); 302 } 303 304 @Override 305 public void updatePosition() { 306 } 307 308 @Override 309 public boolean isDrawable() { 310 // Not possible to draw a node without coordinates. 311 return super.isDrawable() && isLatLonKnown(); 312 } 313 314 /** 315 * Check whether this node connects 2 ways. 316 * 317 * @return true if isReferredByWays(2) returns true 318 * @see #isReferredByWays(int) 319 */ 320 public boolean isConnectionNode() { 321 return isReferredByWays(2); 322 } 323 324 /** 325 * Invoke to invalidate the internal cache of projected east/north coordinates. 326 * Coordinates are reprojected on demand when the {@link #getEastNorth()} is invoked 327 * next time. 328 */ 329 public void invalidateEastNorthCache() { 330 this.east = Double.NaN; 331 this.north = Double.NaN; 332 } 333 334 @Override 335 public boolean concernsArea() { 336 // A node cannot be an area 337 return false; 338 } 339}