| 1 | // License: GPL. Copyright 2007 by Immanuel Scholz and others |
|---|
| 2 | package org.openstreetmap.josm.data.osm; |
|---|
| 3 | |
|---|
| 4 | import org.openstreetmap.josm.Main; |
|---|
| 5 | import org.openstreetmap.josm.data.coor.EastNorth; |
|---|
| 6 | import org.openstreetmap.josm.data.coor.LatLon; |
|---|
| 7 | import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor; |
|---|
| 8 | import org.openstreetmap.josm.data.osm.visitor.Visitor; |
|---|
| 9 | import org.openstreetmap.josm.data.projection.Projections; |
|---|
| 10 | |
|---|
| 11 | /** |
|---|
| 12 | * One node data, consisting of one world coordinate waypoint. |
|---|
| 13 | * |
|---|
| 14 | * @author imi |
|---|
| 15 | */ |
|---|
| 16 | public final class Node extends OsmPrimitive implements INode { |
|---|
| 17 | |
|---|
| 18 | /* |
|---|
| 19 | * We "inline" lat/lon rather than using a LatLon-object => reduces memory footprint |
|---|
| 20 | */ |
|---|
| 21 | private double lat = Double.NaN; |
|---|
| 22 | private double lon = Double.NaN; |
|---|
| 23 | |
|---|
| 24 | /* |
|---|
| 25 | * the cached projected coordinates |
|---|
| 26 | */ |
|---|
| 27 | private double east = Double.NaN; |
|---|
| 28 | private double north = Double.NaN; |
|---|
| 29 | |
|---|
| 30 | private boolean isLatLonKnown() { |
|---|
| 31 | return !Double.isNaN(lat) && !Double.isNaN(lon); |
|---|
| 32 | } |
|---|
| 33 | |
|---|
| 34 | @Override |
|---|
| 35 | public final void setCoor(LatLon coor) { |
|---|
| 36 | if(coor != null){ |
|---|
| 37 | updateCoor(coor, null); |
|---|
| 38 | } |
|---|
| 39 | } |
|---|
| 40 | |
|---|
| 41 | @Override |
|---|
| 42 | public final void setEastNorth(EastNorth eastNorth) { |
|---|
| 43 | if(eastNorth != null) { |
|---|
| 44 | updateCoor(null, eastNorth); |
|---|
| 45 | } |
|---|
| 46 | } |
|---|
| 47 | |
|---|
| 48 | private void updateCoor(LatLon coor, EastNorth eastNorth) { |
|---|
| 49 | if (getDataSet() != null) { |
|---|
| 50 | boolean locked = writeLock(); |
|---|
| 51 | try { |
|---|
| 52 | getDataSet().fireNodeMoved(this, coor, eastNorth); |
|---|
| 53 | } finally { |
|---|
| 54 | writeUnlock(locked); |
|---|
| 55 | } |
|---|
| 56 | } else { |
|---|
| 57 | setCoorInternal(coor, eastNorth); |
|---|
| 58 | } |
|---|
| 59 | } |
|---|
| 60 | |
|---|
| 61 | @Override |
|---|
| 62 | public final LatLon getCoor() { |
|---|
| 63 | if (!isLatLonKnown()) return null; |
|---|
| 64 | return new LatLon(lat,lon); |
|---|
| 65 | } |
|---|
| 66 | |
|---|
| 67 | /** |
|---|
| 68 | * <p>Replies the projected east/north coordinates.</p> |
|---|
| 69 | * |
|---|
| 70 | * <p>Uses the {@link Main#getProjection() global projection} to project the lan/lon-coordinates. |
|---|
| 71 | * Internally caches the projected coordinates.</p> |
|---|
| 72 | * |
|---|
| 73 | * <p><strong>Caveat:</strong> doesn't listen to projection changes. Clients must |
|---|
| 74 | * {@link #invalidateEastNorthCache() invalidate the internal cache}.</p> |
|---|
| 75 | * |
|---|
| 76 | * <p>Replies {@code null} if this node doesn't know lat/lon-coordinates, i.e. because it is an incomplete node. |
|---|
| 77 | * |
|---|
| 78 | * @return the east north coordinates or {@code null} |
|---|
| 79 | * @see #invalidateEastNorthCache() |
|---|
| 80 | * |
|---|
| 81 | */ |
|---|
| 82 | @Override |
|---|
| 83 | public final EastNorth getEastNorth() { |
|---|
| 84 | if (!isLatLonKnown()) return null; |
|---|
| 85 | |
|---|
| 86 | if (getDataSet() == null) |
|---|
| 87 | // there is no dataset that listens for projection changes |
|---|
| 88 | // and invalidates the cache, so we don't use the cache at all |
|---|
| 89 | return Projections.project(new LatLon(lat, lon)); |
|---|
| 90 | |
|---|
| 91 | if (Double.isNaN(east) || Double.isNaN(north)) { |
|---|
| 92 | // projected coordinates haven't been calculated yet, |
|---|
| 93 | // so fill the cache of the projected node coordinates |
|---|
| 94 | EastNorth en = Projections.project(new LatLon(lat, lon)); |
|---|
| 95 | this.east = en.east(); |
|---|
| 96 | this.north = en.north(); |
|---|
| 97 | } |
|---|
| 98 | return new EastNorth(east, north); |
|---|
| 99 | } |
|---|
| 100 | |
|---|
| 101 | /** |
|---|
| 102 | * To be used only by Dataset.reindexNode |
|---|
| 103 | */ |
|---|
| 104 | protected void setCoorInternal(LatLon coor, EastNorth eastNorth) { |
|---|
| 105 | if (coor != null) { |
|---|
| 106 | this.lat = coor.lat(); |
|---|
| 107 | this.lon = coor.lon(); |
|---|
| 108 | invalidateEastNorthCache(); |
|---|
| 109 | } else if (eastNorth != null) { |
|---|
| 110 | LatLon ll = Projections.inverseProject(eastNorth); |
|---|
| 111 | this.lat = ll.lat(); |
|---|
| 112 | this.lon = ll.lon(); |
|---|
| 113 | this.east = eastNorth.east(); |
|---|
| 114 | this.north = eastNorth.north(); |
|---|
| 115 | } else |
|---|
| 116 | throw new IllegalArgumentException(); |
|---|
| 117 | } |
|---|
| 118 | |
|---|
| 119 | protected Node(long id, boolean allowNegative) { |
|---|
| 120 | super(id, allowNegative); |
|---|
| 121 | } |
|---|
| 122 | |
|---|
| 123 | /** |
|---|
| 124 | * Create a new local node. |
|---|
| 125 | * |
|---|
| 126 | */ |
|---|
| 127 | public Node() { |
|---|
| 128 | this(0, false); |
|---|
| 129 | } |
|---|
| 130 | |
|---|
| 131 | /** |
|---|
| 132 | * Create an incomplete Node object |
|---|
| 133 | */ |
|---|
| 134 | public Node(long id) { |
|---|
| 135 | super(id, false); |
|---|
| 136 | } |
|---|
| 137 | |
|---|
| 138 | /** |
|---|
| 139 | * Create new node |
|---|
| 140 | * @param id |
|---|
| 141 | * @param version |
|---|
| 142 | */ |
|---|
| 143 | public Node(long id, int version) { |
|---|
| 144 | super(id, version, false); |
|---|
| 145 | } |
|---|
| 146 | |
|---|
| 147 | /** |
|---|
| 148 | * |
|---|
| 149 | * @param clone |
|---|
| 150 | * @param clearId If true, set version to 0 and id to new unique value |
|---|
| 151 | */ |
|---|
| 152 | public Node(Node clone, boolean clearId) { |
|---|
| 153 | super(clone.getUniqueId(), true /* allow negative IDs */); |
|---|
| 154 | cloneFrom(clone); |
|---|
| 155 | if (clearId) { |
|---|
| 156 | clearOsmId(); |
|---|
| 157 | } |
|---|
| 158 | } |
|---|
| 159 | |
|---|
| 160 | /** |
|---|
| 161 | * Create an identical clone of the argument (including the id) |
|---|
| 162 | */ |
|---|
| 163 | public Node(Node clone) { |
|---|
| 164 | this(clone, false); |
|---|
| 165 | } |
|---|
| 166 | |
|---|
| 167 | public Node(LatLon latlon) { |
|---|
| 168 | super(0, false); |
|---|
| 169 | setCoor(latlon); |
|---|
| 170 | } |
|---|
| 171 | |
|---|
| 172 | public Node(EastNorth eastNorth) { |
|---|
| 173 | super(0, false); |
|---|
| 174 | setEastNorth(eastNorth); |
|---|
| 175 | } |
|---|
| 176 | |
|---|
| 177 | @Override |
|---|
| 178 | void setDataset(DataSet dataSet) { |
|---|
| 179 | super.setDataset(dataSet); |
|---|
| 180 | if (!isIncomplete() && (getCoor() == null || getEastNorth() == null)) |
|---|
| 181 | throw new DataIntegrityProblemException("Complete node with null coordinates: " + toString() + get3892DebugInfo()); |
|---|
| 182 | } |
|---|
| 183 | |
|---|
| 184 | @Override public void visit(Visitor visitor) { |
|---|
| 185 | visitor.visit(this); |
|---|
| 186 | } |
|---|
| 187 | |
|---|
| 188 | @Override public void visit(PrimitiveVisitor visitor) { |
|---|
| 189 | visitor.visit(this); |
|---|
| 190 | } |
|---|
| 191 | |
|---|
| 192 | @Override public void cloneFrom(OsmPrimitive osm) { |
|---|
| 193 | boolean locked = writeLock(); |
|---|
| 194 | try { |
|---|
| 195 | super.cloneFrom(osm); |
|---|
| 196 | setCoor(((Node)osm).getCoor()); |
|---|
| 197 | } finally { |
|---|
| 198 | writeUnlock(locked); |
|---|
| 199 | } |
|---|
| 200 | } |
|---|
| 201 | |
|---|
| 202 | /** |
|---|
| 203 | * Merges the technical and semantical attributes from <code>other</code> onto this. |
|---|
| 204 | * |
|---|
| 205 | * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code> |
|---|
| 206 | * have an assigend OSM id, the IDs have to be the same. |
|---|
| 207 | * |
|---|
| 208 | * @param other the other primitive. Must not be null. |
|---|
| 209 | * @throws IllegalArgumentException thrown if other is null. |
|---|
| 210 | * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not |
|---|
| 211 | * @throws DataIntegrityProblemException thrown if other is new and other.getId() != this.getId() |
|---|
| 212 | */ |
|---|
| 213 | @Override |
|---|
| 214 | public void mergeFrom(OsmPrimitive other) { |
|---|
| 215 | boolean locked = writeLock(); |
|---|
| 216 | try { |
|---|
| 217 | super.mergeFrom(other); |
|---|
| 218 | if (!other.isIncomplete()) { |
|---|
| 219 | setCoor(((Node)other).getCoor()); |
|---|
| 220 | } |
|---|
| 221 | } finally { |
|---|
| 222 | writeUnlock(locked); |
|---|
| 223 | } |
|---|
| 224 | } |
|---|
| 225 | |
|---|
| 226 | @Override public void load(PrimitiveData data) { |
|---|
| 227 | boolean locked = writeLock(); |
|---|
| 228 | try { |
|---|
| 229 | super.load(data); |
|---|
| 230 | setCoor(((NodeData)data).getCoor()); |
|---|
| 231 | } finally { |
|---|
| 232 | writeUnlock(locked); |
|---|
| 233 | } |
|---|
| 234 | } |
|---|
| 235 | |
|---|
| 236 | @Override public NodeData save() { |
|---|
| 237 | NodeData data = new NodeData(); |
|---|
| 238 | saveCommonAttributes(data); |
|---|
| 239 | if (!isIncomplete()) { |
|---|
| 240 | data.setCoor(getCoor()); |
|---|
| 241 | } |
|---|
| 242 | return data; |
|---|
| 243 | } |
|---|
| 244 | |
|---|
| 245 | @Override public String toString() { |
|---|
| 246 | String coorDesc = isLatLonKnown() ? "lat="+lat+",lon="+lon : ""; |
|---|
| 247 | return "{Node id=" + getUniqueId() + " version=" + getVersion() + " " + getFlagsAsString() + " " + coorDesc+"}"; |
|---|
| 248 | } |
|---|
| 249 | |
|---|
| 250 | @Override |
|---|
| 251 | public boolean hasEqualSemanticAttributes(OsmPrimitive other) { |
|---|
| 252 | if (other == null || ! (other instanceof Node) ) |
|---|
| 253 | return false; |
|---|
| 254 | if (! super.hasEqualSemanticAttributes(other)) |
|---|
| 255 | return false; |
|---|
| 256 | Node n = (Node)other; |
|---|
| 257 | LatLon coor = getCoor(); |
|---|
| 258 | LatLon otherCoor = n.getCoor(); |
|---|
| 259 | if (coor == null && otherCoor == null) |
|---|
| 260 | return true; |
|---|
| 261 | else if (coor != null && otherCoor != null) |
|---|
| 262 | return coor.equalsEpsilon(otherCoor); |
|---|
| 263 | else |
|---|
| 264 | return false; |
|---|
| 265 | } |
|---|
| 266 | |
|---|
| 267 | @Override |
|---|
| 268 | public int compareTo(OsmPrimitive o) { |
|---|
| 269 | return o instanceof Node ? Long.valueOf(getUniqueId()).compareTo(o.getUniqueId()) : 1; |
|---|
| 270 | } |
|---|
| 271 | |
|---|
| 272 | @Override |
|---|
| 273 | public String getDisplayName(NameFormatter formatter) { |
|---|
| 274 | return formatter.format(this); |
|---|
| 275 | } |
|---|
| 276 | |
|---|
| 277 | @Override |
|---|
| 278 | public OsmPrimitiveType getType() { |
|---|
| 279 | return OsmPrimitiveType.NODE; |
|---|
| 280 | } |
|---|
| 281 | |
|---|
| 282 | @Override |
|---|
| 283 | public BBox getBBox() { |
|---|
| 284 | return new BBox(this); |
|---|
| 285 | } |
|---|
| 286 | |
|---|
| 287 | @Override |
|---|
| 288 | public void updatePosition() { |
|---|
| 289 | } |
|---|
| 290 | |
|---|
| 291 | public boolean isConnectionNode() { |
|---|
| 292 | return isReferredByWays(2); |
|---|
| 293 | } |
|---|
| 294 | |
|---|
| 295 | public String get3892DebugInfo() { |
|---|
| 296 | StringBuilder builder = new StringBuilder(); |
|---|
| 297 | builder.append("Unexpected error. Please report it to http://josm.openstreetmap.de/ticket/3892\n"); |
|---|
| 298 | builder.append(toString()); |
|---|
| 299 | builder.append("\n"); |
|---|
| 300 | if (isLatLonKnown()) { |
|---|
| 301 | builder.append("Coor is null\n"); |
|---|
| 302 | } else { |
|---|
| 303 | builder.append(String.format("EastNorth: %s\n", getEastNorth())); |
|---|
| 304 | builder.append(Main.getProjection()); |
|---|
| 305 | builder.append("\n"); |
|---|
| 306 | } |
|---|
| 307 | |
|---|
| 308 | return builder.toString(); |
|---|
| 309 | } |
|---|
| 310 | |
|---|
| 311 | /** |
|---|
| 312 | * Invoke to invalidate the internal cache of projected east/north coordinates. |
|---|
| 313 | * Coordinates are reprojected on demand when the {@link #getEastNorth()} is invoked |
|---|
| 314 | * next time. |
|---|
| 315 | */ |
|---|
| 316 | public void invalidateEastNorthCache() { |
|---|
| 317 | this.east = Double.NaN; |
|---|
| 318 | this.north = Double.NaN; |
|---|
| 319 | } |
|---|
| 320 | } |
|---|