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

Last change on this file since 9171 was 9123, checked in by bastiK, 8 years ago

make sure the scale ("circum") is never 0 (but positive) (see #12186)

  • Property svn:eol-style set to native
File size: 55.7 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() && ll1.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
501 * animated fashion.
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 */
586 public void zoomTo(BoundingXYVisitor box) {
587 if (box == null) {
588 box = new BoundingXYVisitor();
589 }
590 if (box.getBounds() == null) {
591 box.visit(getProjection().getWorldBoundsLatLon());
592 }
593 if (!box.hasExtend()) {
594 box.enlargeBoundingBox();
595 }
596
597 zoomTo(box.getBounds());
598 }
599
600 private class ZoomData {
601 private final LatLon center;
602 private final double scale;
603
604 ZoomData(EastNorth center, double scale) {
605 this.center = Projections.inverseProject(center);
606 this.scale = scale;
607 }
608
609 public EastNorth getCenterEastNorth() {
610 return getProjection().latlon2eastNorth(center);
611 }
612
613 public double getScale() {
614 return scale;
615 }
616 }
617
618 private final Stack<ZoomData> zoomUndoBuffer = new Stack<>();
619 private final Stack<ZoomData> zoomRedoBuffer = new Stack<>();
620 private Date zoomTimestamp = new Date();
621
622 private void pushZoomUndo(EastNorth center, double scale) {
623 Date now = new Date();
624 if ((now.getTime() - zoomTimestamp.getTime()) > (Main.pref.getDouble("zoom.undo.delay", 1.0) * 1000)) {
625 zoomUndoBuffer.push(new ZoomData(center, scale));
626 if (zoomUndoBuffer.size() > Main.pref.getInteger("zoom.undo.max", 50)) {
627 zoomUndoBuffer.remove(0);
628 }
629 zoomRedoBuffer.clear();
630 }
631 zoomTimestamp = now;
632 }
633
634 public void zoomPrevious() {
635 if (!zoomUndoBuffer.isEmpty()) {
636 ZoomData zoom = zoomUndoBuffer.pop();
637 zoomRedoBuffer.push(new ZoomData(center, scale));
638 zoomNoUndoTo(zoom.getCenterEastNorth(), zoom.getScale(), false);
639 }
640 }
641
642 public void zoomNext() {
643 if (!zoomRedoBuffer.isEmpty()) {
644 ZoomData zoom = zoomRedoBuffer.pop();
645 zoomUndoBuffer.push(new ZoomData(center, scale));
646 zoomNoUndoTo(zoom.getCenterEastNorth(), zoom.getScale(), false);
647 }
648 }
649
650 public boolean hasZoomUndoEntries() {
651 return !zoomUndoBuffer.isEmpty();
652 }
653
654 public boolean hasZoomRedoEntries() {
655 return !zoomRedoBuffer.isEmpty();
656 }
657
658 private BBox getBBox(Point p, int snapDistance) {
659 return new BBox(getLatLon(p.x - snapDistance, p.y - snapDistance),
660 getLatLon(p.x + snapDistance, p.y + snapDistance));
661 }
662
663 /**
664 * The *result* does not depend on the current map selection state,
665 * neither does the result *order*.
666 * It solely depends on the distance to point p.
667 *
668 * @return a sorted map with the keys representing the distance of
669 * their associated nodes to point p.
670 */
671 private Map<Double, List<Node>> getNearestNodesImpl(Point p,
672 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
771 * node to p is returned.
772 *
773 * Finally, if a node is not found at all, null is returned.
774 *
775 * @param p the screen point
776 * @param predicate this parameter imposes a condition on the returned object, e.g.
777 * give the nearest node that is tagged.
778 *
779 * @return A node within snap-distance to point p,
780 * that is chosen by the algorithm described.
781 */
782 public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate, boolean useSelected) {
783 return getNearestNode(p, predicate, useSelected, null);
784 }
785
786 /**
787 * The *result* depends on the current map selection state IF use_selected is true
788 *
789 * If more than one node within node.snap-distance pixels is found,
790 * the nearest node selected is returned IF use_selected is true.
791 *
792 * If there are no selected nodes near that point, the node that is related to some of the preferredRefs
793 *
794 * Else the nearest new/id=0 node within about the same distance
795 * as the true nearest node is returned.
796 *
797 * If no such node is found either, the true nearest
798 * node to p is returned.
799 *
800 * Finally, if a node is not found at all, null is returned.
801 *
802 * @param p the screen point
803 * @param predicate this parameter imposes a condition on the returned object, e.g.
804 * give the nearest node that is tagged.
805 * @param preferredRefs primitives, whose nodes we prefer
806 *
807 * @return A node within snap-distance to point p,
808 * that is chosen by the algorithm described.
809 * @since 6065
810 */
811 public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate,
812 boolean useSelected, Collection<OsmPrimitive> preferredRefs) {
813
814 Map<Double, List<Node>> nlists = getNearestNodesImpl(p, predicate);
815 if (nlists.isEmpty()) return null;
816
817 if (preferredRefs != null && preferredRefs.isEmpty()) preferredRefs = null;
818 Node ntsel = null, ntnew = null, ntref = null;
819 boolean useNtsel = useSelected;
820 double minDistSq = nlists.keySet().iterator().next();
821
822 for (Entry<Double, List<Node>> entry : nlists.entrySet()) {
823 Double distSq = entry.getKey();
824 for (Node nd : entry.getValue()) {
825 // find the nearest selected node
826 if (ntsel == null && nd.isSelected()) {
827 ntsel = nd;
828 // if there are multiple nearest nodes, prefer the one
829 // that is selected. This is required in order to drag
830 // the selected node if multiple nodes have the same
831 // coordinates (e.g. after unglue)
832 useNtsel |= Utils.equalsEpsilon(distSq, minDistSq);
833 }
834 if (ntref == null && preferredRefs != null && Utils.equalsEpsilon(distSq, minDistSq)) {
835 List<OsmPrimitive> ndRefs = nd.getReferrers();
836 for (OsmPrimitive ref: preferredRefs) {
837 if (ndRefs.contains(ref)) {
838 ntref = nd;
839 break;
840 }
841 }
842 }
843 // find the nearest newest node that is within about the same
844 // distance as the true nearest node
845 if (ntnew == null && nd.isNew() && (distSq-minDistSq < 1)) {
846 ntnew = nd;
847 }
848 }
849 }
850
851 // take nearest selected, nearest new or true nearest node to p, in that order
852 if (ntsel != null && useNtsel)
853 return ntsel;
854 if (ntref != null)
855 return ntref;
856 if (ntnew != null)
857 return ntnew;
858 return nlists.values().iterator().next().get(0);
859 }
860
861 /**
862 * Convenience method to {@link #getNearestNode(Point, Predicate, boolean)}.
863 * @param p the screen point
864 * @param predicate this parameter imposes a condition on the returned object, e.g.
865 * give the nearest node that is tagged.
866 *
867 * @return The nearest node to point p.
868 */
869 public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate) {
870 return getNearestNode(p, predicate, true);
871 }
872
873 /**
874 * The *result* does not depend on the current map selection state,
875 * neither does the result *order*.
876 * It solely depends on the distance to point p.
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,
882 Predicate<OsmPrimitive> predicate) {
883 Map<Double, List<WaySegment>> nearestMap = new TreeMap<>();
884 DataSet ds = getCurrentDataSet();
885
886 if (ds != null) {
887 double snapDistanceSq = Main.pref.getInteger("mappaint.segment.snap-distance", 10);
888 snapDistanceSq *= snapDistanceSq;
889
890 for (Way w : ds.searchWays(getBBox(p, Main.pref.getInteger("mappaint.segment.snap-distance", 10)))) {
891 if (!predicate.evaluate(w)) {
892 continue;
893 }
894 Node lastN = null;
895 int i = -2;
896 for (Node n : w.getNodes()) {
897 i++;
898 if (n.isDeleted() || n.isIncomplete()) { //FIXME: This shouldn't happen, raise exception?
899 continue;
900 }
901 if (lastN == null) {
902 lastN = n;
903 continue;
904 }
905
906 Point2D A = getPoint2D(lastN);
907 Point2D B = getPoint2D(n);
908 double c = A.distanceSq(B);
909 double a = p.distanceSq(B);
910 double b = p.distanceSq(A);
911
912 /* perpendicular distance squared
913 * loose some precision to account for possible deviations in the calculation above
914 * e.g. if identical (A and B) come about reversed in another way, values may differ
915 * -- zero out least significant 32 dual digits of mantissa..
916 */
917 double perDistSq = Double.longBitsToDouble(
918 Double.doubleToLongBits(a - (a - b + c) * (a - b + c) / 4 / c)
919 >> 32 << 32); // resolution in numbers with large exponent not needed here..
920
921 if (perDistSq < snapDistanceSq && a < c + snapDistanceSq && b < c + snapDistanceSq) {
922 List<WaySegment> wslist;
923 if (nearestMap.containsKey(perDistSq)) {
924 wslist = nearestMap.get(perDistSq);
925 } else {
926 wslist = new LinkedList<>();
927 nearestMap.put(perDistSq, wslist);
928 }
929 wslist.add(new WaySegment(w, i));
930 }
931
932 lastN = n;
933 }
934 }
935 }
936
937 return nearestMap;
938 }
939
940 /**
941 * The result *order* depends on the current map selection state.
942 * Segments within 10px of p are searched and sorted by their distance to @param p,
943 * then, within groups of equally distant segments, prefer those that are selected.
944 *
945 * @param p the point for which to search the nearest segments.
946 * @param ignore a collection of segments which are not to be returned.
947 * @param predicate the returned objects have to fulfill certain properties.
948 *
949 * @return all segments within 10px of p that are not in ignore,
950 * sorted by their perpendicular distance.
951 */
952 public final List<WaySegment> getNearestWaySegments(Point p,
953 Collection<WaySegment> ignore, Predicate<OsmPrimitive> predicate) {
954 List<WaySegment> nearestList = new ArrayList<>();
955 List<WaySegment> unselected = new LinkedList<>();
956
957 for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
958 // put selected waysegs within each distance group first
959 // makes the order of nearestList dependent on current selection state
960 for (WaySegment ws : wss) {
961 (ws.way.isSelected() ? nearestList : unselected).add(ws);
962 }
963 nearestList.addAll(unselected);
964 unselected.clear();
965 }
966 if (ignore != null) {
967 nearestList.removeAll(ignore);
968 }
969
970 return nearestList;
971 }
972
973 /**
974 * The result *order* depends on the current map selection state.
975 *
976 * @param p the point for which to search the nearest segments.
977 * @param predicate the returned objects have to fulfill certain properties.
978 *
979 * @return all segments within 10px of p, sorted by their perpendicular distance.
980 * @see #getNearestWaySegments(Point, Collection, Predicate)
981 */
982 public final List<WaySegment> getNearestWaySegments(Point p, Predicate<OsmPrimitive> predicate) {
983 return getNearestWaySegments(p, null, predicate);
984 }
985
986 /**
987 * The *result* depends on the current map selection state IF use_selected is true.
988 *
989 * @param p the point for which to search the nearest segment.
990 * @param predicate the returned object has to fulfill certain properties.
991 * @param useSelected whether selected way segments should be preferred.
992 *
993 * @return The nearest way segment to point p,
994 * and, depending on use_selected, prefers a selected way segment, if found.
995 * @see #getNearestWaySegments(Point, Collection, Predicate)
996 */
997 public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate, boolean useSelected) {
998 WaySegment wayseg = null, ntsel = null;
999
1000 for (List<WaySegment> wslist : getNearestWaySegmentsImpl(p, predicate).values()) {
1001 if (wayseg != null && ntsel != null) {
1002 break;
1003 }
1004 for (WaySegment ws : wslist) {
1005 if (wayseg == null) {
1006 wayseg = ws;
1007 }
1008 if (ntsel == null && ws.way.isSelected()) {
1009 ntsel = ws;
1010 }
1011 }
1012 }
1013
1014 return (ntsel != null && useSelected) ? ntsel : wayseg;
1015 }
1016
1017 /**
1018 * The *result* depends on the current map selection state IF use_selected is true.
1019 *
1020 * @param p the point for which to search the nearest segment.
1021 * @param predicate the returned object has to fulfill certain properties.
1022 * @param use_selected whether selected way segments should be preferred.
1023 * @param preferredRefs - prefer segments related to these primitives, may be null
1024 *
1025 * @return The nearest way segment to point p,
1026 * and, depending on use_selected, prefers a selected way segment, if found.
1027 * Also prefers segments of ways that are related to one of preferredRefs primitives
1028 *
1029 * @see #getNearestWaySegments(Point, Collection, Predicate)
1030 * @since 6065
1031 */
1032 public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate,
1033 boolean use_selected, Collection<OsmPrimitive> preferredRefs) {
1034 WaySegment wayseg = null, ntsel = null, ntref = null;
1035 if (preferredRefs != null && preferredRefs.isEmpty()) preferredRefs = null;
1036
1037 searchLoop: for (List<WaySegment> wslist : getNearestWaySegmentsImpl(p, predicate).values()) {
1038 for (WaySegment ws : wslist) {
1039 if (wayseg == null) {
1040 wayseg = ws;
1041 }
1042 if (ntsel == null && ws.way.isSelected()) {
1043 ntsel = ws;
1044 break searchLoop;
1045 }
1046 if (ntref == null && preferredRefs != null) {
1047 // prefer ways containing given nodes
1048 for (Node nd: ws.way.getNodes()) {
1049 if (preferredRefs.contains(nd)) {
1050 ntref = ws;
1051 break searchLoop;
1052 }
1053 }
1054 Collection<OsmPrimitive> wayRefs = ws.way.getReferrers();
1055 // prefer member of the given relations
1056 for (OsmPrimitive ref: preferredRefs) {
1057 if (ref instanceof Relation && wayRefs.contains(ref)) {
1058 ntref = ws;
1059 break searchLoop;
1060 }
1061 }
1062 }
1063 }
1064 }
1065 if (ntsel != null && use_selected)
1066 return ntsel;
1067 if (ntref != null)
1068 return ntref;
1069 return wayseg;
1070 }
1071
1072 /**
1073 * Convenience method to {@link #getNearestWaySegment(Point, Predicate, boolean)}.
1074 * @param p the point for which to search the nearest segment.
1075 * @param predicate the returned object has to fulfill certain properties.
1076 *
1077 * @return The nearest way segment to point p.
1078 */
1079 public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate) {
1080 return getNearestWaySegment(p, predicate, true);
1081 }
1082
1083 /**
1084 * The *result* does not depend on the current map selection state,
1085 * neither does the result *order*.
1086 * It solely depends on the perpendicular distance to point p.
1087 *
1088 * @param p the point for which to search the nearest ways.
1089 * @param ignore a collection of ways which are not to be returned.
1090 * @param predicate the returned object has to fulfill certain properties.
1091 *
1092 * @return all nearest ways to the screen point given that are not in ignore.
1093 * @see #getNearestWaySegments(Point, Collection, Predicate)
1094 */
1095 public final List<Way> getNearestWays(Point p,
1096 Collection<Way> ignore, Predicate<OsmPrimitive> predicate) {
1097 List<Way> nearestList = new ArrayList<>();
1098 Set<Way> wset = new HashSet<>();
1099
1100 for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
1101 for (WaySegment ws : wss) {
1102 if (wset.add(ws.way)) {
1103 nearestList.add(ws.way);
1104 }
1105 }
1106 }
1107 if (ignore != null) {
1108 nearestList.removeAll(ignore);
1109 }
1110
1111 return nearestList;
1112 }
1113
1114 /**
1115 * The *result* does not depend on the current map selection state,
1116 * neither does the result *order*.
1117 * It solely depends on the perpendicular distance to point p.
1118 *
1119 * @param p the point for which to search the nearest ways.
1120 * @param predicate the returned object has to fulfill certain properties.
1121 *
1122 * @return all nearest ways to the screen point given.
1123 * @see #getNearestWays(Point, Collection, Predicate)
1124 */
1125 public final List<Way> getNearestWays(Point p, Predicate<OsmPrimitive> predicate) {
1126 return getNearestWays(p, null, predicate);
1127 }
1128
1129 /**
1130 * The *result* depends on the current map selection state.
1131 *
1132 * @param p the point for which to search the nearest segment.
1133 * @param predicate the returned object has to fulfill certain properties.
1134 *
1135 * @return The nearest way to point p, prefer a selected way if there are multiple nearest.
1136 * @see #getNearestWaySegment(Point, Predicate)
1137 */
1138 public final Way getNearestWay(Point p, Predicate<OsmPrimitive> predicate) {
1139 WaySegment nearestWaySeg = getNearestWaySegment(p, predicate);
1140 return (nearestWaySeg == null) ? null : nearestWaySeg.way;
1141 }
1142
1143 /**
1144 * The *result* does not depend on the current map selection state,
1145 * neither does the result *order*.
1146 * It solely depends on the distance to point p.
1147 *
1148 * First, nodes will be searched. If there are nodes within BBox found,
1149 * return a collection of those nodes only.
1150 *
1151 * If no nodes are found, search for nearest ways. If there are ways
1152 * within BBox found, return a collection of those ways only.
1153 *
1154 * If nothing is found, return an empty collection.
1155 *
1156 * @param p The point on screen.
1157 * @param ignore a collection of ways which are not to be returned.
1158 * @param predicate the returned object has to fulfill certain properties.
1159 *
1160 * @return Primitives nearest to the given screen point that are not in ignore.
1161 * @see #getNearestNodes(Point, Collection, Predicate)
1162 * @see #getNearestWays(Point, Collection, Predicate)
1163 */
1164 public final List<OsmPrimitive> getNearestNodesOrWays(Point p,
1165 Collection<OsmPrimitive> ignore, Predicate<OsmPrimitive> predicate) {
1166 List<OsmPrimitive> nearestList = Collections.emptyList();
1167 OsmPrimitive osm = getNearestNodeOrWay(p, predicate, false);
1168
1169 if (osm != null) {
1170 if (osm instanceof Node) {
1171 nearestList = new ArrayList<OsmPrimitive>(getNearestNodes(p, predicate));
1172 } else if (osm instanceof Way) {
1173 nearestList = new ArrayList<OsmPrimitive>(getNearestWays(p, predicate));
1174 }
1175 if (ignore != null) {
1176 nearestList.removeAll(ignore);
1177 }
1178 }
1179
1180 return nearestList;
1181 }
1182
1183 /**
1184 * The *result* does not depend on the current map selection state,
1185 * neither does the result *order*.
1186 * It solely depends on the distance to point p.
1187 *
1188 * @param p The point on screen.
1189 * @param predicate the returned object has to fulfill certain properties.
1190 * @return Primitives nearest to the given screen point.
1191 * @see #getNearestNodesOrWays(Point, Collection, Predicate)
1192 */
1193 public final List<OsmPrimitive> getNearestNodesOrWays(Point p, Predicate<OsmPrimitive> predicate) {
1194 return getNearestNodesOrWays(p, null, predicate);
1195 }
1196
1197 /**
1198 * This is used as a helper routine to {@link #getNearestNodeOrWay(Point, Predicate, boolean)}
1199 * It decides, whether to yield the node to be tested or look for further (way) candidates.
1200 *
1201 * @param osm node to check
1202 * @param p point clicked
1203 * @param use_selected whether to prefer selected nodes
1204 * @return true, if the node fulfills the properties of the function body
1205 */
1206 private boolean isPrecedenceNode(Node osm, Point p, boolean use_selected) {
1207 if (osm != null) {
1208 if (!(p.distanceSq(getPoint2D(osm)) > (4)*(4))) return true;
1209 if (osm.isTagged()) return true;
1210 if (use_selected && osm.isSelected()) return true;
1211 }
1212 return false;
1213 }
1214
1215 /**
1216 * The *result* depends on the current map selection state IF use_selected is true.
1217 *
1218 * IF use_selected is true, use {@link #getNearestNode(Point, Predicate)} to find
1219 * the nearest, selected node. If not found, try {@link #getNearestWaySegment(Point, Predicate)}
1220 * to find the nearest selected way.
1221 *
1222 * IF use_selected is false, or if no selected primitive was found, do the following.
1223 *
1224 * If the nearest node found is within 4px of p, simply take it.
1225 * Else, find the nearest way segment. Then, if p is closer to its
1226 * middle than to the node, take the way segment, else take the node.
1227 *
1228 * Finally, if no nearest primitive is found at all, return null.
1229 *
1230 * @param p The point on screen.
1231 * @param predicate the returned object has to fulfill certain properties.
1232 * @param use_selected whether to prefer primitives that are currently selected or referred by selected primitives
1233 *
1234 * @return A primitive within snap-distance to point p,
1235 * that is chosen by the algorithm described.
1236 * @see #getNearestNode(Point, Predicate)
1237 * @see #getNearestWay(Point, Predicate)
1238 */
1239 public final OsmPrimitive getNearestNodeOrWay(Point p, Predicate<OsmPrimitive> predicate, boolean use_selected) {
1240 Collection<OsmPrimitive> sel;
1241 DataSet ds = getCurrentDataSet();
1242 if (use_selected && ds != null) {
1243 sel = ds.getSelected();
1244 } else {
1245 sel = null;
1246 }
1247 OsmPrimitive osm = getNearestNode(p, predicate, use_selected, sel);
1248
1249 if (isPrecedenceNode((Node) osm, p, use_selected)) return osm;
1250 WaySegment ws;
1251 if (use_selected) {
1252 ws = getNearestWaySegment(p, predicate, use_selected, sel);
1253 } else {
1254 ws = getNearestWaySegment(p, predicate, use_selected);
1255 }
1256 if (ws == null) return osm;
1257
1258 if ((ws.way.isSelected() && use_selected) || osm == null) {
1259 // either (no _selected_ nearest node found, if desired) or no nearest node was found
1260 osm = ws.way;
1261 } else {
1262 int maxWaySegLenSq = 3*PROP_SNAP_DISTANCE.get();
1263 maxWaySegLenSq *= maxWaySegLenSq;
1264
1265 Point2D wp1 = getPoint2D(ws.way.getNode(ws.lowerIndex));
1266 Point2D wp2 = getPoint2D(ws.way.getNode(ws.lowerIndex+1));
1267
1268 // is wayseg shorter than maxWaySegLenSq and
1269 // is p closer to the middle of wayseg than to the nearest node?
1270 if (wp1.distanceSq(wp2) < maxWaySegLenSq &&
1271 p.distanceSq(project(0.5, wp1, wp2)) < p.distanceSq(getPoint2D((Node) osm))) {
1272 osm = ws.way;
1273 }
1274 }
1275 return osm;
1276 }
1277
1278 public static double perDist(Point2D pt, Point2D a, Point2D b) {
1279 if (pt != null && a != null && b != null) {
1280 double pd =
1281 (a.getX()-pt.getX())*(b.getX()-a.getX()) -
1282 (a.getY()-pt.getY())*(b.getY()-a.getY());
1283 return Math.abs(pd) / a.distance(b);
1284 }
1285 return 0d;
1286 }
1287
1288 /**
1289 *
1290 * @param pt point to project onto (ab)
1291 * @param a root of vector
1292 * @param b vector
1293 * @return point of intersection of line given by (ab)
1294 * with its orthogonal line running through pt
1295 */
1296 public static Point2D project(Point2D pt, Point2D a, Point2D b) {
1297 if (pt != null && a != null && b != null) {
1298 double r = (
1299 (pt.getX()-a.getX())*(b.getX()-a.getX()) +
1300 (pt.getY()-a.getY())*(b.getY()-a.getY()))
1301 / a.distanceSq(b);
1302 return project(r, a, b);
1303 }
1304 return null;
1305 }
1306
1307 /**
1308 * if r = 0 returns a, if r=1 returns b,
1309 * if r = 0.5 returns center between a and b, etc..
1310 *
1311 * @param r scale value
1312 * @param a root of vector
1313 * @param b vector
1314 * @return new point at a + r*(ab)
1315 */
1316 public static Point2D project(double r, Point2D a, Point2D b) {
1317 Point2D ret = null;
1318
1319 if (a != null && b != null) {
1320 ret = new Point2D.Double(a.getX() + r*(b.getX()-a.getX()),
1321 a.getY() + r*(b.getY()-a.getY()));
1322 }
1323 return ret;
1324 }
1325
1326 /**
1327 * The *result* does not depend on the current map selection state, neither does the result *order*.
1328 * It solely depends on the distance to point p.
1329 *
1330 * @param p The point on screen.
1331 * @param ignore a collection of ways which are not to be returned.
1332 * @param predicate the returned object has to fulfill certain properties.
1333 *
1334 * @return a list of all objects that are nearest to point p and
1335 * not in ignore or an empty list if nothing was found.
1336 */
1337 public final List<OsmPrimitive> getAllNearest(Point p,
1338 Collection<OsmPrimitive> ignore, Predicate<OsmPrimitive> predicate) {
1339 List<OsmPrimitive> nearestList = new ArrayList<>();
1340 Set<Way> wset = new HashSet<>();
1341
1342 // add nearby ways
1343 for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
1344 for (WaySegment ws : wss) {
1345 if (wset.add(ws.way)) {
1346 nearestList.add(ws.way);
1347 }
1348 }
1349 }
1350
1351 // add nearby nodes
1352 for (List<Node> nlist : getNearestNodesImpl(p, predicate).values()) {
1353 nearestList.addAll(nlist);
1354 }
1355
1356 // add parent relations of nearby nodes and ways
1357 Set<OsmPrimitive> parentRelations = new HashSet<>();
1358 for (OsmPrimitive o : nearestList) {
1359 for (OsmPrimitive r : o.getReferrers()) {
1360 if (r instanceof Relation && predicate.evaluate(r)) {
1361 parentRelations.add(r);
1362 }
1363 }
1364 }
1365 nearestList.addAll(parentRelations);
1366
1367 if (ignore != null) {
1368 nearestList.removeAll(ignore);
1369 }
1370
1371 return nearestList;
1372 }
1373
1374 /**
1375 * The *result* does not depend on the current map selection state, neither does the result *order*.
1376 * It solely depends on the distance to point p.
1377 *
1378 * @param p The point on screen.
1379 * @param predicate the returned object has to fulfill certain properties.
1380 *
1381 * @return a list of all objects that are nearest to point p
1382 * or an empty list if nothing was found.
1383 * @see #getAllNearest(Point, Collection, Predicate)
1384 */
1385 public final List<OsmPrimitive> getAllNearest(Point p, Predicate<OsmPrimitive> predicate) {
1386 return getAllNearest(p, null, predicate);
1387 }
1388
1389 /**
1390 * @return The projection to be used in calculating stuff.
1391 */
1392 public Projection getProjection() {
1393 return Main.getProjection();
1394 }
1395
1396 @Override
1397 public String helpTopic() {
1398 String n = getClass().getName();
1399 return n.substring(n.lastIndexOf('.')+1);
1400 }
1401
1402 /**
1403 * Return a ID which is unique as long as viewport dimensions are the same
1404 * @return A unique ID, as long as viewport dimensions are the same
1405 */
1406 public int getViewID() {
1407 String x = center.east() + '_' + center.north() + '_' + scale + '_' +
1408 getWidth() + '_' + getHeight() + '_' + getProjection().toString();
1409 CRC32 id = new CRC32();
1410 id.update(x.getBytes(StandardCharsets.UTF_8));
1411 return (int) id.getValue();
1412 }
1413
1414 /**
1415 * Set new cursor.
1416 */
1417 public void setNewCursor(Cursor cursor, Object reference) {
1418 cursorManager.setNewCursor(cursor, reference);
1419 }
1420
1421 public void setNewCursor(int cursor, Object reference) {
1422 setNewCursor(Cursor.getPredefinedCursor(cursor), reference);
1423 }
1424
1425 /**
1426 * Remove the new cursor and reset to previous
1427 */
1428 public void resetCursor(Object reference) {
1429 cursorManager.resetCursor(reference);
1430 }
1431
1432 /**
1433 * Gets the cursor manager that is used for this NavigatableComponent.
1434 * @return The cursor manager.
1435 */
1436 public CursorManager getCursorManager() {
1437 return cursorManager;
1438 }
1439
1440 @Override
1441 public void paint(Graphics g) {
1442 synchronized (paintRequestLock) {
1443 if (paintRect != null) {
1444 Graphics g2 = g.create();
1445 g2.setColor(Utils.complement(PaintColors.getBackgroundColor()));
1446 g2.drawRect(paintRect.x, paintRect.y, paintRect.width, paintRect.height);
1447 g2.dispose();
1448 }
1449 if (paintPoly != null) {
1450 Graphics g2 = g.create();
1451 g2.setColor(Utils.complement(PaintColors.getBackgroundColor()));
1452 g2.drawPolyline(paintPoly.xpoints, paintPoly.ypoints, paintPoly.npoints);
1453 g2.dispose();
1454 }
1455 }
1456 super.paint(g);
1457 }
1458
1459 /**
1460 * Requests to paint the given {@code Rectangle}.
1461 * @param r The Rectangle to draw
1462 * @see #requestClearRect
1463 * @since 5500
1464 */
1465 public void requestPaintRect(Rectangle r) {
1466 if (r != null) {
1467 synchronized (paintRequestLock) {
1468 paintRect = r;
1469 }
1470 repaint();
1471 }
1472 }
1473
1474 /**
1475 * Requests to paint the given {@code Polygon} as a polyline (unclosed polygon).
1476 * @param p The Polygon to draw
1477 * @see #requestClearPoly
1478 * @since 5500
1479 */
1480 public void requestPaintPoly(Polygon p) {
1481 if (p != null) {
1482 synchronized (paintRequestLock) {
1483 paintPoly = p;
1484 }
1485 repaint();
1486 }
1487 }
1488
1489 /**
1490 * Requests to clear the rectangled previously drawn.
1491 * @see #requestPaintRect
1492 * @since 5500
1493 */
1494 public void requestClearRect() {
1495 synchronized (paintRequestLock) {
1496 paintRect = null;
1497 }
1498 repaint();
1499 }
1500
1501 /**
1502 * Requests to clear the polyline previously drawn.
1503 * @see #requestPaintPoly
1504 * @since 5500
1505 */
1506 public void requestClearPoly() {
1507 synchronized (paintRequestLock) {
1508 paintPoly = null;
1509 }
1510 repaint();
1511 }
1512}
Note: See TracBrowser for help on using the repository browser.