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

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

findbugs - fix RpC: Repeated conditional test (typo from r9118)

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