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

Last change on this file since 11369 was 11292, checked in by simon04, 7 years ago

see #14025 - Merge layers performance: run quick/decisive checks first

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