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

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

checkstyle - Comments Indentation

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