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

Last change on this file since 3558 was 3490, checked in by bastiK, 14 years ago

fixed #5349 - A LOT of gpx tracks fill entire screen with popup information

  • Property svn:eol-style set to native
File size: 27.1 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.Point;
7import java.awt.Rectangle;
8import java.util.ArrayList;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.Date;
12import java.util.HashSet;
13import java.util.LinkedHashMap;
14import java.util.LinkedList;
15import java.util.List;
16import java.util.Locale;
17import java.util.Map;
18import java.util.Stack;
19import java.util.TreeMap;
20import java.util.concurrent.CopyOnWriteArrayList;
21
22import javax.swing.JComponent;
23
24import org.openstreetmap.josm.Main;
25import org.openstreetmap.josm.data.Bounds;
26import org.openstreetmap.josm.data.ProjectionBounds;
27import org.openstreetmap.josm.data.coor.CachedLatLon;
28import org.openstreetmap.josm.data.coor.EastNorth;
29import org.openstreetmap.josm.data.coor.LatLon;
30import org.openstreetmap.josm.data.osm.BBox;
31import org.openstreetmap.josm.data.osm.DataSet;
32import org.openstreetmap.josm.data.osm.Node;
33import org.openstreetmap.josm.data.osm.OsmPrimitive;
34import org.openstreetmap.josm.data.osm.Way;
35import org.openstreetmap.josm.data.osm.WaySegment;
36import org.openstreetmap.josm.data.projection.Projection;
37import org.openstreetmap.josm.gui.help.Helpful;
38import org.openstreetmap.josm.gui.preferences.ProjectionPreference;
39import org.openstreetmap.josm.tools.Predicate;
40
41/**
42 * An component that can be navigated by a mapmover. Used as map view and for the
43 * zoomer in the download dialog.
44 *
45 * @author imi
46 */
47public class NavigatableComponent extends JComponent implements Helpful {
48
49 /**
50 * Interface to notify listeners of the change of the zoom area.
51 */
52 public interface ZoomChangeListener {
53 void zoomChanged();
54 }
55
56 /**
57 * the zoom listeners
58 */
59 private static final CopyOnWriteArrayList<ZoomChangeListener> zoomChangeListeners = new CopyOnWriteArrayList<ZoomChangeListener>();
60
61 /**
62 * Removes a zoom change listener
63 *
64 * @param listener the listener. Ignored if null or already absent
65 */
66 public static void removeZoomChangeListener(NavigatableComponent.ZoomChangeListener listener) {
67 zoomChangeListeners.remove(listener);
68 }
69
70 /**
71 * Adds a zoom change listener
72 *
73 * @param listener the listener. Ignored if null or already registered.
74 */
75 public static void addZoomChangeListener(NavigatableComponent.ZoomChangeListener listener) {
76 if (listener != null) {
77 zoomChangeListeners.addIfAbsent(listener);
78 }
79 }
80
81 protected static void fireZoomChanged() {
82 for (ZoomChangeListener l : zoomChangeListeners) {
83 l.zoomChanged();
84 }
85 }
86
87 public static final int snapDistance = Main.pref.getInteger("node.snap-distance", 10);
88 public static final int snapDistanceSq = sqr(snapDistance);
89
90 private static int sqr(int a) { return a*a;}
91 /**
92 * The scale factor in x or y-units per pixel. This means, if scale = 10,
93 * every physical pixel on screen are 10 x or 10 y units in the
94 * northing/easting space of the projection.
95 */
96 private double scale = Main.proj.getDefaultZoomInPPD();
97 /**
98 * Center n/e coordinate of the desired screen center.
99 */
100 protected EastNorth center = calculateDefaultCenter();
101
102 public NavigatableComponent() {
103 setLayout(null);
104 }
105
106 protected DataSet getCurrentDataSet() {
107 return Main.main.getCurrentDataSet();
108 }
109
110 private EastNorth calculateDefaultCenter() {
111 Bounds b = Main.proj.getWorldBoundsLatLon();
112 double lat = (b.getMax().lat() + b.getMin().lat())/2;
113 double lon = (b.getMax().lon() + b.getMin().lon())/2;
114
115 return Main.proj.latlon2eastNorth(new LatLon(lat, lon));
116 }
117
118 public static String getDistText(double dist) {
119 return getSystemOfMeasurement().getDistText(dist);
120 }
121
122 public String getDist100PixelText()
123 {
124 return getDistText(getDist100Pixel());
125 }
126
127 public double getDist100Pixel()
128 {
129 int w = getWidth()/2;
130 int h = getHeight()/2;
131 LatLon ll1 = getLatLon(w-50,h);
132 LatLon ll2 = getLatLon(w+50,h);
133 return ll1.greatCircleDistance(ll2);
134 }
135
136 /**
137 * @return Returns the center point. A copy is returned, so users cannot
138 * change the center by accessing the return value. Use zoomTo instead.
139 */
140 public EastNorth getCenter() {
141 return center;
142 }
143
144 /**
145 * @param x X-Pixelposition to get coordinate from
146 * @param y Y-Pixelposition to get coordinate from
147 *
148 * @return Geographic coordinates from a specific pixel coordination
149 * on the screen.
150 */
151 public EastNorth getEastNorth(int x, int y) {
152 return new EastNorth(
153 center.east() + (x - getWidth()/2.0)*scale,
154 center.north() - (y - getHeight()/2.0)*scale);
155 }
156
157 public ProjectionBounds getProjectionBounds() {
158 return new ProjectionBounds(
159 new EastNorth(
160 center.east() - getWidth()/2.0*scale,
161 center.north() - getHeight()/2.0*scale),
162 new EastNorth(
163 center.east() + getWidth()/2.0*scale,
164 center.north() + getHeight()/2.0*scale));
165 }
166
167 /* FIXME: replace with better method - used by MapSlider */
168 public ProjectionBounds getMaxProjectionBounds() {
169 Bounds b = getProjection().getWorldBoundsLatLon();
170 return new ProjectionBounds(getProjection().latlon2eastNorth(b.getMin()),
171 getProjection().latlon2eastNorth(b.getMax()));
172 }
173
174 /* FIXME: replace with better method - used by Main to reset Bounds when projection changes, don't use otherwise */
175 public Bounds getRealBounds() {
176 return new Bounds(
177 getProjection().eastNorth2latlon(new EastNorth(
178 center.east() - getWidth()/2.0*scale,
179 center.north() - getHeight()/2.0*scale)),
180 getProjection().eastNorth2latlon(new EastNorth(
181 center.east() + getWidth()/2.0*scale,
182 center.north() + getHeight()/2.0*scale)));
183 }
184
185 /**
186 * @param x X-Pixelposition to get coordinate from
187 * @param y Y-Pixelposition to get coordinate from
188 *
189 * @return Geographic unprojected coordinates from a specific pixel coordination
190 * on the screen.
191 */
192 public LatLon getLatLon(int x, int y) {
193 return getProjection().eastNorth2latlon(getEastNorth(x, y));
194 }
195
196 /**
197 * @param r
198 * @return Minimum bounds that will cover rectangle
199 */
200 public Bounds getLatLonBounds(Rectangle r) {
201 // TODO Maybe this should be (optional) method of Projection implementation
202 EastNorth p1 = getEastNorth(r.x, r.y);
203 EastNorth p2 = getEastNorth(r.x + r.width, r.y + r.height);
204
205 Bounds result = new Bounds(Main.proj.eastNorth2latlon(p1));
206
207 double eastMin = Math.min(p1.east(), p2.east());
208 double eastMax = Math.max(p1.east(), p2.east());
209 double northMin = Math.min(p1.north(), p2.north());
210 double northMax = Math.max(p1.north(), p2.north());
211 double deltaEast = (eastMax - eastMin) / 10;
212 double deltaNorth = (northMax - northMin) / 10;
213
214 for (int i=0; i < 10; i++) {
215 result.extend(Main.proj.eastNorth2latlon(new EastNorth(eastMin + i * deltaEast, northMin)));
216 result.extend(Main.proj.eastNorth2latlon(new EastNorth(eastMin + i * deltaEast, northMax)));
217 result.extend(Main.proj.eastNorth2latlon(new EastNorth(eastMin, northMin + i * deltaNorth)));
218 result.extend(Main.proj.eastNorth2latlon(new EastNorth(eastMax, northMin + i * deltaNorth)));
219 }
220
221 return result;
222 }
223
224 /**
225 * Return the point on the screen where this Coordinate would be.
226 * @param p The point, where this geopoint would be drawn.
227 * @return The point on screen where "point" would be drawn, relative
228 * to the own top/left.
229 */
230 public Point getPoint(EastNorth p) {
231 if (null == p)
232 return new Point();
233 double x = (p.east()-center.east())/scale + getWidth()/2;
234 double y = (center.north()-p.north())/scale + getHeight()/2;
235 return new Point((int)x,(int)y);
236 }
237
238 public Point getPoint(LatLon latlon) {
239 if (latlon == null)
240 return new Point();
241 else if (latlon instanceof CachedLatLon)
242 return getPoint(((CachedLatLon)latlon).getEastNorth());
243 else
244 return getPoint(getProjection().latlon2eastNorth(latlon));
245 }
246 public Point getPoint(Node n) {
247 return getPoint(n.getEastNorth());
248 }
249
250 /**
251 * Zoom to the given coordinate.
252 * @param newCenter The center x-value (easting) to zoom to.
253 * @param scale The scale to use.
254 */
255 private void zoomTo(EastNorth newCenter, double newScale) {
256 Bounds b = getProjection().getWorldBoundsLatLon();
257 CachedLatLon cl = new CachedLatLon(newCenter);
258 boolean changed = false;
259 double lat = cl.lat();
260 double lon = cl.lon();
261 if(lat < b.getMin().lat()) {changed = true; lat = b.getMin().lat(); }
262 else if(lat > b.getMax().lat()) {changed = true; lat = b.getMax().lat(); }
263 if(lon < b.getMin().lon()) {changed = true; lon = b.getMin().lon(); }
264 else if(lon > b.getMax().lon()) {changed = true; lon = b.getMax().lon(); }
265 if(changed) {
266 newCenter = new CachedLatLon(lat, lon).getEastNorth();
267 }
268 int width = getWidth()/2;
269 int height = getHeight()/2;
270 LatLon l1 = new LatLon(b.getMin().lat(), lon);
271 LatLon l2 = new LatLon(b.getMax().lat(), lon);
272 EastNorth e1 = getProjection().latlon2eastNorth(l1);
273 EastNorth e2 = getProjection().latlon2eastNorth(l2);
274 double d = e2.north() - e1.north();
275 if(d < height*newScale)
276 {
277 double newScaleH = d/height;
278 e1 = getProjection().latlon2eastNorth(new LatLon(lat, b.getMin().lon()));
279 e2 = getProjection().latlon2eastNorth(new LatLon(lat, b.getMax().lon()));
280 d = e2.east() - e1.east();
281 if(d < width*newScale) {
282 newScale = Math.max(newScaleH, d/width);
283 }
284 }
285 else
286 {
287 d = d/(l1.greatCircleDistance(l2)*height*10);
288 if(newScale < d) {
289 newScale = d;
290 }
291 }
292
293 if (!newCenter.equals(center) || (scale != newScale)) {
294 pushZoomUndo(center, scale);
295 zoomNoUndoTo(newCenter, newScale);
296 }
297 }
298
299 /**
300 * Zoom to the given coordinate without adding to the zoom undo buffer.
301 * @param newCenter The center x-value (easting) to zoom to.
302 * @param scale The scale to use.
303 */
304 private void zoomNoUndoTo(EastNorth newCenter, double newScale) {
305 if (!newCenter.equals(center)) {
306 EastNorth oldCenter = center;
307 center = newCenter;
308 firePropertyChange("center", oldCenter, newCenter);
309 }
310 if (scale != newScale) {
311 double oldScale = scale;
312 scale = newScale;
313 firePropertyChange("scale", oldScale, newScale);
314 }
315
316 repaint();
317 fireZoomChanged();
318 }
319
320 public void zoomTo(EastNorth newCenter) {
321 zoomTo(newCenter, scale);
322 }
323
324 public void zoomTo(LatLon newCenter) {
325 if(newCenter instanceof CachedLatLon) {
326 zoomTo(((CachedLatLon)newCenter).getEastNorth(), scale);
327 } else {
328 zoomTo(getProjection().latlon2eastNorth(newCenter), scale);
329 }
330 }
331
332 public void zoomToFactor(double x, double y, double factor) {
333 double newScale = scale*factor;
334 // New center position so that point under the mouse pointer stays the same place as it was before zooming
335 // You will get the formula by simplifying this expression: newCenter = oldCenter + mouseCoordinatesInNewZoom - mouseCoordinatesInOldZoom
336 zoomTo(new EastNorth(
337 center.east() - (x - getWidth()/2.0) * (newScale - scale),
338 center.north() + (y - getHeight()/2.0) * (newScale - scale)),
339 newScale);
340 }
341
342 public void zoomToFactor(EastNorth newCenter, double factor) {
343 zoomTo(newCenter, scale*factor);
344 }
345
346 public void zoomToFactor(double factor) {
347 zoomTo(center, scale*factor);
348 }
349
350 public void zoomTo(ProjectionBounds box) {
351 // -20 to leave some border
352 int w = getWidth()-20;
353 if (w < 20) {
354 w = 20;
355 }
356 int h = getHeight()-20;
357 if (h < 20) {
358 h = 20;
359 }
360
361 double scaleX = (box.max.east()-box.min.east())/w;
362 double scaleY = (box.max.north()-box.min.north())/h;
363 double newScale = Math.max(scaleX, scaleY);
364
365 zoomTo(box.getCenter(), newScale);
366 }
367
368 public void zoomTo(Bounds box) {
369 zoomTo(new ProjectionBounds(getProjection().latlon2eastNorth(box.getMin()),
370 getProjection().latlon2eastNorth(box.getMax())));
371 }
372
373 private class ZoomData {
374 LatLon center;
375 double scale;
376
377 public ZoomData(EastNorth center, double scale) {
378 this.center = new CachedLatLon(center);
379 this.scale = scale;
380 }
381
382 public EastNorth getCenterEastNorth() {
383 return getProjection().latlon2eastNorth(center);
384 }
385
386 public double getScale() {
387 return scale;
388 }
389 }
390
391 private Stack<ZoomData> zoomUndoBuffer = new Stack<ZoomData>();
392 private Stack<ZoomData> zoomRedoBuffer = new Stack<ZoomData>();
393 private Date zoomTimestamp = new Date();
394
395 private void pushZoomUndo(EastNorth center, double scale) {
396 Date now = new Date();
397 if ((now.getTime() - zoomTimestamp.getTime()) > (Main.pref.getDouble("zoom.undo.delay", 1.0) * 1000)) {
398 zoomUndoBuffer.push(new ZoomData(center, scale));
399 if (zoomUndoBuffer.size() > Main.pref.getInteger("zoom.undo.max", 50)) {
400 zoomUndoBuffer.remove(0);
401 }
402 zoomRedoBuffer.clear();
403 }
404 zoomTimestamp = now;
405 }
406
407 public void zoomPrevious() {
408 if (!zoomUndoBuffer.isEmpty()) {
409 ZoomData zoom = zoomUndoBuffer.pop();
410 zoomRedoBuffer.push(new ZoomData(center, scale));
411 zoomNoUndoTo(zoom.getCenterEastNorth(), zoom.getScale());
412 }
413 }
414
415 public void zoomNext() {
416 if (!zoomRedoBuffer.isEmpty()) {
417 ZoomData zoom = zoomRedoBuffer.pop();
418 zoomUndoBuffer.push(new ZoomData(center, scale));
419 zoomNoUndoTo(zoom.getCenterEastNorth(), zoom.getScale());
420 }
421 }
422
423 public boolean hasZoomUndoEntries() {
424 return !zoomUndoBuffer.isEmpty();
425 }
426
427 public boolean hasZoomRedoEntries() {
428 return !zoomRedoBuffer.isEmpty();
429 }
430
431 private BBox getSnapDistanceBBox(Point p) {
432 return new BBox(getLatLon(p.x - snapDistance, p.y - snapDistance),
433 getLatLon(p.x + snapDistance, p.y + snapDistance));
434 }
435
436 @Deprecated
437 public final Node getNearestNode(Point p) {
438 return getNearestNode(p, OsmPrimitive.isUsablePredicate);
439 }
440
441 /**
442 * Return the nearest node to the screen point given.
443 * If more then one node within snapDistance pixel is found,
444 * the nearest node is returned.
445 * @param p the screen point
446 * @param predicate this parameter imposes a condition on the returned object, e.g.
447 * give the nearest node that is tagged.
448 */
449 public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate) {
450 DataSet ds = getCurrentDataSet();
451 if (ds == null)
452 return null;
453
454 double minDistanceSq = snapDistanceSq;
455 Node minPrimitive = null;
456 for (Node n : ds.searchNodes(getSnapDistanceBBox(p))) {
457 if (! predicate.evaluate(n))
458 continue;
459 Point sp = getPoint(n);
460 double dist = p.distanceSq(sp);
461 if (dist < minDistanceSq) {
462 minDistanceSq = dist;
463 minPrimitive = n;
464 }
465 // when multiple nodes on one point, prefer new or selected nodes
466 else if (dist == minDistanceSq && minPrimitive != null
467 && ((n.isNew() && ds.isSelected(n))
468 || (!ds.isSelected(minPrimitive) && (ds.isSelected(n) || n.isNew())))) {
469 minPrimitive = n;
470 }
471 }
472 return minPrimitive;
473 }
474
475 /**
476 * @return all way segments within 10px of p, sorted by their
477 * perpendicular distance.
478 *
479 * @param p the point for which to search the nearest segment.
480 * @param predicate the returned objects have to fulfill certain properties.
481 */
482 public final List<WaySegment> getNearestWaySegments(Point p, Predicate<OsmPrimitive> predicate) {
483 TreeMap<Double, List<WaySegment>> nearest = new TreeMap<Double, List<WaySegment>>();
484 DataSet ds = getCurrentDataSet();
485 if (ds == null)
486 return null;
487
488 for (Way w : ds.searchWays(getSnapDistanceBBox(p))) {
489 if (!predicate.evaluate(w))
490 continue;
491 Node lastN = null;
492 int i = -2;
493 for (Node n : w.getNodes()) {
494 i++;
495 if (n.isDeleted() || n.isIncomplete()) {//FIXME: This shouldn't happen, raise exception?
496 continue;
497 }
498 if (lastN == null) {
499 lastN = n;
500 continue;
501 }
502
503 Point A = getPoint(lastN);
504 Point B = getPoint(n);
505 double c = A.distanceSq(B);
506 double a = p.distanceSq(B);
507 double b = p.distanceSq(A);
508 double perDist = a - (a - b + c) * (a - b + c) / 4 / c; // perpendicular distance squared
509 if (perDist < snapDistanceSq && a < c + snapDistanceSq && b < c + snapDistanceSq) {
510 if (ds.isSelected(w)) {
511 perDist -= 0.00001;
512 }
513 List<WaySegment> l;
514 if (nearest.containsKey(perDist)) {
515 l = nearest.get(perDist);
516 } else {
517 l = new LinkedList<WaySegment>();
518 nearest.put(perDist, l);
519 }
520 l.add(new WaySegment(w, i));
521 }
522
523 lastN = n;
524 }
525 }
526 ArrayList<WaySegment> nearestList = new ArrayList<WaySegment>();
527 for (List<WaySegment> wss : nearest.values()) {
528 nearestList.addAll(wss);
529 }
530 return nearestList;
531 }
532
533 /**
534 * @return the nearest way segment to the screen point given that is not
535 * in ignore.
536 *
537 * @param p the point for which to search the nearest segment.
538 * @param ignore a collection of segments which are not to be returned.
539 * @param predicate the returned object has to fulfill certain properties.
540 * May be null.
541 */
542 public final WaySegment getNearestWaySegment
543 (Point p, Collection<WaySegment> ignore, Predicate<OsmPrimitive> predicate) {
544 List<WaySegment> nearest = getNearestWaySegments(p, predicate);
545 if(nearest == null)
546 return null;
547 if (ignore != null) {
548 nearest.removeAll(ignore);
549 }
550 return nearest.isEmpty() ? null : nearest.get(0);
551 }
552
553 /**
554 * @return the nearest way segment to the screen point given.
555 */
556 public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate) {
557 return getNearestWaySegment(p, null, predicate);
558 }
559
560 @Deprecated
561 public final Way getNearestWay(Point p) {
562 return getNearestWay(p, OsmPrimitive.isUsablePredicate);
563 }
564
565 /**
566 * @return the nearest way to the screen point given.
567 */
568 public final Way getNearestWay(Point p, Predicate<OsmPrimitive> predicate) {
569 WaySegment nearestWaySeg = getNearestWaySegment(p, predicate);
570 return nearestWaySeg == null ? null : nearestWaySeg.way;
571 }
572
573 /**
574 * Return the object, that is nearest to the given screen point.
575 *
576 * First, a node will be searched. If a node within 10 pixel is found, the
577 * nearest node is returned.
578 *
579 * If no node is found, search for near ways.
580 *
581 * If nothing is found, return <code>null</code>.
582 *
583 * @param p The point on screen.
584 * @param predicate the returned object has to fulfill certain properties.
585 * @return The primitive that is nearest to the point p.
586 */
587 public OsmPrimitive getNearest(Point p, Predicate<OsmPrimitive> predicate) {
588 OsmPrimitive osm = getNearestNode(p, predicate);
589 if (osm == null)
590 {
591 osm = getNearestWay(p, predicate);
592 }
593 return osm;
594 }
595
596 /**
597 * Returns a singleton of the nearest object, or else an empty collection.
598 */
599 public Collection<OsmPrimitive> getNearestCollection(Point p, Predicate<OsmPrimitive> predicate) {
600 OsmPrimitive osm = getNearest(p, predicate);
601 if (osm == null)
602 return Collections.emptySet();
603 return Collections.singleton(osm);
604 }
605
606 /**
607 * @return A list of all objects that are nearest to
608 * the mouse.
609 *
610 * @return A collection of all items or <code>null</code>
611 * if no item under or near the point. The returned
612 * list is never empty.
613 */
614 public Collection<OsmPrimitive> getAllNearest(Point p, Predicate<OsmPrimitive> predicate) {
615 Collection<OsmPrimitive> nearest = new HashSet<OsmPrimitive>();
616 DataSet ds = getCurrentDataSet();
617 if (ds == null)
618 return null;
619 for (Way w : ds.searchWays(getSnapDistanceBBox(p))) {
620 if (!predicate.evaluate(w))
621 continue;
622 Node lastN = null;
623 for (Node n : w.getNodes()) {
624 if (!predicate.evaluate(n))
625 continue;
626 if (lastN == null) {
627 lastN = n;
628 continue;
629 }
630 Point A = getPoint(lastN);
631 Point B = getPoint(n);
632 double c = A.distanceSq(B);
633 double a = p.distanceSq(B);
634 double b = p.distanceSq(A);
635 double perDist = a - (a - b + c) * (a - b + c) / 4 / c; // perpendicular distance squared
636 if (perDist < snapDistanceSq && a < c + snapDistanceSq && b < c + snapDistanceSq) {
637 nearest.add(w);
638 break;
639 }
640 lastN = n;
641 }
642 }
643 for (Node n : ds.searchNodes(getSnapDistanceBBox(p))) {
644 if (n.isUsable()
645 && getPoint(n).distanceSq(p) < snapDistanceSq) {
646 nearest.add(n);
647 }
648 }
649 return nearest.isEmpty() ? null : nearest;
650 }
651
652 /**
653 * @return A list of all nodes that are nearest to
654 * the mouse.
655 *
656 * @return A collection of all nodes or <code>null</code>
657 * if no node under or near the point. The returned
658 * list is never empty.
659 */
660 public Collection<Node> getNearestNodes(Point p, Predicate<OsmPrimitive> predicate) {
661 Collection<Node> nearest = new HashSet<Node>();
662 DataSet ds = getCurrentDataSet();
663 if (ds == null)
664 return null;
665
666 for (Node n : ds.searchNodes(getSnapDistanceBBox(p))) {
667 if (!predicate.evaluate(n))
668 continue;
669 if (getPoint(n).distanceSq(p) < snapDistanceSq) {
670 nearest.add(n);
671 }
672 }
673 return nearest.isEmpty() ? null : nearest;
674 }
675
676 /**
677 * @return the nearest nodes to the screen point given that is not
678 * in ignore.
679 *
680 * @param p the point for which to search the nearest segment.
681 * @param ignore a collection of nodes which are not to be returned.
682 * @param predicate the returned objects have to fulfill certain properties.
683 * May be null.
684 */
685 public final Collection<Node> getNearestNodes(Point p, Collection<Node> ignore, Predicate<OsmPrimitive> predicate) {
686 Collection<Node> nearest = getNearestNodes(p, predicate);
687 if (nearest == null) return null;
688 if (ignore != null) {
689 nearest.removeAll(ignore);
690 }
691 return nearest.isEmpty() ? null : nearest;
692 }
693
694 /**
695 * @return The projection to be used in calculating stuff.
696 */
697 public Projection getProjection() {
698 return Main.proj;
699 }
700
701 public String helpTopic() {
702 String n = getClass().getName();
703 return n.substring(n.lastIndexOf('.')+1);
704 }
705
706 /**
707 * Return a ID which is unique as long as viewport dimensions are the same
708 */
709 public int getViewID() {
710 String x = center.east() + "_" + center.north() + "_" + scale + "_" +
711 getWidth() + "_" + getHeight() + "_" + getProjection().toString();
712 java.util.zip.CRC32 id = new java.util.zip.CRC32();
713 id.update(x.getBytes());
714 return (int)id.getValue();
715 }
716
717 public static SystemOfMeasurement getSystemOfMeasurement() {
718 SystemOfMeasurement som = SYSTEMS_OF_MEASUREMENT.get(ProjectionPreference.PROP_SYSTEM_OF_MEASUREMENT.get());
719 if (som == null)
720 return METRIC_SOM;
721 return som;
722 }
723
724 public static class SystemOfMeasurement {
725 public final double aValue;
726 public final double bValue;
727 public final String aName;
728 public final String bName;
729
730 /**
731 * System of measurement. Currently covers only length units.
732 *
733 * If a quantity x is given in m (x_m) and in unit a (x_a) then it translates as
734 * x_a == x_m / aValue
735 */
736 public SystemOfMeasurement(double aValue, String aName, double bValue, String bName) {
737 this.aValue = aValue;
738 this.aName = aName;
739 this.bValue = bValue;
740 this.bName = bName;
741 }
742
743 public String getDistText(double dist) {
744 double a = dist / aValue;
745 if (!Main.pref.getBoolean("system_of_measurement.use_only_lower_unit", false) && a > bValue / aValue) {
746 double b = dist / bValue;
747 return String.format(Locale.US, "%." + (b<10 ? 2 : 1) + "f %s", b, bName);
748 } else if (a < 0.01)
749 return "< 0.01 " + aName;
750 else
751 return String.format(Locale.US, "%." + (a<10 ? 2 : 1) + "f %s", a, aName);
752 }
753 }
754
755 public static final SystemOfMeasurement METRIC_SOM = new SystemOfMeasurement(1, "m", 1000, "km");
756 public static final SystemOfMeasurement CHINESE_SOM = new SystemOfMeasurement(1.0/3.0, "\u5e02\u5c3a" /* chi */, 500, "\u5e02\u91cc" /* li */);
757 public static final SystemOfMeasurement IMPERIAL_SOM = new SystemOfMeasurement(0.3048, "ft", 1609.344, "mi");
758
759 public static Map<String, SystemOfMeasurement> SYSTEMS_OF_MEASUREMENT;
760 static {
761 SYSTEMS_OF_MEASUREMENT = new LinkedHashMap<String, SystemOfMeasurement>();
762 SYSTEMS_OF_MEASUREMENT.put(marktr("Metric"), METRIC_SOM);
763 SYSTEMS_OF_MEASUREMENT.put(marktr("Chinese"), CHINESE_SOM);
764 SYSTEMS_OF_MEASUREMENT.put(marktr("Imperial"), IMPERIAL_SOM);
765 }
766}
Note: See TracBrowser for help on using the repository browser.