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

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

fix #2029

  • Property svn:eol-style set to native
File size: 15.0 KB
Line 
1// License: GPL. See LICENSE file for details.
2
3package org.openstreetmap.josm.gui;
4
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.Color;
8import java.awt.Graphics;
9import java.awt.Graphics2D;
10import java.awt.Point;
11import java.awt.event.MouseEvent;
12import java.awt.event.MouseMotionListener;
13import java.awt.event.ComponentAdapter;
14import java.awt.event.ComponentEvent;
15import java.awt.image.BufferedImage;
16import java.util.ArrayList;
17import java.util.Collection;
18import java.util.Collections;
19import java.util.LinkedList;
20
21import javax.swing.JComponent;
22import javax.swing.JOptionPane;
23
24import org.openstreetmap.josm.Main;
25import org.openstreetmap.josm.actions.AutoScaleAction;
26import org.openstreetmap.josm.actions.JosmAction;
27import org.openstreetmap.josm.actions.MoveAction;
28import org.openstreetmap.josm.data.Bounds;
29import org.openstreetmap.josm.data.SelectionChangedListener;
30import org.openstreetmap.josm.data.coor.EastNorth;
31import org.openstreetmap.josm.data.coor.LatLon;
32import org.openstreetmap.josm.data.osm.DataSet;
33import org.openstreetmap.josm.data.osm.DataSource;
34import org.openstreetmap.josm.data.osm.OsmPrimitive;
35import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
36import org.openstreetmap.josm.data.projection.Projection;
37import org.openstreetmap.josm.gui.layer.Layer;
38import org.openstreetmap.josm.gui.layer.MapViewPaintable;
39import org.openstreetmap.josm.gui.layer.OsmDataLayer;
40import org.openstreetmap.josm.gui.layer.OsmDataLayer.ModifiedChangedListener;
41import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
42import org.openstreetmap.josm.gui.layer.markerlayer.PlayHeadMarker;
43
44/**
45 * This is a component used in the MapFrame for browsing the map. It use is to
46 * provide the MapMode's enough capabilities to operate.
47 *
48 * MapView hold meta-data about the data set currently displayed, as scale level,
49 * center point viewed, what scrolling mode or editing mode is selected or with
50 * what projection the map is viewed etc..
51 *
52 * MapView is able to administrate several layers.
53 *
54 * @author imi
55 */
56public class MapView extends NavigatableComponent {
57
58 /**
59 * A list of all layers currently loaded.
60 */
61 private ArrayList<Layer> layers = new ArrayList<Layer>();
62 /**
63 * The play head marker: there is only one of these so it isn't in any specific layer
64 */
65 public PlayHeadMarker playHeadMarker = null;
66 /**
67 * Direct link to the edit layer (if any) in the layers list.
68 */
69 public OsmDataLayer editLayer;
70 /**
71 * The layer from the layers list that is currently active.
72 */
73 private Layer activeLayer;
74
75 /**
76 * The last event performed by mouse.
77 */
78 public MouseEvent lastMEvent;
79
80 private LinkedList<MapViewPaintable> temporaryLayers = new LinkedList<MapViewPaintable>();
81
82 private BufferedImage offscreenBuffer;
83
84 public MapView() {
85 addComponentListener(new ComponentAdapter(){
86 @Override public void componentResized(ComponentEvent e) {
87 removeComponentListener(this);
88
89 if (!zoomToEditLayerBoundingBox())
90 new AutoScaleAction("data").actionPerformed(null);
91
92 new MapMover(MapView.this, Main.contentPane);
93 JosmAction mv;
94 mv = new MoveAction(MoveAction.Direction.UP);
95 if (mv.getShortcut() != null) {
96 Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(mv.getShortcut().getKeyStroke(), "UP");
97 Main.contentPane.getActionMap().put("UP", mv);
98 }
99 mv = new MoveAction(MoveAction.Direction.DOWN);
100 if (mv.getShortcut() != null) {
101 Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(mv.getShortcut().getKeyStroke(), "DOWN");
102 Main.contentPane.getActionMap().put("DOWN", mv);
103 }
104 mv = new MoveAction(MoveAction.Direction.LEFT);
105 if (mv.getShortcut() != null) {
106 Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(mv.getShortcut().getKeyStroke(), "LEFT");
107 Main.contentPane.getActionMap().put("LEFT", mv);
108 }
109 mv = new MoveAction(MoveAction.Direction.RIGHT);
110 if (mv.getShortcut() != null) {
111 Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(mv.getShortcut().getKeyStroke(), "RIGHT");
112 Main.contentPane.getActionMap().put("RIGHT", mv);
113 }
114
115 MapSlider zoomSlider = new MapSlider(MapView.this);
116 add(zoomSlider);
117 zoomSlider.setBounds(3, 0, 114, 30);
118
119 MapScaler scaler = new MapScaler(MapView.this, Main.proj);
120 add(scaler);
121 scaler.setLocation(10,30);
122 }
123 });
124
125 // listend to selection changes to redraw the map
126 DataSet.selListeners.add(new SelectionChangedListener(){
127 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
128 repaint();
129 }
130 });
131
132 //store the last mouse action
133 this.addMouseMotionListener(new MouseMotionListener() {
134 public void mouseDragged(MouseEvent e) {
135 mouseMoved(e);
136 }
137 public void mouseMoved(MouseEvent e) {
138 lastMEvent = e;
139 }
140 });
141 }
142
143 /**
144 * Add a layer to the current MapView. The layer will be added at topmost
145 * position.
146 */
147 public void addLayer(Layer layer) {
148 if (layer instanceof OsmDataLayer) {
149 editLayer = (OsmDataLayer)layer;
150 Main.ds = editLayer.data;
151 editLayer.listenerModified.add(new ModifiedChangedListener(){
152 public void modifiedChanged(boolean value, OsmDataLayer source) {
153 JOptionPane.getFrameForComponent(Main.parent).setTitle((value?"*":"")
154 +tr("Java OpenStreetMap Editor"));
155 }
156 });
157 }
158 if (layer instanceof MarkerLayer && playHeadMarker == null)
159 playHeadMarker = PlayHeadMarker.create();
160 int pos = layers.size();
161 while(pos > 0 && layers.get(pos-1).background)
162 --pos;
163 layers.add(pos, layer);
164
165 for (Layer.LayerChangeListener l : Layer.listeners)
166 l.layerAdded(layer);
167 if (layer instanceof OsmDataLayer || activeLayer == null) {
168 // autoselect the new layer
169 Layer old = activeLayer;
170 setActiveLayer(layer);
171 for (Layer.LayerChangeListener l : Layer.listeners)
172 l.activeLayerChange(old, layer);
173 }
174 repaint();
175 }
176
177 @Override
178 protected DataSet getData()
179 {
180 if(activeLayer != null && activeLayer instanceof OsmDataLayer)
181 return ((OsmDataLayer)activeLayer).data;
182 return new DataSet();
183 }
184
185 public Boolean isDrawableLayer()
186 {
187 return activeLayer != null && activeLayer instanceof OsmDataLayer;
188 }
189
190 /**
191 * Remove the layer from the mapview. If the layer was in the list before,
192 * an LayerChange event is fired.
193 */
194 public void removeLayer(Layer layer) {
195 if (layers.remove(layer)) {
196 for (Layer.LayerChangeListener l : Layer.listeners)
197 l.layerRemoved(layer);
198 }
199 if (layer == editLayer) {
200 editLayer = null;
201 Main.ds.setSelected();
202 }
203 layer.destroy();
204 }
205
206 private Boolean virtualnodes = false;
207 public void enableVirtualNodes(Boolean state)
208 {
209 if(virtualnodes != state)
210 {
211 virtualnodes = state;
212 repaint();
213 }
214 }
215 public Boolean useVirtualNodes()
216 {
217 return virtualnodes;
218 }
219
220 /**
221 * Moves the layer to the given new position. No event is fired.
222 * @param layer The layer to move
223 * @param pos The new position of the layer
224 */
225 public void moveLayer(Layer layer, int pos) {
226 int curLayerPos = layers.indexOf(layer);
227 if (curLayerPos == -1)
228 throw new IllegalArgumentException(tr("layer not in list."));
229 if (pos == curLayerPos)
230 return; // already in place.
231 layers.remove(curLayerPos);
232 if (pos >= layers.size())
233 layers.add(layer);
234 else
235 layers.add(pos, layer);
236 }
237
238
239 public int getLayerPos(Layer layer) {
240 int curLayerPos = layers.indexOf(layer);
241 if (curLayerPos == -1)
242 throw new IllegalArgumentException(tr("layer not in list."));
243 return curLayerPos;
244 }
245
246 /**
247 * Draw the component.
248 */
249 @Override public void paint(Graphics g) {
250 if (center == null)
251 return; // no data loaded yet.
252
253 // re-create offscreen-buffer if we've been resized, otherwise
254 // just re-use it.
255 if (null == offscreenBuffer || offscreenBuffer.getWidth() != getWidth()
256 || offscreenBuffer.getHeight() != getHeight())
257 offscreenBuffer = new BufferedImage(getWidth(), getHeight(),
258 BufferedImage.TYPE_INT_ARGB);
259
260 Graphics2D tempG = offscreenBuffer.createGraphics();
261 tempG.setColor(Main.pref.getColor("background", Color.BLACK));
262 tempG.fillRect(0, 0, getWidth(), getHeight());
263
264 for (int i = layers.size()-1; i >= 0; --i) {
265 Layer l = layers.get(i);
266 if (l.visible/* && l != getActiveLayer()*/)
267 l.paint(tempG, this);
268 }
269
270 /*if (getActiveLayer() != null && getActiveLayer().visible)
271 getActiveLayer().paint(tempG, this);*/
272
273 for (MapViewPaintable mvp : temporaryLayers) {
274 mvp.paint(tempG, this);
275 }
276
277 // draw world borders
278 tempG.setColor(Color.WHITE);
279 Bounds b = new Bounds();
280 Point min = getPoint(getProjection().latlon2eastNorth(b.min));
281 Point max = getPoint(getProjection().latlon2eastNorth(b.max));
282 int x1 = Math.min(min.x, max.x);
283 int y1 = Math.min(min.y, max.y);
284 int x2 = Math.max(min.x, max.x);
285 int y2 = Math.max(min.y, max.y);
286 if (x1 > 0 || y1 > 0 || x2 < getWidth() || y2 < getHeight())
287 tempG.drawRect(x1, y1, x2-x1+1, y2-y1+1);
288
289 if (playHeadMarker != null)
290 playHeadMarker.paint(tempG, this);
291
292 g.drawImage(offscreenBuffer, 0, 0, null);
293 super.paint(g);
294 }
295
296 /**
297 * Set the new dimension to the projection class. Also adjust the components
298 * scale, if in autoScale mode.
299 */
300 public void recalculateCenterScale(BoundingXYVisitor box) {
301 // -20 to leave some border
302 int w = getWidth()-20;
303 if (w < 20)
304 w = 20;
305 int h = getHeight()-20;
306 if (h < 20)
307 h = 20;
308
309 EastNorth oldCenter = center;
310 double oldScale = this.scale;
311
312 if (box == null || box.min == null || box.max == null || box.min.equals(box.max)) {
313 // no bounds means whole world
314 center = getProjection().latlon2eastNorth(new LatLon(0,0));
315 EastNorth world = getProjection().latlon2eastNorth(new LatLon(Projection.MAX_LAT,Projection.MAX_LON));
316 double scaleX = world.east()*2/w;
317 double scaleY = world.north()*2/h;
318 scale = Math.max(scaleX, scaleY); // minimum scale to see all of the screen
319 } else {
320 center = new EastNorth(box.min.east()/2+box.max.east()/2, box.min.north()/2+box.max.north()/2);
321 double scaleX = (box.max.east()-box.min.east())/w;
322 double scaleY = (box.max.north()-box.min.north())/h;
323 scale = Math.max(scaleX, scaleY); // minimum scale to see all of the screen
324 }
325
326 if (!center.equals(oldCenter))
327 firePropertyChange("center", oldCenter, center);
328 if (oldScale != scale)
329 firePropertyChange("scale", oldScale, scale);
330 repaint();
331 }
332
333 /**
334 * @return An unmodificable list of all layers
335 */
336 public Collection<Layer> getAllLayers() {
337 return Collections.unmodifiableCollection(layers);
338 }
339
340 /**
341 * Set the active selection to the given value and raise an layerchange event.
342 */
343 public void setActiveLayer(Layer layer) {
344 if (!layers.contains(layer))
345 throw new IllegalArgumentException("Layer must be in layerlist");
346 if (layer instanceof OsmDataLayer) {
347 editLayer = (OsmDataLayer)layer;
348 Main.ds = editLayer.data;
349 }
350 else
351 Main.ds.setSelected();
352 DataSet.fireSelectionChanged(Main.ds.getSelected());
353 Layer old = activeLayer;
354 activeLayer = layer;
355 if (old != layer) {
356 for (Layer.LayerChangeListener l : Layer.listeners)
357 l.activeLayerChange(old, layer);
358 }
359 repaint();
360 }
361
362 /**
363 * @return The current active layer
364 */
365 public Layer getActiveLayer() {
366 return activeLayer;
367 }
368
369 /**
370 * In addition to the base class funcitonality, this keep trak of the autoscale
371 * feature.
372 */
373 @Override public void zoomTo(EastNorth newCenter, double scale) {
374 EastNorth oldCenter = center;
375 double oldScale = this.scale;
376 super.zoomTo(newCenter, scale);
377 if ((oldCenter == null && center != null) || !oldCenter.equals(center))
378 firePropertyChange("center", oldCenter, center);
379 if (oldScale != scale)
380 firePropertyChange("scale", oldScale, scale);
381 }
382
383 /**
384 * Tries to zoom to the download boundingbox[es] of the current edit layer
385 * (aka {@link OsmDataLayer}). If the edit layer has multiple download bounding
386 * boxes it zooms to a large virtual bounding box containing all smaller ones.
387 * This implementation can be used for resolving ticket #1461.
388 *
389 * @return <code>true</code> if a zoom operation has been performed
390 * @author Jan Peter Stotz
391 */
392 public boolean zoomToEditLayerBoundingBox() {
393 // workaround for #1461 (zoom to download bounding box instead of all data)
394 // In case we already have an existing data layer ...
395 Collection<DataSource> dataSources = Main.main.editLayer().data.dataSources;
396 // ... with bounding box[es] of data loaded from OSM or a file...
397 BoundingXYVisitor bbox = new BoundingXYVisitor();
398 for (DataSource ds : dataSources) {
399 if (ds.bounds != null) {
400 bbox.visit(Main.proj.latlon2eastNorth(ds.bounds.max));
401 bbox.visit(Main.proj.latlon2eastNorth(ds.bounds.min));
402 }
403 if (bbox.max != null && bbox.min != null && !bbox.max.equals(bbox.min)) {
404 // ... we zoom to it's bounding box
405 recalculateCenterScale(bbox);
406 return true;
407 }
408 }
409 return false;
410 }
411
412 public boolean addTemporaryLayer(MapViewPaintable mvp) {
413 if (temporaryLayers.contains(mvp)) return false;
414 return temporaryLayers.add(mvp);
415 }
416
417 public boolean removeTemporaryLayer(MapViewPaintable mvp) {
418 return temporaryLayers.remove(mvp);
419 }
420}
Note: See TracBrowser for help on using the repository browser.