001    // License: GPL. See LICENSE file for details.
002    package org.openstreetmap.josm.gui;
003    
004    import static org.openstreetmap.josm.tools.I18n.marktr;
005    
006    import java.awt.Cursor;
007    import java.awt.Point;
008    import java.awt.Rectangle;
009    import java.awt.geom.AffineTransform;
010    import java.awt.geom.Point2D;
011    import java.util.ArrayList;
012    import java.util.Collection;
013    import java.util.Collections;
014    import java.util.Date;
015    import java.util.HashSet;
016    import java.util.LinkedHashMap;
017    import java.util.LinkedList;
018    import java.util.List;
019    import java.util.Locale;
020    import java.util.Map;
021    import java.util.Set;
022    import java.util.Stack;
023    import java.util.TreeMap;
024    import java.util.concurrent.CopyOnWriteArrayList;
025    
026    import javax.swing.JComponent;
027    
028    import org.openstreetmap.josm.Main;
029    import org.openstreetmap.josm.data.Bounds;
030    import org.openstreetmap.josm.data.ProjectionBounds;
031    import org.openstreetmap.josm.data.coor.CachedLatLon;
032    import org.openstreetmap.josm.data.coor.EastNorth;
033    import org.openstreetmap.josm.data.coor.LatLon;
034    import org.openstreetmap.josm.data.osm.BBox;
035    import org.openstreetmap.josm.data.osm.DataSet;
036    import org.openstreetmap.josm.data.osm.Node;
037    import org.openstreetmap.josm.data.osm.OsmPrimitive;
038    import org.openstreetmap.josm.data.osm.Relation;
039    import org.openstreetmap.josm.data.osm.Way;
040    import org.openstreetmap.josm.data.osm.WaySegment;
041    import org.openstreetmap.josm.data.preferences.IntegerProperty;
042    import org.openstreetmap.josm.data.projection.Projection;
043    import org.openstreetmap.josm.data.projection.Projections;
044    import org.openstreetmap.josm.gui.help.Helpful;
045    import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
046    import org.openstreetmap.josm.tools.Predicate;
047    
048    /**
049     * An component that can be navigated by a mapmover. Used as map view and for the
050     * zoomer in the download dialog.
051     *
052     * @author imi
053     */
054    public class NavigatableComponent extends JComponent implements Helpful {
055    
056        /**
057         * Interface to notify listeners of the change of the zoom area.
058         */
059        public interface ZoomChangeListener {
060            void zoomChanged();
061        }
062    
063        public static final IntegerProperty PROP_SNAP_DISTANCE = new IntegerProperty("mappaint.node.snap-distance", 10);
064    
065        public static final String PROPNAME_CENTER = "center";
066        public static final String PROPNAME_SCALE  = "scale";
067    
068        /**
069         * the zoom listeners
070         */
071        private static final CopyOnWriteArrayList<ZoomChangeListener> zoomChangeListeners = new CopyOnWriteArrayList<ZoomChangeListener>();
072    
073        /**
074         * Removes a zoom change listener
075         *
076         * @param listener the listener. Ignored if null or already absent
077         */
078        public static void removeZoomChangeListener(NavigatableComponent.ZoomChangeListener listener) {
079            zoomChangeListeners.remove(listener);
080        }
081    
082        /**
083         * Adds a zoom change listener
084         *
085         * @param listener the listener. Ignored if null or already registered.
086         */
087        public static void addZoomChangeListener(NavigatableComponent.ZoomChangeListener listener) {
088            if (listener != null) {
089                zoomChangeListeners.addIfAbsent(listener);
090            }
091        }
092    
093        protected static void fireZoomChanged() {
094            for (ZoomChangeListener l : zoomChangeListeners) {
095                l.zoomChanged();
096            }
097        }
098    
099        /**
100         * The scale factor in x or y-units per pixel. This means, if scale = 10,
101         * every physical pixel on screen are 10 x or 10 y units in the
102         * northing/easting space of the projection.
103         */
104        private double scale = Main.getProjection().getDefaultZoomInPPD();
105        /**
106         * Center n/e coordinate of the desired screen center.
107         */
108        protected EastNorth center = calculateDefaultCenter();
109    
110        public NavigatableComponent() {
111            setLayout(null);
112        }
113    
114        protected DataSet getCurrentDataSet() {
115            return Main.main.getCurrentDataSet();
116        }
117    
118        private EastNorth calculateDefaultCenter() {
119            Bounds b = Main.getProjection().getWorldBoundsLatLon();
120            double lat = (b.getMax().lat() + b.getMin().lat())/2;
121            double lon = (b.getMax().lon() + b.getMin().lon())/2;
122    
123            return Main.getProjection().latlon2eastNorth(new LatLon(lat, lon));
124        }
125    
126        public static String getDistText(double dist) {
127            return getSystemOfMeasurement().getDistText(dist);
128        }
129    
130        public String getDist100PixelText()
131        {
132            return getDistText(getDist100Pixel());
133        }
134    
135        public double getDist100Pixel()
136        {
137            int w = getWidth()/2;
138            int h = getHeight()/2;
139            LatLon ll1 = getLatLon(w-50,h);
140            LatLon ll2 = getLatLon(w+50,h);
141            return ll1.greatCircleDistance(ll2);
142        }
143    
144        /**
145         * @return Returns the center point. A copy is returned, so users cannot
146         *      change the center by accessing the return value. Use zoomTo instead.
147         */
148        public EastNorth getCenter() {
149            return center;
150        }
151    
152        /**
153         * @param x X-Pixelposition to get coordinate from
154         * @param y Y-Pixelposition to get coordinate from
155         *
156         * @return Geographic coordinates from a specific pixel coordination
157         *      on the screen.
158         */
159        public EastNorth getEastNorth(int x, int y) {
160            return new EastNorth(
161                    center.east() + (x - getWidth()/2.0)*scale,
162                    center.north() - (y - getHeight()/2.0)*scale);
163        }
164    
165        public ProjectionBounds getProjectionBounds() {
166            return new ProjectionBounds(
167                    new EastNorth(
168                            center.east() - getWidth()/2.0*scale,
169                            center.north() - getHeight()/2.0*scale),
170                            new EastNorth(
171                                    center.east() + getWidth()/2.0*scale,
172                                    center.north() + getHeight()/2.0*scale));
173        }
174    
175        /* FIXME: replace with better method - used by MapSlider */
176        public ProjectionBounds getMaxProjectionBounds() {
177            Bounds b = getProjection().getWorldBoundsLatLon();
178            return new ProjectionBounds(getProjection().latlon2eastNorth(b.getMin()),
179                    getProjection().latlon2eastNorth(b.getMax()));
180        }
181    
182        /* FIXME: replace with better method - used by Main to reset Bounds when projection changes, don't use otherwise */
183        public Bounds getRealBounds() {
184            return new Bounds(
185                    getProjection().eastNorth2latlon(new EastNorth(
186                            center.east() - getWidth()/2.0*scale,
187                            center.north() - getHeight()/2.0*scale)),
188                            getProjection().eastNorth2latlon(new EastNorth(
189                                    center.east() + getWidth()/2.0*scale,
190                                    center.north() + getHeight()/2.0*scale)));
191        }
192    
193        /**
194         * @param x X-Pixelposition to get coordinate from
195         * @param y Y-Pixelposition to get coordinate from
196         *
197         * @return Geographic unprojected coordinates from a specific pixel coordination
198         *      on the screen.
199         */
200        public LatLon getLatLon(int x, int y) {
201            return getProjection().eastNorth2latlon(getEastNorth(x, y));
202        }
203    
204        public LatLon getLatLon(double x, double y) {
205            return getLatLon((int)x, (int)y);
206        }
207    
208        /**
209         * @param r
210         * @return Minimum bounds that will cover rectangle
211         */
212        public Bounds getLatLonBounds(Rectangle r) {
213            // TODO Maybe this should be (optional) method of Projection implementation
214            EastNorth p1 = getEastNorth(r.x, r.y);
215            EastNorth p2 = getEastNorth(r.x + r.width, r.y + r.height);
216    
217            Bounds result = new Bounds(Main.getProjection().eastNorth2latlon(p1));
218    
219            double eastMin = Math.min(p1.east(), p2.east());
220            double eastMax = Math.max(p1.east(), p2.east());
221            double northMin = Math.min(p1.north(), p2.north());
222            double northMax = Math.max(p1.north(), p2.north());
223            double deltaEast = (eastMax - eastMin) / 10;
224            double deltaNorth = (northMax - northMin) / 10;
225    
226            for (int i=0; i < 10; i++) {
227                result.extend(Main.getProjection().eastNorth2latlon(new EastNorth(eastMin + i * deltaEast, northMin)));
228                result.extend(Main.getProjection().eastNorth2latlon(new EastNorth(eastMin + i * deltaEast, northMax)));
229                result.extend(Main.getProjection().eastNorth2latlon(new EastNorth(eastMin, northMin  + i * deltaNorth)));
230                result.extend(Main.getProjection().eastNorth2latlon(new EastNorth(eastMax, northMin  + i * deltaNorth)));
231            }
232    
233            return result;
234        }
235    
236        public AffineTransform getAffineTransform() {
237            return new AffineTransform(
238                    1.0/scale, 0.0, 0.0, -1.0/scale, getWidth()/2.0 - center.east()/scale, getHeight()/2.0 + center.north()/scale);
239        }
240    
241        /**
242         * Return the point on the screen where this Coordinate would be.
243         * @param p The point, where this geopoint would be drawn.
244         * @return The point on screen where "point" would be drawn, relative
245         *      to the own top/left.
246         */
247        public Point2D getPoint2D(EastNorth p) {
248            if (null == p)
249                return new Point();
250            double x = (p.east()-center.east())/scale + getWidth()/2;
251            double y = (center.north()-p.north())/scale + getHeight()/2;
252            return new Point2D.Double(x, y);
253        }
254    
255        public Point2D getPoint2D(LatLon latlon) {
256            if (latlon == null)
257                return new Point();
258            else if (latlon instanceof CachedLatLon)
259                return getPoint2D(((CachedLatLon)latlon).getEastNorth());
260            else
261                return getPoint2D(getProjection().latlon2eastNorth(latlon));
262        }
263    
264        public Point2D getPoint2D(Node n) {
265            return getPoint2D(n.getEastNorth());
266        }
267    
268        // looses precision, may overflow (depends on p and current scale)
269        //@Deprecated
270        public Point getPoint(EastNorth p) {
271            Point2D d = getPoint2D(p);
272            return new Point((int) d.getX(), (int) d.getY());
273        }
274    
275        // looses precision, may overflow (depends on p and current scale)
276        //@Deprecated
277        public Point getPoint(LatLon latlon) {
278            Point2D d = getPoint2D(latlon);
279            return new Point((int) d.getX(), (int) d.getY());
280        }
281    
282        // looses precision, may overflow (depends on p and current scale)
283        //@Deprecated
284        public Point getPoint(Node n) {
285            Point2D d = getPoint2D(n);
286            return new Point((int) d.getX(), (int) d.getY());
287        }
288    
289        /**
290         * Zoom to the given coordinate.
291         * @param newCenter The center x-value (easting) to zoom to.
292         * @param scale The scale to use.
293         */
294        public void zoomTo(EastNorth newCenter, double newScale) {
295            Bounds b = getProjection().getWorldBoundsLatLon();
296            LatLon cl = Projections.inverseProject(newCenter);
297            boolean changed = false;
298            double lat = cl.lat();
299            double lon = cl.lon();
300            if(lat < b.getMin().lat()) {changed = true; lat = b.getMin().lat(); }
301            else if(lat > b.getMax().lat()) {changed = true; lat = b.getMax().lat(); }
302            if(lon < b.getMin().lon()) {changed = true; lon = b.getMin().lon(); }
303            else if(lon > b.getMax().lon()) {changed = true; lon = b.getMax().lon(); }
304            if(changed) {
305                newCenter = Projections.project(new LatLon(lat,lon));
306            }
307            int width = getWidth()/2;
308            int height = getHeight()/2;
309            LatLon l1 = new LatLon(b.getMin().lat(), lon);
310            LatLon l2 = new LatLon(b.getMax().lat(), lon);
311            EastNorth e1 = getProjection().latlon2eastNorth(l1);
312            EastNorth e2 = getProjection().latlon2eastNorth(l2);
313            double d = e2.north() - e1.north();
314            if(d < height*newScale)
315            {
316                double newScaleH = d/height;
317                e1 = getProjection().latlon2eastNorth(new LatLon(lat, b.getMin().lon()));
318                e2 = getProjection().latlon2eastNorth(new LatLon(lat, b.getMax().lon()));
319                d = e2.east() - e1.east();
320                if(d < width*newScale) {
321                    newScale = Math.max(newScaleH, d/width);
322                }
323            }
324            else
325            {
326                d = d/(l1.greatCircleDistance(l2)*height*10);
327                if(newScale < d) {
328                    newScale = d;
329                }
330            }
331    
332            if (!newCenter.equals(center) || (scale != newScale)) {
333                pushZoomUndo(center, scale);
334                zoomNoUndoTo(newCenter, newScale);
335            }
336        }
337    
338        /**
339         * Zoom to the given coordinate without adding to the zoom undo buffer.
340         * @param newCenter The center x-value (easting) to zoom to.
341         * @param scale The scale to use.
342         */
343        private void zoomNoUndoTo(EastNorth newCenter, double newScale) {
344            if (!newCenter.equals(center)) {
345                EastNorth oldCenter = center;
346                center = newCenter;
347                firePropertyChange(PROPNAME_CENTER, oldCenter, newCenter);
348            }
349            if (scale != newScale) {
350                double oldScale = scale;
351                scale = newScale;
352                firePropertyChange(PROPNAME_SCALE, oldScale, newScale);
353            }
354    
355            repaint();
356            fireZoomChanged();
357        }
358    
359        public void zoomTo(EastNorth newCenter) {
360            zoomTo(newCenter, scale);
361        }
362    
363        public void zoomTo(LatLon newCenter) {
364            zoomTo(Projections.project(newCenter));
365        }
366    
367        public void smoothScrollTo(LatLon newCenter) {
368            smoothScrollTo(Projections.project(newCenter));
369        }
370    
371        /**
372         * Create a thread that moves the viewport to the given center in an
373         * animated fashion.
374         */
375        public void smoothScrollTo(EastNorth newCenter) {
376            // fixme make these configurable.
377            final int fps = 20;     // animation frames per second
378            final int speed = 1500; // milliseconds for full-screen-width pan
379            if (!newCenter.equals(center)) {
380                final EastNorth oldCenter = center;
381                final double distance = newCenter.distance(oldCenter) / scale;
382                final double milliseconds = distance / getWidth() * speed;
383                final double frames = milliseconds * fps / 1000;
384                final EastNorth finalNewCenter = newCenter;
385    
386                new Thread(){
387                    @Override
388                    public void run() {
389                        for (int i=0; i<frames; i++)
390                        {
391                            // fixme - not use zoom history here
392                            zoomTo(oldCenter.interpolate(finalNewCenter, (i+1) / frames));
393                            try { Thread.sleep(1000 / fps); } catch (InterruptedException ex) { };
394                        }
395                    }
396                }.start();
397            }
398        }
399    
400        public void zoomToFactor(double x, double y, double factor) {
401            double newScale = scale*factor;
402            // New center position so that point under the mouse pointer stays the same place as it was before zooming
403            // You will get the formula by simplifying this expression: newCenter = oldCenter + mouseCoordinatesInNewZoom - mouseCoordinatesInOldZoom
404            zoomTo(new EastNorth(
405                    center.east() - (x - getWidth()/2.0) * (newScale - scale),
406                    center.north() + (y - getHeight()/2.0) * (newScale - scale)),
407                    newScale);
408        }
409    
410        public void zoomToFactor(EastNorth newCenter, double factor) {
411            zoomTo(newCenter, scale*factor);
412        }
413    
414        public void zoomToFactor(double factor) {
415            zoomTo(center, scale*factor);
416        }
417    
418        public void zoomTo(ProjectionBounds box) {
419            // -20 to leave some border
420            int w = getWidth()-20;
421            if (w < 20) {
422                w = 20;
423            }
424            int h = getHeight()-20;
425            if (h < 20) {
426                h = 20;
427            }
428    
429            double scaleX = (box.maxEast-box.minEast)/w;
430            double scaleY = (box.maxNorth-box.minNorth)/h;
431            double newScale = Math.max(scaleX, scaleY);
432    
433            zoomTo(box.getCenter(), newScale);
434        }
435    
436        public void zoomTo(Bounds box) {
437            zoomTo(new ProjectionBounds(getProjection().latlon2eastNorth(box.getMin()),
438                    getProjection().latlon2eastNorth(box.getMax())));
439        }
440    
441        private class ZoomData {
442            LatLon center;
443            double scale;
444    
445            public ZoomData(EastNorth center, double scale) {
446                this.center = Projections.inverseProject(center);
447                this.scale = scale;
448            }
449    
450            public EastNorth getCenterEastNorth() {
451                return getProjection().latlon2eastNorth(center);
452            }
453    
454            public double getScale() {
455                return scale;
456            }
457        }
458    
459        private Stack<ZoomData> zoomUndoBuffer = new Stack<ZoomData>();
460        private Stack<ZoomData> zoomRedoBuffer = new Stack<ZoomData>();
461        private Date zoomTimestamp = new Date();
462    
463        private void pushZoomUndo(EastNorth center, double scale) {
464            Date now = new Date();
465            if ((now.getTime() - zoomTimestamp.getTime()) > (Main.pref.getDouble("zoom.undo.delay", 1.0) * 1000)) {
466                zoomUndoBuffer.push(new ZoomData(center, scale));
467                if (zoomUndoBuffer.size() > Main.pref.getInteger("zoom.undo.max", 50)) {
468                    zoomUndoBuffer.remove(0);
469                }
470                zoomRedoBuffer.clear();
471            }
472            zoomTimestamp = now;
473        }
474    
475        public void zoomPrevious() {
476            if (!zoomUndoBuffer.isEmpty()) {
477                ZoomData zoom = zoomUndoBuffer.pop();
478                zoomRedoBuffer.push(new ZoomData(center, scale));
479                zoomNoUndoTo(zoom.getCenterEastNorth(), zoom.getScale());
480            }
481        }
482    
483        public void zoomNext() {
484            if (!zoomRedoBuffer.isEmpty()) {
485                ZoomData zoom = zoomRedoBuffer.pop();
486                zoomUndoBuffer.push(new ZoomData(center, scale));
487                zoomNoUndoTo(zoom.getCenterEastNorth(), zoom.getScale());
488            }
489        }
490    
491        public boolean hasZoomUndoEntries() {
492            return !zoomUndoBuffer.isEmpty();
493        }
494    
495        public boolean hasZoomRedoEntries() {
496            return !zoomRedoBuffer.isEmpty();
497        }
498    
499        private BBox getBBox(Point p, int snapDistance) {
500            return new BBox(getLatLon(p.x - snapDistance, p.y - snapDistance),
501                    getLatLon(p.x + snapDistance, p.y + snapDistance));
502        }
503    
504        /**
505         * The *result* does not depend on the current map selection state,
506         * neither does the result *order*.
507         * It solely depends on the distance to point p.
508         *
509         * @return a sorted map with the keys representing the distance of
510         *      their associated nodes to point p.
511         */
512        private Map<Double, List<Node>> getNearestNodesImpl(Point p,
513                Predicate<OsmPrimitive> predicate) {
514            TreeMap<Double, List<Node>> nearestMap = new TreeMap<Double, List<Node>>();
515            DataSet ds = getCurrentDataSet();
516    
517            if (ds != null) {
518                double dist, snapDistanceSq = PROP_SNAP_DISTANCE.get();
519                snapDistanceSq *= snapDistanceSq;
520    
521                for (Node n : ds.searchNodes(getBBox(p, PROP_SNAP_DISTANCE.get()))) {
522                    if (predicate.evaluate(n)
523                            && (dist = getPoint2D(n).distanceSq(p)) < snapDistanceSq)
524                    {
525                        List<Node> nlist;
526                        if (nearestMap.containsKey(dist)) {
527                            nlist = nearestMap.get(dist);
528                        } else {
529                            nlist = new LinkedList<Node>();
530                            nearestMap.put(dist, nlist);
531                        }
532                        nlist.add(n);
533                    }
534                }
535            }
536    
537            return nearestMap;
538        }
539    
540        /**
541         * The *result* does not depend on the current map selection state,
542         * neither does the result *order*.
543         * It solely depends on the distance to point p.
544         *
545         * @return All nodes nearest to point p that are in a belt from
546         *      dist(nearest) to dist(nearest)+4px around p and
547         *      that are not in ignore.
548         *
549         * @param p the point for which to search the nearest segment.
550         * @param ignore a collection of nodes which are not to be returned.
551         * @param predicate the returned objects have to fulfill certain properties.
552         */
553        public final List<Node> getNearestNodes(Point p,
554                Collection<Node> ignore, Predicate<OsmPrimitive> predicate) {
555            List<Node> nearestList = Collections.emptyList();
556    
557            if (ignore == null) {
558                ignore = Collections.emptySet();
559            }
560    
561            Map<Double, List<Node>> nlists = getNearestNodesImpl(p, predicate);
562            if (!nlists.isEmpty()) {
563                Double minDistSq = null;
564                List<Node> nlist;
565                for (Double distSq : nlists.keySet()) {
566                    nlist = nlists.get(distSq);
567    
568                    // filter nodes to be ignored before determining minDistSq..
569                    nlist.removeAll(ignore);
570                    if (minDistSq == null) {
571                        if (!nlist.isEmpty()) {
572                            minDistSq = distSq;
573                            nearestList = new ArrayList<Node>();
574                            nearestList.addAll(nlist);
575                        }
576                    } else {
577                        if (distSq-minDistSq < (4)*(4)) {
578                            nearestList.addAll(nlist);
579                        }
580                    }
581                }
582            }
583    
584            return nearestList;
585        }
586    
587        /**
588         * The *result* does not depend on the current map selection state,
589         * neither does the result *order*.
590         * It solely depends on the distance to point p.
591         *
592         * @return All nodes nearest to point p that are in a belt from
593         *      dist(nearest) to dist(nearest)+4px around p.
594         * @see #getNearestNodes(Point, Collection, Predicate)
595         *
596         * @param p the point for which to search the nearest segment.
597         * @param predicate the returned objects have to fulfill certain properties.
598         */
599        public final List<Node> getNearestNodes(Point p, Predicate<OsmPrimitive> predicate) {
600            return getNearestNodes(p, null, predicate);
601        }
602    
603        /**
604         * The *result* depends on the current map selection state IF use_selected is true.
605         *
606         * If more than one node within node.snap-distance pixels is found,
607         * the nearest node selected is returned IF use_selected is true.
608         *
609         * Else the nearest new/id=0 node within about the same distance
610         * as the true nearest node is returned.
611         *
612         * If no such node is found either, the true nearest
613         * node to p is returned.
614         *
615         * Finally, if a node is not found at all, null is returned.
616         *
617         * @return A node within snap-distance to point p,
618         *      that is chosen by the algorithm described.
619         *
620         * @param p the screen point
621         * @param predicate this parameter imposes a condition on the returned object, e.g.
622         *        give the nearest node that is tagged.
623         */
624        public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate, boolean use_selected) {
625            Node n = null;
626    
627            Map<Double, List<Node>> nlists = getNearestNodesImpl(p, predicate);
628            if (!nlists.isEmpty()) {
629                Node ntsel = null, ntnew = null;
630                double minDistSq = nlists.keySet().iterator().next();
631    
632                for (Double distSq : nlists.keySet()) {
633                    for (Node nd : nlists.get(distSq)) {
634                        // find the nearest selected node
635                        if (ntsel == null && nd.isSelected()) {
636                            ntsel = nd;
637                            // if there are multiple nearest nodes, prefer the one
638                            // that is selected. This is required in order to drag
639                            // the selected node if multiple nodes have the same
640                            // coordinates (e.g. after unglue)
641                            use_selected |= (distSq == minDistSq);
642                        }
643                        // find the nearest newest node that is within about the same
644                        // distance as the true nearest node
645                        if (ntnew == null && nd.isNew() && (distSq-minDistSq < 1)) {
646                            ntnew = nd;
647                        }
648                    }
649                }
650    
651                // take nearest selected, nearest new or true nearest node to p, in that order
652                n = (ntsel != null && use_selected) ? ntsel
653                        : (ntnew != null) ? ntnew
654                                : nlists.values().iterator().next().get(0);
655            }
656            return n;
657        }
658    
659        /**
660         * Convenience method to {@link #getNearestNode(Point, Predicate, boolean)}.
661         *
662         * @return The nearest node to point p.
663         */
664        public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate) {
665            return getNearestNode(p, predicate, true);
666        }
667    
668        /**
669         * The *result* does not depend on the current map selection state,
670         * neither does the result *order*.
671         * It solely depends on the distance to point p.
672         *
673         * @return a sorted map with the keys representing the perpendicular
674         *      distance of their associated way segments to point p.
675         */
676        private Map<Double, List<WaySegment>> getNearestWaySegmentsImpl(Point p,
677                Predicate<OsmPrimitive> predicate) {
678            Map<Double, List<WaySegment>> nearestMap = new TreeMap<Double, List<WaySegment>>();
679            DataSet ds = getCurrentDataSet();
680    
681            if (ds != null) {
682                double snapDistanceSq = Main.pref.getInteger("mappaint.segment.snap-distance", 10);
683                snapDistanceSq *= snapDistanceSq;
684    
685                for (Way w : ds.searchWays(getBBox(p, Main.pref.getInteger("mappaint.segment.snap-distance", 10)))) {
686                    if (!predicate.evaluate(w)) {
687                        continue;
688                    }
689                    Node lastN = null;
690                    int i = -2;
691                    for (Node n : w.getNodes()) {
692                        i++;
693                        if (n.isDeleted() || n.isIncomplete()) { //FIXME: This shouldn't happen, raise exception?
694                            continue;
695                        }
696                        if (lastN == null) {
697                            lastN = n;
698                            continue;
699                        }
700    
701                        Point2D A = getPoint2D(lastN);
702                        Point2D B = getPoint2D(n);
703                        double c = A.distanceSq(B);
704                        double a = p.distanceSq(B);
705                        double b = p.distanceSq(A);
706    
707                        /* perpendicular distance squared
708                         * loose some precision to account for possible deviations in the calculation above
709                         * e.g. if identical (A and B) come about reversed in another way, values may differ
710                         * -- zero out least significant 32 dual digits of mantissa..
711                         */
712                        double perDistSq = Double.longBitsToDouble(
713                                Double.doubleToLongBits( a - (a - b + c) * (a - b + c) / 4 / c )
714                                >> 32 << 32); // resolution in numbers with large exponent not needed here..
715    
716                        if (perDistSq < snapDistanceSq && a < c + snapDistanceSq && b < c + snapDistanceSq) {
717                            //System.err.println(Double.toHexString(perDistSq));
718    
719                            List<WaySegment> wslist;
720                            if (nearestMap.containsKey(perDistSq)) {
721                                wslist = nearestMap.get(perDistSq);
722                            } else {
723                                wslist = new LinkedList<WaySegment>();
724                                nearestMap.put(perDistSq, wslist);
725                            }
726                            wslist.add(new WaySegment(w, i));
727                        }
728    
729                        lastN = n;
730                    }
731                }
732            }
733    
734            return nearestMap;
735        }
736    
737        /**
738         * The result *order* depends on the current map selection state.
739         * Segments within 10px of p are searched and sorted by their distance to @param p,
740         * then, within groups of equally distant segments, prefer those that are selected.
741         *
742         * @return all segments within 10px of p that are not in ignore,
743         *          sorted by their perpendicular distance.
744         *
745         * @param p the point for which to search the nearest segments.
746         * @param ignore a collection of segments which are not to be returned.
747         * @param predicate the returned objects have to fulfill certain properties.
748         */
749        public final List<WaySegment> getNearestWaySegments(Point p,
750                Collection<WaySegment> ignore, Predicate<OsmPrimitive> predicate) {
751            List<WaySegment> nearestList = new ArrayList<WaySegment>();
752            List<WaySegment> unselected = new LinkedList<WaySegment>();
753    
754            for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
755                // put selected waysegs within each distance group first
756                // makes the order of nearestList dependent on current selection state
757                for (WaySegment ws : wss) {
758                    (ws.way.isSelected() ? nearestList : unselected).add(ws);
759                }
760                nearestList.addAll(unselected);
761                unselected.clear();
762            }
763            if (ignore != null) {
764                nearestList.removeAll(ignore);
765            }
766    
767            return nearestList;
768        }
769    
770        /**
771         * The result *order* depends on the current map selection state.
772         *
773         * @return all segments within 10px of p, sorted by their perpendicular distance.
774         * @see #getNearestWaySegments(Point, Collection, Predicate)
775         *
776         * @param p the point for which to search the nearest segments.
777         * @param predicate the returned objects have to fulfill certain properties.
778         */
779        public final List<WaySegment> getNearestWaySegments(Point p, Predicate<OsmPrimitive> predicate) {
780            return getNearestWaySegments(p, null, predicate);
781        }
782    
783        /**
784         * The *result* depends on the current map selection state IF use_selected is true.
785         *
786         * @return The nearest way segment to point p,
787         *      and, depending on use_selected, prefers a selected way segment, if found.
788         * @see #getNearestWaySegments(Point, Collection, Predicate)
789         *
790         * @param p the point for which to search the nearest segment.
791         * @param predicate the returned object has to fulfill certain properties.
792         * @param use_selected whether selected way segments should be preferred.
793         */
794        public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate, boolean use_selected) {
795            WaySegment wayseg = null, ntsel = null;
796    
797            for (List<WaySegment> wslist : getNearestWaySegmentsImpl(p, predicate).values()) {
798                if (wayseg != null && ntsel != null) {
799                    break;
800                }
801                for (WaySegment ws : wslist) {
802                    if (wayseg == null) {
803                        wayseg = ws;
804                    }
805                    if (ntsel == null && ws.way.isSelected()) {
806                        ntsel = ws;
807                    }
808                }
809            }
810    
811            return (ntsel != null && use_selected) ? ntsel : wayseg;
812        }
813    
814        /**
815         * Convenience method to {@link #getNearestWaySegment(Point, Predicate, boolean)}.
816         *
817         * @return The nearest way segment to point p.
818         */
819        public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate) {
820            return getNearestWaySegment(p, predicate, true);
821        }
822    
823        /**
824         * The *result* does not depend on the current map selection state,
825         * neither does the result *order*.
826         * It solely depends on the perpendicular distance to point p.
827         *
828         * @return all nearest ways to the screen point given that are not in ignore.
829         * @see #getNearestWaySegments(Point, Collection, Predicate)
830         *
831         * @param p the point for which to search the nearest ways.
832         * @param ignore a collection of ways which are not to be returned.
833         * @param predicate the returned object has to fulfill certain properties.
834         */
835        public final List<Way> getNearestWays(Point p,
836                Collection<Way> ignore, Predicate<OsmPrimitive> predicate) {
837            List<Way> nearestList = new ArrayList<Way>();
838            Set<Way> wset = new HashSet<Way>();
839    
840            for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
841                for (WaySegment ws : wss) {
842                    if (wset.add(ws.way)) {
843                        nearestList.add(ws.way);
844                    }
845                }
846            }
847            if (ignore != null) {
848                nearestList.removeAll(ignore);
849            }
850    
851            return nearestList;
852        }
853    
854        /**
855         * The *result* does not depend on the current map selection state,
856         * neither does the result *order*.
857         * It solely depends on the perpendicular distance to point p.
858         *
859         * @return all nearest ways to the screen point given.
860         * @see #getNearestWays(Point, Collection, Predicate)
861         *
862         * @param p the point for which to search the nearest ways.
863         * @param predicate the returned object has to fulfill certain properties.
864         */
865        public final List<Way> getNearestWays(Point p, Predicate<OsmPrimitive> predicate) {
866            return getNearestWays(p, null, predicate);
867        }
868    
869        /**
870         * The *result* depends on the current map selection state.
871         *
872         * @return The nearest way to point p,
873         *      prefer a selected way if there are multiple nearest.
874         * @see #getNearestWaySegment(Point, Collection, Predicate)
875         *
876         * @param p the point for which to search the nearest segment.
877         * @param predicate the returned object has to fulfill certain properties.
878         */
879        public final Way getNearestWay(Point p, Predicate<OsmPrimitive> predicate) {
880            WaySegment nearestWaySeg = getNearestWaySegment(p, predicate);
881            return (nearestWaySeg == null) ? null : nearestWaySeg.way;
882        }
883    
884        /**
885         * The *result* does not depend on the current map selection state,
886         * neither does the result *order*.
887         * It solely depends on the distance to point p.
888         *
889         * First, nodes will be searched. If there are nodes within BBox found,
890         * return a collection of those nodes only.
891         *
892         * If no nodes are found, search for nearest ways. If there are ways
893         * within BBox found, return a collection of those ways only.
894         *
895         * If nothing is found, return an empty collection.
896         *
897         * @return Primitives nearest to the given screen point that are not in ignore.
898         * @see #getNearestNodes(Point, Collection, Predicate)
899         * @see #getNearestWays(Point, Collection, Predicate)
900         *
901         * @param p The point on screen.
902         * @param ignore a collection of ways which are not to be returned.
903         * @param predicate the returned object has to fulfill certain properties.
904         */
905        public final List<OsmPrimitive> getNearestNodesOrWays(Point p,
906                Collection<OsmPrimitive> ignore, Predicate<OsmPrimitive> predicate) {
907            List<OsmPrimitive> nearestList = Collections.emptyList();
908            OsmPrimitive osm = getNearestNodeOrWay(p, predicate, false);
909    
910            if (osm != null) {
911                if (osm instanceof Node) {
912                    nearestList = new ArrayList<OsmPrimitive>(getNearestNodes(p, predicate));
913                } else if (osm instanceof Way) {
914                    nearestList = new ArrayList<OsmPrimitive>(getNearestWays(p, predicate));
915                }
916                if (ignore != null) {
917                    nearestList.removeAll(ignore);
918                }
919            }
920    
921            return nearestList;
922        }
923    
924        /**
925         * The *result* does not depend on the current map selection state,
926         * neither does the result *order*.
927         * It solely depends on the distance to point p.
928         *
929         * @return Primitives nearest to the given screen point.
930         * @see #getNearests(Point, Collection, Predicate)
931         *
932         * @param p The point on screen.
933         * @param predicate the returned object has to fulfill certain properties.
934         */
935        public final List<OsmPrimitive> getNearestNodesOrWays(Point p, Predicate<OsmPrimitive> predicate) {
936            return getNearestNodesOrWays(p, null, predicate);
937        }
938    
939        /**
940         * This is used as a helper routine to {@link #getNearestNodeOrWay(Point, Predicate, boolean)}
941         * It decides, whether to yield the node to be tested or look for further (way) candidates.
942         *
943         * @return true, if the node fulfills the properties of the function body
944         *
945         * @param osm node to check
946         * @param p point clicked
947         * @param use_selected whether to prefer selected nodes
948         */
949        private boolean isPrecedenceNode(Node osm, Point p, boolean use_selected) {
950            boolean ret = false;
951    
952            if (osm != null) {
953                ret |= !(p.distanceSq(getPoint2D(osm)) > (4)*(4));
954                ret |= osm.isTagged();
955                if (use_selected) {
956                    ret |= osm.isSelected();
957                }
958            }
959    
960            return ret;
961        }
962    
963        /**
964         * The *result* depends on the current map selection state IF use_selected is true.
965         *
966         * IF use_selected is true, use {@link #getNearestNode(Point, Predicate)} to find
967         * the nearest, selected node.  If not found, try {@link #getNearestWaySegment(Point, Predicate)}
968         * to find the nearest selected way.
969         *
970         * IF use_selected is false, or if no selected primitive was found, do the following.
971         *
972         * If the nearest node found is within 4px of p, simply take it.
973         * Else, find the nearest way segment. Then, if p is closer to its
974         * middle than to the node, take the way segment, else take the node.
975         *
976         * Finally, if no nearest primitive is found at all, return null.
977         *
978         * @return A primitive within snap-distance to point p,
979         *      that is chosen by the algorithm described.
980         * @see getNearestNode(Point, Predicate)
981         * @see getNearestNodesImpl(Point, Predicate)
982         * @see getNearestWay(Point, Predicate)
983         *
984         * @param p The point on screen.
985         * @param predicate the returned object has to fulfill certain properties.
986         * @param use_selected whether to prefer primitives that are currently selected.
987         */
988        public final OsmPrimitive getNearestNodeOrWay(Point p, Predicate<OsmPrimitive> predicate, boolean use_selected) {
989            OsmPrimitive osm = getNearestNode(p, predicate, use_selected);
990            WaySegment ws = null;
991    
992            if (!isPrecedenceNode((Node)osm, p, use_selected)) {
993                ws = getNearestWaySegment(p, predicate, use_selected);
994    
995                if (ws != null) {
996                    if ((ws.way.isSelected() && use_selected) || osm == null) {
997                        // either (no _selected_ nearest node found, if desired) or no nearest node was found
998                        osm = ws.way;
999                    } else {
1000                        int maxWaySegLenSq = 3*PROP_SNAP_DISTANCE.get();
1001                        maxWaySegLenSq *= maxWaySegLenSq;
1002    
1003                        Point2D wp1 = getPoint2D(ws.way.getNode(ws.lowerIndex));
1004                        Point2D wp2 = getPoint2D(ws.way.getNode(ws.lowerIndex+1));
1005    
1006                        // is wayseg shorter than maxWaySegLenSq and
1007                        // is p closer to the middle of wayseg  than  to the nearest node?
1008                        if (wp1.distanceSq(wp2) < maxWaySegLenSq &&
1009                                p.distanceSq(project(0.5, wp1, wp2)) < p.distanceSq(getPoint2D((Node)osm))) {
1010                            osm = ws.way;
1011                        }
1012                    }
1013                }
1014            }
1015    
1016            return osm;
1017        }
1018    
1019        /**
1020         * @return o as collection of o's type.
1021         */
1022        public static <T> Collection<T> asColl(T o) {
1023            if (o == null)
1024                return Collections.emptySet();
1025            return Collections.singleton(o);
1026        }
1027    
1028        public static double perDist(Point2D pt, Point2D a, Point2D b) {
1029            if (pt != null && a != null && b != null) {
1030                double pd = (
1031                        (a.getX()-pt.getX())*(b.getX()-a.getX()) -
1032                        (a.getY()-pt.getY())*(b.getY()-a.getY()) );
1033                return Math.abs(pd) / a.distance(b);
1034            }
1035            return 0d;
1036        }
1037    
1038        /**
1039         *
1040         * @param pt point to project onto (ab)
1041         * @param a root of vector
1042         * @param b vector
1043         * @return point of intersection of line given by (ab)
1044         *      with its orthogonal line running through pt
1045         */
1046        public static Point2D project(Point2D pt, Point2D a, Point2D b) {
1047            if (pt != null && a != null && b != null) {
1048                double r = ((
1049                        (pt.getX()-a.getX())*(b.getX()-a.getX()) +
1050                        (pt.getY()-a.getY())*(b.getY()-a.getY()) )
1051                        / a.distanceSq(b));
1052                return project(r, a, b);
1053            }
1054            return null;
1055        }
1056    
1057        /**
1058         * if r = 0 returns a, if r=1 returns b,
1059         * if r = 0.5 returns center between a and b, etc..
1060         *
1061         * @param r scale value
1062         * @param a root of vector
1063         * @param b vector
1064         * @return new point at a + r*(ab)
1065         */
1066        public static Point2D project(double r, Point2D a, Point2D b) {
1067            Point2D ret = null;
1068    
1069            if (a != null && b != null) {
1070                ret = new Point2D.Double(a.getX() + r*(b.getX()-a.getX()),
1071                        a.getY() + r*(b.getY()-a.getY()));
1072            }
1073            return ret;
1074        }
1075    
1076        /**
1077         * The *result* does not depend on the current map selection state,
1078         * neither does the result *order*.
1079         * It solely depends on the distance to point p.
1080         *
1081         * @return a list of all objects that are nearest to point p and
1082         *          not in ignore or an empty list if nothing was found.
1083         *
1084         * @param p The point on screen.
1085         * @param ignore a collection of ways which are not to be returned.
1086         * @param predicate the returned object has to fulfill certain properties.
1087         */
1088        public final List<OsmPrimitive> getAllNearest(Point p,
1089                Collection<OsmPrimitive> ignore, Predicate<OsmPrimitive> predicate) {
1090            List<OsmPrimitive> nearestList = new ArrayList<OsmPrimitive>();
1091            Set<Way> wset = new HashSet<Way>();
1092    
1093            // add nearby ways
1094            for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
1095                for (WaySegment ws : wss) {
1096                    if (wset.add(ws.way)) {
1097                        nearestList.add(ws.way);
1098                    }
1099                }
1100            }
1101            
1102            // add nearby nodes
1103            for (List<Node> nlist : getNearestNodesImpl(p, predicate).values()) {
1104                nearestList.addAll(nlist);
1105            }
1106            
1107            // add parent relations of nearby nodes and ways
1108            Set<OsmPrimitive> parentRelations = new HashSet<OsmPrimitive>();
1109            for (OsmPrimitive o : nearestList) {
1110                for (OsmPrimitive r : o.getReferrers()) {
1111                    if (r instanceof Relation && predicate.evaluate(r)) {
1112                        parentRelations.add(r);
1113                    }
1114                }
1115            }
1116            nearestList.addAll(parentRelations);
1117            
1118            if (ignore != null) {
1119                nearestList.removeAll(ignore);
1120            }
1121    
1122            return nearestList;
1123        }
1124    
1125        /**
1126         * The *result* does not depend on the current map selection state,
1127         * neither does the result *order*.
1128         * It solely depends on the distance to point p.
1129         *
1130         * @return a list of all objects that are nearest to point p
1131         *          or an empty list if nothing was found.
1132         * @see #getAllNearest(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> getAllNearest(Point p, Predicate<OsmPrimitive> predicate) {
1138            return getAllNearest(p, null, predicate);
1139        }
1140    
1141        /**
1142         * @return The projection to be used in calculating stuff.
1143         */
1144        public Projection getProjection() {
1145            return Main.getProjection();
1146        }
1147    
1148        public String helpTopic() {
1149            String n = getClass().getName();
1150            return n.substring(n.lastIndexOf('.')+1);
1151        }
1152    
1153        /**
1154         * Return a ID which is unique as long as viewport dimensions are the same
1155         */
1156        public int getViewID() {
1157            String x = center.east() + "_" + center.north() + "_" + scale + "_" +
1158                    getWidth() + "_" + getHeight() + "_" + getProjection().toString();
1159            java.util.zip.CRC32 id = new java.util.zip.CRC32();
1160            id.update(x.getBytes());
1161            return (int)id.getValue();
1162        }
1163    
1164        public static SystemOfMeasurement getSystemOfMeasurement() {
1165            SystemOfMeasurement som = SYSTEMS_OF_MEASUREMENT.get(ProjectionPreference.PROP_SYSTEM_OF_MEASUREMENT.get());
1166            if (som == null)
1167                return METRIC_SOM;
1168            return som;
1169        }
1170    
1171        public static class SystemOfMeasurement {
1172            public final double aValue;
1173            public final double bValue;
1174            public final String aName;
1175            public final String bName;
1176    
1177            /**
1178             * System of measurement. Currently covers only length units.
1179             *
1180             * If a quantity x is given in m (x_m) and in unit a (x_a) then it translates as
1181             * x_a == x_m / aValue
1182             */
1183            public SystemOfMeasurement(double aValue, String aName, double bValue, String bName) {
1184                this.aValue = aValue;
1185                this.aName = aName;
1186                this.bValue = bValue;
1187                this.bName = bName;
1188            }
1189    
1190            public String getDistText(double dist) {
1191                double a = dist / aValue;
1192                if (!Main.pref.getBoolean("system_of_measurement.use_only_lower_unit", false) && a > bValue / aValue) {
1193                    double b = dist / bValue;
1194                    return String.format(Locale.US, "%." + (b<10 ? 2 : 1) + "f %s", b, bName);
1195                } else if (a < 0.01)
1196                    return "< 0.01 " + aName;
1197                else
1198                    return String.format(Locale.US, "%." + (a<10 ? 2 : 1) + "f %s", a, aName);
1199            }
1200        }
1201    
1202        public static final SystemOfMeasurement METRIC_SOM = new SystemOfMeasurement(1, "m", 1000, "km");
1203        public static final SystemOfMeasurement CHINESE_SOM = new SystemOfMeasurement(1.0/3.0, "\u5e02\u5c3a" /* chi */, 500, "\u5e02\u91cc" /* li */);
1204        public static final SystemOfMeasurement IMPERIAL_SOM = new SystemOfMeasurement(0.3048, "ft", 1609.344, "mi");
1205    
1206        public static final Map<String, SystemOfMeasurement> SYSTEMS_OF_MEASUREMENT;
1207        static {
1208            SYSTEMS_OF_MEASUREMENT = new LinkedHashMap<String, SystemOfMeasurement>();
1209            SYSTEMS_OF_MEASUREMENT.put(marktr("Metric"), METRIC_SOM);
1210            SYSTEMS_OF_MEASUREMENT.put(marktr("Chinese"), CHINESE_SOM);
1211            SYSTEMS_OF_MEASUREMENT.put(marktr("Imperial"), IMPERIAL_SOM);
1212        }
1213    
1214        private static class CursorInfo {
1215            public Cursor cursor;
1216            public Object object;
1217            public CursorInfo(Cursor c, Object o) {
1218                cursor = c;
1219                object = o;
1220            }
1221        }
1222    
1223        private LinkedList<CursorInfo> Cursors = new LinkedList<CursorInfo>();
1224        /**
1225         * Set new cursor.
1226         */
1227        public void setNewCursor(Cursor cursor, Object reference) {
1228            if(Cursors.size() > 0) {
1229                CursorInfo l = Cursors.getLast();
1230                if(l != null && l.cursor == cursor && l.object == reference)
1231                    return;
1232                stripCursors(reference);
1233            }
1234            Cursors.add(new CursorInfo(cursor, reference));
1235            setCursor(cursor);
1236        }
1237        public void setNewCursor(int cursor, Object reference) {
1238            setNewCursor(Cursor.getPredefinedCursor(cursor), reference);
1239        }
1240        /**
1241         * Remove the new cursor and reset to previous
1242         */
1243        public void resetCursor(Object reference) {
1244            if(Cursors.size() == 0) {
1245                setCursor(null);
1246                return;
1247            }
1248            CursorInfo l = Cursors.getLast();
1249            stripCursors(reference);
1250            if(l != null && l.object == reference) {
1251                if(Cursors.size() == 0) {
1252                    setCursor(null);
1253                } else {
1254                    setCursor(Cursors.getLast().cursor);
1255                }
1256            }
1257        }
1258    
1259        private void stripCursors(Object reference) {
1260            LinkedList<CursorInfo> c = new LinkedList<CursorInfo>();
1261            for(CursorInfo i : Cursors) {
1262                if(i.object != reference) {
1263                    c.add(i);
1264                }
1265            }
1266            Cursors = c;
1267        }
1268    }