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

Last change on this file since 12778 was 12778, checked in by bastiK, 2 weeks ago

see #15229 - deprecate Projections#project and Projections#inverseProject

replacement is a bit more verbose, but the fact that Main.proj is
involved need not be hidden

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