001 // License: GPL. Copyright 2007 by Immanuel Scholz and others 002 package org.openstreetmap.josm.data.osm; 003 004 import org.openstreetmap.josm.Main; 005 import org.openstreetmap.josm.data.coor.EastNorth; 006 import org.openstreetmap.josm.data.coor.LatLon; 007 import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor; 008 import org.openstreetmap.josm.data.osm.visitor.Visitor; 009 import org.openstreetmap.josm.data.projection.Projections; 010 011 /** 012 * One node data, consisting of one world coordinate waypoint. 013 * 014 * @author imi 015 */ 016 public 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 } 116 } 117 118 protected Node(long id, boolean allowNegative) { 119 super(id, allowNegative); 120 } 121 122 /** 123 * Constructs a new local {@code Node} with id 0. 124 */ 125 public Node() { 126 this(0, false); 127 } 128 129 /** 130 * Constructs an incomplete {@code Node} object with the given id. 131 * @param id The id. Must be >= 0 132 * @throws IllegalArgumentException if id < 0 133 */ 134 public Node(long id) throws IllegalArgumentException { 135 super(id, false); 136 } 137 138 /** 139 * Constructs a new {@code Node} with the given id and version. 140 * @param id The id. Must be >= 0 141 * @param version The version 142 * @throws IllegalArgumentException if id < 0 143 */ 144 public Node(long id, int version) throws IllegalArgumentException { 145 super(id, version, false); 146 } 147 148 /** 149 * Constructs an identical clone of the argument. 150 * @param clone The node to clone 151 * @param clearId If true, set version to 0 and id to new unique value 152 */ 153 public Node(Node clone, boolean clearId) { 154 super(clone.getUniqueId(), true /* allow negative IDs */); 155 cloneFrom(clone); 156 if (clearId) { 157 clearOsmId(); 158 } 159 } 160 161 /** 162 * Constructs an identical clone of the argument (including the id). 163 * @param clone The node to clone, including its id 164 */ 165 public Node(Node clone) { 166 this(clone, false); 167 } 168 169 /** 170 * Constructs a new {@code Node} with the given lat/lon with id 0. 171 * @param latlon The {@link LatLon} coordinates 172 */ 173 public Node(LatLon latlon) { 174 super(0, false); 175 setCoor(latlon); 176 } 177 178 /** 179 * Constructs a new {@code Node} with the given east/north with id 0. 180 * @param eastNorth The {@link EastNorth} coordinates 181 */ 182 public Node(EastNorth eastNorth) { 183 super(0, false); 184 setEastNorth(eastNorth); 185 } 186 187 @Override 188 void setDataset(DataSet dataSet) { 189 super.setDataset(dataSet); 190 if (!isIncomplete() && isVisible() && (getCoor() == null || getEastNorth() == null)) 191 throw new DataIntegrityProblemException("Complete node with null coordinates: " + toString() + get3892DebugInfo()); 192 } 193 194 @Override 195 public void visit(Visitor visitor) { 196 visitor.visit(this); 197 } 198 199 @Override 200 public void visit(PrimitiveVisitor visitor) { 201 visitor.visit(this); 202 } 203 204 @Override 205 public void cloneFrom(OsmPrimitive osm) { 206 boolean locked = writeLock(); 207 try { 208 super.cloneFrom(osm); 209 setCoor(((Node)osm).getCoor()); 210 } finally { 211 writeUnlock(locked); 212 } 213 } 214 215 /** 216 * Merges the technical and semantical attributes from <code>other</code> onto this. 217 * 218 * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code> 219 * have an assigend OSM id, the IDs have to be the same. 220 * 221 * @param other the other primitive. Must not be null. 222 * @throws IllegalArgumentException thrown if other is null. 223 * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not 224 * @throws DataIntegrityProblemException thrown if other is new and other.getId() != this.getId() 225 */ 226 @Override 227 public void mergeFrom(OsmPrimitive other) { 228 boolean locked = writeLock(); 229 try { 230 super.mergeFrom(other); 231 if (!other.isIncomplete()) { 232 setCoor(((Node)other).getCoor()); 233 } 234 } finally { 235 writeUnlock(locked); 236 } 237 } 238 239 @Override public void load(PrimitiveData data) { 240 boolean locked = writeLock(); 241 try { 242 super.load(data); 243 setCoor(((NodeData)data).getCoor()); 244 } finally { 245 writeUnlock(locked); 246 } 247 } 248 249 @Override public NodeData save() { 250 NodeData data = new NodeData(); 251 saveCommonAttributes(data); 252 if (!isIncomplete()) { 253 data.setCoor(getCoor()); 254 } 255 return data; 256 } 257 258 @Override 259 public String toString() { 260 String coorDesc = isLatLonKnown() ? "lat="+lat+",lon="+lon : ""; 261 return "{Node id=" + getUniqueId() + " version=" + getVersion() + " " + getFlagsAsString() + " " + coorDesc+"}"; 262 } 263 264 @Override 265 public boolean hasEqualSemanticAttributes(OsmPrimitive other) { 266 if (other == null || ! (other instanceof Node) ) 267 return false; 268 if (! super.hasEqualSemanticAttributes(other)) 269 return false; 270 Node n = (Node)other; 271 LatLon coor = getCoor(); 272 LatLon otherCoor = n.getCoor(); 273 if (coor == null && otherCoor == null) 274 return true; 275 else if (coor != null && otherCoor != null) 276 return coor.equalsEpsilon(otherCoor); 277 else 278 return false; 279 } 280 281 @Override 282 public int compareTo(OsmPrimitive o) { 283 return o instanceof Node ? Long.valueOf(getUniqueId()).compareTo(o.getUniqueId()) : 1; 284 } 285 286 @Override 287 public String getDisplayName(NameFormatter formatter) { 288 return formatter.format(this); 289 } 290 291 @Override 292 public OsmPrimitiveType getType() { 293 return OsmPrimitiveType.NODE; 294 } 295 296 @Override 297 public BBox getBBox() { 298 return new BBox(this); 299 } 300 301 @Override 302 public void updatePosition() { 303 } 304 305 @Override 306 public boolean isDrawable() { 307 // Not possible to draw a node without coordinates. 308 return super.isDrawable() && isLatLonKnown(); 309 } 310 311 /** 312 * Check whether this node connects 2 ways. 313 * 314 * @return true if isReferredByWays(2) returns true 315 * @see #isReferredByWays(int) 316 */ 317 public boolean isConnectionNode() { 318 return isReferredByWays(2); 319 } 320 321 /** 322 * Get debug info for bug #3892. 323 * @return debug info for bug #3892. 324 * @deprecated This method will be remove by the end of 2012 if no report appears. 325 */ 326 public String get3892DebugInfo() { 327 StringBuilder builder = new StringBuilder(); 328 builder.append("Unexpected error. Please report it to http://josm.openstreetmap.de/ticket/3892\n"); 329 builder.append(toString()); 330 builder.append("\n"); 331 if (isLatLonKnown()) { 332 builder.append("Coor is null\n"); 333 } else { 334 builder.append(String.format("EastNorth: %s\n", getEastNorth())); 335 builder.append(Main.getProjection()); 336 builder.append("\n"); 337 } 338 339 return builder.toString(); 340 } 341 342 /** 343 * Invoke to invalidate the internal cache of projected east/north coordinates. 344 * Coordinates are reprojected on demand when the {@link #getEastNorth()} is invoked 345 * next time. 346 */ 347 public void invalidateEastNorthCache() { 348 this.east = Double.NaN; 349 this.north = Double.NaN; 350 } 351 }