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

Last change on this file since 13806 was 13669, checked in by Don-vip, 6 years ago

move a few methods from Node to INode

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