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