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

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

add Node.getParentWays()

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