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

Last change on this file since 10878 was 10829, checked in by Don-vip, 8 years ago

see #13306 - fix wrong version number

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