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

Last change on this file since 7392 was 7389, checked in by bastiK, 10 years ago

see #10363 - you cannot click on object that is not visible on the map

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