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