001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui;
003
004import java.awt.Cursor;
005import java.awt.Graphics;
006import java.awt.Point;
007import java.awt.Polygon;
008import java.awt.Rectangle;
009import java.awt.geom.AffineTransform;
010import java.awt.geom.Point2D;
011import java.nio.charset.StandardCharsets;
012import java.text.NumberFormat;
013import java.util.ArrayList;
014import java.util.Collection;
015import java.util.Collections;
016import java.util.Date;
017import java.util.HashSet;
018import java.util.LinkedList;
019import java.util.List;
020import java.util.Map;
021import java.util.Map.Entry;
022import java.util.Set;
023import java.util.Stack;
024import java.util.TreeMap;
025import java.util.concurrent.CopyOnWriteArrayList;
026import java.util.zip.CRC32;
027
028import javax.swing.JComponent;
029
030import org.openstreetmap.josm.Main;
031import org.openstreetmap.josm.data.Bounds;
032import org.openstreetmap.josm.data.ProjectionBounds;
033import org.openstreetmap.josm.data.SystemOfMeasurement;
034import org.openstreetmap.josm.data.ViewportData;
035import org.openstreetmap.josm.data.coor.CachedLatLon;
036import org.openstreetmap.josm.data.coor.EastNorth;
037import org.openstreetmap.josm.data.coor.LatLon;
038import org.openstreetmap.josm.data.osm.BBox;
039import org.openstreetmap.josm.data.osm.DataSet;
040import org.openstreetmap.josm.data.osm.Node;
041import org.openstreetmap.josm.data.osm.OsmPrimitive;
042import org.openstreetmap.josm.data.osm.Relation;
043import org.openstreetmap.josm.data.osm.Way;
044import org.openstreetmap.josm.data.osm.WaySegment;
045import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
046import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
047import org.openstreetmap.josm.data.preferences.IntegerProperty;
048import org.openstreetmap.josm.data.projection.Projection;
049import org.openstreetmap.josm.data.projection.Projections;
050import org.openstreetmap.josm.gui.download.DownloadDialog;
051import org.openstreetmap.josm.gui.help.Helpful;
052import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
053import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
054import org.openstreetmap.josm.gui.util.CursorManager;
055import org.openstreetmap.josm.tools.Predicate;
056import org.openstreetmap.josm.tools.Utils;
057
058/**
059 * A component that can be navigated by a {@link MapMover}. Used as map view and for the
060 * zoomer in the download dialog.
061 *
062 * @author imi
063 * @since 41
064 */
065public class NavigatableComponent extends JComponent implements Helpful {
066
067    /**
068     * Interface to notify listeners of the change of the zoom area.
069     */
070    public interface ZoomChangeListener {
071        /**
072         * Method called when the zoom area has changed.
073         */
074        void zoomChanged();
075    }
076
077    public transient Predicate<OsmPrimitive> isSelectablePredicate = new Predicate<OsmPrimitive>() {
078        @Override
079        public boolean evaluate(OsmPrimitive prim) {
080            if (!prim.isSelectable()) return false;
081            // if it isn't displayed on screen, you cannot click on it
082            MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().lock();
083            try {
084                return !MapPaintStyles.getStyles().get(prim, getDist100Pixel(), NavigatableComponent.this).isEmpty();
085            } finally {
086                MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().unlock();
087            }
088        }
089    };
090
091    public static final IntegerProperty PROP_SNAP_DISTANCE = new IntegerProperty("mappaint.node.snap-distance", 10);
092
093    public static final String PROPNAME_CENTER = "center";
094    public static final String PROPNAME_SCALE  = "scale";
095
096    /**
097     * the zoom listeners
098     */
099    private static final CopyOnWriteArrayList<ZoomChangeListener> zoomChangeListeners = new CopyOnWriteArrayList<>();
100
101    /**
102     * Removes a zoom change listener
103     *
104     * @param listener the listener. Ignored if null or already absent
105     */
106    public static void removeZoomChangeListener(NavigatableComponent.ZoomChangeListener listener) {
107        zoomChangeListeners.remove(listener);
108    }
109
110    /**
111     * Adds a zoom change listener
112     *
113     * @param listener the listener. Ignored if null or already registered.
114     */
115    public static void addZoomChangeListener(NavigatableComponent.ZoomChangeListener listener) {
116        if (listener != null) {
117            zoomChangeListeners.addIfAbsent(listener);
118        }
119    }
120
121    protected static void fireZoomChanged() {
122        for (ZoomChangeListener l : zoomChangeListeners) {
123            l.zoomChanged();
124        }
125    }
126
127    private double scale = Main.getProjection().getDefaultZoomInPPD();
128    /**
129     * Center n/e coordinate of the desired screen center.
130     */
131    protected EastNorth center = calculateDefaultCenter();
132
133    private final transient Object paintRequestLock = new Object();
134    private Rectangle paintRect;
135    private Polygon paintPoly;
136
137    protected transient ViewportData initialViewport;
138
139    protected final transient CursorManager cursorManager = new CursorManager(this);
140
141    /**
142     * Constructs a new {@code NavigatableComponent}.
143     */
144    public NavigatableComponent() {
145        setLayout(null);
146    }
147
148    protected DataSet getCurrentDataSet() {
149        return Main.main.getCurrentDataSet();
150    }
151
152    private static EastNorth calculateDefaultCenter() {
153        Bounds b = DownloadDialog.getSavedDownloadBounds();
154        if (b == null) {
155            b = Main.getProjection().getWorldBoundsLatLon();
156        }
157        return Main.getProjection().latlon2eastNorth(b.getCenter());
158    }
159
160    /**
161     * Returns the text describing the given distance in the current system of measurement.
162     * @param dist The distance in metres.
163     * @return the text describing the given distance in the current system of measurement.
164     * @since 3406
165     */
166    public static String getDistText(double dist) {
167        return SystemOfMeasurement.getSystemOfMeasurement().getDistText(dist);
168    }
169
170    /**
171     * Returns the text describing the given distance in the current system of measurement.
172     * @param dist The distance in metres
173     * @param format A {@link NumberFormat} to format the area value
174     * @param threshold Values lower than this {@code threshold} are displayed as {@code "< [threshold]"}
175     * @return the text describing the given distance in the current system of measurement.
176     * @since 7135
177     */
178    public static String getDistText(final double dist, final NumberFormat format, final double threshold) {
179        return SystemOfMeasurement.getSystemOfMeasurement().getDistText(dist, format, threshold);
180    }
181
182    /**
183     * Returns the text describing the given area in the current system of measurement.
184     * @param area The distance in square metres.
185     * @return the text describing the given area in the current system of measurement.
186     * @since 5560
187     */
188    public static String getAreaText(double area) {
189        return SystemOfMeasurement.getSystemOfMeasurement().getAreaText(area);
190    }
191
192    /**
193     * Returns the text describing the given area in the current system of measurement.
194     * @param area The area in square metres
195     * @param format A {@link NumberFormat} to format the area value
196     * @param threshold Values lower than this {@code threshold} are displayed as {@code "< [threshold]"}
197     * @return the text describing the given area in the current system of measurement.
198     * @since 7135
199     */
200    public static String getAreaText(final double area, final NumberFormat format, final double threshold) {
201        return SystemOfMeasurement.getSystemOfMeasurement().getAreaText(area, format, threshold);
202    }
203
204    public String getDist100PixelText() {
205        return getDistText(getDist100Pixel());
206    }
207
208    public double getDist100Pixel() {
209        int w = getWidth()/2;
210        int h = getHeight()/2;
211        LatLon ll1 = getLatLon(w-50, h);
212        LatLon ll2 = getLatLon(w+50, h);
213        return ll1.greatCircleDistance(ll2);
214    }
215
216    /**
217     * @return Returns the center point. A copy is returned, so users cannot
218     *      change the center by accessing the return value. Use zoomTo instead.
219     */
220    public EastNorth getCenter() {
221        return center;
222    }
223
224    public double getScale() {
225        return scale;
226    }
227
228    /**
229     * @param x X-Pixelposition to get coordinate from
230     * @param y Y-Pixelposition to get coordinate from
231     *
232     * @return Geographic coordinates from a specific pixel coordination on the screen.
233     */
234    public EastNorth getEastNorth(int x, int y) {
235        return new EastNorth(
236                center.east() + (x - getWidth()/2.0)*scale,
237                center.north() - (y - getHeight()/2.0)*scale);
238    }
239
240    public ProjectionBounds getProjectionBounds() {
241        return new ProjectionBounds(
242                new EastNorth(
243                        center.east() - getWidth()/2.0*scale,
244                        center.north() - getHeight()/2.0*scale),
245                        new EastNorth(
246                                center.east() + getWidth()/2.0*scale,
247                                center.north() + getHeight()/2.0*scale));
248    }
249
250    /* FIXME: replace with better method - used by MapSlider */
251    public ProjectionBounds getMaxProjectionBounds() {
252        Bounds b = getProjection().getWorldBoundsLatLon();
253        return new ProjectionBounds(getProjection().latlon2eastNorth(b.getMin()),
254                getProjection().latlon2eastNorth(b.getMax()));
255    }
256
257    /* FIXME: replace with better method - used by Main to reset Bounds when projection changes, don't use otherwise */
258    public Bounds getRealBounds() {
259        return new Bounds(
260                getProjection().eastNorth2latlon(new EastNorth(
261                        center.east() - getWidth()/2.0*scale,
262                        center.north() - getHeight()/2.0*scale)),
263                        getProjection().eastNorth2latlon(new EastNorth(
264                                center.east() + getWidth()/2.0*scale,
265                                center.north() + getHeight()/2.0*scale)));
266    }
267
268    /**
269     * @param x X-Pixelposition to get coordinate from
270     * @param y Y-Pixelposition to get coordinate from
271     *
272     * @return Geographic unprojected coordinates from a specific pixel coordination
273     *      on the screen.
274     */
275    public LatLon getLatLon(int x, int y) {
276        return getProjection().eastNorth2latlon(getEastNorth(x, y));
277    }
278
279    public LatLon getLatLon(double x, double y) {
280        return getLatLon((int) x, (int) y);
281    }
282
283    /**
284     * @param r rectangle
285     * @return Minimum bounds that will cover rectangle
286     */
287    public Bounds getLatLonBounds(Rectangle r) {
288        // TODO Maybe this should be (optional) method of Projection implementation
289        EastNorth p1 = getEastNorth(r.x, r.y);
290        EastNorth p2 = getEastNorth(r.x + r.width, r.y + r.height);
291
292        Bounds result = new Bounds(Main.getProjection().eastNorth2latlon(p1));
293
294        double eastMin = Math.min(p1.east(), p2.east());
295        double eastMax = Math.max(p1.east(), p2.east());
296        double northMin = Math.min(p1.north(), p2.north());
297        double northMax = Math.max(p1.north(), p2.north());
298        double deltaEast = (eastMax - eastMin) / 10;
299        double deltaNorth = (northMax - northMin) / 10;
300
301        for (int i = 0; i < 10; i++) {
302            result.extend(Main.getProjection().eastNorth2latlon(new EastNorth(eastMin + i * deltaEast, northMin)));
303            result.extend(Main.getProjection().eastNorth2latlon(new EastNorth(eastMin + i * deltaEast, northMax)));
304            result.extend(Main.getProjection().eastNorth2latlon(new EastNorth(eastMin, northMin  + i * deltaNorth)));
305            result.extend(Main.getProjection().eastNorth2latlon(new EastNorth(eastMax, northMin  + i * deltaNorth)));
306        }
307
308        return result;
309    }
310
311    public AffineTransform getAffineTransform() {
312        return new AffineTransform(
313                1.0/scale, 0.0, 0.0, -1.0/scale, getWidth()/2.0 - center.east()/scale, getHeight()/2.0 + center.north()/scale);
314    }
315
316    /**
317     * Return the point on the screen where this Coordinate would be.
318     * @param p The point, where this geopoint would be drawn.
319     * @return The point on screen where "point" would be drawn, relative
320     *      to the own top/left.
321     */
322    public Point2D getPoint2D(EastNorth p) {
323        if (null == p)
324            return new Point();
325        double x = (p.east()-center.east())/scale + getWidth()/2d;
326        double y = (center.north()-p.north())/scale + getHeight()/2d;
327        return new Point2D.Double(x, y);
328    }
329
330    public Point2D getPoint2D(LatLon latlon) {
331        if (latlon == null)
332            return new Point();
333        else if (latlon instanceof CachedLatLon)
334            return getPoint2D(((CachedLatLon) latlon).getEastNorth());
335        else
336            return getPoint2D(getProjection().latlon2eastNorth(latlon));
337    }
338
339    public Point2D getPoint2D(Node n) {
340        return getPoint2D(n.getEastNorth());
341    }
342
343    // looses precision, may overflow (depends on p and current scale)
344    //@Deprecated
345    public Point getPoint(EastNorth p) {
346        Point2D d = getPoint2D(p);
347        return new Point((int) d.getX(), (int) d.getY());
348    }
349
350    // looses precision, may overflow (depends on p and current scale)
351    //@Deprecated
352    public Point getPoint(LatLon latlon) {
353        Point2D d = getPoint2D(latlon);
354        return new Point((int) d.getX(), (int) d.getY());
355    }
356
357    // looses precision, may overflow (depends on p and current scale)
358    //@Deprecated
359    public Point getPoint(Node n) {
360        Point2D d = getPoint2D(n);
361        return new Point((int) d.getX(), (int) d.getY());
362    }
363
364    /**
365     * Zoom to the given coordinate and scale.
366     *
367     * @param newCenter The center x-value (easting) to zoom to.
368     * @param newScale The scale to use.
369     */
370    public void zoomTo(EastNorth newCenter, double newScale) {
371        zoomTo(newCenter, newScale, false);
372    }
373
374    /**
375     * Zoom to the given coordinate and scale.
376     *
377     * @param newCenter The center x-value (easting) to zoom to.
378     * @param newScale The scale to use.
379     * @param initial true if this call initializes the viewport.
380     */
381    public void zoomTo(EastNorth newCenter, double newScale, boolean initial) {
382        Bounds b = getProjection().getWorldBoundsLatLon();
383        LatLon cl = Projections.inverseProject(newCenter);
384        boolean changed = false;
385        double lat = cl.lat();
386        double lon = cl.lon();
387        if (lat < b.getMinLat()) {
388            changed = true;
389            lat = b.getMinLat();
390        } else if (lat > b.getMaxLat()) {
391            changed = true;
392            lat = b.getMaxLat();
393        }
394        if (lon < b.getMinLon()) {
395            changed = true;
396            lon = b.getMinLon();
397        } else if (lon > b.getMaxLon()) {
398            changed = true;
399            lon = b.getMaxLon();
400        }
401        if (changed) {
402            newCenter = Projections.project(new LatLon(lat, lon));
403        }
404        int width = getWidth()/2;
405        int height = getHeight()/2;
406        LatLon l1 = new LatLon(b.getMinLat(), lon);
407        LatLon l2 = new LatLon(b.getMaxLat(), lon);
408        EastNorth e1 = getProjection().latlon2eastNorth(l1);
409        EastNorth e2 = getProjection().latlon2eastNorth(l2);
410        double d = e2.north() - e1.north();
411        if (height > 0 && d < height*newScale) {
412            double newScaleH = d/height;
413            e1 = getProjection().latlon2eastNorth(new LatLon(lat, b.getMinLon()));
414            e2 = getProjection().latlon2eastNorth(new LatLon(lat, b.getMaxLon()));
415            d = e2.east() - e1.east();
416            if (width > 0 && d < width*newScale) {
417                newScale = Math.max(newScaleH, d/width);
418            }
419        } else if (height > 0) {
420            d = d/(l1.greatCircleDistance(l2)*height*10);
421            if (newScale < d) {
422                newScale = d;
423            }
424        }
425
426        if (!newCenter.equals(center) || !Utils.equalsEpsilon(scale, newScale)) {
427            if (!initial) {
428                pushZoomUndo(center, scale);
429            }
430            zoomNoUndoTo(newCenter, newScale, initial);
431        }
432    }
433
434    /**
435     * Zoom to the given coordinate without adding to the zoom undo buffer.
436     *
437     * @param newCenter The center x-value (easting) to zoom to.
438     * @param newScale The scale to use.
439     * @param initial true if this call initializes the viewport.
440     */
441    private void zoomNoUndoTo(EastNorth newCenter, double newScale, boolean initial) {
442        if (!newCenter.equals(center)) {
443            EastNorth oldCenter = center;
444            center = newCenter;
445            if (!initial) {
446                firePropertyChange(PROPNAME_CENTER, oldCenter, newCenter);
447            }
448        }
449        if (!Utils.equalsEpsilon(scale, newScale)) {
450            double oldScale = scale;
451            scale = newScale;
452            if (!initial) {
453                firePropertyChange(PROPNAME_SCALE, oldScale, newScale);
454            }
455        }
456
457        if (!initial) {
458            repaint();
459            fireZoomChanged();
460        }
461    }
462
463    public void zoomTo(EastNorth newCenter) {
464        zoomTo(newCenter, scale);
465    }
466
467    public void zoomTo(LatLon newCenter) {
468        zoomTo(Projections.project(newCenter));
469    }
470
471    public void smoothScrollTo(LatLon newCenter) {
472        smoothScrollTo(Projections.project(newCenter));
473    }
474
475    /**
476     * Create a thread that moves the viewport to the given center in an
477     * animated fashion.
478     */
479    public void smoothScrollTo(EastNorth newCenter) {
480        // FIXME make these configurable.
481        final int fps = 20;     // animation frames per second
482        final int speed = 1500; // milliseconds for full-screen-width pan
483        if (!newCenter.equals(center)) {
484            final EastNorth oldCenter = center;
485            final double distance = newCenter.distance(oldCenter) / scale;
486            final double milliseconds = distance / getWidth() * speed;
487            final double frames = milliseconds * fps / 1000;
488            final EastNorth finalNewCenter = newCenter;
489
490            new Thread("smooth-scroller") {
491                @Override
492                public void run() {
493                    for (int i = 0; i < frames; i++) {
494                        // FIXME - not use zoom history here
495                        zoomTo(oldCenter.interpolate(finalNewCenter, (i+1) / frames));
496                        try {
497                            Thread.sleep(1000 / fps);
498                        } catch (InterruptedException ex) {
499                            Main.warn("InterruptedException in "+NavigatableComponent.class.getSimpleName()+" during smooth scrolling");
500                        }
501                    }
502                }
503            }.start();
504        }
505    }
506
507    public void zoomToFactor(double x, double y, double factor) {
508        double newScale = scale*factor;
509        // New center position so that point under the mouse pointer stays the same place as it was before zooming
510        // You will get the formula by simplifying this expression: newCenter = oldCenter + mouseCoordinatesInNewZoom - mouseCoordinatesInOldZoom
511        zoomTo(new EastNorth(
512                center.east() - (x - getWidth()/2.0) * (newScale - scale),
513                center.north() + (y - getHeight()/2.0) * (newScale - scale)),
514                newScale);
515    }
516
517    public void zoomToFactor(EastNorth newCenter, double factor) {
518        zoomTo(newCenter, scale*factor);
519    }
520
521    public void zoomToFactor(double factor) {
522        zoomTo(center, scale*factor);
523    }
524
525    public void zoomTo(ProjectionBounds box) {
526        // -20 to leave some border
527        int w = getWidth()-20;
528        if (w < 20) {
529            w = 20;
530        }
531        int h = getHeight()-20;
532        if (h < 20) {
533            h = 20;
534        }
535
536        double scaleX = (box.maxEast-box.minEast)/w;
537        double scaleY = (box.maxNorth-box.minNorth)/h;
538        double newScale = Math.max(scaleX, scaleY);
539
540        zoomTo(box.getCenter(), newScale);
541    }
542
543    public void zoomTo(Bounds box) {
544        zoomTo(new ProjectionBounds(getProjection().latlon2eastNorth(box.getMin()),
545                getProjection().latlon2eastNorth(box.getMax())));
546    }
547
548    public void zoomTo(ViewportData viewport) {
549        if (viewport == null) return;
550        if (viewport.getBounds() != null) {
551            BoundingXYVisitor box = new BoundingXYVisitor();
552            box.visit(viewport.getBounds());
553            zoomTo(box);
554        } else {
555            zoomTo(viewport.getCenter(), viewport.getScale(), true);
556        }
557    }
558
559    /**
560     * Set the new dimension to the view.
561     */
562    public void zoomTo(BoundingXYVisitor box) {
563        if (box == null) {
564            box = new BoundingXYVisitor();
565        }
566        if (box.getBounds() == null) {
567            box.visit(getProjection().getWorldBoundsLatLon());
568        }
569        if (!box.hasExtend()) {
570            box.enlargeBoundingBox();
571        }
572
573        zoomTo(box.getBounds());
574    }
575
576    private class ZoomData {
577        private final LatLon center;
578        private final double scale;
579
580        ZoomData(EastNorth center, double scale) {
581            this.center = Projections.inverseProject(center);
582            this.scale = scale;
583        }
584
585        public EastNorth getCenterEastNorth() {
586            return getProjection().latlon2eastNorth(center);
587        }
588
589        public double getScale() {
590            return scale;
591        }
592    }
593
594    private Stack<ZoomData> zoomUndoBuffer = new Stack<>();
595    private Stack<ZoomData> zoomRedoBuffer = new Stack<>();
596    private Date zoomTimestamp = new Date();
597
598    private void pushZoomUndo(EastNorth center, double scale) {
599        Date now = new Date();
600        if ((now.getTime() - zoomTimestamp.getTime()) > (Main.pref.getDouble("zoom.undo.delay", 1.0) * 1000)) {
601            zoomUndoBuffer.push(new ZoomData(center, scale));
602            if (zoomUndoBuffer.size() > Main.pref.getInteger("zoom.undo.max", 50)) {
603                zoomUndoBuffer.remove(0);
604            }
605            zoomRedoBuffer.clear();
606        }
607        zoomTimestamp = now;
608    }
609
610    public void zoomPrevious() {
611        if (!zoomUndoBuffer.isEmpty()) {
612            ZoomData zoom = zoomUndoBuffer.pop();
613            zoomRedoBuffer.push(new ZoomData(center, scale));
614            zoomNoUndoTo(zoom.getCenterEastNorth(), zoom.getScale(), false);
615        }
616    }
617
618    public void zoomNext() {
619        if (!zoomRedoBuffer.isEmpty()) {
620            ZoomData zoom = zoomRedoBuffer.pop();
621            zoomUndoBuffer.push(new ZoomData(center, scale));
622            zoomNoUndoTo(zoom.getCenterEastNorth(), zoom.getScale(), false);
623        }
624    }
625
626    public boolean hasZoomUndoEntries() {
627        return !zoomUndoBuffer.isEmpty();
628    }
629
630    public boolean hasZoomRedoEntries() {
631        return !zoomRedoBuffer.isEmpty();
632    }
633
634    private BBox getBBox(Point p, int snapDistance) {
635        return new BBox(getLatLon(p.x - snapDistance, p.y - snapDistance),
636                getLatLon(p.x + snapDistance, p.y + snapDistance));
637    }
638
639    /**
640     * The *result* does not depend on the current map selection state,
641     * neither does the result *order*.
642     * It solely depends on the distance to point p.
643     *
644     * @return a sorted map with the keys representing the distance of
645     *      their associated nodes to point p.
646     */
647    private Map<Double, List<Node>> getNearestNodesImpl(Point p,
648            Predicate<OsmPrimitive> predicate) {
649        Map<Double, List<Node>> nearestMap = new TreeMap<>();
650        DataSet ds = getCurrentDataSet();
651
652        if (ds != null) {
653            double dist, snapDistanceSq = PROP_SNAP_DISTANCE.get();
654            snapDistanceSq *= snapDistanceSq;
655
656            for (Node n : ds.searchNodes(getBBox(p, PROP_SNAP_DISTANCE.get()))) {
657                if (predicate.evaluate(n)
658                        && (dist = getPoint2D(n).distanceSq(p)) < snapDistanceSq) {
659                    List<Node> nlist;
660                    if (nearestMap.containsKey(dist)) {
661                        nlist = nearestMap.get(dist);
662                    } else {
663                        nlist = new LinkedList<>();
664                        nearestMap.put(dist, nlist);
665                    }
666                    nlist.add(n);
667                }
668            }
669        }
670
671        return nearestMap;
672    }
673
674    /**
675     * The *result* does not depend on the current map selection state,
676     * neither does the result *order*.
677     * It solely depends on the distance to point p.
678     *
679     * @param p the point for which to search the nearest segment.
680     * @param ignore a collection of nodes which are not to be returned.
681     * @param predicate the returned objects have to fulfill certain properties.
682     *
683     * @return All nodes nearest to point p that are in a belt from
684     *      dist(nearest) to dist(nearest)+4px around p and
685     *      that are not in ignore.
686     */
687    public final List<Node> getNearestNodes(Point p,
688            Collection<Node> ignore, Predicate<OsmPrimitive> predicate) {
689        List<Node> nearestList = Collections.emptyList();
690
691        if (ignore == null) {
692            ignore = Collections.emptySet();
693        }
694
695        Map<Double, List<Node>> nlists = getNearestNodesImpl(p, predicate);
696        if (!nlists.isEmpty()) {
697            Double minDistSq = null;
698            for (Entry<Double, List<Node>> entry : nlists.entrySet()) {
699                Double distSq = entry.getKey();
700                List<Node> nlist = entry.getValue();
701
702                // filter nodes to be ignored before determining minDistSq..
703                nlist.removeAll(ignore);
704                if (minDistSq == null) {
705                    if (!nlist.isEmpty()) {
706                        minDistSq = distSq;
707                        nearestList = new ArrayList<>();
708                        nearestList.addAll(nlist);
709                    }
710                } else {
711                    if (distSq-minDistSq < (4)*(4)) {
712                        nearestList.addAll(nlist);
713                    }
714                }
715            }
716        }
717
718        return nearestList;
719    }
720
721    /**
722     * The *result* does not depend on the current map selection state,
723     * neither does the result *order*.
724     * It solely depends on the distance to point p.
725     *
726     * @param p the point for which to search the nearest segment.
727     * @param predicate the returned objects have to fulfill certain properties.
728     *
729     * @return All nodes nearest to point p that are in a belt from
730     *      dist(nearest) to dist(nearest)+4px around p.
731     * @see #getNearestNodes(Point, Collection, Predicate)
732     */
733    public final List<Node> getNearestNodes(Point p, Predicate<OsmPrimitive> predicate) {
734        return getNearestNodes(p, null, predicate);
735    }
736
737    /**
738     * The *result* depends on the current map selection state IF use_selected is true.
739     *
740     * If more than one node within node.snap-distance pixels is found,
741     * the nearest node selected is returned IF use_selected is true.
742     *
743     * Else the nearest new/id=0 node within about the same distance
744     * as the true nearest node is returned.
745     *
746     * If no such node is found either, the true nearest
747     * node to p is returned.
748     *
749     * Finally, if a node is not found at all, null is returned.
750     *
751     * @param p the screen point
752     * @param predicate this parameter imposes a condition on the returned object, e.g.
753     *        give the nearest node that is tagged.
754     *
755     * @return A node within snap-distance to point p,
756     *      that is chosen by the algorithm described.
757     */
758    public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate, boolean useSelected) {
759        return getNearestNode(p, predicate, useSelected, null);
760    }
761
762    /**
763     * The *result* depends on the current map selection state IF use_selected is true
764     *
765     * If more than one node within node.snap-distance pixels is found,
766     * the nearest node selected is returned IF use_selected is true.
767     *
768     * If there are no selected nodes near that point, the node that is related to some of the preferredRefs
769     *
770     * Else the nearest new/id=0 node within about the same distance
771     * as the true nearest node is returned.
772     *
773     * If no such node is found either, the true nearest
774     * node to p is returned.
775     *
776     * Finally, if a node is not found at all, null is returned.
777     *
778     * @param p the screen point
779     * @param predicate this parameter imposes a condition on the returned object, e.g.
780     *        give the nearest node that is tagged.
781     * @param preferredRefs primitives, whose nodes we prefer
782     *
783     * @return A node within snap-distance to point p,
784     *      that is chosen by the algorithm described.
785     * @since 6065
786     */
787    public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate,
788            boolean useSelected, Collection<OsmPrimitive> preferredRefs) {
789
790        Map<Double, List<Node>> nlists = getNearestNodesImpl(p, predicate);
791        if (nlists.isEmpty()) return null;
792
793        if (preferredRefs != null && preferredRefs.isEmpty()) preferredRefs = null;
794        Node ntsel = null, ntnew = null, ntref = null;
795        boolean useNtsel = useSelected;
796        double minDistSq = nlists.keySet().iterator().next();
797
798        for (Entry<Double, List<Node>> entry : nlists.entrySet()) {
799            Double distSq = entry.getKey();
800            for (Node nd : entry.getValue()) {
801                // find the nearest selected node
802                if (ntsel == null && nd.isSelected()) {
803                    ntsel = nd;
804                    // if there are multiple nearest nodes, prefer the one
805                    // that is selected. This is required in order to drag
806                    // the selected node if multiple nodes have the same
807                    // coordinates (e.g. after unglue)
808                    useNtsel |= Utils.equalsEpsilon(distSq, minDistSq);
809                }
810                if (ntref == null && preferredRefs != null && Utils.equalsEpsilon(distSq, minDistSq)) {
811                    List<OsmPrimitive> ndRefs = nd.getReferrers();
812                    for (OsmPrimitive ref: preferredRefs) {
813                        if (ndRefs.contains(ref)) {
814                            ntref = nd;
815                            break;
816                        }
817                    }
818                }
819                // find the nearest newest node that is within about the same
820                // distance as the true nearest node
821                if (ntnew == null && nd.isNew() && (distSq-minDistSq < 1)) {
822                    ntnew = nd;
823                }
824            }
825        }
826
827        // take nearest selected, nearest new or true nearest node to p, in that order
828        if (ntsel != null && useNtsel)
829            return ntsel;
830        if (ntref != null)
831            return ntref;
832        if (ntnew != null)
833            return ntnew;
834        return nlists.values().iterator().next().get(0);
835    }
836
837    /**
838     * Convenience method to {@link #getNearestNode(Point, Predicate, boolean)}.
839     * @param p the screen point
840     * @param predicate this parameter imposes a condition on the returned object, e.g.
841     *        give the nearest node that is tagged.
842     *
843     * @return The nearest node to point p.
844     */
845    public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate) {
846        return getNearestNode(p, predicate, true);
847    }
848
849    /**
850     * The *result* does not depend on the current map selection state,
851     * neither does the result *order*.
852     * It solely depends on the distance to point p.
853     *
854     * @return a sorted map with the keys representing the perpendicular
855     *      distance of their associated way segments to point p.
856     */
857    private Map<Double, List<WaySegment>> getNearestWaySegmentsImpl(Point p,
858            Predicate<OsmPrimitive> predicate) {
859        Map<Double, List<WaySegment>> nearestMap = new TreeMap<>();
860        DataSet ds = getCurrentDataSet();
861
862        if (ds != null) {
863            double snapDistanceSq = Main.pref.getInteger("mappaint.segment.snap-distance", 10);
864            snapDistanceSq *= snapDistanceSq;
865
866            for (Way w : ds.searchWays(getBBox(p, Main.pref.getInteger("mappaint.segment.snap-distance", 10)))) {
867                if (!predicate.evaluate(w)) {
868                    continue;
869                }
870                Node lastN = null;
871                int i = -2;
872                for (Node n : w.getNodes()) {
873                    i++;
874                    if (n.isDeleted() || n.isIncomplete()) { //FIXME: This shouldn't happen, raise exception?
875                        continue;
876                    }
877                    if (lastN == null) {
878                        lastN = n;
879                        continue;
880                    }
881
882                    Point2D A = getPoint2D(lastN);
883                    Point2D B = getPoint2D(n);
884                    double c = A.distanceSq(B);
885                    double a = p.distanceSq(B);
886                    double b = p.distanceSq(A);
887
888                    /* perpendicular distance squared
889                     * loose some precision to account for possible deviations in the calculation above
890                     * e.g. if identical (A and B) come about reversed in another way, values may differ
891                     * -- zero out least significant 32 dual digits of mantissa..
892                     */
893                    double perDistSq = Double.longBitsToDouble(
894                            Double.doubleToLongBits(a - (a - b + c) * (a - b + c) / 4 / c)
895                            >> 32 << 32); // resolution in numbers with large exponent not needed here..
896
897                    if (perDistSq < snapDistanceSq && a < c + snapDistanceSq && b < c + snapDistanceSq) {
898                        List<WaySegment> wslist;
899                        if (nearestMap.containsKey(perDistSq)) {
900                            wslist = nearestMap.get(perDistSq);
901                        } else {
902                            wslist = new LinkedList<>();
903                            nearestMap.put(perDistSq, wslist);
904                        }
905                        wslist.add(new WaySegment(w, i));
906                    }
907
908                    lastN = n;
909                }
910            }
911        }
912
913        return nearestMap;
914    }
915
916    /**
917     * The result *order* depends on the current map selection state.
918     * Segments within 10px of p are searched and sorted by their distance to @param p,
919     * then, within groups of equally distant segments, prefer those that are selected.
920     *
921     * @param p the point for which to search the nearest segments.
922     * @param ignore a collection of segments which are not to be returned.
923     * @param predicate the returned objects have to fulfill certain properties.
924     *
925     * @return all segments within 10px of p that are not in ignore,
926     *          sorted by their perpendicular distance.
927     */
928    public final List<WaySegment> getNearestWaySegments(Point p,
929            Collection<WaySegment> ignore, Predicate<OsmPrimitive> predicate) {
930        List<WaySegment> nearestList = new ArrayList<>();
931        List<WaySegment> unselected = new LinkedList<>();
932
933        for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
934            // put selected waysegs within each distance group first
935            // makes the order of nearestList dependent on current selection state
936            for (WaySegment ws : wss) {
937                (ws.way.isSelected() ? nearestList : unselected).add(ws);
938            }
939            nearestList.addAll(unselected);
940            unselected.clear();
941        }
942        if (ignore != null) {
943            nearestList.removeAll(ignore);
944        }
945
946        return nearestList;
947    }
948
949    /**
950     * The result *order* depends on the current map selection state.
951     *
952     * @param p the point for which to search the nearest segments.
953     * @param predicate the returned objects have to fulfill certain properties.
954     *
955     * @return all segments within 10px of p, sorted by their perpendicular distance.
956     * @see #getNearestWaySegments(Point, Collection, Predicate)
957     */
958    public final List<WaySegment> getNearestWaySegments(Point p, Predicate<OsmPrimitive> predicate) {
959        return getNearestWaySegments(p, null, predicate);
960    }
961
962    /**
963     * The *result* depends on the current map selection state IF use_selected is true.
964     *
965     * @param p the point for which to search the nearest segment.
966     * @param predicate the returned object has to fulfill certain properties.
967     * @param useSelected whether selected way segments should be preferred.
968     *
969     * @return The nearest way segment to point p,
970     *      and, depending on use_selected, prefers a selected way segment, if found.
971     * @see #getNearestWaySegments(Point, Collection, Predicate)
972     */
973    public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate, boolean useSelected) {
974        WaySegment wayseg = null, ntsel = null;
975
976        for (List<WaySegment> wslist : getNearestWaySegmentsImpl(p, predicate).values()) {
977            if (wayseg != null && ntsel != null) {
978                break;
979            }
980            for (WaySegment ws : wslist) {
981                if (wayseg == null) {
982                    wayseg = ws;
983                }
984                if (ntsel == null && ws.way.isSelected()) {
985                    ntsel = ws;
986                }
987            }
988        }
989
990        return (ntsel != null && useSelected) ? ntsel : wayseg;
991    }
992
993     /**
994     * The *result* depends on the current map selection state IF use_selected is true.
995     *
996     * @param p the point for which to search the nearest segment.
997     * @param predicate the returned object has to fulfill certain properties.
998     * @param use_selected whether selected way segments should be preferred.
999     * @param preferredRefs - prefer segments related to these primitives, may be null
1000     *
1001     * @return The nearest way segment to point p,
1002     *      and, depending on use_selected, prefers a selected way segment, if found.
1003     * Also prefers segments of ways that are related to one of preferredRefs primitives
1004     *
1005     * @see #getNearestWaySegments(Point, Collection, Predicate)
1006     * @since 6065
1007     */
1008    public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate,
1009            boolean use_selected,  Collection<OsmPrimitive> preferredRefs) {
1010        WaySegment wayseg = null, ntsel = null, ntref = null;
1011        if (preferredRefs != null && preferredRefs.isEmpty()) preferredRefs = null;
1012
1013        searchLoop: for (List<WaySegment> wslist : getNearestWaySegmentsImpl(p, predicate).values()) {
1014            for (WaySegment ws : wslist) {
1015                if (wayseg == null) {
1016                    wayseg = ws;
1017                }
1018                if (ntsel == null && ws.way.isSelected()) {
1019                    ntsel = ws;
1020                    break searchLoop;
1021                }
1022                if (ntref == null && preferredRefs != null) {
1023                    // prefer ways containing given nodes
1024                    for (Node nd: ws.way.getNodes()) {
1025                        if (preferredRefs.contains(nd)) {
1026                            ntref = ws;
1027                            break searchLoop;
1028                        }
1029                    }
1030                    Collection<OsmPrimitive> wayRefs = ws.way.getReferrers();
1031                    // prefer member of the given relations
1032                    for (OsmPrimitive ref: preferredRefs) {
1033                        if (ref instanceof Relation && wayRefs.contains(ref)) {
1034                            ntref = ws;
1035                            break searchLoop;
1036                        }
1037                    }
1038                }
1039            }
1040        }
1041        if (ntsel != null && use_selected)
1042            return ntsel;
1043        if (ntref != null)
1044            return ntref;
1045        return wayseg;
1046    }
1047
1048    /**
1049     * Convenience method to {@link #getNearestWaySegment(Point, Predicate, boolean)}.
1050     * @param p the point for which to search the nearest segment.
1051     * @param predicate the returned object has to fulfill certain properties.
1052     *
1053     * @return The nearest way segment to point p.
1054     */
1055    public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate) {
1056        return getNearestWaySegment(p, predicate, true);
1057    }
1058
1059    /**
1060     * The *result* does not depend on the current map selection state,
1061     * neither does the result *order*.
1062     * It solely depends on the perpendicular distance to point p.
1063     *
1064     * @param p the point for which to search the nearest ways.
1065     * @param ignore a collection of ways which are not to be returned.
1066     * @param predicate the returned object has to fulfill certain properties.
1067     *
1068     * @return all nearest ways to the screen point given that are not in ignore.
1069     * @see #getNearestWaySegments(Point, Collection, Predicate)
1070     */
1071    public final List<Way> getNearestWays(Point p,
1072            Collection<Way> ignore, Predicate<OsmPrimitive> predicate) {
1073        List<Way> nearestList = new ArrayList<>();
1074        Set<Way> wset = new HashSet<>();
1075
1076        for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
1077            for (WaySegment ws : wss) {
1078                if (wset.add(ws.way)) {
1079                    nearestList.add(ws.way);
1080                }
1081            }
1082        }
1083        if (ignore != null) {
1084            nearestList.removeAll(ignore);
1085        }
1086
1087        return nearestList;
1088    }
1089
1090    /**
1091     * The *result* does not depend on the current map selection state,
1092     * neither does the result *order*.
1093     * It solely depends on the perpendicular distance to point p.
1094     *
1095     * @param p the point for which to search the nearest ways.
1096     * @param predicate the returned object has to fulfill certain properties.
1097     *
1098     * @return all nearest ways to the screen point given.
1099     * @see #getNearestWays(Point, Collection, Predicate)
1100     */
1101    public final List<Way> getNearestWays(Point p, Predicate<OsmPrimitive> predicate) {
1102        return getNearestWays(p, null, predicate);
1103    }
1104
1105    /**
1106     * The *result* depends on the current map selection state.
1107     *
1108     * @param p the point for which to search the nearest segment.
1109     * @param predicate the returned object has to fulfill certain properties.
1110     *
1111     * @return The nearest way to point p, prefer a selected way if there are multiple nearest.
1112     * @see #getNearestWaySegment(Point, Predicate)
1113     */
1114    public final Way getNearestWay(Point p, Predicate<OsmPrimitive> predicate) {
1115        WaySegment nearestWaySeg = getNearestWaySegment(p, predicate);
1116        return (nearestWaySeg == null) ? null : nearestWaySeg.way;
1117    }
1118
1119    /**
1120     * The *result* does not depend on the current map selection state,
1121     * neither does the result *order*.
1122     * It solely depends on the distance to point p.
1123     *
1124     * First, nodes will be searched. If there are nodes within BBox found,
1125     * return a collection of those nodes only.
1126     *
1127     * If no nodes are found, search for nearest ways. If there are ways
1128     * within BBox found, return a collection of those ways only.
1129     *
1130     * If nothing is found, return an empty collection.
1131     *
1132     * @param p The point on screen.
1133     * @param ignore a collection of ways which are not to be returned.
1134     * @param predicate the returned object has to fulfill certain properties.
1135     *
1136     * @return Primitives nearest to the given screen point that are not in ignore.
1137     * @see #getNearestNodes(Point, Collection, Predicate)
1138     * @see #getNearestWays(Point, Collection, Predicate)
1139     */
1140    public final List<OsmPrimitive> getNearestNodesOrWays(Point p,
1141            Collection<OsmPrimitive> ignore, Predicate<OsmPrimitive> predicate) {
1142        List<OsmPrimitive> nearestList = Collections.emptyList();
1143        OsmPrimitive osm = getNearestNodeOrWay(p, predicate, false);
1144
1145        if (osm != null) {
1146            if (osm instanceof Node) {
1147                nearestList = new ArrayList<OsmPrimitive>(getNearestNodes(p, predicate));
1148            } else if (osm instanceof Way) {
1149                nearestList = new ArrayList<OsmPrimitive>(getNearestWays(p, predicate));
1150            }
1151            if (ignore != null) {
1152                nearestList.removeAll(ignore);
1153            }
1154        }
1155
1156        return nearestList;
1157    }
1158
1159    /**
1160     * The *result* does not depend on the current map selection state,
1161     * neither does the result *order*.
1162     * It solely depends on the distance to point p.
1163     *
1164     * @param p The point on screen.
1165     * @param predicate the returned object has to fulfill certain properties.
1166     * @return Primitives nearest to the given screen point.
1167     * @see #getNearestNodesOrWays(Point, Collection, Predicate)
1168     */
1169    public final List<OsmPrimitive> getNearestNodesOrWays(Point p, Predicate<OsmPrimitive> predicate) {
1170        return getNearestNodesOrWays(p, null, predicate);
1171    }
1172
1173    /**
1174     * This is used as a helper routine to {@link #getNearestNodeOrWay(Point, Predicate, boolean)}
1175     * It decides, whether to yield the node to be tested or look for further (way) candidates.
1176     *
1177     * @param osm node to check
1178     * @param p point clicked
1179     * @param use_selected whether to prefer selected nodes
1180     * @return true, if the node fulfills the properties of the function body
1181     */
1182    private boolean isPrecedenceNode(Node osm, Point p, boolean use_selected) {
1183        if (osm != null) {
1184            if (!(p.distanceSq(getPoint2D(osm)) > (4)*(4))) return true;
1185            if (osm.isTagged()) return true;
1186            if (use_selected && osm.isSelected()) return true;
1187        }
1188        return false;
1189    }
1190
1191    /**
1192     * The *result* depends on the current map selection state IF use_selected is true.
1193     *
1194     * IF use_selected is true, use {@link #getNearestNode(Point, Predicate)} to find
1195     * the nearest, selected node.  If not found, try {@link #getNearestWaySegment(Point, Predicate)}
1196     * to find the nearest selected way.
1197     *
1198     * IF use_selected is false, or if no selected primitive was found, do the following.
1199     *
1200     * If the nearest node found is within 4px of p, simply take it.
1201     * Else, find the nearest way segment. Then, if p is closer to its
1202     * middle than to the node, take the way segment, else take the node.
1203     *
1204     * Finally, if no nearest primitive is found at all, return null.
1205     *
1206     * @param p The point on screen.
1207     * @param predicate the returned object has to fulfill certain properties.
1208     * @param use_selected whether to prefer primitives that are currently selected or referred by selected primitives
1209     *
1210     * @return A primitive within snap-distance to point p,
1211     *      that is chosen by the algorithm described.
1212     * @see #getNearestNode(Point, Predicate)
1213     * @see #getNearestWay(Point, Predicate)
1214     */
1215    public final OsmPrimitive getNearestNodeOrWay(Point p, Predicate<OsmPrimitive> predicate, boolean use_selected) {
1216        Collection<OsmPrimitive> sel;
1217        DataSet ds = getCurrentDataSet();
1218        if (use_selected && ds != null) {
1219            sel = ds.getSelected();
1220        } else {
1221            sel = null;
1222        }
1223        OsmPrimitive osm = getNearestNode(p, predicate, use_selected, sel);
1224
1225        if (isPrecedenceNode((Node) osm, p, use_selected)) return osm;
1226        WaySegment ws;
1227        if (use_selected) {
1228            ws = getNearestWaySegment(p, predicate, use_selected, sel);
1229        } else {
1230            ws = getNearestWaySegment(p, predicate, use_selected);
1231        }
1232        if (ws == null) return osm;
1233
1234        if ((ws.way.isSelected() && use_selected) || osm == null) {
1235            // either (no _selected_ nearest node found, if desired) or no nearest node was found
1236            osm = ws.way;
1237        } else {
1238            int maxWaySegLenSq = 3*PROP_SNAP_DISTANCE.get();
1239            maxWaySegLenSq *= maxWaySegLenSq;
1240
1241            Point2D wp1 = getPoint2D(ws.way.getNode(ws.lowerIndex));
1242            Point2D wp2 = getPoint2D(ws.way.getNode(ws.lowerIndex+1));
1243
1244            // is wayseg shorter than maxWaySegLenSq and
1245            // is p closer to the middle of wayseg  than  to the nearest node?
1246            if (wp1.distanceSq(wp2) < maxWaySegLenSq &&
1247                    p.distanceSq(project(0.5, wp1, wp2)) < p.distanceSq(getPoint2D((Node) osm))) {
1248                osm = ws.way;
1249            }
1250        }
1251        return osm;
1252    }
1253
1254    public static double perDist(Point2D pt, Point2D a, Point2D b) {
1255        if (pt != null && a != null && b != null) {
1256            double pd =
1257                    (a.getX()-pt.getX())*(b.getX()-a.getX()) -
1258                    (a.getY()-pt.getY())*(b.getY()-a.getY());
1259            return Math.abs(pd) / a.distance(b);
1260        }
1261        return 0d;
1262    }
1263
1264    /**
1265     *
1266     * @param pt point to project onto (ab)
1267     * @param a root of vector
1268     * @param b vector
1269     * @return point of intersection of line given by (ab)
1270     *      with its orthogonal line running through pt
1271     */
1272    public static Point2D project(Point2D pt, Point2D a, Point2D b) {
1273        if (pt != null && a != null && b != null) {
1274            double r = (
1275                    (pt.getX()-a.getX())*(b.getX()-a.getX()) +
1276                    (pt.getY()-a.getY())*(b.getY()-a.getY()))
1277                    / a.distanceSq(b);
1278            return project(r, a, b);
1279        }
1280        return null;
1281    }
1282
1283    /**
1284     * if r = 0 returns a, if r=1 returns b,
1285     * if r = 0.5 returns center between a and b, etc..
1286     *
1287     * @param r scale value
1288     * @param a root of vector
1289     * @param b vector
1290     * @return new point at a + r*(ab)
1291     */
1292    public static Point2D project(double r, Point2D a, Point2D b) {
1293        Point2D ret = null;
1294
1295        if (a != null && b != null) {
1296            ret = new Point2D.Double(a.getX() + r*(b.getX()-a.getX()),
1297                    a.getY() + r*(b.getY()-a.getY()));
1298        }
1299        return ret;
1300    }
1301
1302    /**
1303     * The *result* does not depend on the current map selection state, neither does the result *order*.
1304     * It solely depends on the distance to point p.
1305     *
1306     * @param p The point on screen.
1307     * @param ignore a collection of ways which are not to be returned.
1308     * @param predicate the returned object has to fulfill certain properties.
1309     *
1310     * @return a list of all objects that are nearest to point p and
1311     *          not in ignore or an empty list if nothing was found.
1312     */
1313    public final List<OsmPrimitive> getAllNearest(Point p,
1314            Collection<OsmPrimitive> ignore, Predicate<OsmPrimitive> predicate) {
1315        List<OsmPrimitive> nearestList = new ArrayList<>();
1316        Set<Way> wset = new HashSet<>();
1317
1318        // add nearby ways
1319        for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
1320            for (WaySegment ws : wss) {
1321                if (wset.add(ws.way)) {
1322                    nearestList.add(ws.way);
1323                }
1324            }
1325        }
1326
1327        // add nearby nodes
1328        for (List<Node> nlist : getNearestNodesImpl(p, predicate).values()) {
1329            nearestList.addAll(nlist);
1330        }
1331
1332        // add parent relations of nearby nodes and ways
1333        Set<OsmPrimitive> parentRelations = new HashSet<>();
1334        for (OsmPrimitive o : nearestList) {
1335            for (OsmPrimitive r : o.getReferrers()) {
1336                if (r instanceof Relation && predicate.evaluate(r)) {
1337                    parentRelations.add(r);
1338                }
1339            }
1340        }
1341        nearestList.addAll(parentRelations);
1342
1343        if (ignore != null) {
1344            nearestList.removeAll(ignore);
1345        }
1346
1347        return nearestList;
1348    }
1349
1350    /**
1351     * The *result* does not depend on the current map selection state, neither does the result *order*.
1352     * It solely depends on the distance to point p.
1353     *
1354     * @param p The point on screen.
1355     * @param predicate the returned object has to fulfill certain properties.
1356     *
1357     * @return a list of all objects that are nearest to point p
1358     *          or an empty list if nothing was found.
1359     * @see #getAllNearest(Point, Collection, Predicate)
1360     */
1361    public final List<OsmPrimitive> getAllNearest(Point p, Predicate<OsmPrimitive> predicate) {
1362        return getAllNearest(p, null, predicate);
1363    }
1364
1365    /**
1366     * @return The projection to be used in calculating stuff.
1367     */
1368    public Projection getProjection() {
1369        return Main.getProjection();
1370    }
1371
1372    @Override
1373    public String helpTopic() {
1374        String n = getClass().getName();
1375        return n.substring(n.lastIndexOf('.')+1);
1376    }
1377
1378    /**
1379     * Return a ID which is unique as long as viewport dimensions are the same
1380     * @return A unique ID, as long as viewport dimensions are the same
1381     */
1382    public int getViewID() {
1383        String x = center.east() + '_' + center.north() + '_' + scale + '_' +
1384                getWidth() + '_' + getHeight() + '_' + getProjection().toString();
1385        CRC32 id = new CRC32();
1386        id.update(x.getBytes(StandardCharsets.UTF_8));
1387        return (int) id.getValue();
1388    }
1389
1390    /**
1391     * Set new cursor.
1392     */
1393    public void setNewCursor(Cursor cursor, Object reference) {
1394        cursorManager.setNewCursor(cursor, reference);
1395    }
1396
1397    public void setNewCursor(int cursor, Object reference) {
1398        setNewCursor(Cursor.getPredefinedCursor(cursor), reference);
1399    }
1400
1401    /**
1402     * Remove the new cursor and reset to previous
1403     */
1404    public void resetCursor(Object reference) {
1405        cursorManager.resetCursor(reference);
1406    }
1407
1408    /**
1409     * Gets the cursor manager that is used for this NavigatableComponent.
1410     * @return The cursor manager.
1411     */
1412    public CursorManager getCursorManager() {
1413        return cursorManager;
1414    }
1415
1416    @Override
1417    public void paint(Graphics g) {
1418        synchronized (paintRequestLock) {
1419            if (paintRect != null) {
1420                Graphics g2 = g.create();
1421                g2.setColor(Utils.complement(PaintColors.getBackgroundColor()));
1422                g2.drawRect(paintRect.x, paintRect.y, paintRect.width, paintRect.height);
1423                g2.dispose();
1424            }
1425            if (paintPoly != null) {
1426                Graphics g2 = g.create();
1427                g2.setColor(Utils.complement(PaintColors.getBackgroundColor()));
1428                g2.drawPolyline(paintPoly.xpoints, paintPoly.ypoints, paintPoly.npoints);
1429                g2.dispose();
1430            }
1431        }
1432        super.paint(g);
1433    }
1434
1435    /**
1436     * Requests to paint the given {@code Rectangle}.
1437     * @param r The Rectangle to draw
1438     * @see #requestClearRect
1439     * @since 5500
1440     */
1441    public void requestPaintRect(Rectangle r) {
1442        if (r != null) {
1443            synchronized (paintRequestLock) {
1444                paintRect = r;
1445            }
1446            repaint();
1447        }
1448    }
1449
1450    /**
1451     * Requests to paint the given {@code Polygon} as a polyline (unclosed polygon).
1452     * @param p The Polygon to draw
1453     * @see #requestClearPoly
1454     * @since 5500
1455     */
1456    public void requestPaintPoly(Polygon p) {
1457        if (p != null) {
1458            synchronized (paintRequestLock) {
1459                paintPoly = p;
1460            }
1461            repaint();
1462        }
1463    }
1464
1465    /**
1466     * Requests to clear the rectangled previously drawn.
1467     * @see #requestPaintRect
1468     * @since 5500
1469     */
1470    public void requestClearRect() {
1471        synchronized (paintRequestLock) {
1472            paintRect = null;
1473        }
1474        repaint();
1475    }
1476
1477    /**
1478     * Requests to clear the polyline previously drawn.
1479     * @see #requestPaintPoly
1480     * @since 5500
1481     */
1482    public void requestClearPoly() {
1483        synchronized (paintRequestLock) {
1484            paintPoly = null;
1485        }
1486        repaint();
1487    }
1488}