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

Last change on this file since 9623 was 9623, checked in by Don-vip, 8 years ago

fix transient/serializable findbugs warnings

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