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

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

javadoc update

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