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

Last change on this file since 3127 was 3116, checked in by jttt, 14 years ago

Reuse offscreenBuffer if layers didn't change

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