001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm;
003
004import org.openstreetmap.josm.Main;
005import org.openstreetmap.josm.data.coor.EastNorth;
006import org.openstreetmap.josm.data.coor.LatLon;
007import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
008import org.openstreetmap.josm.data.osm.visitor.Visitor;
009import org.openstreetmap.josm.data.projection.Projections;
010
011/**
012 * One node data, consisting of one world coordinate waypoint.
013 *
014 * @author imi
015 */
016public final class Node extends OsmPrimitive implements INode {
017
018    /*
019     * We "inline" lat/lon rather than using a LatLon-object => reduces memory footprint
020     */
021    private double lat = Double.NaN;
022    private double lon = Double.NaN;
023
024    /*
025     * the cached projected coordinates
026     */
027    private double east = Double.NaN;
028    private double north = Double.NaN;
029
030    private boolean isLatLonKnown() {
031        return !Double.isNaN(lat) && !Double.isNaN(lon);
032    }
033
034    @Override
035    public final void setCoor(LatLon coor) {
036        updateCoor(coor, null);
037    }
038
039    @Override
040    public final void setEastNorth(EastNorth eastNorth) {
041        updateCoor(null, eastNorth);
042    }
043
044    private void updateCoor(LatLon coor, EastNorth eastNorth) {
045        if (getDataSet() != null) {
046            boolean locked = writeLock();
047            try {
048                getDataSet().fireNodeMoved(this, coor, eastNorth);
049            } finally {
050                writeUnlock(locked);
051            }
052        } else {
053            setCoorInternal(coor, eastNorth);
054        }
055    }
056
057    @Override
058    public final LatLon getCoor() {
059        if (!isLatLonKnown()) return null;
060        return new LatLon(lat,lon);
061    }
062
063    /**
064     * <p>Replies the projected east/north coordinates.</p>
065     *
066     * <p>Uses the {@link Main#getProjection() global projection} to project the lan/lon-coordinates.
067     * Internally caches the projected coordinates.</p>
068     *
069     * <p><strong>Caveat:</strong> doesn't listen to projection changes. Clients must
070     * {@link #invalidateEastNorthCache() invalidate the internal cache}.</p>
071     *
072     * <p>Replies {@code null} if this node doesn't know lat/lon-coordinates, i.e. because it is an incomplete node.
073     *
074     * @return the east north coordinates or {@code null}
075     * @see #invalidateEastNorthCache()
076     *
077     */
078    @Override
079    public final EastNorth getEastNorth() {
080        if (!isLatLonKnown()) return null;
081
082        if (getDataSet() == null)
083            // there is no dataset that listens for projection changes
084            // and invalidates the cache, so we don't use the cache at all
085            return Projections.project(new LatLon(lat, lon));
086
087        if (Double.isNaN(east) || Double.isNaN(north)) {
088            // projected coordinates haven't been calculated yet,
089            // so fill the cache of the projected node coordinates
090            EastNorth en = Projections.project(new LatLon(lat, lon));
091            this.east = en.east();
092            this.north = en.north();
093        }
094        return new EastNorth(east, north);
095    }
096
097    /**
098     * To be used only by Dataset.reindexNode
099     */
100    protected void setCoorInternal(LatLon coor, EastNorth eastNorth) {
101        if (coor != null) {
102            this.lat = coor.lat();
103            this.lon = coor.lon();
104            invalidateEastNorthCache();
105        } else if (eastNorth != null) {
106            LatLon ll = Projections.inverseProject(eastNorth);
107            this.lat = ll.lat();
108            this.lon = ll.lon();
109            this.east = eastNorth.east();
110            this.north = eastNorth.north();
111        } else {
112            this.lat = Double.NaN;
113            this.lon = Double.NaN;
114            invalidateEastNorthCache();
115            if (isVisible()) {
116                setIncomplete(true);
117            }
118        }
119    }
120
121    protected Node(long id, boolean allowNegative) {
122        super(id, allowNegative);
123    }
124
125    /**
126     * Constructs a new local {@code Node} with id 0.
127     */
128    public Node() {
129        this(0, false);
130    }
131
132    /**
133     * Constructs an incomplete {@code Node} object with the given id.
134     * @param id The id. Must be >= 0
135     * @throws IllegalArgumentException if id < 0
136     */
137    public Node(long id) throws IllegalArgumentException {
138        super(id, false);
139    }
140
141    /**
142     * Constructs a new {@code Node} with the given id and version.
143     * @param id The id. Must be >= 0
144     * @param version The version
145     * @throws IllegalArgumentException if id < 0
146     */
147    public Node(long id, int version) throws IllegalArgumentException {
148        super(id, version, false);
149    }
150
151    /**
152     * Constructs an identical clone of the argument.
153     * @param clone The node to clone
154     * @param clearMetadata If {@code true}, clears the OSM id and other metadata as defined by {@link #clearOsmMetadata}. If {@code false}, does nothing
155     */
156    public Node(Node clone, boolean clearMetadata) {
157        super(clone.getUniqueId(), true /* allow negative IDs */);
158        cloneFrom(clone);
159        if (clearMetadata) {
160            clearOsmMetadata();
161        }
162    }
163
164    /**
165     * Constructs an identical clone of the argument (including the id).
166     * @param clone The node to clone, including its id
167     */
168    public Node(Node clone) {
169        this(clone, false);
170    }
171
172    /**
173     * Constructs a new {@code Node} with the given lat/lon with id 0.
174     * @param latlon The {@link LatLon} coordinates
175     */
176    public Node(LatLon latlon) {
177        super(0, false);
178        setCoor(latlon);
179    }
180
181    /**
182     * Constructs a new {@code Node} with the given east/north with id 0.
183     * @param eastNorth The {@link EastNorth} coordinates
184     */
185    public Node(EastNorth eastNorth) {
186        super(0, false);
187        setEastNorth(eastNorth);
188    }
189
190    @Override
191    void setDataset(DataSet dataSet) {
192        super.setDataset(dataSet);
193        if (!isIncomplete() && isVisible() && (getCoor() == null || getEastNorth() == null))
194            throw new DataIntegrityProblemException("Complete node with null coordinates: " + toString());
195    }
196
197    @Override
198    public void accept(Visitor visitor) {
199        visitor.visit(this);
200    }
201
202    @Override
203    public void accept(PrimitiveVisitor visitor) {
204        visitor.visit(this);
205    }
206
207    @Override
208    public void cloneFrom(OsmPrimitive osm) {
209        boolean locked = writeLock();
210        try {
211            super.cloneFrom(osm);
212            setCoor(((Node)osm).getCoor());
213        } finally {
214            writeUnlock(locked);
215        }
216    }
217
218    /**
219     * Merges the technical and semantical attributes from <code>other</code> onto this.
220     *
221     * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
222     * have an assigend OSM id, the IDs have to be the same.
223     *
224     * @param other the other primitive. Must not be null.
225     * @throws IllegalArgumentException thrown if other is null.
226     * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not
227     * @throws DataIntegrityProblemException thrown if other is new and other.getId() != this.getId()
228     */
229    @Override
230    public void mergeFrom(OsmPrimitive other) {
231        boolean locked = writeLock();
232        try {
233            super.mergeFrom(other);
234            if (!other.isIncomplete()) {
235                setCoor(((Node)other).getCoor());
236            }
237        } finally {
238            writeUnlock(locked);
239        }
240    }
241
242    @Override public void load(PrimitiveData data) {
243        boolean locked = writeLock();
244        try {
245            super.load(data);
246            setCoor(((NodeData)data).getCoor());
247        } finally {
248            writeUnlock(locked);
249        }
250    }
251
252    @Override public NodeData save() {
253        NodeData data = new NodeData();
254        saveCommonAttributes(data);
255        if (!isIncomplete()) {
256            data.setCoor(getCoor());
257        }
258        return data;
259    }
260
261    @Override
262    public String toString() {
263        String coorDesc = isLatLonKnown() ? "lat="+lat+",lon="+lon : "";
264        return "{Node id=" + getUniqueId() + " version=" + getVersion() + " " + getFlagsAsString() + " "  + coorDesc+"}";
265    }
266
267    @Override
268    public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
269        if (!(other instanceof Node))
270            return false;
271        if (! super.hasEqualSemanticAttributes(other))
272            return false;
273        Node n = (Node)other;
274        LatLon coor = getCoor();
275        LatLon otherCoor = n.getCoor();
276        if (coor == null && otherCoor == null)
277            return true;
278        else if (coor != null && otherCoor != null)
279            return coor.equalsEpsilon(otherCoor);
280        else
281            return false;
282    }
283
284    @Override
285    public int compareTo(OsmPrimitive o) {
286        return o instanceof Node ? Long.valueOf(getUniqueId()).compareTo(o.getUniqueId()) : 1;
287    }
288
289    @Override
290    public String getDisplayName(NameFormatter formatter) {
291        return formatter.format(this);
292    }
293
294    @Override
295    public OsmPrimitiveType getType() {
296        return OsmPrimitiveType.NODE;
297    }
298
299    @Override
300    public BBox getBBox() {
301        return new BBox(this);
302    }
303
304    @Override
305    public void updatePosition() {
306    }
307
308    @Override
309    public boolean isDrawable() {
310        // Not possible to draw a node without coordinates.
311        return super.isDrawable() && isLatLonKnown();
312    }
313
314    /**
315     * Check whether this node connects 2 ways.
316     *
317     * @return true if isReferredByWays(2) returns true
318     * @see #isReferredByWays(int)
319     */
320    public boolean isConnectionNode() {
321        return isReferredByWays(2);
322    }
323
324    /**
325     * Invoke to invalidate the internal cache of projected east/north coordinates.
326     * Coordinates are reprojected on demand when the {@link #getEastNorth()} is invoked
327     * next time.
328     */
329    public void invalidateEastNorthCache() {
330        this.east = Double.NaN;
331        this.north = Double.NaN;
332    }
333
334    @Override
335    public boolean concernsArea() {
336        // A node cannot be an area
337        return false;
338    }
339}