// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.data.osm; import java.awt.geom.Area; import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.TreeSet; import java.util.function.Predicate; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.coor.EastNorth; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor; import org.openstreetmap.josm.data.osm.visitor.Visitor; import org.openstreetmap.josm.data.projection.Projection; import org.openstreetmap.josm.data.projection.Projections; import org.openstreetmap.josm.tools.CheckParameterUtil; import org.openstreetmap.josm.tools.Utils; /** * One node data, consisting of one world coordinate waypoint. * * @author imi */ public final class Node extends OsmPrimitive implements INode { /* * We "inline" lat/lon rather than using a LatLon-object => reduces memory footprint */ private double lat = Double.NaN; private double lon = Double.NaN; /* * the cached projected coordinates */ private double east = Double.NaN; private double north = Double.NaN; /** * The cache key to use for {@link #east} and {@link #north}. */ private Object eastNorthCacheKey; /** * Determines if this node has valid coordinates. * @return {@code true} if this node has valid coordinates * @since 7828 */ public boolean isLatLonKnown() { return !Double.isNaN(lat) && !Double.isNaN(lon); } @Override public void setCoor(LatLon coor) { updateCoor(coor, null); } @Override public void setEastNorth(EastNorth eastNorth) { updateCoor(null, eastNorth); } private void updateCoor(LatLon coor, EastNorth eastNorth) { if (getDataSet() != null) { boolean locked = writeLock(); try { getDataSet().fireNodeMoved(this, coor, eastNorth); } finally { writeUnlock(locked); } } else { setCoorInternal(coor, eastNorth); } } /** * Returns lat/lon coordinates of this node, or {@code null} unless {@link #isLatLonKnown()} * @return lat/lon coordinates of this node, or {@code null} unless {@link #isLatLonKnown()} */ @Override public LatLon getCoor() { if (!isLatLonKnown()) return null; return new LatLon(lat, lon); } /** *
Replies the projected east/north coordinates.
* *Uses the {@link Main#getProjection() global projection} to project the lan/lon-coordinates. * Internally caches the projected coordinates.
* *Replies {@code null} if this node doesn't know lat/lon-coordinates, i.e. because it is an incomplete node. * * @return the east north coordinates or {@code null} * @see #invalidateEastNorthCache() * */ @Override public EastNorth getEastNorth() { return getEastNorth(Main.getProjection()); } /** * Replies the projected east/north coordinates. *
* The result of the last conversion is cached. The cache object is used as cache key.
* @param projection The projection to use.
* @return The projected east/north coordinates
* @since 10827
*/
public EastNorth getEastNorth(Projection projection) {
if (!isLatLonKnown()) return null;
if (Double.isNaN(east) || Double.isNaN(north) || !Objects.equals(projection.getCacheKey(), eastNorthCacheKey)) {
// projected coordinates haven't been calculated yet,
// so fill the cache of the projected node coordinates
EastNorth en = Projections.project(new LatLon(lat, lon));
this.east = en.east();
this.north = en.north();
this.eastNorthCacheKey = projection.getCacheKey();
}
return new EastNorth(east, north);
}
/**
* To be used only by Dataset.reindexNode
* @param coor lat/lon
* @param eastNorth east/north
*/
void setCoorInternal(LatLon coor, EastNorth eastNorth) {
if (coor != null) {
this.lat = coor.lat();
this.lon = coor.lon();
invalidateEastNorthCache();
} else if (eastNorth != null) {
LatLon ll = Projections.inverseProject(eastNorth);
this.lat = ll.lat();
this.lon = ll.lon();
this.east = eastNorth.east();
this.north = eastNorth.north();
this.eastNorthCacheKey = Main.getProjection().getCacheKey();
} else {
this.lat = Double.NaN;
this.lon = Double.NaN;
invalidateEastNorthCache();
if (isVisible()) {
setIncomplete(true);
}
}
}
protected Node(long id, boolean allowNegative) {
super(id, allowNegative);
}
/**
* Constructs a new local {@code Node} with id 0.
*/
public Node() {
this(0, false);
}
/**
* Constructs an incomplete {@code Node} object with the given id.
* @param id The id. Must be >= 0
* @throws IllegalArgumentException if id < 0
*/
public Node(long id) {
super(id, false);
}
/**
* Constructs a new {@code Node} with the given id and version.
* @param id The id. Must be >= 0
* @param version The version
* @throws IllegalArgumentException if id < 0
*/
public Node(long id, int version) {
super(id, version, false);
}
/**
* Constructs an identical clone of the argument.
* @param clone The node to clone
* @param clearMetadata If {@code true}, clears the OSM id and other metadata as defined by {@link #clearOsmMetadata}.
* If {@code false}, does nothing
*/
public Node(Node clone, boolean clearMetadata) {
super(clone.getUniqueId(), true /* allow negative IDs */);
cloneFrom(clone);
if (clearMetadata) {
clearOsmMetadata();
}
}
/**
* Constructs an identical clone of the argument (including the id).
* @param clone The node to clone, including its id
*/
public Node(Node clone) {
this(clone, false);
}
/**
* Constructs a new {@code Node} with the given lat/lon with id 0.
* @param latlon The {@link LatLon} coordinates
*/
public Node(LatLon latlon) {
super(0, false);
setCoor(latlon);
}
/**
* Constructs a new {@code Node} with the given east/north with id 0.
* @param eastNorth The {@link EastNorth} coordinates
*/
public Node(EastNorth eastNorth) {
super(0, false);
setEastNorth(eastNorth);
}
@Override
void setDataset(DataSet dataSet) {
super.setDataset(dataSet);
if (!isIncomplete() && isVisible() && !isLatLonKnown())
throw new DataIntegrityProblemException("Complete node with null coordinates: " + toString());
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
@Override
public void accept(PrimitiveVisitor visitor) {
visitor.visit(this);
}
@Override
public void cloneFrom(OsmPrimitive osm) {
if (!(osm instanceof Node))
throw new IllegalArgumentException("Not a node: " + osm);
boolean locked = writeLock();
try {
super.cloneFrom(osm);
setCoor(((Node) osm).getCoor());
} finally {
writeUnlock(locked);
}
}
/**
* Merges the technical and semantical attributes from other
onto this.
*
* Both this and other must be new, or both must be assigned an OSM ID. If both this and other
* have an assigend OSM id, the IDs have to be the same.
*
* @param other the other primitive. Must not be null.
* @throws IllegalArgumentException if other is null.
* @throws DataIntegrityProblemException if either this is new and other is not, or other is new and this is not
* @throws DataIntegrityProblemException if other is new and other.getId() != this.getId()
*/
@Override
public void mergeFrom(OsmPrimitive other) {
if (!(other instanceof Node))
throw new IllegalArgumentException("Not a node: " + other);
boolean locked = writeLock();
try {
super.mergeFrom(other);
if (!other.isIncomplete()) {
setCoor(((Node) other).getCoor());
}
} finally {
writeUnlock(locked);
}
}
@Override
public void load(PrimitiveData data) {
if (!(data instanceof NodeData))
throw new IllegalArgumentException("Not a node data: " + data);
boolean locked = writeLock();
try {
super.load(data);
setCoor(((NodeData) data).getCoor());
} finally {
writeUnlock(locked);
}
}
@Override
public NodeData save() {
NodeData data = new NodeData();
saveCommonAttributes(data);
if (!isIncomplete()) {
data.setCoor(getCoor());
}
return data;
}
@Override
public String toString() {
String coorDesc = isLatLonKnown() ? "lat="+lat+",lon="+lon : "";
return "{Node id=" + getUniqueId() + " version=" + getVersion() + ' ' + getFlagsAsString() + ' ' + coorDesc+'}';
}
@Override
public boolean hasEqualSemanticAttributes(OsmPrimitive other, boolean testInterestingTagsOnly) {
return (other instanceof Node)
&& hasEqualSemanticFlags(other)
&& hasEqualCoordinates((Node) other)
&& super.hasEqualSemanticAttributes(other, testInterestingTagsOnly);
}
private boolean hasEqualCoordinates(Node other) {
final LatLon c1 = getCoor();
final LatLon c2 = other.getCoor();
return (c1 == null && c2 == null) || (c1 != null && c2 != null && c1.equalsEpsilon(c2));
}
@Override
public int compareTo(OsmPrimitive o) {
return o instanceof Node ? Long.compare(getUniqueId(), o.getUniqueId()) : 1;
}
@Override
public String getDisplayName(NameFormatter formatter) {
return formatter.format(this);
}
@Override
public OsmPrimitiveType getType() {
return OsmPrimitiveType.NODE;
}
@Override
public BBox getBBox() {
return new BBox(lon, lat);
}
@Override
protected void addToBBox(BBox box, Set