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

Last change on this file since 41 was 41, checked in by imi, 18 years ago
  • renamed EPSG projection
  • started WorldChooser in download menu (not finished)
File size: 14.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.HashSet;
14import java.util.LinkedList;
15
16import javax.swing.event.ChangeEvent;
17import javax.swing.event.ChangeListener;
18
19import org.openstreetmap.josm.Main;
20import org.openstreetmap.josm.data.Bounds;
21import org.openstreetmap.josm.data.GeoPoint;
22import org.openstreetmap.josm.data.osm.DataSet;
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;
29import org.openstreetmap.josm.gui.layer.OsmDataLayer;
30
31/**
32 * This is a component used in the MapFrame for browsing the map. It use is to
33 * provide the MapMode's enough capabilities to operate.
34 *
35 * MapView hold meta-data about the data set currently displayed, as scale level,
36 * center point viewed, what scrolling mode or editing mode is selected or with
37 * what projection the map is viewed etc..
38 *
39 * MapView is able to administrate several layers, but there must be always at
40 * least one layer with a dataset in it (Layer.getDataSet returning non-null).
41 *
42 * @author imi
43 */
44public class MapView extends NavigatableComponent implements ChangeListener, PropertyChangeListener {
45
46 /**
47 * Interface to notify listeners of the change of the active layer.
48 * @author imi
49 */
50 public interface LayerChangeListener {
51 void activeLayerChange(Layer oldLayer, Layer newLayer);
52 void layerAdded(Layer newLayer);
53 void layerRemoved(Layer oldLayer);
54 }
55
56 /**
57 * Whether to adjust the scale property on every resize.
58 */
59 boolean autoScale = true;
60
61 /**
62 * A list of all layers currently loaded.
63 */
64 private ArrayList<Layer> layers = new ArrayList<Layer>();
65 /**
66 * Direct link to the edit layer (if any) in the layers list.
67 */
68 private OsmDataLayer editLayer;
69 /**
70 * The layer from the layers list that is currently active.
71 */
72 private Layer activeLayer;
73 /**
74 * The listener of the active layer changes.
75 */
76 private Collection<LayerChangeListener> listeners = new LinkedList<LayerChangeListener>();
77
78 /**
79 * Construct a MapView.
80 * @param layer The first layer in the view.
81 */
82 public MapView(Layer layer) {
83 addComponentListener(new ComponentAdapter(){
84 @Override
85 public void componentResized(ComponentEvent e) {
86 recalculateCenterScale();
87 }
88 });
89
90 // initialize the movement listener
91 new MapMover(this);
92
93 // initialize the projection
94 addLayer(layer);
95 Main.pref.addPropertyChangeListener(this);
96 }
97
98 /**
99 * Add a layer to the current MapView. The layer will be added at topmost
100 * position.
101 */
102 public void addLayer(Layer layer) {
103 // initialize the projection if it is the first layer
104 if (layers.isEmpty())
105 Main.pref.getProjection().init(layer.getBoundsLatLon());
106
107 // reinitialize layer's data
108 layer.init(Main.pref.getProjection());
109
110 if (layer instanceof OsmDataLayer) {
111 if (editLayer != null) {
112 // merge the layer into the existing one
113 if (!editLayer.isMergable(layer))
114 throw new IllegalArgumentException("Cannot merge argument");
115 editLayer.mergeFrom(layer);
116 repaint();
117 return;
118 }
119 editLayer = (OsmDataLayer)layer;
120 }
121
122 // add as a new layer
123 layers.add(0,layer);
124
125 for (LayerChangeListener l : listeners)
126 l.layerAdded(layer);
127
128 // autoselect the new layer
129 setActiveLayer(layer);
130 recalculateCenterScale();
131 }
132
133 /**
134 * Remove the layer from the mapview. If the layer was in the list before,
135 * an LayerChange event is fired.
136 */
137 public void removeLayer(Layer layer) {
138 if (layers.remove(layer))
139 for (LayerChangeListener l : listeners)
140 l.layerRemoved(layer);
141 if (layer == editLayer)
142 editLayer = null;
143 }
144
145 /**
146 * Moves the layer to the given new position. No event is fired.
147 * @param layer The layer to move
148 * @param pos The new position of the layer
149 */
150 public void moveLayer(Layer layer, int pos) {
151 int curLayerPos = layers.indexOf(layer);
152 if (curLayerPos == -1)
153 throw new IllegalArgumentException("layer not in list.");
154 if (pos == curLayerPos)
155 return; // already in place.
156 layers.remove(curLayerPos);
157 if (pos >= layers.size())
158 layers.add(layer);
159 else
160 layers.add(pos, layer);
161 }
162
163 /**
164 * Return the object, that is nearest to the given screen point.
165 *
166 * First, a node will be searched. If a node within 10 pixel is found, the
167 * nearest node is returned.
168 *
169 * If no node is found, search for pending line segments.
170 *
171 * If no such line segment is found, and a non-pending line segment is
172 * within 10 pixel to p, this segment is returned, except when
173 * <code>wholeTrack</code> is <code>true</code>, in which case the
174 * corresponding Track is returned.
175 *
176 * If no line segment is found and the point is within an area, return that
177 * area.
178 *
179 * If no area is found, return <code>null</code>.
180 *
181 * @param p The point on screen.
182 * @param lsInsteadTrack Whether the line segment (true) or only the whole
183 * track should be returned.
184 * @return The primitive, that is nearest to the point p.
185 */
186 public OsmPrimitive getNearest(Point p, boolean lsInsteadTrack) {
187 double minDistanceSq = Double.MAX_VALUE;
188 OsmPrimitive minPrimitive = null;
189
190 // nodes
191 for (Node n : Main.main.ds.nodes) {
192 if (n.isDeleted())
193 continue;
194 Point sp = getScreenPoint(n.coor);
195 double dist = p.distanceSq(sp);
196 if (minDistanceSq > dist && dist < 100) {
197 minDistanceSq = p.distanceSq(sp);
198 minPrimitive = n;
199 }
200 }
201 if (minPrimitive != null)
202 return minPrimitive;
203
204 // for whole tracks, try the tracks first
205 minDistanceSq = Double.MAX_VALUE;
206 if (!lsInsteadTrack) {
207 for (Track t : Main.main.ds.tracks) {
208 if (t.isDeleted())
209 continue;
210 for (LineSegment ls : t.segments) {
211 if (ls.isDeleted())
212 continue;
213 Point A = getScreenPoint(ls.start.coor);
214 Point B = getScreenPoint(ls.end.coor);
215 double c = A.distanceSq(B);
216 double a = p.distanceSq(B);
217 double b = p.distanceSq(A);
218 double perDist = a-(a-b+c)*(a-b+c)/4/c; // perpendicular distance squared
219 if (perDist < 100 && minDistanceSq > perDist && a < c+100 && b < c+100) {
220 minDistanceSq = perDist;
221 minPrimitive = t;
222 }
223 }
224 }
225 if (minPrimitive != null)
226 return minPrimitive;
227 }
228
229 minDistanceSq = Double.MAX_VALUE;
230 // line segments
231 for (LineSegment ls : Main.main.ds.lineSegments) {
232 if (ls.isDeleted())
233 continue;
234 Point A = getScreenPoint(ls.start.coor);
235 Point B = getScreenPoint(ls.end.coor);
236 double c = A.distanceSq(B);
237 double a = p.distanceSq(B);
238 double b = p.distanceSq(A);
239 double perDist = a-(a-b+c)*(a-b+c)/4/c; // perpendicular distance squared
240 if (perDist < 100 && minDistanceSq > perDist && a < c+100 && b < c+100) {
241 minDistanceSq = perDist;
242 minPrimitive = ls;
243 }
244 }
245
246 return minPrimitive;
247 }
248
249 /**
250 * @return A list of all objects that are nearest to
251 * the mouse. To do this, first the nearest object is
252 * determined.
253 *
254 * If its a node, return all line segments and
255 * streets the node is part of, as well as all nodes
256 * (with their line segments and tracks) with the same
257 * location.
258 *
259 * If its a line segment, return all tracks this segment
260 * belongs to as well as all line segments that are between
261 * the same nodes (in both direction) with all their tracks.
262 *
263 * @return A collection of all items or <code>null</code>
264 * if no item under or near the point. The returned
265 * list is never empty.
266 */
267 public Collection<OsmPrimitive> getAllNearest(Point p) {
268 OsmPrimitive osm = getNearest(p, true);
269 if (osm == null)
270 return null;
271 Collection<OsmPrimitive> c = new HashSet<OsmPrimitive>();
272 c.add(osm);
273 if (osm instanceof Node) {
274 Node node = (Node)osm;
275 for (Node n : Main.main.ds.nodes)
276 if (!n.isDeleted() && n.coor.equalsLatLon(node.coor))
277 c.add(n);
278 for (LineSegment ls : Main.main.ds.lineSegments)
279 // line segments never match nodes, so they are skipped by contains
280 if (!ls.isDeleted() && (c.contains(ls.start) || c.contains(ls.end)))
281 c.add(ls);
282 }
283 if (osm instanceof LineSegment) {
284 LineSegment line = (LineSegment)osm;
285 for (LineSegment ls : Main.main.ds.lineSegments)
286 if (!ls.isDeleted() && ls.equalPlace(line))
287 c.add(ls);
288 }
289 if (osm instanceof Node || osm instanceof LineSegment) {
290 for (Track t : Main.main.ds.tracks) {
291 if (t.isDeleted())
292 continue;
293 for (LineSegment ls : t.segments) {
294 if (!ls.isDeleted() && c.contains(ls)) {
295 c.add(t);
296 break;
297 }
298 }
299 }
300 }
301 return c;
302 }
303
304 /**
305 * Draw the component.
306 */
307 @Override
308 public void paint(Graphics g) {
309 g.setColor(Color.BLACK);
310 g.fillRect(0, 0, getWidth(), getHeight());
311
312 for (int i = layers.size()-1; i >= 0; --i) {
313 Layer l = layers.get(i);
314 if (l.visible)
315 l.paint(g, this);
316 }
317
318 // draw world borders
319 g.setColor(Color.WHITE);
320 Bounds b = new Bounds();
321 Point min = getScreenPoint(b.min);
322 Point max = getScreenPoint(b.max);
323 int x1 = Math.min(min.x, max.x);
324 int y1 = Math.min(min.y, max.y);
325 int x2 = Math.max(min.x, max.x);
326 int y2 = Math.max(min.y, max.y);
327 if (x1 > 0 || y1 > 0 || x2 < getWidth() || y2 < getHeight())
328 g.drawRect(x1, y1, x2-x1+1, y2-y1+1);
329 }
330
331 /**
332 * @return Returns the autoScale.
333 */
334 public boolean isAutoScale() {
335 return autoScale;
336 }
337
338 /**
339 * @param autoScale The autoScale to set.
340 */
341 public void setAutoScale(boolean autoScale) {
342 if (this.autoScale != autoScale) {
343 this.autoScale = autoScale;
344 firePropertyChange("autoScale", !autoScale, autoScale);
345 recalculateCenterScale();
346 }
347 }
348 /**
349 * Set the new dimension to the projection class. Also adjust the components
350 * scale, if in autoScale mode.
351 */
352 void recalculateCenterScale() {
353 if (autoScale) {
354 // -20 to leave some border
355 int w = getWidth()-20;
356 if (w < 20)
357 w = 20;
358 int h = getHeight()-20;
359 if (h < 20)
360 h = 20;
361
362 Bounds bounds = null;
363 for (Layer l : layers) {
364 if (bounds == null)
365 bounds = l.getBoundsXY();
366 else {
367 Bounds lb = l.getBoundsXY();
368 if (lb != null)
369 bounds = bounds.mergeXY(lb);
370 }
371 }
372
373 boolean oldAutoScale = autoScale;
374 GeoPoint oldCenter = center;
375 double oldScale = this.scale;
376
377 if (bounds == null) {
378 // no bounds means standard scale and center
379 center = new GeoPoint(51.526447, -0.14746371);
380 Main.pref.getProjection().latlon2xy(center);
381 scale = 10;
382 } else {
383 center = bounds.centerXY();
384 Main.pref.getProjection().xy2latlon(center);
385 double scaleX = (bounds.max.x-bounds.min.x)/w;
386 double scaleY = (bounds.max.y-bounds.min.y)/h;
387 scale = Math.max(scaleX, scaleY); // minimum scale to see all of the screen
388 }
389
390 firePropertyChange("center", oldCenter, center);
391 if (oldAutoScale != autoScale)
392 firePropertyChange("autoScale", oldAutoScale, autoScale);
393 if (oldScale != scale)
394 firePropertyChange("scale", oldScale, scale);
395 }
396 repaint();
397 }
398
399 /**
400 * Add a listener for changes of active layer.
401 * @param listener The listener that get added.
402 */
403 public void addLayerChangeListener(LayerChangeListener listener) {
404 if (listener != null)
405 listeners.add(listener);
406 }
407
408 /**
409 * Remove the listener.
410 * @param listener The listener that get removed from the list.
411 */
412 public void removeLayerChangeListener(LayerChangeListener listener) {
413 listeners.remove(listener);
414 }
415
416 /**
417 * @return An unmodificable list of all layers
418 */
419 public Collection<Layer> getAllLayers() {
420 return Collections.unmodifiableCollection(layers);
421 }
422
423 /**
424 * Set the active selection to the given value and raise an layerchange event.
425 * Also, swap the active dataset in Main.main if it is a datalayer.
426 */
427 public void setActiveLayer(Layer layer) {
428 if (!layers.contains(layer))
429 throw new IllegalArgumentException("layer must be in layerlist");
430 Layer old = activeLayer;
431 activeLayer = layer;
432 if (layer instanceof OsmDataLayer)
433 Main.main.ds = ((OsmDataLayer)layer).data;
434 if (old != layer) {
435 for (LayerChangeListener l : listeners)
436 l.activeLayerChange(old, layer);
437 recalculateCenterScale();
438 }
439 }
440
441 /**
442 * @return The current active layer
443 */
444 public Layer getActiveLayer() {
445 return activeLayer;
446 }
447
448 /**
449 * @return The current edit layer. If no edit layer exist, one is created.
450 * So editLayer does never return <code>null</code>.
451 */
452 public OsmDataLayer editLayer() {
453 if (editLayer == null)
454 addLayer(new OsmDataLayer(new DataSet(), "unnamed"));
455 return editLayer;
456 }
457
458 /**
459 * In addition to the base class funcitonality, this keep trak of the autoscale
460 * feature.
461 */
462 @Override
463 public void zoomTo(GeoPoint newCenter, double scale) {
464 boolean oldAutoScale = autoScale;
465 GeoPoint oldCenter = center;
466 double oldScale = this.scale;
467 autoScale = false;
468
469 super.zoomTo(newCenter, scale);
470
471 recalculateCenterScale();
472
473 firePropertyChange("center", oldCenter, center);
474 if (oldAutoScale != autoScale)
475 firePropertyChange("autoScale", oldAutoScale, autoScale);
476 if (oldScale != scale)
477 firePropertyChange("scale", oldScale, scale);
478 }
479
480 /**
481 * Notify from the projection, that something has changed.
482 */
483 public void stateChanged(ChangeEvent e) {
484 // reset all datasets.
485 Projection p = Main.pref.getProjection();
486 for (Node n : Main.main.ds.nodes)
487 p.latlon2xy(n.coor);
488 recalculateCenterScale();
489 }
490
491 /**
492 * Change to the new projection. Recalculate the dataset and zoom, if autoZoom
493 * is active.
494 * @param oldProjection The old projection. Unregister from this.
495 * @param newProjection The new projection. Register as state change listener.
496 */
497 public void propertyChange(PropertyChangeEvent evt) {
498 if (!evt.getPropertyName().equals("projection"))
499 return;
500 if (evt.getOldValue() != null)
501 ((Projection)evt.getOldValue()).removeChangeListener(this);
502 if (evt.getNewValue() != null) {
503 Projection p = (Projection)evt.getNewValue();
504 p.addChangeListener(this);
505
506 stateChanged(new ChangeEvent(this));
507 }
508 }
509}
Note: See TracBrowser for help on using the repository browser.