source: josm/trunk/src/org/openstreetmap/josm/data/osm/Node.java@ 8993

Last change on this file since 8993 was 8993, checked in by Don-vip, 8 years ago

fix #12060 - NPE

  • Property svn:eol-style set to native
File size: 12.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm;
3
4import java.util.Collection;
5import java.util.Set;
6import java.util.TreeSet;
7
8import org.openstreetmap.josm.Main;
9import org.openstreetmap.josm.data.coor.EastNorth;
10import org.openstreetmap.josm.data.coor.LatLon;
11import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
12import org.openstreetmap.josm.data.osm.visitor.Visitor;
13import org.openstreetmap.josm.data.projection.Projections;
14import org.openstreetmap.josm.tools.CheckParameterUtil;
15import org.openstreetmap.josm.tools.Predicate;
16import org.openstreetmap.josm.tools.Utils;
17
18/**
19 * One node data, consisting of one world coordinate waypoint.
20 *
21 * @author imi
22 */
23public final class Node extends OsmPrimitive implements INode {
24
25 /*
26 * We "inline" lat/lon rather than using a LatLon-object => reduces memory footprint
27 */
28 private double lat = Double.NaN;
29 private double lon = Double.NaN;
30
31 /*
32 * the cached projected coordinates
33 */
34 private double east = Double.NaN;
35 private double north = Double.NaN;
36
37 /**
38 * Determines if this node has valid coordinates.
39 * @return {@code true} if this node has valid coordinates
40 * @since 7828
41 */
42 public boolean isLatLonKnown() {
43 return !Double.isNaN(lat) && !Double.isNaN(lon);
44 }
45
46 @Override
47 public void setCoor(LatLon coor) {
48 updateCoor(coor, null);
49 }
50
51 @Override
52 public void setEastNorth(EastNorth eastNorth) {
53 updateCoor(null, eastNorth);
54 }
55
56 private void updateCoor(LatLon coor, EastNorth eastNorth) {
57 if (getDataSet() != null) {
58 boolean locked = writeLock();
59 try {
60 getDataSet().fireNodeMoved(this, coor, eastNorth);
61 } finally {
62 writeUnlock(locked);
63 }
64 } else {
65 setCoorInternal(coor, eastNorth);
66 }
67 }
68
69 @Override
70 public LatLon getCoor() {
71 if (!isLatLonKnown()) return null;
72 return new LatLon(lat, lon);
73 }
74
75 /**
76 * <p>Replies the projected east/north coordinates.</p>
77 *
78 * <p>Uses the {@link Main#getProjection() global projection} to project the lan/lon-coordinates.
79 * Internally caches the projected coordinates.</p>
80 *
81 * <p><strong>Caveat:</strong> doesn't listen to projection changes. Clients must
82 * {@link #invalidateEastNorthCache() invalidate the internal cache}.</p>
83 *
84 * <p>Replies {@code null} if this node doesn't know lat/lon-coordinates, i.e. because it is an incomplete node.
85 *
86 * @return the east north coordinates or {@code null}
87 * @see #invalidateEastNorthCache()
88 *
89 */
90 @Override
91 public EastNorth getEastNorth() {
92 if (!isLatLonKnown()) return null;
93
94 if (getDataSet() == null)
95 // there is no dataset that listens for projection changes
96 // and invalidates the cache, so we don't use the cache at all
97 return Projections.project(new LatLon(lat, lon));
98
99 if (Double.isNaN(east) || Double.isNaN(north)) {
100 // projected coordinates haven't been calculated yet,
101 // so fill the cache of the projected node coordinates
102 EastNorth en = Projections.project(new LatLon(lat, lon));
103 this.east = en.east();
104 this.north = en.north();
105 }
106 return new EastNorth(east, north);
107 }
108
109 /**
110 * To be used only by Dataset.reindexNode
111 */
112 protected void setCoorInternal(LatLon coor, EastNorth eastNorth) {
113 if (coor != null) {
114 this.lat = coor.lat();
115 this.lon = coor.lon();
116 invalidateEastNorthCache();
117 } else if (eastNorth != null) {
118 LatLon ll = Projections.inverseProject(eastNorth);
119 this.lat = ll.lat();
120 this.lon = ll.lon();
121 this.east = eastNorth.east();
122 this.north = eastNorth.north();
123 } else {
124 this.lat = Double.NaN;
125 this.lon = Double.NaN;
126 invalidateEastNorthCache();
127 if (isVisible()) {
128 setIncomplete(true);
129 }
130 }
131 }
132
133 protected Node(long id, boolean allowNegative) {
134 super(id, allowNegative);
135 }
136
137 /**
138 * Constructs a new local {@code Node} with id 0.
139 */
140 public Node() {
141 this(0, false);
142 }
143
144 /**
145 * Constructs an incomplete {@code Node} object with the given id.
146 * @param id The id. Must be &gt;= 0
147 * @throws IllegalArgumentException if id &lt; 0
148 */
149 public Node(long id) {
150 super(id, false);
151 }
152
153 /**
154 * Constructs a new {@code Node} with the given id and version.
155 * @param id The id. Must be &gt;= 0
156 * @param version The version
157 * @throws IllegalArgumentException if id &lt; 0
158 */
159 public Node(long id, int version) {
160 super(id, version, false);
161 }
162
163 /**
164 * Constructs an identical clone of the argument.
165 * @param clone The node to clone
166 * @param clearMetadata If {@code true}, clears the OSM id and other metadata as defined by {@link #clearOsmMetadata}.
167 * If {@code false}, does nothing
168 */
169 public Node(Node clone, boolean clearMetadata) {
170 super(clone.getUniqueId(), true /* allow negative IDs */);
171 cloneFrom(clone);
172 if (clearMetadata) {
173 clearOsmMetadata();
174 }
175 }
176
177 /**
178 * Constructs an identical clone of the argument (including the id).
179 * @param clone The node to clone, including its id
180 */
181 public Node(Node clone) {
182 this(clone, false);
183 }
184
185 /**
186 * Constructs a new {@code Node} with the given lat/lon with id 0.
187 * @param latlon The {@link LatLon} coordinates
188 */
189 public Node(LatLon latlon) {
190 super(0, false);
191 setCoor(latlon);
192 }
193
194 /**
195 * Constructs a new {@code Node} with the given east/north with id 0.
196 * @param eastNorth The {@link EastNorth} coordinates
197 */
198 public Node(EastNorth eastNorth) {
199 super(0, false);
200 setEastNorth(eastNorth);
201 }
202
203 @Override
204 void setDataset(DataSet dataSet) {
205 super.setDataset(dataSet);
206 if (!isIncomplete() && isVisible() && !isLatLonKnown())
207 throw new DataIntegrityProblemException("Complete node with null coordinates: " + toString());
208 }
209
210 @Override
211 public void accept(Visitor visitor) {
212 visitor.visit(this);
213 }
214
215 @Override
216 public void accept(PrimitiveVisitor visitor) {
217 visitor.visit(this);
218 }
219
220 @Override
221 public void cloneFrom(OsmPrimitive osm) {
222 boolean locked = writeLock();
223 try {
224 super.cloneFrom(osm);
225 setCoor(((Node) osm).getCoor());
226 } finally {
227 writeUnlock(locked);
228 }
229 }
230
231 /**
232 * Merges the technical and semantical attributes from <code>other</code> onto this.
233 *
234 * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
235 * have an assigend OSM id, the IDs have to be the same.
236 *
237 * @param other the other primitive. Must not be null.
238 * @throws IllegalArgumentException if other is null.
239 * @throws DataIntegrityProblemException if either this is new and other is not, or other is new and this is not
240 * @throws DataIntegrityProblemException if other is new and other.getId() != this.getId()
241 */
242 @Override
243 public void mergeFrom(OsmPrimitive other) {
244 boolean locked = writeLock();
245 try {
246 super.mergeFrom(other);
247 if (!other.isIncomplete()) {
248 setCoor(((Node) other).getCoor());
249 }
250 } finally {
251 writeUnlock(locked);
252 }
253 }
254
255 @Override public void load(PrimitiveData data) {
256 boolean locked = writeLock();
257 try {
258 super.load(data);
259 setCoor(((NodeData) data).getCoor());
260 } finally {
261 writeUnlock(locked);
262 }
263 }
264
265 @Override public NodeData save() {
266 NodeData data = new NodeData();
267 saveCommonAttributes(data);
268 if (!isIncomplete()) {
269 data.setCoor(getCoor());
270 }
271 return data;
272 }
273
274 @Override
275 public String toString() {
276 String coorDesc = isLatLonKnown() ? "lat="+lat+",lon="+lon : "";
277 return "{Node id=" + getUniqueId() + " version=" + getVersion() + ' ' + getFlagsAsString() + ' ' + coorDesc+'}';
278 }
279
280 @Override
281 public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
282 if (!(other instanceof Node))
283 return false;
284 if (!super.hasEqualSemanticAttributes(other))
285 return false;
286 Node n = (Node) other;
287 LatLon coor = getCoor();
288 LatLon otherCoor = n.getCoor();
289 if (coor == null && otherCoor == null)
290 return true;
291 else if (coor != null && otherCoor != null)
292 return coor.equalsEpsilon(otherCoor);
293 else
294 return false;
295 }
296
297 @Override
298 public int compareTo(OsmPrimitive o) {
299 return o instanceof Node ? Long.compare(getUniqueId(), o.getUniqueId()) : 1;
300 }
301
302 @Override
303 public String getDisplayName(NameFormatter formatter) {
304 return formatter.format(this);
305 }
306
307 @Override
308 public OsmPrimitiveType getType() {
309 return OsmPrimitiveType.NODE;
310 }
311
312 @Override
313 public BBox getBBox() {
314 return new BBox(this);
315 }
316
317 @Override
318 public void updatePosition() {
319 }
320
321 @Override
322 public boolean isDrawable() {
323 // Not possible to draw a node without coordinates.
324 return super.isDrawable() && isLatLonKnown();
325 }
326
327 /**
328 * Check whether this node connects 2 ways.
329 *
330 * @return true if isReferredByWays(2) returns true
331 * @see #isReferredByWays(int)
332 */
333 public boolean isConnectionNode() {
334 return isReferredByWays(2);
335 }
336
337 /**
338 * Invoke to invalidate the internal cache of projected east/north coordinates.
339 * Coordinates are reprojected on demand when the {@link #getEastNorth()} is invoked
340 * next time.
341 */
342 public void invalidateEastNorthCache() {
343 this.east = Double.NaN;
344 this.north = Double.NaN;
345 }
346
347 @Override
348 public boolean concernsArea() {
349 // A node cannot be an area
350 return false;
351 }
352
353 /**
354 * Tests whether {@code this} node is connected to {@code otherNode} via at most {@code hops} nodes
355 * matching the {@code predicate} (which may be {@code null} to consider all nodes).
356 */
357 public boolean isConnectedTo(final Collection<Node> otherNodes, final int hops, Predicate<Node> predicate) {
358 CheckParameterUtil.ensureParameterNotNull(otherNodes);
359 CheckParameterUtil.ensureThat(!otherNodes.isEmpty(), "otherNodes must not be empty!");
360 CheckParameterUtil.ensureThat(hops >= 0, "hops must be non-negative!");
361 return hops == 0
362 ? isConnectedTo(otherNodes, hops, predicate, null)
363 : isConnectedTo(otherNodes, hops, predicate, new TreeSet<Node>());
364 }
365
366 private boolean isConnectedTo(final Collection<Node> otherNodes, final int hops, Predicate<Node> predicate, Set<Node> visited) {
367 if (otherNodes.contains(this)) {
368 return true;
369 }
370 if (hops > 0) {
371 visited.add(this);
372 for (final Way w : Utils.filteredCollection(this.getReferrers(), Way.class)) {
373 for (final Node n : w.getNodes()) {
374 final boolean containsN = visited.contains(n);
375 visited.add(n);
376 if (!containsN && (predicate == null || predicate.evaluate(n))
377 && n.isConnectedTo(otherNodes, hops - 1, predicate, visited)) {
378 return true;
379 }
380 }
381 }
382 }
383 return false;
384 }
385
386 @Override
387 public boolean isOutsideDownloadArea() {
388 return !isNewOrUndeleted() && getDataSet() != null && getDataSet().getDataSourceArea() != null
389 && getCoor() != null && !getCoor().isIn(getDataSet().getDataSourceArea());
390 }
391}
Note: See TracBrowser for help on using the repository browser.