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