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

Last change on this file since 12753 was 12753, checked in by bastiK, 3 months ago

see #15251 - suppress (unwarranted) deprecation warning

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