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

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

Large rework in projection handling - now allows only switching and more specific projections
TODO:

  • allow subprojections (i.e. settings for projections)
  • setup preferences for subprojections
  • better support of the new projection depending world bounds (how to handle valid data outside of world)
  • do not allow to zoom out of the world - zoom should stop when whole world is displayed
  • fix Lambert and SwissGrid to handle new OutOfWorld style and subprojections
  • fix new UTM projection
  • handle layers with fixed projection on projection change
  • allow easier projection switching (e.g. in menu)

NOTE:
This checkin very likely will cause problems. Please report or fix them. Older plugins may have trouble. The SVN plugins
have been fixed but may have problems nevertheless. This is a BIG change, but will make JOSMs internal structure much cleaner
and reduce lots of projection related problems.

  • Property svn:eol-style set to native
File size: 14.9 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.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.data.projection.Mercator;
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 getData()
57 {
58 return Main.ds;
59 }
60
61 /**
62 * Return a ID which is unique as long as viewport dimensions are the same
63 */
64 public Integer getViewID()
65 {
66 String x = center.east() + "_" + center.north() + "_" + scale + "_" +
67 getWidth() + "_" + getHeight() + "_" + getProjection().toString();
68 java.util.zip.CRC32 id = new java.util.zip.CRC32();
69 id.update(x.getBytes());
70 return new Long(id.getValue()).intValue();
71 }
72
73 public double getMapScale() {
74 /* TODO: we assume a pixel is 0.025mm */
75 return getDist100Pixel()/(0.00025*100);
76 }
77
78 public double getDist100Pixel()
79 {
80 LatLon ll1 = getLatLon(0,0);
81 LatLon ll2 = getLatLon(100,0);
82 return ll1.greatCircleDistance(ll2);
83 }
84
85 /**
86 * @return Returns the center point. A copy is returned, so users cannot
87 * change the center by accessing the return value. Use zoomTo instead.
88 */
89 public EastNorth getCenter() {
90 return center;
91 }
92
93 /**
94 * @param x X-Pixelposition to get coordinate from
95 * @param y Y-Pixelposition to get coordinate from
96 *
97 * @return Geographic coordinates from a specific pixel coordination
98 * on the screen.
99 */
100 public EastNorth getEastNorth(int x, int y) {
101 return new EastNorth(
102 center.east() + (x - getWidth()/2.0)*scale,
103 center.north() - (y - getHeight()/2.0)*scale);
104 }
105
106 public ProjectionBounds getProjectionBounds() {
107 return new ProjectionBounds(
108 new EastNorth(
109 center.east() - getWidth()/2.0*scale,
110 center.north() - getHeight()/2.0*scale),
111 new EastNorth(
112 center.east() + getWidth()/2.0*scale,
113 center.north() + getHeight()/2.0*scale));
114 };
115
116 public Bounds getRealBounds() {
117 return new Bounds(
118 getProjection().eastNorth2latlon(new EastNorth(
119 center.east() - getWidth()/2.0*scale,
120 center.north() - getHeight()/2.0*scale)),
121 getProjection().eastNorth2latlon(new EastNorth(
122 center.east() + getWidth()/2.0*scale,
123 center.north() + getHeight()/2.0*scale)));
124 };
125
126 /**
127 * @param x X-Pixelposition to get coordinate from
128 * @param y Y-Pixelposition to get coordinate from
129 *
130 * @return Geographic unprojected coordinates from a specific pixel coordination
131 * on the screen.
132 */
133 public LatLon getLatLon(int x, int y) {
134 return getProjection().eastNorth2latlon(getEastNorth(x, y));
135 }
136
137 /**
138 * Return the point on the screen where this Coordinate would be.
139 * @param p The point, where this geopoint would be drawn.
140 * @return The point on screen where "point" would be drawn, relative
141 * to the own top/left.
142 */
143 public Point getPoint(EastNorth p) {
144 if(null == p)
145 return new Point();
146 double x = (p.east()-center.east())/scale + getWidth()/2;
147 double y = (center.north()-p.north())/scale + getHeight()/2;
148 return new Point((int)x,(int)y);
149 }
150
151 /**
152 * Zoom to the given coordinate.
153 * @param newCenter The center x-value (easting) to zoom to.
154 * @param scale The scale to use.
155 */
156 private void zoomTo(EastNorth newCenter, double newScale) {
157/* TODO: check that newCenter is really inside visible world and that scale is correct, don't allow zooming out to much */
158 boolean rep = false;
159 if(!newCenter.equals(center))
160 {
161 EastNorth oldCenter = center;
162 center = newCenter;
163 rep = true;
164 firePropertyChange("center", oldCenter, newCenter);
165 }
166 if(scale != newScale)
167 {
168 double oldScale = scale;
169 scale = newScale;
170 rep = true;
171 firePropertyChange("scale", oldScale, newScale);
172 }
173 if(rep)
174 repaint();
175 }
176
177 public void zoomTo(EastNorth newCenter) {
178 zoomTo(newCenter, scale);
179 }
180
181 public void zoomToFactor(double x, double y, double factor) {
182 double newScale = scale*factor;
183 // New center position so that point under the mouse pointer stays the same place as it was before zooming
184 // You will get the formula by simplifying this expression: newCenter = oldCenter + mouseCoordinatesInNewZoom - mouseCoordinatesInOldZoom
185 zoomTo(new EastNorth(
186 center.east() - (x - getWidth()/2.0) * (newScale - scale),
187 center.north() + (y - getHeight()/2.0) * (newScale - scale)),
188 newScale);
189 }
190
191 public void zoomToFactor(EastNorth newCenter, double factor) {
192 zoomTo(newCenter, scale*factor);
193 }
194
195 public void zoomToFactor(double factor) {
196 zoomTo(center, scale*factor);
197 }
198
199 public void zoomTo(ProjectionBounds box) {
200 // -20 to leave some border
201 int w = getWidth()-20;
202 if (w < 20)
203 w = 20;
204 int h = getHeight()-20;
205 if (h < 20)
206 h = 20;
207
208 double scaleX = (box.max.east()-box.min.east())/w;
209 double scaleY = (box.max.north()-box.min.north())/h;
210 double newScale = Math.max(scaleX, scaleY);
211
212 zoomTo(box.getCenter(), newScale);
213 }
214
215 public void zoomTo(Bounds box) {
216 zoomTo(new ProjectionBounds(getProjection().latlon2eastNorth(box.min),
217 getProjection().latlon2eastNorth(box.max)));
218 }
219
220 /**
221 * Return the nearest point to the screen point given.
222 * If a node within snapDistance pixel is found, the nearest node is returned.
223 */
224 public final Node getNearestNode(Point p) {
225 double minDistanceSq = snapDistance;
226 Node minPrimitive = null;
227 for (Node n : getData().nodes) {
228 if (n.deleted || n.incomplete)
229 continue;
230 Point sp = getPoint(n.getEastNorth());
231 double dist = p.distanceSq(sp);
232 if (dist < minDistanceSq) {
233 minDistanceSq = dist;
234 minPrimitive = n;
235 }
236 // when multiple nodes on one point, prefer new or selected nodes
237 else if(dist == minDistanceSq && minPrimitive != null
238 && ((n.id == 0 && n.selected)
239 || (!minPrimitive.selected && (n.selected || n.id == 0))))
240 minPrimitive = n;
241 }
242 return minPrimitive;
243 }
244
245 /**
246 * @return all way segments within 10px of p, sorted by their
247 * perpendicular distance.
248 *
249 * @param p the point for which to search the nearest segment.
250 */
251 public final List<WaySegment> getNearestWaySegments(Point p) {
252 TreeMap<Double, List<WaySegment>> nearest = new TreeMap<Double, List<WaySegment>>();
253 for (Way w : getData().ways) {
254 if (w.deleted || w.incomplete) continue;
255 Node lastN = null;
256 int i = -2;
257 for (Node n : w.nodes) {
258 i++;
259 if (n.deleted || n.incomplete) continue;
260 if (lastN == null) {
261 lastN = n;
262 continue;
263 }
264
265 Point A = getPoint(lastN.getEastNorth());
266 Point B = getPoint(n.getEastNorth());
267 double c = A.distanceSq(B);
268 double a = p.distanceSq(B);
269 double b = p.distanceSq(A);
270 double perDist = a-(a-b+c)*(a-b+c)/4/c; // perpendicular distance squared
271 if (perDist < snapDistance && a < c+snapDistance && b < c+snapDistance) {
272 if(w.selected) // prefer selected ways a little bit
273 perDist -= 0.00001;
274 List<WaySegment> l;
275 if (nearest.containsKey(perDist)) {
276 l = nearest.get(perDist);
277 } else {
278 l = new LinkedList<WaySegment>();
279 nearest.put(perDist, l);
280 }
281 l.add(new WaySegment(w, i));
282 }
283
284 lastN = n;
285 }
286 }
287 ArrayList<WaySegment> nearestList = new ArrayList<WaySegment>();
288 for (List<WaySegment> wss : nearest.values()) {
289 nearestList.addAll(wss);
290 }
291 return nearestList;
292 }
293
294 /**
295 * @return the nearest way segment to the screen point given that is not
296 * in ignore.
297 *
298 * @param p the point for which to search the nearest segment.
299 * @param ignore a collection of segments which are not to be returned.
300 * May be null.
301 */
302 public final WaySegment getNearestWaySegment(Point p, Collection<WaySegment> ignore) {
303 List<WaySegment> nearest = getNearestWaySegments(p);
304 if (ignore != null) nearest.removeAll(ignore);
305 return nearest.isEmpty() ? null : nearest.get(0);
306 }
307
308 /**
309 * @return the nearest way segment to the screen point given.
310 */
311 public final WaySegment getNearestWaySegment(Point p) {
312 return getNearestWaySegment(p, null);
313 }
314
315 /**
316 * @return the nearest way to the screen point given.
317 */
318 public final Way getNearestWay(Point p) {
319 WaySegment nearestWaySeg = getNearestWaySegment(p);
320 return nearestWaySeg == null ? null : nearestWaySeg.way;
321 }
322
323 /**
324 * Return the object, that is nearest to the given screen point.
325 *
326 * First, a node will be searched. If a node within 10 pixel is found, the
327 * nearest node is returned.
328 *
329 * If no node is found, search for near ways.
330 *
331 * If nothing is found, return <code>null</code>.
332 *
333 * @param p The point on screen.
334 * @return The primitive that is nearest to the point p.
335 */
336 public OsmPrimitive getNearest(Point p) {
337 OsmPrimitive osm = getNearestNode(p);
338 if (osm == null)
339 {
340 osm = getNearestWay(p);
341 }
342 return osm;
343 }
344
345 /**
346 * Returns a singleton of the nearest object, or else an empty collection.
347 */
348 public Collection<OsmPrimitive> getNearestCollection(Point p) {
349 OsmPrimitive osm = getNearest(p);
350 if (osm == null)
351 return Collections.emptySet();
352 return Collections.singleton(osm);
353 }
354
355 /**
356 * @return A list of all objects that are nearest to
357 * the mouse. Does a simple sequential scan on all the data.
358 *
359 * @return A collection of all items or <code>null</code>
360 * if no item under or near the point. The returned
361 * list is never empty.
362 */
363 public Collection<OsmPrimitive> getAllNearest(Point p) {
364 Collection<OsmPrimitive> nearest = new HashSet<OsmPrimitive>();
365 for (Way w : getData().ways) {
366 if (w.deleted || w.incomplete) continue;
367 Node lastN = null;
368 for (Node n : w.nodes) {
369 if (n.deleted || n.incomplete) continue;
370 if (lastN == null) {
371 lastN = n;
372 continue;
373 }
374 Point A = getPoint(lastN.getEastNorth());
375 Point B = getPoint(n.getEastNorth());
376 double c = A.distanceSq(B);
377 double a = p.distanceSq(B);
378 double b = p.distanceSq(A);
379 double perDist = a-(a-b+c)*(a-b+c)/4/c; // perpendicular distance squared
380 if (perDist < snapDistance && a < c+snapDistance && b < c+snapDistance) {
381 nearest.add(w);
382 break;
383 }
384 lastN = n;
385 }
386 }
387 for (Node n : getData().nodes) {
388 if (!n.deleted && !n.incomplete
389 && getPoint(n.getEastNorth()).distanceSq(p) < snapDistance) {
390 nearest.add(n);
391 }
392 }
393 return nearest.isEmpty() ? null : nearest;
394 }
395
396 /**
397 * @return A list of all nodes that are nearest to
398 * the mouse. Does a simple sequential scan on all the data.
399 *
400 * @return A collection of all nodes or <code>null</code>
401 * if no node under or near the point. The returned
402 * list is never empty.
403 */
404 public Collection<Node> getNearestNodes(Point p) {
405 Collection<Node> nearest = new HashSet<Node>();
406 for (Node n : getData().nodes) {
407 if (!n.deleted && !n.incomplete
408 && getPoint(n.getEastNorth()).distanceSq(p) < snapDistance) {
409 nearest.add(n);
410 }
411 }
412 return nearest.isEmpty() ? null : nearest;
413 }
414
415 /**
416 * @return the nearest nodes to the screen point given that is not
417 * in ignore.
418 *
419 * @param p the point for which to search the nearest segment.
420 * @param ignore a collection of nodes which are not to be returned.
421 * May be null.
422 */
423 public final Collection<Node> getNearestNodes(Point p, Collection<Node> ignore) {
424 Collection<Node> nearest = getNearestNodes(p);
425 if (nearest == null) return null;
426 if (ignore != null) nearest.removeAll(ignore);
427 return nearest.isEmpty() ? null : nearest;
428 }
429
430 /**
431 * @return The projection to be used in calculating stuff.
432 */
433 protected Projection getProjection() {
434 return Main.proj;
435 }
436
437 public String helpTopic() {
438 String n = getClass().getName();
439 return n.substring(n.lastIndexOf('.')+1);
440 }
441}
Note: See TracBrowser for help on using the repository browser.