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

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

fixed NPE

  • Property svn:eol-style set to native
File size: 18.1 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 DataSet ds = getCurrentDataSet();
295 if(ds == null)
296 return null;
297 for (Node n : ds.nodes) {
298 if (n.deleted || n.incomplete) {
299 continue;
300 }
301 Point sp = getPoint(n);
302 double dist = p.distanceSq(sp);
303 if (dist < minDistanceSq) {
304 minDistanceSq = dist;
305 minPrimitive = n;
306 }
307 // when multiple nodes on one point, prefer new or selected nodes
308 else if(dist == minDistanceSq && minPrimitive != null
309 && ((n.id == 0 && n.isSelected())
310 || (!minPrimitive.isSelected() && (n.isSelected() || n.id == 0)))) {
311 minPrimitive = n;
312 }
313 }
314 return minPrimitive;
315 }
316
317 /**
318 * @return all way segments within 10px of p, sorted by their
319 * perpendicular distance.
320 *
321 * @param p the point for which to search the nearest segment.
322 */
323 public final List<WaySegment> getNearestWaySegments(Point p) {
324 TreeMap<Double, List<WaySegment>> nearest = new TreeMap<Double, List<WaySegment>>();
325 DataSet ds = getCurrentDataSet();
326 if(ds == null)
327 return null;
328 for (Way w : ds.ways) {
329 if (w.deleted || w.incomplete) {
330 continue;
331 }
332 Node lastN = null;
333 int i = -2;
334 for (Node n : w.getNodes()) {
335 i++;
336 if (n.deleted || n.incomplete) {
337 continue;
338 }
339 if (lastN == null) {
340 lastN = n;
341 continue;
342 }
343
344 Point A = getPoint(lastN);
345 Point B = getPoint(n);
346 double c = A.distanceSq(B);
347 double a = p.distanceSq(B);
348 double b = p.distanceSq(A);
349 double perDist = a-(a-b+c)*(a-b+c)/4/c; // perpendicular distance squared
350 if (perDist < snapDistance && a < c+snapDistance && b < c+snapDistance) {
351 if(w.isSelected()) {
352 perDist -= 0.00001;
353 }
354 List<WaySegment> l;
355 if (nearest.containsKey(perDist)) {
356 l = nearest.get(perDist);
357 } else {
358 l = new LinkedList<WaySegment>();
359 nearest.put(perDist, l);
360 }
361 l.add(new WaySegment(w, i));
362 }
363
364 lastN = n;
365 }
366 }
367 ArrayList<WaySegment> nearestList = new ArrayList<WaySegment>();
368 for (List<WaySegment> wss : nearest.values()) {
369 nearestList.addAll(wss);
370 }
371 return nearestList;
372 }
373
374 /**
375 * @return the nearest way segment to the screen point given that is not
376 * in ignore.
377 *
378 * @param p the point for which to search the nearest segment.
379 * @param ignore a collection of segments which are not to be returned.
380 * May be null.
381 */
382 public final WaySegment getNearestWaySegment(Point p, Collection<WaySegment> ignore) {
383 List<WaySegment> nearest = getNearestWaySegments(p);
384 if(nearest == null)
385 return null;
386 if (ignore != null) {
387 nearest.removeAll(ignore);
388 }
389 return nearest.isEmpty() ? null : nearest.get(0);
390 }
391
392 /**
393 * @return the nearest way segment to the screen point given.
394 */
395 public final WaySegment getNearestWaySegment(Point p) {
396 return getNearestWaySegment(p, null);
397 }
398
399 /**
400 * @return the nearest way to the screen point given.
401 */
402 public final Way getNearestWay(Point p) {
403 WaySegment nearestWaySeg = getNearestWaySegment(p);
404 return nearestWaySeg == null ? null : nearestWaySeg.way;
405 }
406
407 /**
408 * Return the object, that is nearest to the given screen point.
409 *
410 * First, a node will be searched. If a node within 10 pixel is found, the
411 * nearest node is returned.
412 *
413 * If no node is found, search for near ways.
414 *
415 * If nothing is found, return <code>null</code>.
416 *
417 * @param p The point on screen.
418 * @return The primitive that is nearest to the point p.
419 */
420 public OsmPrimitive getNearest(Point p) {
421 OsmPrimitive osm = getNearestNode(p);
422 if (osm == null)
423 {
424 osm = getNearestWay(p);
425 }
426 return osm;
427 }
428
429 /**
430 * Returns a singleton of the nearest object, or else an empty collection.
431 */
432 public Collection<OsmPrimitive> getNearestCollection(Point p) {
433 OsmPrimitive osm = getNearest(p);
434 if (osm == null)
435 return Collections.emptySet();
436 return Collections.singleton(osm);
437 }
438
439 /**
440 * @return A list of all objects that are nearest to
441 * the mouse. Does a simple sequential scan on all the data.
442 *
443 * @return A collection of all items or <code>null</code>
444 * if no item under or near the point. The returned
445 * list is never empty.
446 */
447 public Collection<OsmPrimitive> getAllNearest(Point p) {
448 Collection<OsmPrimitive> nearest = new HashSet<OsmPrimitive>();
449 DataSet ds = getCurrentDataSet();
450 if(ds == null)
451 return null;
452 for (Way w : ds.ways) {
453 if (w.deleted || w.incomplete) {
454 continue;
455 }
456 Node lastN = null;
457 for (Node n : w.getNodes()) {
458 if (n.deleted || n.incomplete) {
459 continue;
460 }
461 if (lastN == null) {
462 lastN = n;
463 continue;
464 }
465 Point A = getPoint(lastN);
466 Point B = getPoint(n);
467 double c = A.distanceSq(B);
468 double a = p.distanceSq(B);
469 double b = p.distanceSq(A);
470 double perDist = a-(a-b+c)*(a-b+c)/4/c; // perpendicular distance squared
471 if (perDist < snapDistance && a < c+snapDistance && b < c+snapDistance) {
472 nearest.add(w);
473 break;
474 }
475 lastN = n;
476 }
477 }
478 for (Node n : ds.nodes) {
479 if (!n.deleted && !n.incomplete
480 && getPoint(n).distanceSq(p) < snapDistance) {
481 nearest.add(n);
482 }
483 }
484 return nearest.isEmpty() ? null : nearest;
485 }
486
487 /**
488 * @return A list of all nodes that are nearest to
489 * the mouse. Does a simple sequential scan on all the data.
490 *
491 * @return A collection of all nodes or <code>null</code>
492 * if no node under or near the point. The returned
493 * list is never empty.
494 */
495 public Collection<Node> getNearestNodes(Point p) {
496 Collection<Node> nearest = new HashSet<Node>();
497 DataSet ds = getCurrentDataSet();
498 if(ds == null)
499 return null;
500 for (Node n : ds.nodes) {
501 if (!n.deleted && !n.incomplete
502 && getPoint(n).distanceSq(p) < snapDistance) {
503 nearest.add(n);
504 }
505 }
506 return nearest.isEmpty() ? null : nearest;
507 }
508
509 /**
510 * @return the nearest nodes to the screen point given that is not
511 * in ignore.
512 *
513 * @param p the point for which to search the nearest segment.
514 * @param ignore a collection of nodes which are not to be returned.
515 * May be null.
516 */
517 public final Collection<Node> getNearestNodes(Point p, Collection<Node> ignore) {
518 Collection<Node> nearest = getNearestNodes(p);
519 if (nearest == null) return null;
520 if (ignore != null) {
521 nearest.removeAll(ignore);
522 }
523 return nearest.isEmpty() ? null : nearest;
524 }
525
526 /**
527 * @return The projection to be used in calculating stuff.
528 */
529 public Projection getProjection() {
530 return Main.proj;
531 }
532
533 public String helpTopic() {
534 String n = getClass().getName();
535 return n.substring(n.lastIndexOf('.')+1);
536 }
537}
Note: See TracBrowser for help on using the repository browser.