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