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