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

Last change on this file since 2273 was 2273, checked in by jttt, 15 years ago

Replace testing for id <= 0 with isNew() method

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