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

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

replace abstract class AbstractVisitor by interface OsmPrimitiveVisitor; deprecate Visitor

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