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

Last change on this file since 2104 was 2025, checked in by Gubaer, 15 years ago

new: improved dialog for uploading/saving modified layers on exit
new: improved dialog for uploading/saving modified layers if layers are deleted
new: new progress monitor which can delegate rendering to any Swing component
more setters/getters for properties in OSM data classes (fields are @deprecated); started to update references in the code base

  • Property svn:eol-style set to native
File size: 18.2 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 }
194 if (!newCenter.equals(center)) {
195 EastNorth oldCenter = center;
196 center = newCenter;
197 rep = true;
198 firePropertyChange("center", oldCenter, newCenter);
199 }
200
201 int width = getWidth()/2;
202 int height = getHeight()/2;
203 LatLon l1 = new LatLon(b.min.lat(), lon);
204 LatLon l2 = new LatLon(b.max.lat(), lon);
205 EastNorth e1 = getProjection().latlon2eastNorth(l1);
206 EastNorth e2 = getProjection().latlon2eastNorth(l2);
207 double d = e2.north() - e1.north();
208 if(d < height*newScale)
209 {
210 double newScaleH = d/height;
211 e1 = getProjection().latlon2eastNorth(new LatLon(lat, b.min.lon()));
212 e2 = getProjection().latlon2eastNorth(new LatLon(lat, b.max.lon()));
213 d = e2.east() - e1.east();
214 if(d < width*newScale) {
215 newScale = Math.max(newScaleH, d/width);
216 }
217 }
218 else
219 {
220 d = d/(l1.greatCircleDistance(l2)*height*10);
221 if(newScale < d) {
222 newScale = d;
223 }
224 }
225 if (scale != newScale) {
226 double oldScale = scale;
227 scale = newScale;
228 rep = true;
229 firePropertyChange("scale", oldScale, newScale);
230 }
231
232 if(rep) {
233 repaint();
234 }
235 }
236
237 public void zoomTo(EastNorth newCenter) {
238 zoomTo(newCenter, scale);
239 }
240
241 public void zoomTo(LatLon newCenter) {
242 if(newCenter instanceof CachedLatLon) {
243 zoomTo(((CachedLatLon)newCenter).getEastNorth(), scale);
244 } else {
245 zoomTo(getProjection().latlon2eastNorth(newCenter), scale);
246 }
247 }
248
249 public void zoomToFactor(double x, double y, double factor) {
250 double newScale = scale*factor;
251 // New center position so that point under the mouse pointer stays the same place as it was before zooming
252 // You will get the formula by simplifying this expression: newCenter = oldCenter + mouseCoordinatesInNewZoom - mouseCoordinatesInOldZoom
253 zoomTo(new EastNorth(
254 center.east() - (x - getWidth()/2.0) * (newScale - scale),
255 center.north() + (y - getHeight()/2.0) * (newScale - scale)),
256 newScale);
257 }
258
259 public void zoomToFactor(EastNorth newCenter, double factor) {
260 zoomTo(newCenter, scale*factor);
261 }
262
263 public void zoomToFactor(double factor) {
264 zoomTo(center, scale*factor);
265 }
266
267 public void zoomTo(ProjectionBounds box) {
268 // -20 to leave some border
269 int w = getWidth()-20;
270 if (w < 20) {
271 w = 20;
272 }
273 int h = getHeight()-20;
274 if (h < 20) {
275 h = 20;
276 }
277
278 double scaleX = (box.max.east()-box.min.east())/w;
279 double scaleY = (box.max.north()-box.min.north())/h;
280 double newScale = Math.max(scaleX, scaleY);
281
282 zoomTo(box.getCenter(), newScale);
283 }
284
285 public void zoomTo(Bounds box) {
286 zoomTo(new ProjectionBounds(getProjection().latlon2eastNorth(box.min),
287 getProjection().latlon2eastNorth(box.max)));
288 }
289
290 /**
291 * Return the nearest point to the screen point given.
292 * If a node within snapDistance pixel is found, the nearest node is returned.
293 */
294 public final Node getNearestNode(Point p) {
295 double minDistanceSq = snapDistance;
296 Node minPrimitive = null;
297 DataSet ds = getCurrentDataSet();
298 if(ds == null)
299 return null;
300 for (Node n : ds.nodes) {
301 if (n.isDeleted() || n.incomplete) {
302 continue;
303 }
304 Point sp = getPoint(n);
305 double dist = p.distanceSq(sp);
306 if (dist < minDistanceSq) {
307 minDistanceSq = dist;
308 minPrimitive = n;
309 }
310 // when multiple nodes on one point, prefer new or selected nodes
311 else if(dist == minDistanceSq && minPrimitive != null
312 && ((n.getId() == 0 && n.isSelected())
313 || (!minPrimitive.isSelected() && (n.isSelected() || n.getId() == 0)))) {
314 minPrimitive = n;
315 }
316 }
317 return minPrimitive;
318 }
319
320 /**
321 * @return all way segments within 10px of p, sorted by their
322 * perpendicular distance.
323 *
324 * @param p the point for which to search the nearest segment.
325 */
326 public final List<WaySegment> getNearestWaySegments(Point p) {
327 TreeMap<Double, List<WaySegment>> nearest = new TreeMap<Double, List<WaySegment>>();
328 DataSet ds = getCurrentDataSet();
329 if(ds == null)
330 return null;
331 for (Way w : ds.ways) {
332 if (w.isDeleted() || w.incomplete) {
333 continue;
334 }
335 Node lastN = null;
336 int i = -2;
337 for (Node n : w.getNodes()) {
338 i++;
339 if (n.isDeleted() || n.incomplete) {
340 continue;
341 }
342 if (lastN == null) {
343 lastN = n;
344 continue;
345 }
346
347 Point A = getPoint(lastN);
348 Point B = getPoint(n);
349 double c = A.distanceSq(B);
350 double a = p.distanceSq(B);
351 double b = p.distanceSq(A);
352 double perDist = a-(a-b+c)*(a-b+c)/4/c; // perpendicular distance squared
353 if (perDist < snapDistance && a < c+snapDistance && b < c+snapDistance) {
354 if(w.isSelected()) {
355 perDist -= 0.00001;
356 }
357 List<WaySegment> l;
358 if (nearest.containsKey(perDist)) {
359 l = nearest.get(perDist);
360 } else {
361 l = new LinkedList<WaySegment>();
362 nearest.put(perDist, l);
363 }
364 l.add(new WaySegment(w, i));
365 }
366
367 lastN = n;
368 }
369 }
370 ArrayList<WaySegment> nearestList = new ArrayList<WaySegment>();
371 for (List<WaySegment> wss : nearest.values()) {
372 nearestList.addAll(wss);
373 }
374 return nearestList;
375 }
376
377 /**
378 * @return the nearest way segment to the screen point given that is not
379 * in ignore.
380 *
381 * @param p the point for which to search the nearest segment.
382 * @param ignore a collection of segments which are not to be returned.
383 * May be null.
384 */
385 public final WaySegment getNearestWaySegment(Point p, Collection<WaySegment> ignore) {
386 List<WaySegment> nearest = getNearestWaySegments(p);
387 if(nearest == null)
388 return null;
389 if (ignore != null) {
390 nearest.removeAll(ignore);
391 }
392 return nearest.isEmpty() ? null : nearest.get(0);
393 }
394
395 /**
396 * @return the nearest way segment to the screen point given.
397 */
398 public final WaySegment getNearestWaySegment(Point p) {
399 return getNearestWaySegment(p, null);
400 }
401
402 /**
403 * @return the nearest way to the screen point given.
404 */
405 public final Way getNearestWay(Point p) {
406 WaySegment nearestWaySeg = getNearestWaySegment(p);
407 return nearestWaySeg == null ? null : nearestWaySeg.way;
408 }
409
410 /**
411 * Return the object, that is nearest to the given screen point.
412 *
413 * First, a node will be searched. If a node within 10 pixel is found, the
414 * nearest node is returned.
415 *
416 * If no node is found, search for near ways.
417 *
418 * If nothing is found, return <code>null</code>.
419 *
420 * @param p The point on screen.
421 * @return The primitive that is nearest to the point p.
422 */
423 public OsmPrimitive getNearest(Point p) {
424 OsmPrimitive osm = getNearestNode(p);
425 if (osm == null)
426 {
427 osm = getNearestWay(p);
428 }
429 return osm;
430 }
431
432 /**
433 * Returns a singleton of the nearest object, or else an empty collection.
434 */
435 public Collection<OsmPrimitive> getNearestCollection(Point p) {
436 OsmPrimitive osm = getNearest(p);
437 if (osm == null)
438 return Collections.emptySet();
439 return Collections.singleton(osm);
440 }
441
442 /**
443 * @return A list of all objects that are nearest to
444 * the mouse. Does a simple sequential scan on all the data.
445 *
446 * @return A collection of all items or <code>null</code>
447 * if no item under or near the point. The returned
448 * list is never empty.
449 */
450 public Collection<OsmPrimitive> getAllNearest(Point p) {
451 Collection<OsmPrimitive> nearest = new HashSet<OsmPrimitive>();
452 DataSet ds = getCurrentDataSet();
453 if(ds == null)
454 return null;
455 for (Way w : ds.ways) {
456 if (w.isDeleted() || w.incomplete) {
457 continue;
458 }
459 Node lastN = null;
460 for (Node n : w.getNodes()) {
461 if (n.isDeleted() || n.incomplete) {
462 continue;
463 }
464 if (lastN == null) {
465 lastN = n;
466 continue;
467 }
468 Point A = getPoint(lastN);
469 Point B = getPoint(n);
470 double c = A.distanceSq(B);
471 double a = p.distanceSq(B);
472 double b = p.distanceSq(A);
473 double perDist = a-(a-b+c)*(a-b+c)/4/c; // perpendicular distance squared
474 if (perDist < snapDistance && a < c+snapDistance && b < c+snapDistance) {
475 nearest.add(w);
476 break;
477 }
478 lastN = n;
479 }
480 }
481 for (Node n : ds.nodes) {
482 if (!n.isDeleted() && !n.incomplete
483 && getPoint(n).distanceSq(p) < snapDistance) {
484 nearest.add(n);
485 }
486 }
487 return nearest.isEmpty() ? null : nearest;
488 }
489
490 /**
491 * @return A list of all nodes that are nearest to
492 * the mouse. Does a simple sequential scan on all the data.
493 *
494 * @return A collection of all nodes or <code>null</code>
495 * if no node under or near the point. The returned
496 * list is never empty.
497 */
498 public Collection<Node> getNearestNodes(Point p) {
499 Collection<Node> nearest = new HashSet<Node>();
500 DataSet ds = getCurrentDataSet();
501 if(ds == null)
502 return null;
503 for (Node n : ds.nodes) {
504 if (!n.isDeleted() && !n.incomplete
505 && getPoint(n).distanceSq(p) < snapDistance) {
506 nearest.add(n);
507 }
508 }
509 return nearest.isEmpty() ? null : nearest;
510 }
511
512 /**
513 * @return the nearest nodes to the screen point given that is not
514 * in ignore.
515 *
516 * @param p the point for which to search the nearest segment.
517 * @param ignore a collection of nodes which are not to be returned.
518 * May be null.
519 */
520 public final Collection<Node> getNearestNodes(Point p, Collection<Node> ignore) {
521 Collection<Node> nearest = getNearestNodes(p);
522 if (nearest == null) return null;
523 if (ignore != null) {
524 nearest.removeAll(ignore);
525 }
526 return nearest.isEmpty() ? null : nearest;
527 }
528
529 /**
530 * @return The projection to be used in calculating stuff.
531 */
532 public Projection getProjection() {
533 return Main.proj;
534 }
535
536 public String helpTopic() {
537 String n = getClass().getName();
538 return n.substring(n.lastIndexOf('.')+1);
539 }
540}
Note: See TracBrowser for help on using the repository browser.