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 }