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

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

Sonar/Findbugs - Performance - Inefficient use of keySet iterator instead of entrySet iterator

  • Property svn:eol-style set to native
File size: 61.0 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 {
495 // fixme - not use zoom history here
496 zoomTo(oldCenter.interpolate(finalNewCenter, (i+1) / frames));
497 try { Thread.sleep(1000 / fps); } catch (InterruptedException ex) { }
498 }
499 }
500 }.start();
501 }
502 }
503
504 public void zoomToFactor(double x, double y, double factor) {
505 double newScale = scale*factor;
506 // New center position so that point under the mouse pointer stays the same place as it was before zooming
507 // You will get the formula by simplifying this expression: newCenter = oldCenter + mouseCoordinatesInNewZoom - mouseCoordinatesInOldZoom
508 zoomTo(new EastNorth(
509 center.east() - (x - getWidth()/2.0) * (newScale - scale),
510 center.north() + (y - getHeight()/2.0) * (newScale - scale)),
511 newScale);
512 }
513
514 public void zoomToFactor(EastNorth newCenter, double factor) {
515 zoomTo(newCenter, scale*factor);
516 }
517
518 public void zoomToFactor(double factor) {
519 zoomTo(center, scale*factor);
520 }
521
522 public void zoomTo(ProjectionBounds box) {
523 // -20 to leave some border
524 int w = getWidth()-20;
525 if (w < 20) {
526 w = 20;
527 }
528 int h = getHeight()-20;
529 if (h < 20) {
530 h = 20;
531 }
532
533 double scaleX = (box.maxEast-box.minEast)/w;
534 double scaleY = (box.maxNorth-box.minNorth)/h;
535 double newScale = Math.max(scaleX, scaleY);
536
537 zoomTo(box.getCenter(), newScale);
538 }
539
540 public void zoomTo(Bounds box) {
541 zoomTo(new ProjectionBounds(getProjection().latlon2eastNorth(box.getMin()),
542 getProjection().latlon2eastNorth(box.getMax())));
543 }
544
545 private class ZoomData {
546 LatLon center;
547 double scale;
548
549 public ZoomData(EastNorth center, double scale) {
550 this.center = Projections.inverseProject(center);
551 this.scale = scale;
552 }
553
554 public EastNorth getCenterEastNorth() {
555 return getProjection().latlon2eastNorth(center);
556 }
557
558 public double getScale() {
559 return scale;
560 }
561 }
562
563 private Stack<ZoomData> zoomUndoBuffer = new Stack<ZoomData>();
564 private Stack<ZoomData> zoomRedoBuffer = new Stack<ZoomData>();
565 private Date zoomTimestamp = new Date();
566
567 private void pushZoomUndo(EastNorth center, double scale) {
568 Date now = new Date();
569 if ((now.getTime() - zoomTimestamp.getTime()) > (Main.pref.getDouble("zoom.undo.delay", 1.0) * 1000)) {
570 zoomUndoBuffer.push(new ZoomData(center, scale));
571 if (zoomUndoBuffer.size() > Main.pref.getInteger("zoom.undo.max", 50)) {
572 zoomUndoBuffer.remove(0);
573 }
574 zoomRedoBuffer.clear();
575 }
576 zoomTimestamp = now;
577 }
578
579 public void zoomPrevious() {
580 if (!zoomUndoBuffer.isEmpty()) {
581 ZoomData zoom = zoomUndoBuffer.pop();
582 zoomRedoBuffer.push(new ZoomData(center, scale));
583 zoomNoUndoTo(zoom.getCenterEastNorth(), zoom.getScale());
584 }
585 }
586
587 public void zoomNext() {
588 if (!zoomRedoBuffer.isEmpty()) {
589 ZoomData zoom = zoomRedoBuffer.pop();
590 zoomUndoBuffer.push(new ZoomData(center, scale));
591 zoomNoUndoTo(zoom.getCenterEastNorth(), zoom.getScale());
592 }
593 }
594
595 public boolean hasZoomUndoEntries() {
596 return !zoomUndoBuffer.isEmpty();
597 }
598
599 public boolean hasZoomRedoEntries() {
600 return !zoomRedoBuffer.isEmpty();
601 }
602
603 private BBox getBBox(Point p, int snapDistance) {
604 return new BBox(getLatLon(p.x - snapDistance, p.y - snapDistance),
605 getLatLon(p.x + snapDistance, p.y + snapDistance));
606 }
607
608 /**
609 * The *result* does not depend on the current map selection state,
610 * neither does the result *order*.
611 * It solely depends on the distance to point p.
612 *
613 * @return a sorted map with the keys representing the distance of
614 * their associated nodes to point p.
615 */
616 private Map<Double, List<Node>> getNearestNodesImpl(Point p,
617 Predicate<OsmPrimitive> predicate) {
618 TreeMap<Double, List<Node>> nearestMap = new TreeMap<Double, List<Node>>();
619 DataSet ds = getCurrentDataSet();
620
621 if (ds != null) {
622 double dist, snapDistanceSq = PROP_SNAP_DISTANCE.get();
623 snapDistanceSq *= snapDistanceSq;
624
625 for (Node n : ds.searchNodes(getBBox(p, PROP_SNAP_DISTANCE.get()))) {
626 if (predicate.evaluate(n)
627 && (dist = getPoint2D(n).distanceSq(p)) < snapDistanceSq)
628 {
629 List<Node> nlist;
630 if (nearestMap.containsKey(dist)) {
631 nlist = nearestMap.get(dist);
632 } else {
633 nlist = new LinkedList<Node>();
634 nearestMap.put(dist, nlist);
635 }
636 nlist.add(n);
637 }
638 }
639 }
640
641 return nearestMap;
642 }
643
644 /**
645 * The *result* does not depend on the current map selection state,
646 * neither does the result *order*.
647 * It solely depends on the distance to point p.
648 *
649 * @return All nodes nearest to point p that are in a belt from
650 * dist(nearest) to dist(nearest)+4px around p and
651 * that are not in ignore.
652 *
653 * @param p the point for which to search the nearest segment.
654 * @param ignore a collection of nodes which are not to be returned.
655 * @param predicate the returned objects have to fulfill certain properties.
656 */
657 public final List<Node> getNearestNodes(Point p,
658 Collection<Node> ignore, Predicate<OsmPrimitive> predicate) {
659 List<Node> nearestList = Collections.emptyList();
660
661 if (ignore == null) {
662 ignore = Collections.emptySet();
663 }
664
665 Map<Double, List<Node>> nlists = getNearestNodesImpl(p, predicate);
666 if (!nlists.isEmpty()) {
667 Double minDistSq = null;
668 for (Entry<Double, List<Node>> entry : nlists.entrySet()) {
669 Double distSq = entry.getKey();
670 List<Node> nlist = entry.getValue();
671
672 // filter nodes to be ignored before determining minDistSq..
673 nlist.removeAll(ignore);
674 if (minDistSq == null) {
675 if (!nlist.isEmpty()) {
676 minDistSq = distSq;
677 nearestList = new ArrayList<Node>();
678 nearestList.addAll(nlist);
679 }
680 } else {
681 if (distSq-minDistSq < (4)*(4)) {
682 nearestList.addAll(nlist);
683 }
684 }
685 }
686 }
687
688 return nearestList;
689 }
690
691 /**
692 * The *result* does not depend on the current map selection state,
693 * neither does the result *order*.
694 * It solely depends on the distance to point p.
695 *
696 * @return All nodes nearest to point p that are in a belt from
697 * dist(nearest) to dist(nearest)+4px around p.
698 * @see #getNearestNodes(Point, Collection, Predicate)
699 *
700 * @param p the point for which to search the nearest segment.
701 * @param predicate the returned objects have to fulfill certain properties.
702 */
703 public final List<Node> getNearestNodes(Point p, Predicate<OsmPrimitive> predicate) {
704 return getNearestNodes(p, null, predicate);
705 }
706
707 /**
708 * The *result* depends on the current map selection state IF use_selected is true.
709 *
710 * If more than one node within node.snap-distance pixels is found,
711 * the nearest node selected is returned IF use_selected is true.
712 *
713 * Else the nearest new/id=0 node within about the same distance
714 * as the true nearest node is returned.
715 *
716 * If no such node is found either, the true nearest
717 * node to p is returned.
718 *
719 * Finally, if a node is not found at all, null is returned.
720 *
721 * @return A node within snap-distance to point p,
722 * that is chosen by the algorithm described.
723 *
724 * @param p the screen point
725 * @param predicate this parameter imposes a condition on the returned object, e.g.
726 * give the nearest node that is tagged.
727 */
728 public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate, boolean use_selected) {
729 return getNearestNode(p, predicate, use_selected, null);
730 }
731
732 /**
733 * The *result* depends on the current map selection state IF use_selected is true
734 *
735 * If more than one node within node.snap-distance pixels is found,
736 * the nearest node selected is returned IF use_selected is true.
737 *
738 * If there are no selected nodes near that point, the node that is related to some of the preferredRefs
739 *
740 * Else the nearest new/id=0 node within about the same distance
741 * as the true nearest node is returned.
742 *
743 * If no such node is found either, the true nearest
744 * node to p is returned.
745 *
746 * Finally, if a node is not found at all, null is returned.
747 * @since 6065
748 * @return A node within snap-distance to point p,
749 * that is chosen by the algorithm described.
750 *
751 * @param p the screen point
752 * @param predicate this parameter imposes a condition on the returned object, e.g.
753 * give the nearest node that is tagged.
754 * @param preferredRefs primitives, whose nodes we prefer
755 */
756 public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate,
757 boolean useSelected, Collection<OsmPrimitive> preferredRefs) {
758
759 Map<Double, List<Node>> nlists = getNearestNodesImpl(p, predicate);
760 if (nlists.isEmpty()) return null;
761
762 if (preferredRefs != null && preferredRefs.isEmpty()) preferredRefs = null;
763 Node ntsel = null, ntnew = null, ntref = null;
764 boolean useNtsel = useSelected;
765 double minDistSq = nlists.keySet().iterator().next();
766
767 for (Entry<Double, List<Node>> entry : nlists.entrySet()) {
768 Double distSq = entry.getKey();
769 for (Node nd : entry.getValue()) {
770 // find the nearest selected node
771 if (ntsel == null && nd.isSelected()) {
772 ntsel = nd;
773 // if there are multiple nearest nodes, prefer the one
774 // that is selected. This is required in order to drag
775 // the selected node if multiple nodes have the same
776 // coordinates (e.g. after unglue)
777 useNtsel |= (distSq == minDistSq);
778 }
779 if (ntref == null && preferredRefs != null && distSq == minDistSq) {
780 List<OsmPrimitive> ndRefs = nd.getReferrers();
781 for (OsmPrimitive ref: preferredRefs) {
782 if (ndRefs.contains(ref)) {
783 ntref = nd;
784 break;
785 }
786 }
787 }
788 // find the nearest newest node that is within about the same
789 // distance as the true nearest node
790 if (ntnew == null && nd.isNew() && (distSq-minDistSq < 1)) {
791 ntnew = nd;
792 }
793 }
794 }
795
796 // take nearest selected, nearest new or true nearest node to p, in that order
797 if (ntsel != null && useNtsel)
798 return ntsel;
799 if (ntref != null)
800 return ntref;
801 if (ntnew != null)
802 return ntnew;
803 return nlists.values().iterator().next().get(0);
804 }
805
806 /**
807 * Convenience method to {@link #getNearestNode(Point, Predicate, boolean)}.
808 *
809 * @return The nearest node to point p.
810 */
811 public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate) {
812 return getNearestNode(p, predicate, true);
813 }
814
815 /**
816 * The *result* does not depend on the current map selection state,
817 * neither does the result *order*.
818 * It solely depends on the distance to point p.
819 *
820 * @return a sorted map with the keys representing the perpendicular
821 * distance of their associated way segments to point p.
822 */
823 private Map<Double, List<WaySegment>> getNearestWaySegmentsImpl(Point p,
824 Predicate<OsmPrimitive> predicate) {
825 Map<Double, List<WaySegment>> nearestMap = new TreeMap<Double, List<WaySegment>>();
826 DataSet ds = getCurrentDataSet();
827
828 if (ds != null) {
829 double snapDistanceSq = Main.pref.getInteger("mappaint.segment.snap-distance", 10);
830 snapDistanceSq *= snapDistanceSq;
831
832 for (Way w : ds.searchWays(getBBox(p, Main.pref.getInteger("mappaint.segment.snap-distance", 10)))) {
833 if (!predicate.evaluate(w)) {
834 continue;
835 }
836 Node lastN = null;
837 int i = -2;
838 for (Node n : w.getNodes()) {
839 i++;
840 if (n.isDeleted() || n.isIncomplete()) { //FIXME: This shouldn't happen, raise exception?
841 continue;
842 }
843 if (lastN == null) {
844 lastN = n;
845 continue;
846 }
847
848 Point2D A = getPoint2D(lastN);
849 Point2D B = getPoint2D(n);
850 double c = A.distanceSq(B);
851 double a = p.distanceSq(B);
852 double b = p.distanceSq(A);
853
854 /* perpendicular distance squared
855 * loose some precision to account for possible deviations in the calculation above
856 * e.g. if identical (A and B) come about reversed in another way, values may differ
857 * -- zero out least significant 32 dual digits of mantissa..
858 */
859 double perDistSq = Double.longBitsToDouble(
860 Double.doubleToLongBits( a - (a - b + c) * (a - b + c) / 4 / c )
861 >> 32 << 32); // resolution in numbers with large exponent not needed here..
862
863 if (perDistSq < snapDistanceSq && a < c + snapDistanceSq && b < c + snapDistanceSq) {
864 List<WaySegment> wslist;
865 if (nearestMap.containsKey(perDistSq)) {
866 wslist = nearestMap.get(perDistSq);
867 } else {
868 wslist = new LinkedList<WaySegment>();
869 nearestMap.put(perDistSq, wslist);
870 }
871 wslist.add(new WaySegment(w, i));
872 }
873
874 lastN = n;
875 }
876 }
877 }
878
879 return nearestMap;
880 }
881
882 /**
883 * The result *order* depends on the current map selection state.
884 * Segments within 10px of p are searched and sorted by their distance to @param p,
885 * then, within groups of equally distant segments, prefer those that are selected.
886 *
887 * @return all segments within 10px of p that are not in ignore,
888 * sorted by their perpendicular distance.
889 *
890 * @param p the point for which to search the nearest segments.
891 * @param ignore a collection of segments which are not to be returned.
892 * @param predicate the returned objects have to fulfill certain properties.
893 */
894 public final List<WaySegment> getNearestWaySegments(Point p,
895 Collection<WaySegment> ignore, Predicate<OsmPrimitive> predicate) {
896 List<WaySegment> nearestList = new ArrayList<WaySegment>();
897 List<WaySegment> unselected = new LinkedList<WaySegment>();
898
899 for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
900 // put selected waysegs within each distance group first
901 // makes the order of nearestList dependent on current selection state
902 for (WaySegment ws : wss) {
903 (ws.way.isSelected() ? nearestList : unselected).add(ws);
904 }
905 nearestList.addAll(unselected);
906 unselected.clear();
907 }
908 if (ignore != null) {
909 nearestList.removeAll(ignore);
910 }
911
912 return nearestList;
913 }
914
915 /**
916 * The result *order* depends on the current map selection state.
917 *
918 * @return all segments within 10px of p, sorted by their perpendicular distance.
919 * @see #getNearestWaySegments(Point, Collection, Predicate)
920 *
921 * @param p the point for which to search the nearest segments.
922 * @param predicate the returned objects have to fulfill certain properties.
923 */
924 public final List<WaySegment> getNearestWaySegments(Point p, Predicate<OsmPrimitive> predicate) {
925 return getNearestWaySegments(p, null, predicate);
926 }
927
928 /**
929 * The *result* depends on the current map selection state IF use_selected is true.
930 *
931 * @return The nearest way segment to point p,
932 * and, depending on use_selected, prefers a selected way segment, if found.
933 * @see #getNearestWaySegments(Point, Collection, Predicate)
934 *
935 * @param p the point for which to search the nearest segment.
936 * @param predicate the returned object has to fulfill certain properties.
937 * @param use_selected whether selected way segments should be preferred.
938 */
939 public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate, boolean use_selected) {
940 WaySegment wayseg = null, ntsel = null;
941
942 for (List<WaySegment> wslist : getNearestWaySegmentsImpl(p, predicate).values()) {
943 if (wayseg != null && ntsel != null) {
944 break;
945 }
946 for (WaySegment ws : wslist) {
947 if (wayseg == null) {
948 wayseg = ws;
949 }
950 if (ntsel == null && ws.way.isSelected()) {
951 ntsel = ws;
952 }
953 }
954 }
955
956 return (ntsel != null && use_selected) ? ntsel : wayseg;
957 }
958
959 /**
960 * The *result* depends on the current map selection state IF use_selected is true.
961 *
962 * @return The nearest way segment to point p,
963 * and, depending on use_selected, prefers a selected way segment, if found.
964 * Also prefers segments of ways that are related to one of preferredRefs primitives
965 * @see #getNearestWaySegments(Point, Collection, Predicate)
966 * @since 6065
967 * @param p the point for which to search the nearest segment.
968 * @param predicate the returned object has to fulfill certain properties.
969 * @param use_selected whether selected way segments should be preferred.
970 * @param preferredRefs - prefer segments related to these primitives, may be null
971 */
972 public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate,
973 boolean use_selected, Collection<OsmPrimitive> preferredRefs) {
974 WaySegment wayseg = null, ntsel = null, ntref = null;
975 if (preferredRefs != null && preferredRefs.isEmpty()) preferredRefs = null;
976
977 searchLoop: for (List<WaySegment> wslist : getNearestWaySegmentsImpl(p, predicate).values()) {
978 for (WaySegment ws : wslist) {
979 if (wayseg == null) {
980 wayseg = ws;
981 }
982 if (ntsel == null && ws.way.isSelected()) {
983 ntsel = ws;
984 break searchLoop;
985 }
986 if (ntref == null && preferredRefs != null) {
987 // prefer ways containing given nodes
988 for (Node nd: ws.way.getNodes()) {
989 if (preferredRefs.contains(nd)) {
990 ntref = ws;
991 break searchLoop;
992 }
993 }
994 Collection<OsmPrimitive> wayRefs = ws.way.getReferrers();
995 // prefer member of the given relations
996 for (OsmPrimitive ref: preferredRefs) {
997 if (ref instanceof Relation && wayRefs.contains(ref)) {
998 ntref = ws;
999 break searchLoop;
1000 }
1001 }
1002 }
1003 }
1004 }
1005 if (ntsel != null && use_selected)
1006 return ntsel;
1007 if (ntref != null)
1008 return ntref;
1009 return wayseg;
1010 }
1011
1012 /**
1013 * Convenience method to {@link #getNearestWaySegment(Point, Predicate, boolean)}.
1014 *
1015 * @return The nearest way segment to point p.
1016 */
1017 public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate) {
1018 return getNearestWaySegment(p, predicate, true);
1019 }
1020
1021 /**
1022 * The *result* does not depend on the current map selection state,
1023 * neither does the result *order*.
1024 * It solely depends on the perpendicular distance to point p.
1025 *
1026 * @return all nearest ways to the screen point given that are not in ignore.
1027 * @see #getNearestWaySegments(Point, Collection, Predicate)
1028 *
1029 * @param p the point for which to search the nearest ways.
1030 * @param ignore a collection of ways which are not to be returned.
1031 * @param predicate the returned object has to fulfill certain properties.
1032 */
1033 public final List<Way> getNearestWays(Point p,
1034 Collection<Way> ignore, Predicate<OsmPrimitive> predicate) {
1035 List<Way> nearestList = new ArrayList<Way>();
1036 Set<Way> wset = new HashSet<Way>();
1037
1038 for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
1039 for (WaySegment ws : wss) {
1040 if (wset.add(ws.way)) {
1041 nearestList.add(ws.way);
1042 }
1043 }
1044 }
1045 if (ignore != null) {
1046 nearestList.removeAll(ignore);
1047 }
1048
1049 return nearestList;
1050 }
1051
1052 /**
1053 * The *result* does not depend on the current map selection state,
1054 * neither does the result *order*.
1055 * It solely depends on the perpendicular distance to point p.
1056 *
1057 * @return all nearest ways to the screen point given.
1058 * @see #getNearestWays(Point, Collection, Predicate)
1059 *
1060 * @param p the point for which to search the nearest ways.
1061 * @param predicate the returned object has to fulfill certain properties.
1062 */
1063 public final List<Way> getNearestWays(Point p, Predicate<OsmPrimitive> predicate) {
1064 return getNearestWays(p, null, predicate);
1065 }
1066
1067 /**
1068 * The *result* depends on the current map selection state.
1069 *
1070 * @return The nearest way to point p,
1071 * prefer a selected way if there are multiple nearest.
1072 * @see #getNearestWaySegment(Point, Predicate)
1073 *
1074 * @param p the point for which to search the nearest segment.
1075 * @param predicate the returned object has to fulfill certain properties.
1076 */
1077 public final Way getNearestWay(Point p, Predicate<OsmPrimitive> predicate) {
1078 WaySegment nearestWaySeg = getNearestWaySegment(p, predicate);
1079 return (nearestWaySeg == null) ? null : nearestWaySeg.way;
1080 }
1081
1082 /**
1083 * The *result* does not depend on the current map selection state,
1084 * neither does the result *order*.
1085 * It solely depends on the distance to point p.
1086 *
1087 * First, nodes will be searched. If there are nodes within BBox found,
1088 * return a collection of those nodes only.
1089 *
1090 * If no nodes are found, search for nearest ways. If there are ways
1091 * within BBox found, return a collection of those ways only.
1092 *
1093 * If nothing is found, return an empty collection.
1094 *
1095 * @return Primitives nearest to the given screen point that are not in ignore.
1096 * @see #getNearestNodes(Point, Collection, Predicate)
1097 * @see #getNearestWays(Point, Collection, Predicate)
1098 *
1099 * @param p The point on screen.
1100 * @param ignore a collection of ways which are not to be returned.
1101 * @param predicate the returned object has to fulfill certain properties.
1102 */
1103 public final List<OsmPrimitive> getNearestNodesOrWays(Point p,
1104 Collection<OsmPrimitive> ignore, Predicate<OsmPrimitive> predicate) {
1105 List<OsmPrimitive> nearestList = Collections.emptyList();
1106 OsmPrimitive osm = getNearestNodeOrWay(p, predicate, false);
1107
1108 if (osm != null) {
1109 if (osm instanceof Node) {
1110 nearestList = new ArrayList<OsmPrimitive>(getNearestNodes(p, predicate));
1111 } else if (osm instanceof Way) {
1112 nearestList = new ArrayList<OsmPrimitive>(getNearestWays(p, predicate));
1113 }
1114 if (ignore != null) {
1115 nearestList.removeAll(ignore);
1116 }
1117 }
1118
1119 return nearestList;
1120 }
1121
1122 /**
1123 * The *result* does not depend on the current map selection state,
1124 * neither does the result *order*.
1125 * It solely depends on the distance to point p.
1126 *
1127 * @return Primitives nearest to the given screen point.
1128 * @see #getNearestNodesOrWays(Point, Collection, Predicate)
1129 *
1130 * @param p The point on screen.
1131 * @param predicate the returned object has to fulfill certain properties.
1132 */
1133 public final List<OsmPrimitive> getNearestNodesOrWays(Point p, Predicate<OsmPrimitive> predicate) {
1134 return getNearestNodesOrWays(p, null, predicate);
1135 }
1136
1137 /**
1138 * This is used as a helper routine to {@link #getNearestNodeOrWay(Point, Predicate, boolean)}
1139 * It decides, whether to yield the node to be tested or look for further (way) candidates.
1140 *
1141 * @return true, if the node fulfills the properties of the function body
1142 *
1143 * @param osm node to check
1144 * @param p point clicked
1145 * @param use_selected whether to prefer selected nodes
1146 */
1147 private boolean isPrecedenceNode(Node osm, Point p, boolean use_selected) {
1148 if (osm != null) {
1149 if (!(p.distanceSq(getPoint2D(osm)) > (4)*(4))) return true;
1150 if (osm.isTagged()) return true;
1151 if (use_selected && osm.isSelected()) return true;
1152 }
1153 return false;
1154 }
1155
1156 /**
1157 * The *result* depends on the current map selection state IF use_selected is true.
1158 *
1159 * IF use_selected is true, use {@link #getNearestNode(Point, Predicate)} to find
1160 * the nearest, selected node. If not found, try {@link #getNearestWaySegment(Point, Predicate)}
1161 * to find the nearest selected way.
1162 *
1163 * IF use_selected is false, or if no selected primitive was found, do the following.
1164 *
1165 * If the nearest node found is within 4px of p, simply take it.
1166 * Else, find the nearest way segment. Then, if p is closer to its
1167 * middle than to the node, take the way segment, else take the node.
1168 *
1169 * Finally, if no nearest primitive is found at all, return null.
1170 *
1171 * @return A primitive within snap-distance to point p,
1172 * that is chosen by the algorithm described.
1173 * @see #getNearestNode(Point, Predicate)
1174 * @see #getNearestNodesImpl(Point, Predicate)
1175 * @see #getNearestWay(Point, Predicate)
1176 *
1177 * @param p The point on screen.
1178 * @param predicate the returned object has to fulfill certain properties.
1179 * @param use_selected whether to prefer primitives that are currently selected or referred by selected primitives
1180 */
1181 public final OsmPrimitive getNearestNodeOrWay(Point p, Predicate<OsmPrimitive> predicate, boolean use_selected) {
1182 Collection<OsmPrimitive> sel;
1183 DataSet ds = getCurrentDataSet();
1184 if (use_selected && ds!=null) {
1185 sel = ds.getSelected();
1186 } else {
1187 sel = null;
1188 }
1189 OsmPrimitive osm = getNearestNode(p, predicate, use_selected, sel);
1190
1191 if (isPrecedenceNode((Node)osm, p, use_selected)) return osm;
1192 WaySegment ws;
1193 if (use_selected) {
1194 ws = getNearestWaySegment(p, predicate, use_selected, sel);
1195 } else {
1196 ws = getNearestWaySegment(p, predicate, use_selected);
1197 }
1198 if (ws == null) return osm;
1199
1200 if ((ws.way.isSelected() && use_selected) || osm == null) {
1201 // either (no _selected_ nearest node found, if desired) or no nearest node was found
1202 osm = ws.way;
1203 } else {
1204 int maxWaySegLenSq = 3*PROP_SNAP_DISTANCE.get();
1205 maxWaySegLenSq *= maxWaySegLenSq;
1206
1207 Point2D wp1 = getPoint2D(ws.way.getNode(ws.lowerIndex));
1208 Point2D wp2 = getPoint2D(ws.way.getNode(ws.lowerIndex+1));
1209
1210 // is wayseg shorter than maxWaySegLenSq and
1211 // is p closer to the middle of wayseg than to the nearest node?
1212 if (wp1.distanceSq(wp2) < maxWaySegLenSq &&
1213 p.distanceSq(project(0.5, wp1, wp2)) < p.distanceSq(getPoint2D((Node)osm))) {
1214 osm = ws.way;
1215 }
1216 }
1217 return osm;
1218 }
1219
1220 /**
1221 * @return o as collection of o's type.
1222 */
1223 public static <T> Collection<T> asColl(T o) {
1224 if (o == null)
1225 return Collections.emptySet();
1226 return Collections.singleton(o);
1227 }
1228
1229 public static double perDist(Point2D pt, Point2D a, Point2D b) {
1230 if (pt != null && a != null && b != null) {
1231 double pd = (
1232 (a.getX()-pt.getX())*(b.getX()-a.getX()) -
1233 (a.getY()-pt.getY())*(b.getY()-a.getY()) );
1234 return Math.abs(pd) / a.distance(b);
1235 }
1236 return 0d;
1237 }
1238
1239 /**
1240 *
1241 * @param pt point to project onto (ab)
1242 * @param a root of vector
1243 * @param b vector
1244 * @return point of intersection of line given by (ab)
1245 * with its orthogonal line running through pt
1246 */
1247 public static Point2D project(Point2D pt, Point2D a, Point2D b) {
1248 if (pt != null && a != null && b != null) {
1249 double r = ((
1250 (pt.getX()-a.getX())*(b.getX()-a.getX()) +
1251 (pt.getY()-a.getY())*(b.getY()-a.getY()) )
1252 / a.distanceSq(b));
1253 return project(r, a, b);
1254 }
1255 return null;
1256 }
1257
1258 /**
1259 * if r = 0 returns a, if r=1 returns b,
1260 * if r = 0.5 returns center between a and b, etc..
1261 *
1262 * @param r scale value
1263 * @param a root of vector
1264 * @param b vector
1265 * @return new point at a + r*(ab)
1266 */
1267 public static Point2D project(double r, Point2D a, Point2D b) {
1268 Point2D ret = null;
1269
1270 if (a != null && b != null) {
1271 ret = new Point2D.Double(a.getX() + r*(b.getX()-a.getX()),
1272 a.getY() + r*(b.getY()-a.getY()));
1273 }
1274 return ret;
1275 }
1276
1277 /**
1278 * The *result* does not depend on the current map selection state,
1279 * neither does the result *order*.
1280 * It solely depends on the distance to point p.
1281 *
1282 * @return a list of all objects that are nearest to point p and
1283 * not in ignore or an empty list if nothing was found.
1284 *
1285 * @param p The point on screen.
1286 * @param ignore a collection of ways which are not to be returned.
1287 * @param predicate the returned object has to fulfill certain properties.
1288 */
1289 public final List<OsmPrimitive> getAllNearest(Point p,
1290 Collection<OsmPrimitive> ignore, Predicate<OsmPrimitive> predicate) {
1291 List<OsmPrimitive> nearestList = new ArrayList<OsmPrimitive>();
1292 Set<Way> wset = new HashSet<Way>();
1293
1294 // add nearby ways
1295 for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
1296 for (WaySegment ws : wss) {
1297 if (wset.add(ws.way)) {
1298 nearestList.add(ws.way);
1299 }
1300 }
1301 }
1302
1303 // add nearby nodes
1304 for (List<Node> nlist : getNearestNodesImpl(p, predicate).values()) {
1305 nearestList.addAll(nlist);
1306 }
1307
1308 // add parent relations of nearby nodes and ways
1309 Set<OsmPrimitive> parentRelations = new HashSet<OsmPrimitive>();
1310 for (OsmPrimitive o : nearestList) {
1311 for (OsmPrimitive r : o.getReferrers()) {
1312 if (r instanceof Relation && predicate.evaluate(r)) {
1313 parentRelations.add(r);
1314 }
1315 }
1316 }
1317 nearestList.addAll(parentRelations);
1318
1319 if (ignore != null) {
1320 nearestList.removeAll(ignore);
1321 }
1322
1323 return nearestList;
1324 }
1325
1326 /**
1327 * The *result* does not depend on the current map selection state,
1328 * neither does the result *order*.
1329 * It solely depends on the distance to point p.
1330 *
1331 * @return a list of all objects that are nearest to point p
1332 * or an empty list if nothing was found.
1333 * @see #getAllNearest(Point, Collection, Predicate)
1334 *
1335 * @param p The point on screen.
1336 * @param predicate the returned object has to fulfill certain properties.
1337 */
1338 public final List<OsmPrimitive> getAllNearest(Point p, Predicate<OsmPrimitive> predicate) {
1339 return getAllNearest(p, null, predicate);
1340 }
1341
1342 /**
1343 * @return The projection to be used in calculating stuff.
1344 */
1345 public Projection getProjection() {
1346 return Main.getProjection();
1347 }
1348
1349 @Override
1350 public String helpTopic() {
1351 String n = getClass().getName();
1352 return n.substring(n.lastIndexOf('.')+1);
1353 }
1354
1355 /**
1356 * Return a ID which is unique as long as viewport dimensions are the same
1357 */
1358 public int getViewID() {
1359 String x = center.east() + "_" + center.north() + "_" + scale + "_" +
1360 getWidth() + "_" + getHeight() + "_" + getProjection().toString();
1361 java.util.zip.CRC32 id = new java.util.zip.CRC32();
1362 id.update(x.getBytes());
1363 return (int)id.getValue();
1364 }
1365
1366 /**
1367 * Returns the current system of measurement.
1368 * @return The current system of measurement (metric system by default).
1369 * @since 3490
1370 */
1371 public static SystemOfMeasurement getSystemOfMeasurement() {
1372 SystemOfMeasurement som = SYSTEMS_OF_MEASUREMENT.get(ProjectionPreference.PROP_SYSTEM_OF_MEASUREMENT.get());
1373 if (som == null)
1374 return METRIC_SOM;
1375 return som;
1376 }
1377
1378 /**
1379 * Sets the current system of measurement.
1380 * @param somKey The system of measurement key. Must be defined in {@link NavigatableComponent#SYSTEMS_OF_MEASUREMENT}.
1381 * @since 6056
1382 * @throws IllegalArgumentException if {@code somKey} is not known
1383 */
1384 public static void setSystemOfMeasurement(String somKey) {
1385 if (!SYSTEMS_OF_MEASUREMENT.containsKey(somKey)) {
1386 throw new IllegalArgumentException("Invalid system of measurement: "+somKey);
1387 }
1388 String oldKey = ProjectionPreference.PROP_SYSTEM_OF_MEASUREMENT.get();
1389 if (ProjectionPreference.PROP_SYSTEM_OF_MEASUREMENT.put(somKey)) {
1390 fireSoMChanged(oldKey, somKey);
1391 }
1392 }
1393
1394 /**
1395 * A system of units used to express length and area measurements.
1396 * @since 3406
1397 */
1398 public static class SystemOfMeasurement {
1399
1400 /** First value, in meters, used to translate unit according to above formula. */
1401 public final double aValue;
1402 /** Second value, in meters, used to translate unit according to above formula. */
1403 public final double bValue;
1404 /** First unit used to format text. */
1405 public final String aName;
1406 /** Second unit used to format text. */
1407 public final String bName;
1408 /** Specific optional area value, in squared meters, between {@code aValue*aValue} and {@code bValue*bValue}. Set to {@code -1} if not used.
1409 * @since 5870 */
1410 public final double areaCustomValue;
1411 /** Specific optional area unit. Set to {@code null} if not used.
1412 * @since 5870 */
1413 public final String areaCustomName;
1414
1415 /**
1416 * System of measurement. Currently covers only length (and area) units.
1417 *
1418 * If a quantity x is given in m (x_m) and in unit a (x_a) then it translates as
1419 * x_a == x_m / aValue
1420 *
1421 * @param aValue First value, in meters, used to translate unit according to above formula.
1422 * @param aName First unit used to format text.
1423 * @param bValue Second value, in meters, used to translate unit according to above formula.
1424 * @param bName Second unit used to format text.
1425 */
1426 public SystemOfMeasurement(double aValue, String aName, double bValue, String bName) {
1427 this(aValue, aName, bValue, bName, -1, null);
1428 }
1429
1430 /**
1431 * System of measurement. Currently covers only length (and area) units.
1432 *
1433 * If a quantity x is given in m (x_m) and in unit a (x_a) then it translates as
1434 * x_a == x_m / aValue
1435 *
1436 * @param aValue First value, in meters, used to translate unit according to above formula.
1437 * @param aName First unit used to format text.
1438 * @param bValue Second value, in meters, used to translate unit according to above formula.
1439 * @param bName Second unit used to format text.
1440 * @param areaCustomValue Specific optional area value, in squared meters, between {@code aValue*aValue} and {@code bValue*bValue}.
1441 * Set to {@code -1} if not used.
1442 * @param areaCustomName Specific optional area unit. Set to {@code null} if not used.
1443 *
1444 * @since 5870
1445 */
1446 public SystemOfMeasurement(double aValue, String aName, double bValue, String bName, double areaCustomValue, String areaCustomName) {
1447 this.aValue = aValue;
1448 this.aName = aName;
1449 this.bValue = bValue;
1450 this.bName = bName;
1451 this.areaCustomValue = areaCustomValue;
1452 this.areaCustomName = areaCustomName;
1453 }
1454
1455 /**
1456 * Returns the text describing the given distance in this system of measurement.
1457 * @param dist The distance in metres
1458 * @return The text describing the given distance in this system of measurement.
1459 */
1460 public String getDistText(double dist) {
1461 double a = dist / aValue;
1462 if (!Main.pref.getBoolean("system_of_measurement.use_only_lower_unit", false) && a > bValue / aValue)
1463 return formatText(dist / bValue, bName);
1464 else if (a < 0.01)
1465 return "< 0.01 " + aName;
1466 else
1467 return formatText(a, aName);
1468 }
1469
1470 /**
1471 * Returns the text describing the given area in this system of measurement.
1472 * @param area The area in square metres
1473 * @return The text describing the given area in this system of measurement.
1474 * @since 5560
1475 */
1476 public String getAreaText(double area) {
1477 double a = area / (aValue*aValue);
1478 boolean lowerOnly = Main.pref.getBoolean("system_of_measurement.use_only_lower_unit", false);
1479 boolean customAreaOnly = Main.pref.getBoolean("system_of_measurement.use_only_custom_area_unit", false);
1480 if ((!lowerOnly && areaCustomValue > 0 && a > areaCustomValue / (aValue*aValue) && a < (bValue*bValue) / (aValue*aValue)) || customAreaOnly)
1481 return formatText(area / areaCustomValue, areaCustomName);
1482 else if (!lowerOnly && a >= (bValue*bValue) / (aValue*aValue))
1483 return formatText(area / (bValue*bValue), bName+"\u00b2");
1484 else if (a < 0.01)
1485 return "< 0.01 " + aName+"\u00b2";
1486 else
1487 return formatText(a, aName+"\u00b2");
1488 }
1489
1490 private static String formatText(double v, String unit) {
1491 return String.format(Locale.US, "%." + (v<9.999999 ? 2 : 1) + "f %s", v, unit);
1492 }
1493 }
1494
1495 /**
1496 * Metric system (international standard).
1497 * @since 3406
1498 */
1499 public static final SystemOfMeasurement METRIC_SOM = new SystemOfMeasurement(1, "m", 1000, "km", 10000, "ha");
1500
1501 /**
1502 * Chinese system.
1503 * @since 3406
1504 */
1505 public static final SystemOfMeasurement CHINESE_SOM = new SystemOfMeasurement(1.0/3.0, "\u5e02\u5c3a" /* chi */, 500, "\u5e02\u91cc" /* li */);
1506
1507 /**
1508 * Imperial system (British Commonwealth and former British Empire).
1509 * @since 3406
1510 */
1511 public static final SystemOfMeasurement IMPERIAL_SOM = new SystemOfMeasurement(0.3048, "ft", 1609.344, "mi", 4046.86, "ac");
1512
1513 /**
1514 * Nautical mile system (navigation, polar exploration).
1515 * @since 5549
1516 */
1517 public static final SystemOfMeasurement NAUTICAL_MILE_SOM = new SystemOfMeasurement(185.2, "kbl", 1852, "NM");
1518
1519 /**
1520 * Known systems of measurement.
1521 * @since 3406
1522 */
1523 public static final Map<String, SystemOfMeasurement> SYSTEMS_OF_MEASUREMENT;
1524 static {
1525 SYSTEMS_OF_MEASUREMENT = new LinkedHashMap<String, SystemOfMeasurement>();
1526 SYSTEMS_OF_MEASUREMENT.put(marktr("Metric"), METRIC_SOM);
1527 SYSTEMS_OF_MEASUREMENT.put(marktr("Chinese"), CHINESE_SOM);
1528 SYSTEMS_OF_MEASUREMENT.put(marktr("Imperial"), IMPERIAL_SOM);
1529 SYSTEMS_OF_MEASUREMENT.put(marktr("Nautical Mile"), NAUTICAL_MILE_SOM);
1530 }
1531
1532 private static class CursorInfo {
1533 public Cursor cursor;
1534 public Object object;
1535 public CursorInfo(Cursor c, Object o) {
1536 cursor = c;
1537 object = o;
1538 }
1539 }
1540
1541 private LinkedList<CursorInfo> Cursors = new LinkedList<CursorInfo>();
1542 /**
1543 * Set new cursor.
1544 */
1545 public void setNewCursor(Cursor cursor, Object reference) {
1546 if(!Cursors.isEmpty()) {
1547 CursorInfo l = Cursors.getLast();
1548 if(l != null && l.cursor == cursor && l.object == reference)
1549 return;
1550 stripCursors(reference);
1551 }
1552 Cursors.add(new CursorInfo(cursor, reference));
1553 setCursor(cursor);
1554 }
1555 public void setNewCursor(int cursor, Object reference) {
1556 setNewCursor(Cursor.getPredefinedCursor(cursor), reference);
1557 }
1558 /**
1559 * Remove the new cursor and reset to previous
1560 */
1561 public void resetCursor(Object reference) {
1562 if(Cursors.isEmpty()) {
1563 setCursor(null);
1564 return;
1565 }
1566 CursorInfo l = Cursors.getLast();
1567 stripCursors(reference);
1568 if(l != null && l.object == reference) {
1569 if(Cursors.isEmpty()) {
1570 setCursor(null);
1571 } else {
1572 setCursor(Cursors.getLast().cursor);
1573 }
1574 }
1575 }
1576
1577 private void stripCursors(Object reference) {
1578 LinkedList<CursorInfo> c = new LinkedList<CursorInfo>();
1579 for(CursorInfo i : Cursors) {
1580 if(i.object != reference) {
1581 c.add(i);
1582 }
1583 }
1584 Cursors = c;
1585 }
1586
1587 @Override
1588 public void paint(Graphics g) {
1589 synchronized (paintRequestLock) {
1590 if (paintRect != null) {
1591 Graphics g2 = g.create();
1592 g2.setColor(Utils.complement(PaintColors.getBackgroundColor()));
1593 g2.drawRect(paintRect.x, paintRect.y, paintRect.width, paintRect.height);
1594 g2.dispose();
1595 }
1596 if (paintPoly != null) {
1597 Graphics g2 = g.create();
1598 g2.setColor(Utils.complement(PaintColors.getBackgroundColor()));
1599 g2.drawPolyline(paintPoly.xpoints, paintPoly.ypoints, paintPoly.npoints);
1600 g2.dispose();
1601 }
1602 }
1603 super.paint(g);
1604 }
1605
1606 /**
1607 * Requests to paint the given {@code Rectangle}.
1608 * @param r The Rectangle to draw
1609 * @see #requestClearRect
1610 * @since 5500
1611 */
1612 public void requestPaintRect(Rectangle r) {
1613 if (r != null) {
1614 synchronized (paintRequestLock) {
1615 paintRect = r;
1616 }
1617 repaint();
1618 }
1619 }
1620
1621 /**
1622 * Requests to paint the given {@code Polygon} as a polyline (unclosed polygon).
1623 * @param p The Polygon to draw
1624 * @see #requestClearPoly
1625 * @since 5500
1626 */
1627 public void requestPaintPoly(Polygon p) {
1628 if (p != null) {
1629 synchronized (paintRequestLock) {
1630 paintPoly = p;
1631 }
1632 repaint();
1633 }
1634 }
1635
1636 /**
1637 * Requests to clear the rectangled previously drawn.
1638 * @see #requestPaintRect
1639 * @since 5500
1640 */
1641 public void requestClearRect() {
1642 synchronized (paintRequestLock) {
1643 paintRect = null;
1644 }
1645 repaint();
1646 }
1647
1648 /**
1649 * Requests to clear the polyline previously drawn.
1650 * @see #requestPaintPoly
1651 * @since 5500
1652 */
1653 public void requestClearPoly() {
1654 synchronized (paintRequestLock) {
1655 paintPoly = null;
1656 }
1657 repaint();
1658 }
1659}
Note: See TracBrowser for help on using the repository browser.