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

Last change on this file since 15820 was 15820, checked in by Don-vip, 4 years ago

fix #18654 - Separate unique identifiers per primitive type

This allows to easily update .osm files with negative ids across multiple sessions, such as internal JOSM boundaries file.

  • Property svn:eol-style set to native
File size: 13.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;
11import java.util.stream.Collectors;
12
13import org.openstreetmap.josm.data.Bounds;
14import org.openstreetmap.josm.data.coor.EastNorth;
15import org.openstreetmap.josm.data.coor.LatLon;
16import org.openstreetmap.josm.data.osm.visitor.OsmPrimitiveVisitor;
17import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
18import org.openstreetmap.josm.data.projection.Projecting;
19import org.openstreetmap.josm.data.projection.ProjectionRegistry;
20import org.openstreetmap.josm.tools.CheckParameterUtil;
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 static final UniqueIdGenerator idGenerator = new UniqueIdGenerator();
30
31 /*
32 * We "inline" lat/lon rather than using a LatLon-object => reduces memory footprint
33 */
34 private double lat = Double.NaN;
35 private double lon = Double.NaN;
36
37 /*
38 * the cached projected coordinates
39 */
40 private double east = Double.NaN;
41 private double north = Double.NaN;
42 /**
43 * The cache key to use for {@link #east} and {@link #north}.
44 */
45 private Object eastNorthCacheKey;
46
47 @Override
48 public void setCoor(LatLon coor) {
49 updateCoor(coor, null);
50 }
51
52 @Override
53 public void setEastNorth(EastNorth eastNorth) {
54 updateCoor(null, eastNorth);
55 }
56
57 private void updateCoor(LatLon coor, EastNorth eastNorth) {
58 if (getDataSet() != null) {
59 boolean locked = writeLock();
60 try {
61 getDataSet().fireNodeMoved(this, coor, eastNorth);
62 } finally {
63 writeUnlock(locked);
64 }
65 } else {
66 setCoorInternal(coor, eastNorth);
67 }
68 }
69
70 /**
71 * Returns lat/lon coordinates of this node, or {@code null} unless {@link #isLatLonKnown()}
72 * @return lat/lon coordinates of this node, or {@code null} unless {@link #isLatLonKnown()}
73 */
74 @Override
75 public LatLon getCoor() {
76 if (!isLatLonKnown()) {
77 return null;
78 } else {
79 return new LatLon(lat, lon);
80 }
81 }
82
83 @Override
84 public double lat() {
85 return lat;
86 }
87
88 @Override
89 public double lon() {
90 return lon;
91 }
92
93 @Override
94 public EastNorth getEastNorth(Projecting projection) {
95 if (!isLatLonKnown()) return null;
96
97 if (Double.isNaN(east) || Double.isNaN(north) || !Objects.equals(projection.getCacheKey(), eastNorthCacheKey)) {
98 // projected coordinates haven't been calculated yet,
99 // so fill the cache of the projected node coordinates
100 EastNorth en = projection.latlon2eastNorth(this);
101 this.east = en.east();
102 this.north = en.north();
103 this.eastNorthCacheKey = projection.getCacheKey();
104 }
105 return new EastNorth(east, north);
106 }
107
108 /**
109 * To be used only by Dataset.reindexNode
110 * @param coor lat/lon
111 * @param eastNorth east/north
112 */
113 void setCoorInternal(LatLon coor, EastNorth eastNorth) {
114 if (coor != null) {
115 this.lat = coor.lat();
116 this.lon = coor.lon();
117 invalidateEastNorthCache();
118 } else if (eastNorth != null) {
119 LatLon ll = ProjectionRegistry.getProjection().eastNorth2latlon(eastNorth);
120 this.lat = ll.lat();
121 this.lon = ll.lon();
122 this.east = eastNorth.east();
123 this.north = eastNorth.north();
124 this.eastNorthCacheKey = ProjectionRegistry.getProjection().getCacheKey();
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 >= 0
149 * @throws IllegalArgumentException if id < 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 >= 0
158 * @param version The version
159 * @throws IllegalArgumentException if id < 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(OsmPrimitiveVisitor 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 if (!(osm instanceof Node))
225 throw new IllegalArgumentException("Not a node: " + osm);
226 boolean locked = writeLock();
227 try {
228 super.cloneFrom(osm);
229 setCoor(((Node) osm).getCoor());
230 } finally {
231 writeUnlock(locked);
232 }
233 }
234
235 /**
236 * Merges the technical and semantical attributes from <code>other</code> onto this.
237 *
238 * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
239 * have an assigend OSM id, the IDs have to be the same.
240 *
241 * @param other the other primitive. Must not be null.
242 * @throws IllegalArgumentException if other is null.
243 * @throws DataIntegrityProblemException if either this is new and other is not, or other is new and this is not
244 * @throws DataIntegrityProblemException if other is new and other.getId() != this.getId()
245 */
246 @Override
247 public void mergeFrom(OsmPrimitive other) {
248 if (!(other instanceof Node))
249 throw new IllegalArgumentException("Not a node: " + other);
250 boolean locked = writeLock();
251 try {
252 super.mergeFrom(other);
253 if (!other.isIncomplete()) {
254 setCoor(((Node) other).getCoor());
255 }
256 } finally {
257 writeUnlock(locked);
258 }
259 }
260
261 @Override
262 public void load(PrimitiveData data) {
263 if (!(data instanceof NodeData))
264 throw new IllegalArgumentException("Not a node data: " + data);
265 boolean locked = writeLock();
266 try {
267 super.load(data);
268 setCoor(((NodeData) data).getCoor());
269 } finally {
270 writeUnlock(locked);
271 }
272 }
273
274 @Override
275 public NodeData save() {
276 NodeData data = new NodeData();
277 saveCommonAttributes(data);
278 if (!isIncomplete()) {
279 data.setCoor(getCoor());
280 }
281 return data;
282 }
283
284 @Override
285 public String toString() {
286 String coorDesc = isLatLonKnown() ? "lat="+lat+",lon="+lon : "";
287 return "{Node id=" + getUniqueId() + " version=" + getVersion() + ' ' + getFlagsAsString() + ' ' + coorDesc+'}';
288 }
289
290 @Override
291 public boolean hasEqualSemanticAttributes(OsmPrimitive other, boolean testInterestingTagsOnly) {
292 return (other instanceof Node)
293 && hasEqualSemanticFlags(other)
294 && hasEqualCoordinates((Node) other)
295 && super.hasEqualSemanticAttributes(other, testInterestingTagsOnly);
296 }
297
298 private boolean hasEqualCoordinates(Node other) {
299 final LatLon c1 = getCoor();
300 final LatLon c2 = other.getCoor();
301 return (c1 == null && c2 == null) || (c1 != null && c2 != null && c1.equalsEpsilon(c2));
302 }
303
304 @Override
305 public OsmPrimitiveType getType() {
306 return OsmPrimitiveType.NODE;
307 }
308
309 @Override
310 public BBox getBBox() {
311 return new BBox(lon, lat);
312 }
313
314 @Override
315 protected void addToBBox(BBox box, Set<PrimitiveId> visited) {
316 box.add(lon, lat);
317 }
318
319 @Override
320 public void updatePosition() {
321 // Do nothing
322 }
323
324 @Override
325 public boolean isDrawable() {
326 // Not possible to draw a node without coordinates.
327 return super.isDrawable() && isLatLonKnown();
328 }
329
330 @Override
331 public boolean isReferredByWays(int n) {
332 return isNodeReferredByWays(n);
333 }
334
335 /**
336 * Invoke to invalidate the internal cache of projected east/north coordinates.
337 * Coordinates are reprojected on demand when the {@link #getEastNorth()} is invoked
338 * next time.
339 */
340 public void invalidateEastNorthCache() {
341 this.east = Double.NaN;
342 this.north = Double.NaN;
343 this.eastNorthCacheKey = null;
344 }
345
346 @Override
347 public boolean concernsArea() {
348 // A node cannot be an area
349 return false;
350 }
351
352 /**
353 * Tests whether {@code this} node is connected to {@code otherNode} via at most {@code hops} nodes
354 * matching the {@code predicate} (which may be {@code null} to consider all nodes).
355 * @param otherNodes other nodes
356 * @param hops number of hops
357 * @param predicate predicate to match
358 * @return {@code true} if {@code this} node mets the conditions
359 */
360 public boolean isConnectedTo(final Collection<Node> otherNodes, final int hops, Predicate<Node> predicate) {
361 CheckParameterUtil.ensureParameterNotNull(otherNodes);
362 CheckParameterUtil.ensureThat(!otherNodes.isEmpty(), "otherNodes must not be empty!");
363 CheckParameterUtil.ensureThat(hops >= 0, "hops must be non-negative!");
364 return hops == 0
365 ? isConnectedTo(otherNodes, hops, predicate, null)
366 : isConnectedTo(otherNodes, hops, predicate, new TreeSet<>());
367 }
368
369 private boolean isConnectedTo(final Collection<Node> otherNodes, final int hops, Predicate<Node> predicate, Set<Node> visited) {
370 if (otherNodes.contains(this)) {
371 return true;
372 }
373 if (hops > 0 && visited != null) {
374 visited.add(this);
375 for (final Way w : getParentWays()) {
376 for (final Node n : w.getNodes()) {
377 final boolean containsN = visited.contains(n);
378 if (!containsN && (predicate == null || predicate.test(n))
379 && n.isConnectedTo(otherNodes, hops - 1, predicate, visited)) {
380 return true;
381 }
382 }
383 }
384 }
385 return false;
386 }
387
388 @Override
389 public boolean isOutsideDownloadArea() {
390 if (isNewOrUndeleted() || getDataSet() == null)
391 return false;
392 Area area = getDataSet().getDataSourceArea();
393 if (area == null)
394 return false;
395 LatLon coor = getCoor();
396 return coor != null && !coor.isIn(area);
397 }
398
399 /**
400 * Replies the set of referring ways.
401 * @return the set of referring ways
402 * @since 12031
403 */
404 public List<Way> getParentWays() {
405 return referrers(Way.class).collect(Collectors.toList());
406 }
407
408 /**
409 * Determines if this node is outside of the world. See also #13538.
410 * @return <code>true</code>, if the coordinate is outside the world, compared by using lat/lon and east/north
411 * @since 14960
412 */
413 public boolean isOutSideWorld() {
414 LatLon ll = getCoor();
415 if (ll != null) {
416 Bounds b = ProjectionRegistry.getProjection().getWorldBoundsLatLon();
417 if (lat() < b.getMinLat() || lat() > b.getMaxLat() || lon() < b.getMinLon() || lon() > b.getMaxLon()) {
418 return true;
419 }
420 if (!ProjectionRegistry.getProjection().latlon2eastNorth(ll).equalsEpsilon(getEastNorth(), 1.0)) {
421 // we get here if a node was moved or created left from -180 or right from +180
422 return true;
423 }
424 }
425 return false;
426 }
427
428 @Override
429 public UniqueIdGenerator getIdGenerator() {
430 return idGenerator;
431 }
432}
Note: See TracBrowser for help on using the repository browser.