source: josm/trunk/src/org/openstreetmap/josm/gui/NavigatableComponent.java@ 5549

Last change on this file since 5549 was 5549, checked in by bastiK, 12 years ago

applied #8089 - "nautical mile" (NM) as new system of measurement (patch by jjaf)

  • Property svn:eol-style set to native
File size: 49.1 KB
Line 
1// License: GPL. See LICENSE file for details.
2package org.openstreetmap.josm.gui;
3
4import static org.openstreetmap.josm.tools.I18n.marktr;
5
6import java.awt.Color;
7import java.awt.Cursor;
8import java.awt.Graphics;
9import java.awt.Point;
10import java.awt.Polygon;
11import java.awt.Rectangle;
12import java.awt.geom.AffineTransform;
13import java.awt.geom.Point2D;
14import java.util.ArrayList;
15import java.util.Collection;
16import java.util.Collections;
17import java.util.Date;
18import java.util.HashSet;
19import java.util.LinkedHashMap;
20import java.util.LinkedList;
21import java.util.List;
22import java.util.Locale;
23import java.util.Map;
24import java.util.Set;
25import java.util.Stack;
26import java.util.TreeMap;
27import java.util.concurrent.CopyOnWriteArrayList;
28
29import javax.swing.JComponent;
30
31import org.openstreetmap.josm.Main;
32import org.openstreetmap.josm.data.Bounds;
33import org.openstreetmap.josm.data.ProjectionBounds;
34import org.openstreetmap.josm.data.coor.CachedLatLon;
35import org.openstreetmap.josm.data.coor.EastNorth;
36import org.openstreetmap.josm.data.coor.LatLon;
37import org.openstreetmap.josm.data.osm.BBox;
38import org.openstreetmap.josm.data.osm.DataSet;
39import org.openstreetmap.josm.data.osm.Node;
40import org.openstreetmap.josm.data.osm.OsmPrimitive;
41import org.openstreetmap.josm.data.osm.Relation;
42import org.openstreetmap.josm.data.osm.Way;
43import org.openstreetmap.josm.data.osm.WaySegment;
44import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
45import org.openstreetmap.josm.data.preferences.IntegerProperty;
46import org.openstreetmap.josm.data.projection.Projection;
47import org.openstreetmap.josm.data.projection.Projections;
48import org.openstreetmap.josm.gui.help.Helpful;
49import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
50import org.openstreetmap.josm.tools.Predicate;
51import org.openstreetmap.josm.tools.Utils;
52
53/**
54 * An component that can be navigated by a mapmover. Used as map view and for the
55 * zoomer in the download dialog.
56 *
57 * @author imi
58 */
59public class NavigatableComponent extends JComponent implements Helpful {
60
61 /**
62 * Interface to notify listeners of the change of the zoom area.
63 */
64 public interface ZoomChangeListener {
65 void zoomChanged();
66 }
67
68 public static final IntegerProperty PROP_SNAP_DISTANCE = new IntegerProperty("mappaint.node.snap-distance", 10);
69
70 public static final String PROPNAME_CENTER = "center";
71 public static final String PROPNAME_SCALE = "scale";
72
73 /**
74 * the zoom listeners
75 */
76 private static final CopyOnWriteArrayList<ZoomChangeListener> zoomChangeListeners = new CopyOnWriteArrayList<ZoomChangeListener>();
77
78 /**
79 * Removes a zoom change listener
80 *
81 * @param listener the listener. Ignored if null or already absent
82 */
83 public static void removeZoomChangeListener(NavigatableComponent.ZoomChangeListener listener) {
84 zoomChangeListeners.remove(listener);
85 }
86
87 /**
88 * Adds a zoom change listener
89 *
90 * @param listener the listener. Ignored if null or already registered.
91 */
92 public static void addZoomChangeListener(NavigatableComponent.ZoomChangeListener listener) {
93 if (listener != null) {
94 zoomChangeListeners.addIfAbsent(listener);
95 }
96 }
97
98 protected static void fireZoomChanged() {
99 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 public static final SystemOfMeasurement NAUTICAL_MILE_SOM = new SystemOfMeasurement(185.2, "kbl", 1852, "NM");
1215
1216 public static final Map<String, SystemOfMeasurement> SYSTEMS_OF_MEASUREMENT;
1217 static {
1218 SYSTEMS_OF_MEASUREMENT = new LinkedHashMap<String, SystemOfMeasurement>();
1219 SYSTEMS_OF_MEASUREMENT.put(marktr("Metric"), METRIC_SOM);
1220 SYSTEMS_OF_MEASUREMENT.put(marktr("Chinese"), CHINESE_SOM);
1221 SYSTEMS_OF_MEASUREMENT.put(marktr("Imperial"), IMPERIAL_SOM);
1222 SYSTEMS_OF_MEASUREMENT.put(marktr("Nautical Mile"), NAUTICAL_MILE_SOM);
1223 }
1224
1225 private static class CursorInfo {
1226 public Cursor cursor;
1227 public Object object;
1228 public CursorInfo(Cursor c, Object o) {
1229 cursor = c;
1230 object = o;
1231 }
1232 }
1233
1234 private LinkedList<CursorInfo> Cursors = new LinkedList<CursorInfo>();
1235 /**
1236 * Set new cursor.
1237 */
1238 public void setNewCursor(Cursor cursor, Object reference) {
1239 if(Cursors.size() > 0) {
1240 CursorInfo l = Cursors.getLast();
1241 if(l != null && l.cursor == cursor && l.object == reference)
1242 return;
1243 stripCursors(reference);
1244 }
1245 Cursors.add(new CursorInfo(cursor, reference));
1246 setCursor(cursor);
1247 }
1248 public void setNewCursor(int cursor, Object reference) {
1249 setNewCursor(Cursor.getPredefinedCursor(cursor), reference);
1250 }
1251 /**
1252 * Remove the new cursor and reset to previous
1253 */
1254 public void resetCursor(Object reference) {
1255 if(Cursors.size() == 0) {
1256 setCursor(null);
1257 return;
1258 }
1259 CursorInfo l = Cursors.getLast();
1260 stripCursors(reference);
1261 if(l != null && l.object == reference) {
1262 if(Cursors.size() == 0) {
1263 setCursor(null);
1264 } else {
1265 setCursor(Cursors.getLast().cursor);
1266 }
1267 }
1268 }
1269
1270 private void stripCursors(Object reference) {
1271 LinkedList<CursorInfo> c = new LinkedList<CursorInfo>();
1272 for(CursorInfo i : Cursors) {
1273 if(i.object != reference) {
1274 c.add(i);
1275 }
1276 }
1277 Cursors = c;
1278 }
1279
1280 @Override
1281 public void paint(Graphics g) {
1282 synchronized (paintRequestLock) {
1283 if (paintRect != null) {
1284 Graphics g2 = g.create();
1285 g2.setColor(Utils.complement(PaintColors.getBackgroundColor()));
1286 g2.drawRect(paintRect.x, paintRect.y, paintRect.width, paintRect.height);
1287 g2.dispose();
1288 }
1289 if (paintPoly != null) {
1290 Graphics g2 = g.create();
1291 g2.setColor(Utils.complement(PaintColors.getBackgroundColor()));
1292 g2.drawPolyline(paintPoly.xpoints, paintPoly.ypoints, paintPoly.npoints);
1293 g2.dispose();
1294 }
1295 }
1296 super.paint(g);
1297 }
1298
1299 /**
1300 * Requests to paint the given {@code Rectangle}.
1301 * @param r The Rectangle to draw
1302 * @see #requestClearRect
1303 * @since 5500
1304 */
1305 public void requestPaintRect(Rectangle r) {
1306 if (r != null) {
1307 synchronized (paintRequestLock) {
1308 paintRect = r;
1309 }
1310 repaint();
1311 }
1312 }
1313
1314 /**
1315 * Requests to paint the given {@code Polygon} as a polyline (unclosed polygon).
1316 * @param p The Polygon to draw
1317 * @see #requestClearPoly
1318 * @since 5500
1319 */
1320 public void requestPaintPoly(Polygon p) {
1321 if (p != null) {
1322 synchronized (paintRequestLock) {
1323 paintPoly = p;
1324 }
1325 repaint();
1326 }
1327 }
1328
1329 /**
1330 * Requests to clear the rectangled previously drawn.
1331 * @see #requestPaintRect
1332 * @since 5500
1333 */
1334 public void requestClearRect() {
1335 synchronized (paintRequestLock) {
1336 paintRect = null;
1337 }
1338 repaint();
1339 }
1340
1341 /**
1342 * Requests to clear the polyline previously drawn.
1343 * @see #requestPaintPoly
1344 * @since 5500
1345 */
1346 public void requestClearPoly() {
1347 synchronized (paintRequestLock) {
1348 paintPoly = null;
1349 }
1350 repaint();
1351 }
1352}
Note: See TracBrowser for help on using the repository browser.