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

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

fix #11957 - partial revert of r8851 - do not replace Stack by ArrayDeque because of different iteration behaviour + add unit test

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