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

Last change on this file since 1923 was 1908, checked in by stoecker, 15 years ago

minor cleanup

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