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