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

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

Added parameter Bounds to MapView, draw only currently visible primitives in MapPaintVisititor

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