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

Last change on this file since 11269 was 11269, checked in by michael2402, 7 years ago

Fix #13361: Use a more consistent invalid bbox for primitives.

Patch by Gerd Petermann

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