// License: GPL. See LICENSE file for details.
package org.openstreetmap.josm.gui;
import static org.openstreetmap.josm.tools.I18n.marktr;
//import java.awt.Cursor;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
//import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArrayList;
//import javax.swing.JComponent;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.beboj.CanvasView;
import org.openstreetmap.josm.beboj.PlatformFactory;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.ProjectionBounds;
import org.openstreetmap.josm.data.coor.CachedLatLon;
import org.openstreetmap.josm.data.coor.EastNorth;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.osm.BBox;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.data.osm.WaySegment;
import org.openstreetmap.josm.data.preferences.IntegerProperty;
import org.openstreetmap.josm.data.projection.Projection;
//import org.openstreetmap.josm.gui.help.Helpful;
import org.openstreetmap.josm.gui.preferences.ProjectionPreference;
import org.openstreetmap.josm.tools.Predicate;
/**
* GWT
*
* FIXME
* support viewID
* getNearestWaySegmentsImpl:
* there is some rounding, that cannot be done like this in gwt
* (rounding is omitted for now)
*
* note
* NavigatableComponent does no longer subclass JComponent
* Functionality needed from JComponent is extracted to a new interface CanvasView
* and to a PropertyChangeSupport field
* Constructor has a CanvasView argument
*/
/**
* An component that can be navigated by a mapmover. Used as map view and for the
* zoomer in the download dialog.
*
* @author imi
*/
public class NavigatableComponent {//extends JComponent implements Helpful {
/*************
* GWT part
*************/
public CanvasView view;
public NavigationSupport nav;
/*************
* JOSM part
*************/
public static final IntegerProperty PROP_SNAP_DISTANCE = new IntegerProperty("mappaint.node.snap-distance", 10);
public NavigatableComponent(CanvasView view) {
this.view = view;
nav = Main.platformFactory.getNavigationSupport(view);
// setLayout(null);
}
protected DataSet getCurrentDataSet() {
return Main.main.getCurrentDataSet();
}
public static String getDistText(double dist) {
return getSystemOfMeasurement().getDistText(dist);
}
public String getDist100PixelText()
{
return getDistText(getDist100Pixel());
}
public double getDist100Pixel()
{
int w = view.getWidth()/2;
int h = view.getHeight()/2;
LatLon ll1 = getLatLon(w-50,h);
LatLon ll2 = getLatLon(w+50,h);
return ll1.greatCircleDistance(ll2);
}
/**
* @return Returns the center point. A copy is returned, so users cannot
* change the center by accessing the return value. Use zoomTo instead.
*/
@Deprecated
public EastNorth getCenter() {
return nav.getCenter();
}
/**
* @param x X-Pixelposition to get coordinate from
* @param y Y-Pixelposition to get coordinate from
*
* @return Geographic coordinates from a specific pixel coordination
* on the screen.
*/
public EastNorth getEastNorth(int x, int y) {
return new EastNorth(
nav.getCenter().east() + (x - view.getWidth()/2.0)*nav.getScale(),
nav.getCenter().north() - (y - view.getHeight()/2.0)*nav.getScale());
}
public ProjectionBounds getProjectionBounds() {
return new ProjectionBounds(
new EastNorth(
nav.getCenter().east() - view.getWidth()/2.0*nav.getScale(),
nav.getCenter().north() - view.getHeight()/2.0*nav.getScale()),
new EastNorth(
nav.getCenter().east() + view.getWidth()/2.0*nav.getScale(),
nav.getCenter().north() + view.getHeight()/2.0*nav.getScale()));
}
/* FIXME: replace with better method - used by MapSlider */
public ProjectionBounds getMaxProjectionBounds() {
Bounds b = getProjection().getWorldBoundsLatLon();
return new ProjectionBounds(getProjection().latlon2eastNorth(b.getMin()),
getProjection().latlon2eastNorth(b.getMax()));
}
/* FIXME: replace with better method - used by Main to reset Bounds when projection changes, don't use otherwise */
public Bounds getRealBounds() {
return new Bounds(
getProjection().eastNorth2latlon(new EastNorth(
nav.getCenter().east() - view.getWidth()/2.0*nav.getScale(),
nav.getCenter().north() - view.getHeight()/2.0*nav.getScale())),
getProjection().eastNorth2latlon(new EastNorth(
nav.getCenter().east() + view.getWidth()/2.0*nav.getScale(),
nav.getCenter().north() + view.getHeight()/2.0*nav.getScale())));
}
/**
* @param x X-Pixelposition to get coordinate from
* @param y Y-Pixelposition to get coordinate from
*
* @return Geographic unprojected coordinates from a specific pixel coordination
* on the screen.
*/
public LatLon getLatLon(int x, int y) {
return getProjection().eastNorth2latlon(getEastNorth(x, y));
}
public LatLon getLatLon(double x, double y) {
return getLatLon((int)x, (int)y);
}
/**
* @param r
* @return Minimum bounds that will cover rectangle
*/
public Bounds getLatLonBounds(Rectangle r) {
// TODO Maybe this should be (optional) method of Projection implementation
EastNorth p1 = getEastNorth(r.x, r.y);
EastNorth p2 = getEastNorth(r.x + r.width, r.y + r.height);
Bounds result = new Bounds(Main.proj.eastNorth2latlon(p1));
double eastMin = Math.min(p1.east(), p2.east());
double eastMax = Math.max(p1.east(), p2.east());
double northMin = Math.min(p1.north(), p2.north());
double northMax = Math.max(p1.north(), p2.north());
double deltaEast = (eastMax - eastMin) / 10;
double deltaNorth = (northMax - northMin) / 10;
for (int i=0; i < 10; i++) {
result.extend(Main.proj.eastNorth2latlon(new EastNorth(eastMin + i * deltaEast, northMin)));
result.extend(Main.proj.eastNorth2latlon(new EastNorth(eastMin + i * deltaEast, northMax)));
result.extend(Main.proj.eastNorth2latlon(new EastNorth(eastMin, northMin + i * deltaNorth)));
result.extend(Main.proj.eastNorth2latlon(new EastNorth(eastMax, northMin + i * deltaNorth)));
}
return result;
}
/**
* Return the point on the screen where this Coordinate would be.
* @param p The point, where this geopoint would be drawn.
* @return The point on screen where "point" would be drawn, relative
* to the own top/left.
*/
public Point2D getPoint2D(EastNorth p) {
if (null == p)
return new Point();
double x = (p.east()-nav.getCenter().east())/nav.getScale() + view.getWidth()/2;
double y = (nav.getCenter().north()-p.north())/nav.getScale() + view.getHeight()/2;
return new Point2D.Double(x, y);
}
public Point2D getPoint2D(LatLon latlon) {
if (latlon == null)
return new Point();
else if (latlon instanceof CachedLatLon)
return getPoint2D(((CachedLatLon)latlon).getEastNorth());
else
return getPoint2D(getProjection().latlon2eastNorth(latlon));
}
public Point2D getPoint2D(Node n) {
return getPoint2D(n.getEastNorth());
}
// looses precision, may overflow (depends on p and current scale)
//@Deprecated
public Point getPoint(EastNorth p) {
Point2D d = getPoint2D(p);
return new Point((int) d.getX(), (int) d.getY());
}
// looses precision, may overflow (depends on p and current scale)
//@Deprecated
public Point getPoint(LatLon latlon) {
Point2D d = getPoint2D(latlon);
return new Point((int) d.getX(), (int) d.getY());
}
// looses precision, may overflow (depends on p and current scale)
//@Deprecated
public Point getPoint(Node n) {
Point2D d = getPoint2D(n);
return new Point((int) d.getX(), (int) d.getY());
}
// public void smoothScrollTo(LatLon newCenter) {
// if (newCenter instanceof CachedLatLon) {
// smoothScrollTo(((CachedLatLon)newCenter).getEastNorth());
// } else {
// smoothScrollTo(getProjection().latlon2eastNorth(newCenter));
// }
// }
// /**
// * Create a thread that moves the viewport to the given center in an
// * animated fashion.
// */
// public void smoothScrollTo(EastNorth newCenter) {
// // fixme make these configurable.
// final int fps = 20; // animation frames per second
// final int speed = 1500; // milliseconds for full-screen-width pan
// if (!newCenter.equals(center)) {
// final EastNorth oldCenter = center;
// final double distance = newCenter.distance(oldCenter) / scale;
// final double milliseconds = distance / getWidth() * speed;
// final double frames = milliseconds * fps / 1000;
// final EastNorth finalNewCenter = newCenter;
//
// new Thread(
// new Runnable() {
// public void run() {
// for (int i=0; i<frames; i++)
// {
// // fixme - not use zoom history here
// zoomTo(oldCenter.interpolate(finalNewCenter, (double) (i+1) / (double) frames));
// try { Thread.sleep(1000 / fps); } catch (InterruptedException ex) { };
// }
// }
// }
// ).start();
// }
// }
private BBox getBBox(Point p, int snapDistance) {
return new BBox(getLatLon(p.x - snapDistance, p.y - snapDistance),
getLatLon(p.x + snapDistance, p.y + snapDistance));
}
/**
* The *result* does not depend on the current map selection state,
* neither does the result *order*.
* It solely depends on the distance to point p.
*
* @return a sorted map with the keys representing the distance of
* their associated nodes to point p.
*/
private Map<Double, List<Node>> getNearestNodesImpl(Point p,
Predicate<OsmPrimitive> predicate) {
TreeMap<Double, List<Node>> nearestMap = new TreeMap<Double, List<Node>>();
DataSet ds = getCurrentDataSet();
if (ds != null) {
double dist, snapDistanceSq = PROP_SNAP_DISTANCE.get();
snapDistanceSq *= snapDistanceSq;
for (Node n : ds.searchNodes(getBBox(p, PROP_SNAP_DISTANCE.get()))) {
if (predicate.evaluate(n)
&& (dist = getPoint2D(n).distanceSq(p)) < snapDistanceSq)
{
List<Node> nlist;
if (nearestMap.containsKey(dist)) {
nlist = nearestMap.get(dist);
} else {
nlist = new LinkedList<Node>();
nearestMap.put(dist, nlist);
}
nlist.add(n);
}
}
}
return nearestMap;
}
/**
* The *result* does not depend on the current map selection state,
* neither does the result *order*.
* It solely depends on the distance to point p.
*
* @return All nodes nearest to point p that are in a belt from
* dist(nearest) to dist(nearest)+4px around p and
* that are not in ignore.
*
* @param p the point for which to search the nearest segment.
* @param ignore a collection of nodes which are not to be returned.
* @param predicate the returned objects have to fulfill certain properties.
*/
public final List<Node> getNearestNodes(Point p,
Collection<Node> ignore, Predicate<OsmPrimitive> predicate) {
List<Node> nearestList = Collections.emptyList();
if (ignore == null) {
ignore = Collections.emptySet();
}
Map<Double, List<Node>> nlists = getNearestNodesImpl(p, predicate);
if (!nlists.isEmpty()) {
Double minDistSq = null;
List<Node> nlist;
for (Double distSq : nlists.keySet()) {
nlist = nlists.get(distSq);
// filter nodes to be ignored before determining minDistSq..
nlist.removeAll(ignore);
if (minDistSq == null) {
if (!nlist.isEmpty()) {
minDistSq = distSq;
nearestList = new ArrayList<Node>();
nearestList.addAll(nlist);
}
} else {
if (distSq-minDistSq < (4)*(4)) {
nearestList.addAll(nlist);
}
}
}
}
return nearestList;
}
/**
* The *result* does not depend on the current map selection state,
* neither does the result *order*.
* It solely depends on the distance to point p.
*
* @return All nodes nearest to point p that are in a belt from
* dist(nearest) to dist(nearest)+4px around p.
* @see #getNearestNodes(Point, Collection, Predicate)
*
* @param p the point for which to search the nearest segment.
* @param predicate the returned objects have to fulfill certain properties.
*/
public final List<Node> getNearestNodes(Point p, Predicate<OsmPrimitive> predicate) {
return getNearestNodes(p, null, predicate);
}
/**
* The *result* depends on the current map selection state IF use_selected is true.
*
* If more than one node within node.snap-distance pixels is found,
* the nearest node selected is returned IF use_selected is true.
*
* Else the nearest new/id=0 node within about the same distance
* as the true nearest node is returned.
*
* If no such node is found either, the true nearest
* node to p is returned.
*
* Finally, if a node is not found at all, null is returned.
*
* @return A node within snap-distance to point p,
* that is chosen by the algorithm described.
*
* @param p the screen point
* @param predicate this parameter imposes a condition on the returned object, e.g.
* give the nearest node that is tagged.
*/
public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate, boolean use_selected) {
Node n = null;
Map<Double, List<Node>> nlists = getNearestNodesImpl(p, predicate);
if (!nlists.isEmpty()) {
Node ntsel = null, ntnew = null;
double minDistSq = nlists.keySet().iterator().next();
for (Double distSq : nlists.keySet()) {
for (Node nd : nlists.get(distSq)) {
// find the nearest selected node
if (ntsel == null && nd.isSelected()) {
ntsel = nd;
// if there are multiple nearest nodes, prefer the one
// that is selected. This is required in order to drag
// the selected node if multiple nodes have the same
// coordinates (e.g. after unglue)
use_selected |= (distSq == minDistSq);
}
// find the nearest newest node that is within about the same
// distance as the true nearest node
if (ntnew == null && nd.isNew() && (distSq-minDistSq < 1)) {
ntnew = nd;
}
}
}
// take nearest selected, nearest new or true nearest node to p, in that order
n = (ntsel != null && use_selected) ? ntsel
: (ntnew != null) ? ntnew
: nlists.values().iterator().next().get(0);
}
return n;
}
/**
* Convenience method to {@link #getNearestNode(Point, Predicate, boolean)}.
*
* @return The nearest node to point p.
*/
public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate) {
return getNearestNode(p, predicate, true);
}
@Deprecated
public final Node getNearestNode(Point p) {
return getNearestNode(p, OsmPrimitive.isUsablePredicate);
}
/**
* The *result* does not depend on the current map selection state,
* neither does the result *order*.
* It solely depends on the distance to point p.
*
* @return a sorted map with the keys representing the perpendicular
* distance of their associated way segments to point p.
*/
private Map<Double, List<WaySegment>> getNearestWaySegmentsImpl(Point p,
Predicate<OsmPrimitive> predicate) {
Map<Double, List<WaySegment>> nearestMap = new TreeMap<Double, List<WaySegment>>();
DataSet ds = getCurrentDataSet();
if (ds != null) {
double snapDistanceSq = Main.pref.getInteger("mappaint.segment.snap-distance", 10);
snapDistanceSq *= snapDistanceSq;
for (Way w : ds.searchWays(getBBox(p, Main.pref.getInteger("mappaint.segment.snap-distance", 10)))) {
if (!predicate.evaluate(w)) {
continue;
}
Node lastN = null;
int i = -2;
for (Node n : w.getNodes()) {
i++;
if (n.isDeleted() || n.isIncomplete()) { //FIXME: This shouldn't happen, raise exception?
continue;
}
if (lastN == null) {
lastN = n;
continue;
}
Point2D A = getPoint2D(lastN);
Point2D B = getPoint2D(n);
double c = A.distanceSq(B);
double a = p.distanceSq(B);
double b = p.distanceSq(A);
// /* perpendicular distance squared
// * loose some precision to account for possible deviations in the calculation above
// * e.g. if identical (A and B) come about reversed in another way, values may differ
// * -- zero out least significant 32 dual digits of mantissa..
// */
// double perDistSq = Double.longBitsToDouble(
// Double.doubleToLongBits( a - (a - b + c) * (a - b + c) / 4 / c )
// >> 32 << 32); // resolution in numbers with large exponent not needed here..
double perDistSq = a - (a - b + c) * (a - b + c) / 4 / c; // FIXME: GWT
if (perDistSq < snapDistanceSq && a < c + snapDistanceSq && b < c + snapDistanceSq) {
//System.err.println(Double.toHexString(perDistSq));
List<WaySegment> wslist;
if (nearestMap.containsKey(perDistSq)) {
wslist = nearestMap.get(perDistSq);
} else {
wslist = new LinkedList<WaySegment>();
nearestMap.put(perDistSq, wslist);
}
wslist.add(new WaySegment(w, i));
}
lastN = n;
}
}
}
return nearestMap;
}
/**
* The result *order* depends on the current map selection state.
* Segments within 10px of p are searched and sorted by their distance to @param p,
* then, within groups of equally distant segments, prefer those that are selected.
*
* @return all segments within 10px of p that are not in ignore,
* sorted by their perpendicular distance.
*
* @param p the point for which to search the nearest segments.
* @param ignore a collection of segments which are not to be returned.
* @param predicate the returned objects have to fulfill certain properties.
*/
public final List<WaySegment> getNearestWaySegments(Point p,
Collection<WaySegment> ignore, Predicate<OsmPrimitive> predicate) {
List<WaySegment> nearestList = new ArrayList<WaySegment>();
List<WaySegment> unselected = new LinkedList<WaySegment>();
for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
// put selected waysegs within each distance group first
// makes the order of nearestList dependent on current selection state
for (WaySegment ws : wss) {
(ws.way.isSelected() ? nearestList : unselected).add(ws);
}
nearestList.addAll(unselected);
unselected.clear();
}
if (ignore != null) {
nearestList.removeAll(ignore);
}
return nearestList;
}
/**
* The result *order* depends on the current map selection state.
*
* @return all segments within 10px of p, sorted by their perpendicular distance.
* @see #getNearestWaySegments(Point, Collection, Predicate)
*
* @param p the point for which to search the nearest segments.
* @param predicate the returned objects have to fulfill certain properties.
*/
public final List<WaySegment> getNearestWaySegments(Point p, Predicate<OsmPrimitive> predicate) {
return getNearestWaySegments(p, null, predicate);
}
/**
* The *result* depends on the current map selection state IF use_selected is true.
*
* @return The nearest way segment to point p,
* and, depending on use_selected, prefers a selected way segment, if found.
* @see #getNearestWaySegments(Point, Collection, Predicate)
*
* @param p the point for which to search the nearest segment.
* @param predicate the returned object has to fulfill certain properties.
* @param use_selected whether selected way segments should be preferred.
*/
public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate, boolean use_selected) {
WaySegment wayseg = null, ntsel = null;
for (List<WaySegment> wslist : getNearestWaySegmentsImpl(p, predicate).values()) {
if (wayseg != null && ntsel != null) {
break;
}
for (WaySegment ws : wslist) {
if (wayseg == null) {
wayseg = ws;
}
if (ntsel == null && ws.way.isSelected()) {
ntsel = ws;
}
}
}
return (ntsel != null && use_selected) ? ntsel : wayseg;
}
/**
* Convenience method to {@link #getNearestWaySegment(Point, Predicate, boolean)}.
*
* @return The nearest way segment to point p.
*/
public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate) {
return getNearestWaySegment(p, predicate, true);
}
/**
* The *result* does not depend on the current map selection state,
* neither does the result *order*.
* It solely depends on the perpendicular distance to point p.
*
* @return all nearest ways to the screen point given that are not in ignore.
* @see #getNearestWaySegments(Point, Collection, Predicate)
*
* @param p the point for which to search the nearest ways.
* @param ignore a collection of ways which are not to be returned.
* @param predicate the returned object has to fulfill certain properties.
*/
public final List<Way> getNearestWays(Point p,
Collection<Way> ignore, Predicate<OsmPrimitive> predicate) {
List<Way> nearestList = new ArrayList<Way>();
Set<Way> wset = new HashSet<Way>();
for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
for (WaySegment ws : wss) {
if (wset.add(ws.way)) {
nearestList.add(ws.way);
}
}
}
if (ignore != null) {
nearestList.removeAll(ignore);
}
return nearestList;
}
/**
* The *result* does not depend on the current map selection state,
* neither does the result *order*.
* It solely depends on the perpendicular distance to point p.
*
* @return all nearest ways to the screen point given.
* @see #getNearestWays(Point, Collection, Predicate)
*
* @param p the point for which to search the nearest ways.
* @param predicate the returned object has to fulfill certain properties.
*/
public final List<Way> getNearestWays(Point p, Predicate<OsmPrimitive> predicate) {
return getNearestWays(p, null, predicate);
}
/**
* The *result* depends on the current map selection state.
*
* @return The nearest way to point p,
* prefer a selected way if there are multiple nearest.
* @see #getNearestWaySegment(Point, Collection, Predicate)
*
* @param p the point for which to search the nearest segment.
* @param predicate the returned object has to fulfill certain properties.
*/
public final Way getNearestWay(Point p, Predicate<OsmPrimitive> predicate) {
WaySegment nearestWaySeg = getNearestWaySegment(p, predicate);
return (nearestWaySeg == null) ? null : nearestWaySeg.way;
}
@Deprecated
public final Way getNearestWay(Point p) {
return getNearestWay(p, OsmPrimitive.isUsablePredicate);
}
/**
* The *result* does not depend on the current map selection state,
* neither does the result *order*.
* It solely depends on the distance to point p.
*
* First, nodes will be searched. If there are nodes within BBox found,
* return a collection of those nodes only.
*
* If no nodes are found, search for nearest ways. If there are ways
* within BBox found, return a collection of those ways only.
*
* If nothing is found, return an empty collection.
*
* @return Primitives nearest to the given screen point that are not in ignore.
* @see #getNearestNodes(Point, Collection, Predicate)
* @see #getNearestWays(Point, Collection, Predicate)
*
* @param p The point on screen.
* @param ignore a collection of ways which are not to be returned.
* @param predicate the returned object has to fulfill certain properties.
*/
public final List<OsmPrimitive> getNearestNodesOrWays(Point p,
Collection<OsmPrimitive> ignore, Predicate<OsmPrimitive> predicate) {
List<OsmPrimitive> nearestList = Collections.emptyList();
OsmPrimitive osm = getNearestNodeOrWay(p, predicate, false);
if (osm != null) {
if (osm instanceof Node) {
nearestList = new ArrayList<OsmPrimitive>(getNearestNodes(p, predicate));
} else if (osm instanceof Way) {
nearestList = new ArrayList<OsmPrimitive>(getNearestWays(p, predicate));
}
if (ignore != null) {
nearestList.removeAll(ignore);
}
}
return nearestList;
}
/**
* The *result* does not depend on the current map selection state,
* neither does the result *order*.
* It solely depends on the distance to point p.
*
* @return Primitives nearest to the given screen point.
* @see #getNearests(Point, Collection, Predicate)
*
* @param p The point on screen.
* @param predicate the returned object has to fulfill certain properties.
*/
public final List<OsmPrimitive> getNearestNodesOrWays(Point p, Predicate<OsmPrimitive> predicate) {
return getNearestNodesOrWays(p, null, predicate);
}
/**
* This is used as a helper routine to {@link #getNearestNodeOrWay(Point, Predicate, boolean)}
* It decides, whether to yield the node to be tested or look for further (way) candidates.
*
* @return true, if the node fulfills the properties of the function body
*
* @param osm node to check
* @param p point clicked
* @param use_selected whether to prefer selected nodes
*/
private boolean isPrecedenceNode(Node osm, Point p, boolean use_selected) {
boolean ret = false;
if (osm != null) {
ret |= !(p.distanceSq(getPoint2D(osm)) > (4)*(4));
ret |= osm.isTagged();
if (use_selected) {
ret |= osm.isSelected();
}
}
return ret;
}
/**
* The *result* depends on the current map selection state IF use_selected is true.
*
* IF use_selected is true, use {@link #getNearestNode(Point, Predicate)} to find
* the nearest, selected node. If not found, try {@link #getNearestWaySegment(Point, Predicate)}
* to find the nearest selected way.
*
* IF use_selected is false, or if no selected primitive was found, do the following.
*
* If the nearest node found is within 4px of p, simply take it.
* Else, find the nearest way segment. Then, if p is closer to its
* middle than to the node, take the way segment, else take the node.
*
* Finally, if no nearest primitive is found at all, return null.
*
* @return A primitive within snap-distance to point p,
* that is chosen by the algorithm described.
* @see getNearestNode(Point, Predicate)
* @see getNearestNodesImpl(Point, Predicate)
* @see getNearestWay(Point, Predicate)
*
* @param p The point on screen.
* @param predicate the returned object has to fulfill certain properties.
* @param use_selected whether to prefer primitives that are currently selected.
*/
public final OsmPrimitive getNearestNodeOrWay(Point p, Predicate<OsmPrimitive> predicate, boolean use_selected) {
OsmPrimitive osm = getNearestNode(p, predicate, use_selected);
WaySegment ws = null;
if (!isPrecedenceNode((Node)osm, p, use_selected)) {
ws = getNearestWaySegment(p, predicate, use_selected);
if (ws != null) {
if ((ws.way.isSelected() && use_selected) || osm == null) {
// either (no _selected_ nearest node found, if desired) or no nearest node was found
osm = ws.way;
} else {
int maxWaySegLenSq = 3*PROP_SNAP_DISTANCE.get();
maxWaySegLenSq *= maxWaySegLenSq;
Point2D wp1 = getPoint2D(ws.way.getNode(ws.lowerIndex));
Point2D wp2 = getPoint2D(ws.way.getNode(ws.lowerIndex+1));
// is wayseg shorter than maxWaySegLenSq and
// is p closer to the middle of wayseg than to the nearest node?
if (wp1.distanceSq(wp2) < maxWaySegLenSq &&
p.distanceSq(project(0.5, wp1, wp2)) < p.distanceSq(getPoint2D((Node)osm))) {
osm = ws.way;
}
}
}
}
return osm;
}
@Deprecated
public final OsmPrimitive getNearest(Point p, Predicate<OsmPrimitive> predicate) {
return getNearestNodeOrWay(p, predicate, false);
}
@Deprecated
public final Collection<OsmPrimitive> getNearestCollection(Point p, Predicate<OsmPrimitive> predicate) {
return asColl(getNearest(p, predicate));
}
/**
* @return o as collection of o's type.
*/
public static <T> Collection<T> asColl(T o) {
if (o == null)
return Collections.emptySet();
return Collections.singleton(o);
}
public static double perDist(Point2D pt, Point2D a, Point2D b) {
if (pt != null && a != null && b != null) {
double pd = (
(a.getX()-pt.getX())*(b.getX()-a.getX()) -
(a.getY()-pt.getY())*(b.getY()-a.getY()) );
return Math.abs(pd) / a.distance(b);
}
return 0d;
}
/**
*
* @param pt point to project onto (ab)
* @param a root of vector
* @param b vector
* @return point of intersection of line given by (ab)
* with its orthogonal line running through pt
*/
public static Point2D project(Point2D pt, Point2D a, Point2D b) {
if (pt != null && a != null && b != null) {
double r = ((
(pt.getX()-a.getX())*(b.getX()-a.getX()) +
(pt.getY()-a.getY())*(b.getY()-a.getY()) )
/ a.distanceSq(b));
return project(r, a, b);
}
return null;
}
/**
* if r = 0 returns a, if r=1 returns b,
* if r = 0.5 returns center between a and b, etc..
*
* @param r scale value
* @param a root of vector
* @param b vector
* @return new point at a + r*(ab)
*/
public static Point2D project(double r, Point2D a, Point2D b) {
Point2D ret = null;
if (a != null && b != null) {
ret = new Point2D.Double(a.getX() + r*(b.getX()-a.getX()),
a.getY() + r*(b.getY()-a.getY()));
}
return ret;
}
/**
* The *result* does not depend on the current map selection state,
* neither does the result *order*.
* It solely depends on the distance to point p.
*
* @return a list of all objects that are nearest to point p and
* not in ignore or an empty list if nothing was found.
*
* @param p The point on screen.
* @param ignore a collection of ways which are not to be returned.
* @param predicate the returned object has to fulfill certain properties.
*/
public final List<OsmPrimitive> getAllNearest(Point p,
Collection<OsmPrimitive> ignore, Predicate<OsmPrimitive> predicate) {
List<OsmPrimitive> nearestList = new ArrayList<OsmPrimitive>();
Set<Way> wset = new HashSet<Way>();
for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
for (WaySegment ws : wss) {
if (wset.add(ws.way)) {
nearestList.add(ws.way);
}
}
}
for (List<Node> nlist : getNearestNodesImpl(p, predicate).values()) {
nearestList.addAll(nlist);
}
if (ignore != null) {
nearestList.removeAll(ignore);
}
return nearestList;
}
/**
* The *result* does not depend on the current map selection state,
* neither does the result *order*.
* It solely depends on the distance to point p.
*
* @return a list of all objects that are nearest to point p
* or an empty list if nothing was found.
* @see #getAllNearest(Point, Collection, Predicate)
*
* @param p The point on screen.
* @param predicate the returned object has to fulfill certain properties.
*/
public final List<OsmPrimitive> getAllNearest(Point p, Predicate<OsmPrimitive> predicate) {
return getAllNearest(p, null, predicate);
}
/**
* @return The projection to be used in calculating stuff.
*/
public Projection getProjection() {
return Main.proj;
}
public String helpTopic() {
String n = getClass().getName();
return n.substring(n.lastIndexOf('.')+1);
}
int viewID_FIXME = 0;
/**
* Return a ID which is unique as long as viewport dimensions are the same
*/
public int getViewID() {
return ++viewID_FIXME;
// String x = center.east() + "_" + center.north() + "_" + scale + "_" +
// getWidth() + "_" + getHeight() + "_" + getProjection().toString();
// java.util.zip.CRC32 id = new java.util.zip.CRC32();
// id.update(x.getBytes());
// return (int)id.getValue();
}
public static SystemOfMeasurement getSystemOfMeasurement() {
SystemOfMeasurement som = SYSTEMS_OF_MEASUREMENT.get(ProjectionPreference.PROP_SYSTEM_OF_MEASUREMENT.get());
if (som == null)
return METRIC_SOM;
return som;
}
public static class SystemOfMeasurement {
public final double aValue;
public final double bValue;
public final String aName;
public final String bName;
/**
* System of measurement. Currently covers only length units.
*
* If a quantity x is given in m (x_m) and in unit a (x_a) then it translates as
* x_a == x_m / aValue
*/
public SystemOfMeasurement(double aValue, String aName, double bValue, String bName) {
this.aValue = aValue;
this.aName = aName;
this.bValue = bValue;
this.bName = bName;
}
public String getDistText(double dist) {
throw new UnsupportedOperationException("gwt - implement me");
// double a = dist / aValue;
// if (!Main.pref.getBoolean("system_of_measurement.use_only_lower_unit", false) && a > bValue / aValue) {
// double b = dist / bValue;
// return String.format(Locale.US, "%." + (b<10 ? 2 : 1) + "f %s", b, bName);
// } else if (a < 0.01)
// return "< 0.01 " + aName;
// else
// return String.format(Locale.US, "%." + (a<10 ? 2 : 1) + "f %s", a, aName);
}
}
public static final SystemOfMeasurement METRIC_SOM = new SystemOfMeasurement(1, "m", 1000, "km");
public static final SystemOfMeasurement CHINESE_SOM = new SystemOfMeasurement(1.0/3.0, "\u5e02\u5c3a" /* chi */, 500, "\u5e02\u91cc" /* li */);
public static final SystemOfMeasurement IMPERIAL_SOM = new SystemOfMeasurement(0.3048, "ft", 1609.344, "mi");
public static Map<String, SystemOfMeasurement> SYSTEMS_OF_MEASUREMENT;
static {
SYSTEMS_OF_MEASUREMENT = new LinkedHashMap<String, SystemOfMeasurement>();
SYSTEMS_OF_MEASUREMENT.put(marktr("Metric"), METRIC_SOM);
SYSTEMS_OF_MEASUREMENT.put(marktr("Chinese"), CHINESE_SOM);
SYSTEMS_OF_MEASUREMENT.put(marktr("Imperial"), IMPERIAL_SOM);
}
// private class CursorInfo {
// public Cursor cursor;
// public Object object;
// public CursorInfo(Cursor c, Object o) {
// cursor = c;
// object = o;
// }
// }
//
// private LinkedList<CursorInfo> Cursors = new LinkedList<CursorInfo>();
// /**
// * Set new cursor.
// */
// public void setNewCursor(Cursor cursor, Object reference) {
// if(Cursors.size() > 0) {
// CursorInfo l = Cursors.getLast();
// if(l != null && l.cursor == cursor && l.object == reference) {
// return;
// }
// stripCursors(reference);
// }
// Cursors.add(new CursorInfo(cursor, reference));
// setCursor(cursor);
// }
// public void setNewCursor(int cursor, Object reference) {
// setNewCursor(Cursor.getPredefinedCursor(cursor), reference);
// }
// /**
// * Remove the new cursor and reset to previous
// */
// public void resetCursor(Object reference) {
// if(Cursors.size() == 0) {
// setCursor(null);
// return;
// }
// CursorInfo l = Cursors.getLast();
// stripCursors(reference);
// if(l != null && l.object == reference) {
// if(Cursors.size() == 0)
// setCursor(null);
// else
// setCursor(Cursors.getLast().cursor);
// }
// }
//
// private void stripCursors(Object reference) {
// LinkedList<CursorInfo> c = new LinkedList<CursorInfo>();
// for(CursorInfo i : Cursors) {
// if(i.object != reference)
// c.add(i);
// }
// Cursors = c;
// }
}