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