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

Last change on this file since 12753 was 12753, checked in by bastiK, 7 years ago

see #15251 - suppress (unwarranted) deprecation warning

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