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

Last change on this file since 6699 was 6639, checked in by simon04, 10 years ago

fix #9544 - Skip nodes outside of download area for BarriersEntrances and WayConnectedToArea validation tests

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