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

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

see #13036 - deprecate Command() default constructor, fix unit tests and java warnings

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