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

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

Rework console output:

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