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

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

Sonar/FindBugs - Loose coupling

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