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

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

sonar - Immutable Field

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