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

Last change on this file since 7505 was 7448, checked in by bastiK, 10 years ago

see #10425 - fix [7447]: lock prevented mappaint code in StyledMapRenderer from running in parallel

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