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