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

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

Make WayPoint implement ILatLon.

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