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

Last change on this file since 11608 was 11397, checked in by Don-vip, 7 years ago

sonar - squid:S2259 - Null pointers should not be dereferenced

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