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

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

little bit more refactoring of coordinate access - patch by jttt

  • Property svn:eol-style set to native
File size: 12.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.actions.HelpAction.Helpful;
18import org.openstreetmap.josm.data.coor.EastNorth;
19import org.openstreetmap.josm.data.coor.LatLon;
20import org.openstreetmap.josm.data.osm.DataSet;
21import org.openstreetmap.josm.data.osm.Node;
22import org.openstreetmap.josm.data.osm.OsmPrimitive;
23import org.openstreetmap.josm.data.osm.Way;
24import org.openstreetmap.josm.data.osm.WaySegment;
25import org.openstreetmap.josm.data.projection.Projection;
26
27/**
28 * An component that can be navigated by a mapmover. Used as map view and for the
29 * zoomer in the download dialog.
30 *
31 * @author imi
32 */
33public class NavigatableComponent extends JComponent implements Helpful {
34
35 public static final EastNorth world = Main.proj.latlon2eastNorth(new LatLon(Projection.MAX_LAT, Projection.MAX_LON));
36 public static final int snapDistance = sqr(Main.pref.getInteger("node.snap-distance", 10));
37
38 private static int sqr(int a) { return a*a;}
39 /**
40 * The scale factor in x or y-units per pixel. This means, if scale = 10,
41 * every physical pixel on screen are 10 x or 10 y units in the
42 * northing/easting space of the projection.
43 */
44 protected double scale;
45 /**
46 * Center n/e coordinate of the desired screen center.
47 */
48 protected EastNorth center = new EastNorth(0, 0);
49
50 public NavigatableComponent() {
51 setLayout(null);
52 }
53
54 protected DataSet getData()
55 {
56 return Main.ds;
57 }
58
59 /**
60 * Return the OSM-conform zoom factor (0 for whole world, 1 for half, 2 for quarter...)
61 */
62 public int zoom() {
63 double sizex = scale * getWidth();
64 double sizey = scale * getHeight();
65 for (int zoom = 0; zoom <= 32; zoom++, sizex *= 2, sizey *= 2)
66 if (sizex > world.east() || sizey > world.north())
67 return zoom;
68 return 32;
69 }
70
71 /**
72 * Return a ID which is unique as long as viewport dimensions are the same
73 */
74 public Integer getViewID()
75 {
76 String x = center.east() + "_" + center.north() + "_" + scale + "_" +
77 getWidth() + "_" + getHeight();
78 java.util.zip.CRC32 id = new java.util.zip.CRC32();
79 id.update(x.getBytes());
80 return new Long(id.getValue()).intValue();
81 }
82
83 /**
84 * Return the current scale value.
85 * @return The scale value currently used in display
86 */
87 public double getScale() {
88 return scale;
89 }
90
91 /**
92 * @return Returns the center point. A copy is returned, so users cannot
93 * change the center by accessing the return value. Use zoomTo instead.
94 */
95 public EastNorth getCenter() {
96 return center;
97 }
98
99 /**
100 * @param x X-Pixelposition to get coordinate from
101 * @param y Y-Pixelposition to get coordinate from
102 *
103 * @return Geographic coordinates from a specific pixel coordination
104 * on the screen.
105 */
106 public EastNorth getEastNorth(int x, int y) {
107 return new EastNorth(
108 center.east() + (x - getWidth()/2.0)*scale,
109 center.north() - (y - getHeight()/2.0)*scale);
110 }
111
112 /**
113 * @param x X-Pixelposition to get coordinate from
114 * @param y Y-Pixelposition to get coordinate from
115 *
116 * @return Geographic unprojected coordinates from a specific pixel coordination
117 * on the screen.
118 */
119 public LatLon getLatLon(int x, int y) {
120
121 return getProjection().eastNorth2latlon(getEastNorth(x, y));
122 }
123
124 /**
125 * Return the point on the screen where this Coordinate would be.
126 * @param p The point, where this geopoint would be drawn.
127 * @return The point on screen where "point" would be drawn, relative
128 * to the own top/left.
129 */
130 public Point getPoint(EastNorth p) {
131 if(null == p)
132 return new Point();
133 double x = (p.east()-center.east())/scale + getWidth()/2;
134 double y = (center.north()-p.north())/scale + getHeight()/2;
135 return new Point((int)x,(int)y);
136 }
137
138 /**
139 * Zoom to the given coordinate.
140 * @param newCenter The center x-value (easting) to zoom to.
141 * @param scale The scale to use.
142 */
143 public void zoomTo(EastNorth newCenter, double scale) {
144 center = newCenter;
145 getProjection().eastNorth2latlon(center);
146 this.scale = scale;
147 repaint();
148 }
149
150 /**
151 * Return the nearest point to the screen point given.
152 * If a node within snapDistance pixel is found, the nearest node is returned.
153 */
154 public final Node getNearestNode(Point p) {
155 double minDistanceSq = snapDistance;
156 Node minPrimitive = null;
157 for (Node n : getData().nodes) {
158 if (n.deleted || n.incomplete)
159 continue;
160 Point sp = getPoint(n.getEastNorth());
161 double dist = p.distanceSq(sp);
162 if (dist < minDistanceSq) {
163 minDistanceSq = dist;
164 minPrimitive = n;
165 }
166 // when multiple nodes on one point, prefer new or selected nodes
167 else if(dist == minDistanceSq && minPrimitive != null
168 && ((n.id == 0 && n.selected)
169 || (!minPrimitive.selected && (n.selected || n.id == 0))))
170 minPrimitive = n;
171 }
172 return minPrimitive;
173 }
174
175 /**
176 * @return all way segments within 10px of p, sorted by their
177 * perpendicular distance.
178 *
179 * @param p the point for which to search the nearest segment.
180 */
181 public final List<WaySegment> getNearestWaySegments(Point p) {
182 TreeMap<Double, List<WaySegment>> nearest = new TreeMap<Double, List<WaySegment>>();
183 for (Way w : getData().ways) {
184 if (w.deleted || w.incomplete) continue;
185 Node lastN = null;
186 int i = -2;
187 for (Node n : w.nodes) {
188 i++;
189 if (n.deleted || n.incomplete) continue;
190 if (lastN == null) {
191 lastN = n;
192 continue;
193 }
194
195 Point A = getPoint(lastN.getEastNorth());
196 Point B = getPoint(n.getEastNorth());
197 double c = A.distanceSq(B);
198 double a = p.distanceSq(B);
199 double b = p.distanceSq(A);
200 double perDist = a-(a-b+c)*(a-b+c)/4/c; // perpendicular distance squared
201 if (perDist < snapDistance && a < c+snapDistance && b < c+snapDistance) {
202 if(w.selected) // prefer selected ways a little bit
203 perDist -= 0.00001;
204 List<WaySegment> l;
205 if (nearest.containsKey(perDist)) {
206 l = nearest.get(perDist);
207 } else {
208 l = new LinkedList<WaySegment>();
209 nearest.put(perDist, l);
210 }
211 l.add(new WaySegment(w, i));
212 }
213
214 lastN = n;
215 }
216 }
217 ArrayList<WaySegment> nearestList = new ArrayList<WaySegment>();
218 for (List<WaySegment> wss : nearest.values()) {
219 nearestList.addAll(wss);
220 }
221 return nearestList;
222 }
223
224 /**
225 * @return the nearest way segment to the screen point given that is not
226 * in ignore.
227 *
228 * @param p the point for which to search the nearest segment.
229 * @param ignore a collection of segments which are not to be returned.
230 * May be null.
231 */
232 public final WaySegment getNearestWaySegment(Point p, Collection<WaySegment> ignore) {
233 List<WaySegment> nearest = getNearestWaySegments(p);
234 if (ignore != null) nearest.removeAll(ignore);
235 return nearest.isEmpty() ? null : nearest.get(0);
236 }
237
238 /**
239 * @return the nearest way segment to the screen point given.
240 */
241 public final WaySegment getNearestWaySegment(Point p) {
242 return getNearestWaySegment(p, null);
243 }
244
245 /**
246 * @return the nearest way to the screen point given.
247 */
248 public final Way getNearestWay(Point p) {
249 WaySegment nearestWaySeg = getNearestWaySegment(p);
250 return nearestWaySeg == null ? null : nearestWaySeg.way;
251 }
252
253 /**
254 * Return the object, that is nearest to the given screen point.
255 *
256 * First, a node will be searched. If a node within 10 pixel is found, the
257 * nearest node is returned.
258 *
259 * If no node is found, search for near ways.
260 *
261 * If nothing is found, return <code>null</code>.
262 *
263 * @param p The point on screen.
264 * @return The primitive that is nearest to the point p.
265 */
266 public OsmPrimitive getNearest(Point p) {
267 OsmPrimitive osm = getNearestNode(p);
268 if (osm == null)
269 {
270 osm = getNearestWay(p);
271 }
272 return osm;
273 }
274
275 /**
276 * Returns a singleton of the nearest object, or else an empty collection.
277 */
278 public Collection<OsmPrimitive> getNearestCollection(Point p) {
279 OsmPrimitive osm = getNearest(p);
280 if (osm == null)
281 return Collections.emptySet();
282 return Collections.singleton(osm);
283 }
284
285 /**
286 * @return A list of all objects that are nearest to
287 * the mouse. Does a simple sequential scan on all the data.
288 *
289 * @return A collection of all items or <code>null</code>
290 * if no item under or near the point. The returned
291 * list is never empty.
292 */
293 public Collection<OsmPrimitive> getAllNearest(Point p) {
294 Collection<OsmPrimitive> nearest = new HashSet<OsmPrimitive>();
295 for (Way w : getData().ways) {
296 if (w.deleted || w.incomplete) continue;
297 Node lastN = null;
298 for (Node n : w.nodes) {
299 if (n.deleted || n.incomplete) continue;
300 if (lastN == null) {
301 lastN = n;
302 continue;
303 }
304 Point A = getPoint(lastN.getEastNorth());
305 Point B = getPoint(n.getEastNorth());
306 double c = A.distanceSq(B);
307 double a = p.distanceSq(B);
308 double b = p.distanceSq(A);
309 double perDist = a-(a-b+c)*(a-b+c)/4/c; // perpendicular distance squared
310 if (perDist < snapDistance && a < c+snapDistance && b < c+snapDistance) {
311 nearest.add(w);
312 break;
313 }
314 lastN = n;
315 }
316 }
317 for (Node n : getData().nodes) {
318 if (!n.deleted && !n.incomplete
319 && getPoint(n.getEastNorth()).distanceSq(p) < snapDistance) {
320 nearest.add(n);
321 }
322 }
323 return nearest.isEmpty() ? null : nearest;
324 }
325
326 /**
327 * @return A list of all nodes that are nearest to
328 * the mouse. Does a simple sequential scan on all the data.
329 *
330 * @return A collection of all nodes or <code>null</code>
331 * if no node under or near the point. The returned
332 * list is never empty.
333 */
334 public Collection<Node> getNearestNodes(Point p) {
335 Collection<Node> nearest = new HashSet<Node>();
336 for (Node n : getData().nodes) {
337 if (!n.deleted && !n.incomplete
338 && getPoint(n.getEastNorth()).distanceSq(p) < snapDistance) {
339 nearest.add(n);
340 }
341 }
342 return nearest.isEmpty() ? null : nearest;
343 }
344
345 /**
346 * @return the nearest nodes to the screen point given that is not
347 * in ignore.
348 *
349 * @param p the point for which to search the nearest segment.
350 * @param ignore a collection of nodes which are not to be returned.
351 * May be null.
352 */
353 public final Collection<Node> getNearestNodes(Point p, Collection<Node> ignore) {
354 Collection<Node> nearest = getNearestNodes(p);
355 if (nearest == null) return null;
356 if (ignore != null) nearest.removeAll(ignore);
357 return nearest.isEmpty() ? null : nearest;
358 }
359
360 /**
361 * @return The projection to be used in calculating stuff.
362 */
363 protected Projection getProjection() {
364 return Main.proj;
365 }
366
367 public String helpTopic() {
368 String n = getClass().getName();
369 return n.substring(n.lastIndexOf('.')+1);
370 }
371}
Note: See TracBrowser for help on using the repository browser.