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

Last change on this file since 7206 was 7135, checked in by simon04, 10 years ago

Display numbers in map status line according to locale/language

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