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

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

Fixed #3883 [PATCH] small correction to snap distance

  • Property svn:eol-style set to native
File size: 18.9 KB
Line 
1// License: GPL. See LICENSE file for details.
2
3package org.openstreetmap.josm.gui;
4
5import java.awt.Point;
6import java.util.ArrayList;
7import java.util.Collection;
8import java.util.Collections;
9import java.util.HashSet;
10import java.util.LinkedList;
11import java.util.List;
12import java.util.TreeMap;
13
14import javax.swing.JComponent;
15
16import org.openstreetmap.josm.Main;
17import org.openstreetmap.josm.data.Bounds;
18import org.openstreetmap.josm.data.ProjectionBounds;
19import org.openstreetmap.josm.data.coor.CachedLatLon;
20import org.openstreetmap.josm.data.coor.EastNorth;
21import org.openstreetmap.josm.data.coor.LatLon;
22import org.openstreetmap.josm.data.osm.DataSet;
23import org.openstreetmap.josm.data.osm.Node;
24import org.openstreetmap.josm.data.osm.OsmPrimitive;
25import org.openstreetmap.josm.data.osm.Way;
26import org.openstreetmap.josm.data.osm.WaySegment;
27import org.openstreetmap.josm.data.osm.QuadBuckets.BBox;
28import org.openstreetmap.josm.data.projection.Projection;
29import org.openstreetmap.josm.gui.help.Helpful;
30
31/**
32 * An component that can be navigated by a mapmover. Used as map view and for the
33 * zoomer in the download dialog.
34 *
35 * @author imi
36 */
37public class NavigatableComponent extends JComponent implements Helpful {
38
39 public static final int snapDistance = Main.pref.getInteger("node.snap-distance", 10);
40 public static final int snapDistanceSq = sqr(snapDistance);
41
42 private static int sqr(int a) { return a*a;}
43 /**
44 * The scale factor in x or y-units per pixel. This means, if scale = 10,
45 * every physical pixel on screen are 10 x or 10 y units in the
46 * northing/easting space of the projection.
47 */
48 private double scale = Main.proj.getDefaultZoomInPPD();
49 /**
50 * Center n/e coordinate of the desired screen center.
51 */
52 protected EastNorth center = calculateDefaultCenter();
53
54 public NavigatableComponent() {
55 setLayout(null);
56 }
57
58 protected DataSet getCurrentDataSet() {
59 return Main.main.getCurrentDataSet();
60 }
61
62 private EastNorth calculateDefaultCenter() {
63 Bounds b = Main.proj.getWorldBoundsLatLon();
64 double lat = (b.getMax().lat() + b.getMin().lat())/2;
65 double lon = (b.getMax().lon() + b.getMin().lon())/2;
66
67 return Main.proj.latlon2eastNorth(new LatLon(lat, lon));
68 }
69
70 /**
71 * Return a ID which is unique as long as viewport dimensions are the same
72 */
73 public Integer getViewID()
74 {
75 String x = center.east() + "_" + center.north() + "_" + scale + "_" +
76 getWidth() + "_" + getHeight() + "_" + getProjection().toString();
77 java.util.zip.CRC32 id = new java.util.zip.CRC32();
78 id.update(x.getBytes());
79 return new Long(id.getValue()).intValue();
80 }
81
82 public String getDist100PixelText()
83 {
84 double dist = getDist100Pixel();
85 return dist >= 2000 ? Math.round(dist/100)/10 +" km" : (dist >= 1
86 ? Math.round(dist*10)/10 +" m" : "< 1 m");
87 }
88
89 public double getDist100Pixel()
90 {
91 int w = getWidth()/2;
92 int h = getHeight()/2;
93 LatLon ll1 = getLatLon(w-50,h);
94 LatLon ll2 = getLatLon(w+50,h);
95 return ll1.greatCircleDistance(ll2);
96 }
97
98 /**
99 * @return Returns the center point. A copy is returned, so users cannot
100 * change the center by accessing the return value. Use zoomTo instead.
101 */
102 public EastNorth getCenter() {
103 return center;
104 }
105
106 /**
107 * @param x X-Pixelposition to get coordinate from
108 * @param y Y-Pixelposition to get coordinate from
109 *
110 * @return Geographic coordinates from a specific pixel coordination
111 * on the screen.
112 */
113 public EastNorth getEastNorth(int x, int y) {
114 return new EastNorth(
115 center.east() + (x - getWidth()/2.0)*scale,
116 center.north() - (y - getHeight()/2.0)*scale);
117 }
118
119 public ProjectionBounds getProjectionBounds() {
120 return new ProjectionBounds(
121 new EastNorth(
122 center.east() - getWidth()/2.0*scale,
123 center.north() - getHeight()/2.0*scale),
124 new EastNorth(
125 center.east() + getWidth()/2.0*scale,
126 center.north() + getHeight()/2.0*scale));
127 }
128
129 /* FIXME: replace with better method - used by MapSlider */
130 public ProjectionBounds getMaxProjectionBounds() {
131 Bounds b = getProjection().getWorldBoundsLatLon();
132 return new ProjectionBounds(getProjection().latlon2eastNorth(b.getMin()),
133 getProjection().latlon2eastNorth(b.getMax()));
134 }
135
136 /* FIXME: replace with better method - used by Main to reset Bounds when projection changes, don't use otherwise */
137 public Bounds getRealBounds() {
138 return new Bounds(
139 getProjection().eastNorth2latlon(new EastNorth(
140 center.east() - getWidth()/2.0*scale,
141 center.north() - getHeight()/2.0*scale)),
142 getProjection().eastNorth2latlon(new EastNorth(
143 center.east() + getWidth()/2.0*scale,
144 center.north() + getHeight()/2.0*scale)));
145 }
146
147 /**
148 * @param x X-Pixelposition to get coordinate from
149 * @param y Y-Pixelposition to get coordinate from
150 *
151 * @return Geographic unprojected coordinates from a specific pixel coordination
152 * on the screen.
153 */
154 public LatLon getLatLon(int x, int y) {
155 return getProjection().eastNorth2latlon(getEastNorth(x, y));
156 }
157
158 /**
159 * Return the point on the screen where this Coordinate would be.
160 * @param p The point, where this geopoint would be drawn.
161 * @return The point on screen where "point" would be drawn, relative
162 * to the own top/left.
163 */
164 public Point getPoint(EastNorth p) {
165 if (null == p)
166 return new Point();
167 double x = (p.east()-center.east())/scale + getWidth()/2;
168 double y = (center.north()-p.north())/scale + getHeight()/2;
169 return new Point((int)x,(int)y);
170 }
171
172 public Point getPoint(LatLon latlon) {
173 if (latlon == null)
174 return new Point();
175 else if (latlon instanceof CachedLatLon)
176 return getPoint(((CachedLatLon)latlon).getEastNorth());
177 else
178 return getPoint(getProjection().latlon2eastNorth(latlon));
179 }
180 public Point getPoint(Node n) {
181 return getPoint(n.getEastNorth());
182 }
183
184 /**
185 * Zoom to the given coordinate.
186 * @param newCenter The center x-value (easting) to zoom to.
187 * @param scale The scale to use.
188 */
189 private void zoomTo(EastNorth newCenter, double newScale) {
190 boolean rep = false;
191
192 Bounds b = getProjection().getWorldBoundsLatLon();
193 CachedLatLon cl = new CachedLatLon(newCenter);
194 boolean changed = false;
195 double lat = cl.lat();
196 double lon = cl.lon();
197 if(lat < b.getMin().lat()) {changed = true; lat = b.getMin().lat(); }
198 else if(lat > b.getMax().lat()) {changed = true; lat = b.getMax().lat(); }
199 if(lon < b.getMin().lon()) {changed = true; lon = b.getMin().lon(); }
200 else if(lon > b.getMax().lon()) {changed = true; lon = b.getMax().lon(); }
201 if(changed) {
202 newCenter = new CachedLatLon(lat, lon).getEastNorth();
203 }
204 if (!newCenter.equals(center)) {
205 EastNorth oldCenter = center;
206 center = newCenter;
207 rep = true;
208 firePropertyChange("center", oldCenter, newCenter);
209 }
210
211 int width = getWidth()/2;
212 int height = getHeight()/2;
213 LatLon l1 = new LatLon(b.getMin().lat(), lon);
214 LatLon l2 = new LatLon(b.getMax().lat(), lon);
215 EastNorth e1 = getProjection().latlon2eastNorth(l1);
216 EastNorth e2 = getProjection().latlon2eastNorth(l2);
217 double d = e2.north() - e1.north();
218 if(d < height*newScale)
219 {
220 double newScaleH = d/height;
221 e1 = getProjection().latlon2eastNorth(new LatLon(lat, b.getMin().lon()));
222 e2 = getProjection().latlon2eastNorth(new LatLon(lat, b.getMax().lon()));
223 d = e2.east() - e1.east();
224 if(d < width*newScale) {
225 newScale = Math.max(newScaleH, d/width);
226 }
227 }
228 else
229 {
230 d = d/(l1.greatCircleDistance(l2)*height*10);
231 if(newScale < d) {
232 newScale = d;
233 }
234 }
235 if (scale != newScale) {
236 double oldScale = scale;
237 scale = newScale;
238 rep = true;
239 firePropertyChange("scale", oldScale, newScale);
240 }
241
242 if(rep) {
243 repaint();
244 }
245 }
246
247 public void zoomTo(EastNorth newCenter) {
248 zoomTo(newCenter, scale);
249 }
250
251 public void zoomTo(LatLon newCenter) {
252 if(newCenter instanceof CachedLatLon) {
253 zoomTo(((CachedLatLon)newCenter).getEastNorth(), scale);
254 } else {
255 zoomTo(getProjection().latlon2eastNorth(newCenter), scale);
256 }
257 }
258
259 public void zoomToFactor(double x, double y, double factor) {
260 double newScale = scale*factor;
261 // New center position so that point under the mouse pointer stays the same place as it was before zooming
262 // You will get the formula by simplifying this expression: newCenter = oldCenter + mouseCoordinatesInNewZoom - mouseCoordinatesInOldZoom
263 zoomTo(new EastNorth(
264 center.east() - (x - getWidth()/2.0) * (newScale - scale),
265 center.north() + (y - getHeight()/2.0) * (newScale - scale)),
266 newScale);
267 }
268
269 public void zoomToFactor(EastNorth newCenter, double factor) {
270 zoomTo(newCenter, scale*factor);
271 }
272
273 public void zoomToFactor(double factor) {
274 zoomTo(center, scale*factor);
275 }
276
277 public void zoomTo(ProjectionBounds box) {
278 // -20 to leave some border
279 int w = getWidth()-20;
280 if (w < 20) {
281 w = 20;
282 }
283 int h = getHeight()-20;
284 if (h < 20) {
285 h = 20;
286 }
287
288 double scaleX = (box.max.east()-box.min.east())/w;
289 double scaleY = (box.max.north()-box.min.north())/h;
290 double newScale = Math.max(scaleX, scaleY);
291
292 zoomTo(box.getCenter(), newScale);
293 }
294
295 public void zoomTo(Bounds box) {
296 zoomTo(new ProjectionBounds(getProjection().latlon2eastNorth(box.getMin()),
297 getProjection().latlon2eastNorth(box.getMax())));
298 }
299
300 private BBox getSnapDistanceBBox(Point p) {
301 return new BBox(getLatLon(p.x - snapDistance, p.y - snapDistance),
302 getLatLon(p.x + snapDistance, p.y + snapDistance));
303 }
304
305 /**
306 * Return the nearest point to the screen point given.
307 * If a node within snapDistance pixel is found, the nearest node is returned.
308 */
309 public final Node getNearestNode(Point p) {
310 DataSet ds = getCurrentDataSet();
311 if (ds == null)
312 return null;
313
314 double minDistanceSq = snapDistanceSq;
315 Node minPrimitive = null;
316 for (Node n : ds.searchNodes(getSnapDistanceBBox(p))) {
317 if (!n.isUsable()) {
318 continue;
319 }
320 Point sp = getPoint(n);
321 double dist = p.distanceSq(sp);
322 if (dist < minDistanceSq) {
323 minDistanceSq = dist;
324 minPrimitive = n;
325 }
326 // when multiple nodes on one point, prefer new or selected nodes
327 else if (dist == minDistanceSq && minPrimitive != null
328 && ((n.isNew() && ds.isSelected(n))
329 || (!ds.isSelected(minPrimitive) && (ds.isSelected(n) || n.isNew())))) {
330 minPrimitive = n;
331 }
332 }
333 return minPrimitive;
334 }
335
336 /**
337 * @return all way segments within 10px of p, sorted by their
338 * perpendicular distance.
339 *
340 * @param p the point for which to search the nearest segment.
341 */
342 public final List<WaySegment> getNearestWaySegments(Point p) {
343 TreeMap<Double, List<WaySegment>> nearest = new TreeMap<Double, List<WaySegment>>();
344 DataSet ds = getCurrentDataSet();
345 if (ds == null)
346 return null;
347
348 for (Way w : ds.searchWays(getSnapDistanceBBox(p))) {
349 if (!w.isUsable()) {
350 continue;
351 }
352 Node lastN = null;
353 int i = -2;
354 for (Node n : w.getNodes()) {
355 i++;
356 if (n.isDeleted() || n.incomplete) {
357 continue;
358 }
359 if (lastN == null) {
360 lastN = n;
361 continue;
362 }
363
364 Point A = getPoint(lastN);
365 Point B = getPoint(n);
366 double c = A.distanceSq(B);
367 double a = p.distanceSq(B);
368 double b = p.distanceSq(A);
369 double perDist = a - (a - b + c) * (a - b + c) / 4 / c; // perpendicular distance squared
370 if (perDist < snapDistanceSq && a < c + snapDistanceSq && b < c + snapDistanceSq) {
371 if (ds.isSelected(w)) {
372 perDist -= 0.00001;
373 }
374 List<WaySegment> l;
375 if (nearest.containsKey(perDist)) {
376 l = nearest.get(perDist);
377 } else {
378 l = new LinkedList<WaySegment>();
379 nearest.put(perDist, l);
380 }
381 l.add(new WaySegment(w, i));
382 }
383
384 lastN = n;
385 }
386 }
387 ArrayList<WaySegment> nearestList = new ArrayList<WaySegment>();
388 for (List<WaySegment> wss : nearest.values()) {
389 nearestList.addAll(wss);
390 }
391 return nearestList;
392 }
393
394 /**
395 * @return the nearest way segment to the screen point given that is not
396 * in ignore.
397 *
398 * @param p the point for which to search the nearest segment.
399 * @param ignore a collection of segments which are not to be returned.
400 * May be null.
401 */
402 public final WaySegment getNearestWaySegment(Point p, Collection<WaySegment> ignore) {
403 List<WaySegment> nearest = getNearestWaySegments(p);
404 if(nearest == null)
405 return null;
406 if (ignore != null) {
407 nearest.removeAll(ignore);
408 }
409 return nearest.isEmpty() ? null : nearest.get(0);
410 }
411
412 /**
413 * @return the nearest way segment to the screen point given.
414 */
415 public final WaySegment getNearestWaySegment(Point p) {
416 return getNearestWaySegment(p, null);
417 }
418
419 /**
420 * @return the nearest way to the screen point given.
421 */
422 public final Way getNearestWay(Point p) {
423 WaySegment nearestWaySeg = getNearestWaySegment(p);
424 return nearestWaySeg == null ? null : nearestWaySeg.way;
425 }
426
427 /**
428 * Return the object, that is nearest to the given screen point.
429 *
430 * First, a node will be searched. If a node within 10 pixel is found, the
431 * nearest node is returned.
432 *
433 * If no node is found, search for near ways.
434 *
435 * If nothing is found, return <code>null</code>.
436 *
437 * @param p The point on screen.
438 * @return The primitive that is nearest to the point p.
439 */
440 public OsmPrimitive getNearest(Point p) {
441 OsmPrimitive osm = getNearestNode(p);
442 if (osm == null)
443 {
444 osm = getNearestWay(p);
445 }
446 return osm;
447 }
448
449 /**
450 * Returns a singleton of the nearest object, or else an empty collection.
451 */
452 public Collection<OsmPrimitive> getNearestCollection(Point p) {
453 OsmPrimitive osm = getNearest(p);
454 if (osm == null)
455 return Collections.emptySet();
456 return Collections.singleton(osm);
457 }
458
459 /**
460 * @return A list of all objects that are nearest to
461 * the mouse.
462 *
463 * @return A collection of all items or <code>null</code>
464 * if no item under or near the point. The returned
465 * list is never empty.
466 */
467 public Collection<OsmPrimitive> getAllNearest(Point p) {
468 Collection<OsmPrimitive> nearest = new HashSet<OsmPrimitive>();
469 DataSet ds = getCurrentDataSet();
470 if (ds == null)
471 return null;
472 for (Way w : ds.searchWays(getSnapDistanceBBox(p))) {
473 if (!w.isUsable()) {
474 continue;
475 }
476 Node lastN = null;
477 for (Node n : w.getNodes()) {
478 if (!n.isUsable()) {
479 continue;
480 }
481 if (lastN == null) {
482 lastN = n;
483 continue;
484 }
485 Point A = getPoint(lastN);
486 Point B = getPoint(n);
487 double c = A.distanceSq(B);
488 double a = p.distanceSq(B);
489 double b = p.distanceSq(A);
490 double perDist = a - (a - b + c) * (a - b + c) / 4 / c; // perpendicular distance squared
491 if (perDist < snapDistanceSq && a < c + snapDistanceSq && b < c + snapDistanceSq) {
492 nearest.add(w);
493 break;
494 }
495 lastN = n;
496 }
497 }
498 for (Node n : ds.searchNodes(getSnapDistanceBBox(p))) {
499 if (n.isUsable()
500 && getPoint(n).distanceSq(p) < snapDistanceSq) {
501 nearest.add(n);
502 }
503 }
504 return nearest.isEmpty() ? null : nearest;
505 }
506
507 /**
508 * @return A list of all nodes that are nearest to
509 * the mouse.
510 *
511 * @return A collection of all nodes or <code>null</code>
512 * if no node under or near the point. The returned
513 * list is never empty.
514 */
515 public Collection<Node> getNearestNodes(Point p) {
516 Collection<Node> nearest = new HashSet<Node>();
517 DataSet ds = getCurrentDataSet();
518 if (ds == null)
519 return null;
520
521 for (Node n : ds.searchNodes(getSnapDistanceBBox(p))) {
522 if (n.isUsable()
523 && getPoint(n).distanceSq(p) < snapDistanceSq) {
524 nearest.add(n);
525 }
526 }
527 return nearest.isEmpty() ? null : nearest;
528 }
529
530 /**
531 * @return the nearest nodes to the screen point given that is not
532 * in ignore.
533 *
534 * @param p the point for which to search the nearest segment.
535 * @param ignore a collection of nodes which are not to be returned.
536 * May be null.
537 */
538 public final Collection<Node> getNearestNodes(Point p, Collection<Node> ignore) {
539 Collection<Node> nearest = getNearestNodes(p);
540 if (nearest == null) return null;
541 if (ignore != null) {
542 nearest.removeAll(ignore);
543 }
544 return nearest.isEmpty() ? null : nearest;
545 }
546
547 /**
548 * @return The projection to be used in calculating stuff.
549 */
550 public Projection getProjection() {
551 return Main.proj;
552 }
553
554 public String helpTopic() {
555 String n = getClass().getName();
556 return n.substring(n.lastIndexOf('.')+1);
557 }
558}
Note: See TracBrowser for help on using the repository browser.