source: josm/src/org/openstreetmap/josm/gui/MapView.java@ 22

Last change on this file since 22 was 22, checked in by imi, 18 years ago

starting restructure of dataset. Checkpoint is broken!

File size: 13.1 KB
Line 
1package org.openstreetmap.josm.gui;
2
3import java.awt.Color;
4import java.awt.Graphics;
5import java.awt.Point;
6import java.awt.event.ComponentAdapter;
7import java.awt.event.ComponentEvent;
8import java.beans.PropertyChangeEvent;
9import java.beans.PropertyChangeListener;
10import java.util.ArrayList;
11import java.util.Collection;
12import java.util.Collections;
13import java.util.LinkedList;
14
15import javax.swing.JComponent;
16import javax.swing.event.ChangeEvent;
17import javax.swing.event.ChangeListener;
18
19import org.openstreetmap.josm.Main;
20import org.openstreetmap.josm.command.DataSet;
21import org.openstreetmap.josm.data.Bounds;
22import org.openstreetmap.josm.data.GeoPoint;
23import org.openstreetmap.josm.data.osm.LineSegment;
24import org.openstreetmap.josm.data.osm.Node;
25import org.openstreetmap.josm.data.osm.OsmPrimitive;
26import org.openstreetmap.josm.data.osm.Track;
27import org.openstreetmap.josm.data.projection.Projection;
28import org.openstreetmap.josm.gui.layer.Layer;
29
30/**
31 * This is a component used in the MapFrame for browsing the map. It use is to
32 * provide the MapMode's enough capabilities to operate.
33 *
34 * MapView hold meta-data about the data set currently displayed, as scale level,
35 * center point viewed, what scrolling mode or editing mode is selected or with
36 * what projection the map is viewed etc..
37 *
38 * MapView is able to administrate several layers, but there must be always at
39 * least one layer with a dataset in it (Layer.getDataSet returning non-null).
40 *
41 * @author imi
42 */
43public class MapView extends JComponent implements ChangeListener, PropertyChangeListener {
44
45 /**
46 * Interface to notify listeners of the change of the active layer.
47 * @author imi
48 */
49 public interface LayerChangeListener {
50 void activeLayerChange(Layer oldLayer, Layer newLayer);
51 void layerAdded(Layer newLayer);
52 void layerRemoved(Layer oldLayer);
53 }
54
55 /**
56 * Whether to adjust the scale property on every resize.
57 */
58 boolean autoScale = true;
59
60 /**
61 * The scale factor in meter per pixel.
62 */
63 private double scale;
64 /**
65 * Center n/e coordinate of the desired screen center.
66 */
67 private GeoPoint center;
68
69 /**
70 * A list of all layers currently loaded.
71 */
72 private ArrayList<Layer> layers = new ArrayList<Layer>();
73 /**
74 * The layer from the layers list that is currently active.
75 */
76 private Layer activeLayer;
77 /**
78 * The listener of the active layer changes.
79 */
80 private Collection<LayerChangeListener> listeners = new LinkedList<LayerChangeListener>();
81
82 /**
83 * Construct a MapView.
84 * @param layer The first layer in the view.
85 */
86 public MapView(Layer layer) {
87 addComponentListener(new ComponentAdapter(){
88 @Override
89 public void componentResized(ComponentEvent e) {
90 recalculateCenterScale();
91 }
92 });
93
94 // initialize the movement listener
95 new MapMover(this);
96
97 // initialize the projection
98 addLayer(layer);
99 Main.pref.addPropertyChangeListener(this);
100
101 // init screen
102 recalculateCenterScale();
103 }
104
105 /**
106 * Add a layer to the current MapView. The layer will be added at topmost
107 * position.
108 */
109 public void addLayer(Layer layer) {
110 layers.add(0,layer);
111
112 if (layer.dataSet != null) {
113 // initialize the projection if it was the first layer
114 if (layers.size() == 1)
115 Main.pref.getProjection().init();
116
117 // initialize the dataset in the new layer
118 for (Node n : layer.dataSet.nodes)
119 Main.pref.getProjection().latlon2xy(n.coor);
120 }
121
122 for (LayerChangeListener l : listeners)
123 l.layerAdded(layer);
124
125 setActiveLayer(layer);
126 }
127
128 /**
129 * Remove the layer from the mapview. If the layer was in the list before,
130 * an LayerChange event is fired.
131 */
132 public void removeLayer(Layer layer) {
133 if (layers.remove(layer))
134 for (LayerChangeListener l : listeners)
135 l.layerRemoved(layer);
136 }
137
138 /**
139 * Moves the layer to the given new position. No event is fired.
140 * @param layer The layer to move
141 * @param pos The new position of the layer
142 */
143 public void moveLayer(Layer layer, int pos) {
144 int curLayerPos = layers.indexOf(layer);
145 if (curLayerPos == -1)
146 throw new IllegalArgumentException("layer not in list.");
147 if (pos == curLayerPos)
148 return; // already in place.
149 layers.remove(curLayerPos);
150 if (pos >= layers.size())
151 layers.add(layer);
152 else
153 layers.add(pos, layer);
154 }
155
156 /**
157 * Get geographic coordinates from a specific pixel coordination
158 * on the screen.
159 *
160 * If you don't need it, provide false at third parameter to speed
161 * up the calculation.
162 *
163 * @param x X-Pixelposition to get coordinate from
164 * @param y Y-Pixelposition to get coordinate from
165 * @param latlon If set, the return value will also have the
166 * latitude/longitude filled.
167 *
168 * @return The geographic coordinate, filled with x/y (northing/easting)
169 * settings and, if requested with latitude/longitude.
170 */
171 public GeoPoint getPoint(int x, int y, boolean latlon) {
172 GeoPoint p = new GeoPoint();
173 p.x = (x - getWidth()/2.0)*scale + center.x;
174 p.y = (getHeight()/2.0 - y)*scale + center.y;
175 if (latlon)
176 Main.pref.getProjection().xy2latlon(p);
177 return p;
178 }
179
180 /**
181 * Return the point on the screen where this GeoPoint would be.
182 * @param point The point, where this geopoint would be drawn.
183 * @return The point on screen where "point" would be drawn, relative
184 * to the own top/left.
185 */
186 public Point getScreenPoint(GeoPoint point) {
187 GeoPoint p;
188 if (!Double.isNaN(point.x) && !Double.isNaN(point.y))
189 p = point;
190 else {
191 if (Double.isNaN(point.lat) || Double.isNaN(point.lon))
192 throw new IllegalArgumentException("point: Either lat/lon or x/y must be set.");
193 p = point.clone();
194 Main.pref.getProjection().latlon2xy(p);
195 }
196 int x = ((int)Math.round((p.x-center.x) / scale + getWidth()/2));
197 int y = ((int)Math.round((center.y-p.y) / scale + getHeight()/2));
198 return new Point(x,y);
199 }
200
201 /**
202 * Return the object, that is nearest to the given screen point.
203 *
204 * First, a node will be searched. If a node within 10 pixel is found, the
205 * nearest node is returned.
206 *
207 * If no node is found, search for pending line segments.
208 *
209 * If no such line segment is found, and a non-pending line segment is
210 * within 10 pixel to p, this segment is returned, except when
211 * <code>wholeTrack</code> is <code>true</code>, in which case the
212 * corresponding Track is returned.
213 *
214 * If no line segment is found and the point is within an area, return that
215 * area.
216 *
217 * If no area is found, return <code>null</code>.
218 *
219 * @param p The point on screen.
220 * @param wholeTrack Whether the whole track or only the line segment
221 * should be returned.
222 * @return The primitive, that is nearest to the point p.
223 */
224 public OsmPrimitive getNearest(Point p, boolean wholeTrack) {
225 double minDistanceSq = Double.MAX_VALUE;
226 OsmPrimitive minPrimitive = null;
227
228 // nodes
229 for (Node n : Main.main.ds.nodes) {
230 Point sp = getScreenPoint(n.coor);
231 double dist = p.distanceSq(sp);
232 if (minDistanceSq > dist && dist < 100) {
233 minDistanceSq = p.distanceSq(sp);
234 minPrimitive = n;
235 }
236 }
237 if (minPrimitive != null)
238 return minPrimitive;
239
240 // pending line segments
241 for (LineSegment ls : Main.main.ds.pendingLineSegments()) {
242 Point A = getScreenPoint(ls.getStart().coor);
243 Point B = getScreenPoint(ls.getEnd().coor);
244 double c = A.distanceSq(B);
245 double a = p.distanceSq(B);
246 double b = p.distanceSq(A);
247 double perDist = a-(a-b+c)*(a-b+c)/4/c; // perpendicular distance squared
248 if (perDist < 100 && minDistanceSq > perDist && a < c+100 && b < c+100) {
249 minDistanceSq = perDist;
250 minPrimitive = ls;
251 }
252 }
253
254 // tracks & line segments
255 minDistanceSq = Double.MAX_VALUE;
256 for (Track t : Main.main.ds.tracks()) {
257 for (LineSegment ls : t.segments()) {
258 Point A = getScreenPoint(ls.getStart().coor);
259 Point B = getScreenPoint(ls.getEnd().coor);
260 double c = A.distanceSq(B);
261 double a = p.distanceSq(B);
262 double b = p.distanceSq(A);
263 double perDist = a-(a-b+c)*(a-b+c)/4/c; // perpendicular distance squared
264 if (perDist < 100 && minDistanceSq > perDist && a < c+100 && b < c+100) {
265 minDistanceSq = perDist;
266 minPrimitive = wholeTrack ? t : ls;
267 }
268 }
269 }
270 if (minPrimitive != null)
271 return minPrimitive;
272
273 // TODO areas
274
275 return null; // nothing found
276 }
277
278
279 /**
280 * Zoom to the given coordinate.
281 * @param centerX The center x-value (easting) to zoom to.
282 * @param centerY The center y-value (northing) to zoom to.
283 * @param scale The scale to use.
284 */
285 public void zoomTo(GeoPoint newCenter, double scale) {
286 boolean oldAutoScale = autoScale;
287 GeoPoint oldCenter = center;
288 double oldScale = this.scale;
289
290 autoScale = false;
291 center = newCenter.clone();
292 Main.pref.getProjection().xy2latlon(center);
293 this.scale = scale;
294 recalculateCenterScale();
295
296 firePropertyChange("center", oldCenter, center);
297 if (oldAutoScale != autoScale)
298 firePropertyChange("autoScale", oldAutoScale, autoScale);
299 if (oldScale != scale)
300 firePropertyChange("scale", oldScale, scale);
301 }
302
303 /**
304 * Draw the component.
305 */
306 @Override
307 public void paint(Graphics g) {
308 g.setColor(Color.BLACK);
309 g.fillRect(0, 0, getWidth(), getHeight());
310
311 for (int i = layers.size()-1; i >= 0; --i) {
312 Layer l = layers.get(i);
313 if (l.visible)
314 l.paint(g, this);
315 }
316 }
317
318 /**
319 * Notify from the projection, that something has changed.
320 * @param e
321 */
322 public void stateChanged(ChangeEvent e) {
323 // reset all datasets.
324 Projection p = Main.pref.getProjection();
325 for (Layer l : layers) {
326 DataSet ds = l.dataSet;
327 if (ds != null)
328 for (Node n : ds.nodes)
329 p.latlon2xy(n.coor);
330 }
331 recalculateCenterScale();
332 }
333
334 /**
335 * Return the current scale value.
336 * @return The scale value currently used in display
337 */
338 public double getScale() {
339 return scale;
340 }
341
342 /**
343 * @return Returns the autoScale.
344 */
345 public boolean isAutoScale() {
346 return autoScale;
347 }
348
349 /**
350 * @param autoScale The autoScale to set.
351 */
352 public void setAutoScale(boolean autoScale) {
353 if (this.autoScale != autoScale) {
354 this.autoScale = autoScale;
355 firePropertyChange("autoScale", !autoScale, autoScale);
356 recalculateCenterScale();
357 }
358 }
359 /**
360 * @return Returns the center point. A copy is returned, so users cannot
361 * change the center by accessing the return value. Use zoomTo instead.
362 */
363 public GeoPoint getCenter() {
364 return center.clone();
365 }
366
367
368 /**
369 * Change to the new projection. Recalculate the dataset and zoom, if autoZoom
370 * is active.
371 * @param oldProjection The old projection. Unregister from this.
372 * @param newProjection The new projection. Register as state change listener.
373 */
374 public void propertyChange(PropertyChangeEvent evt) {
375 if (!evt.getPropertyName().equals("projection"))
376 return;
377 if (evt.getOldValue() != null)
378 ((Projection)evt.getOldValue()).removeChangeListener(this);
379 if (evt.getNewValue() != null) {
380 Projection p = (Projection)evt.getNewValue();
381 p.addChangeListener(this);
382
383 stateChanged(new ChangeEvent(this));
384 }
385 }
386
387 /**
388 * Set the new dimension to the projection class. Also adjust the components
389 * scale, if in autoScale mode.
390 */
391 void recalculateCenterScale() {
392 if (autoScale) {
393 // -20 to leave some border
394 int w = getWidth()-20;
395 if (w < 20)
396 w = 20;
397 int h = getHeight()-20;
398 if (h < 20)
399 h = 20;
400
401 Bounds bounds = Main.main.ds.getBoundsXY();
402
403 boolean oldAutoScale = autoScale;
404 GeoPoint oldCenter = center;
405 double oldScale = this.scale;
406
407 if (bounds == null) {
408 // no bounds means standard scale and center
409 center = new GeoPoint(51.526447, -0.14746371);
410 Main.pref.getProjection().latlon2xy(center);
411 scale = 10;
412 } else {
413 center = bounds.centerXY();
414 Main.pref.getProjection().xy2latlon(center);
415 double scaleX = (bounds.max.x-bounds.min.x)/w;
416 double scaleY = (bounds.max.y-bounds.min.y)/h;
417 scale = Math.max(scaleX, scaleY); // minimum scale to see all of the screen
418 }
419
420 firePropertyChange("center", oldCenter, center);
421 if (oldAutoScale != autoScale)
422 firePropertyChange("autoScale", oldAutoScale, autoScale);
423 if (oldScale != scale)
424 firePropertyChange("scale", oldScale, scale);
425 }
426 repaint();
427 }
428
429 /**
430 * Add a listener for changes of active layer.
431 * @param listener The listener that get added.
432 */
433 public void addLayerChangeListener(LayerChangeListener listener) {
434 if (listener != null)
435 listeners.add(listener);
436 }
437
438 /**
439 * Remove the listener.
440 * @param listener The listener that get removed from the list.
441 */
442 public void removeLayerChangeListener(LayerChangeListener listener) {
443 listeners.remove(listener);
444 }
445
446 /**
447 * @return An unmodificable list of all layers
448 */
449 public Collection<Layer> getAllLayers() {
450 return Collections.unmodifiableCollection(layers);
451 }
452
453 /**
454 * Set the active selection to the given value and raise an layerchange event.
455 */
456 public void setActiveLayer(Layer layer) {
457 if (!layers.contains(layer))
458 throw new IllegalArgumentException("layer must be in layerlist");
459 Layer old = activeLayer;
460 activeLayer = layer;
461 if (old != layer) {
462 if (old != null && old.dataSet != null)
463 old.dataSet.clearSelection();
464 for (LayerChangeListener l : listeners)
465 l.activeLayerChange(old, layer);
466 recalculateCenterScale();
467 }
468 }
469
470 /**
471 * @return The current active layer
472 */
473 public Layer getActiveLayer() {
474 return activeLayer;
475 }
476}
Note: See TracBrowser for help on using the repository browser.