source: josm/trunk/src/org/openstreetmap/josm/gui/layer/Layer.java@ 8860

Last change on this file since 8860 was 8860, checked in by wiktorn, 9 years ago

Make MemoryTileCache instiated per-layer instead static.

Static MemoryTIleCache put quite a lot of pressure on the cache, esp. when
working with multiple layers. Instead of growing MemoryTileCache, small
instances per layer are implemented.

As MemoryTileCache might also put a lot pressure on memory, now size of
MemoryTileCache is calculated based on screen resolution, assuming, that user
will work in full screen mode. Implementation also checks, if all added layers
will not require more memory that is allocated for JOSM, to prevent unexpected
OutOfMemoryException.

  • Property svn:eol-style set to native
File size: 17.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.layer;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Color;
7import java.awt.Component;
8import java.awt.Graphics2D;
9import java.awt.event.ActionEvent;
10import java.beans.PropertyChangeListener;
11import java.beans.PropertyChangeSupport;
12import java.io.File;
13import java.util.List;
14
15import javax.swing.AbstractAction;
16import javax.swing.Action;
17import javax.swing.Icon;
18import javax.swing.JOptionPane;
19import javax.swing.JSeparator;
20
21import org.openstreetmap.josm.Main;
22import org.openstreetmap.josm.actions.GpxExportAction;
23import org.openstreetmap.josm.actions.SaveAction;
24import org.openstreetmap.josm.actions.SaveActionBase;
25import org.openstreetmap.josm.actions.SaveAsAction;
26import org.openstreetmap.josm.data.Bounds;
27import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
28import org.openstreetmap.josm.data.projection.Projection;
29import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
30import org.openstreetmap.josm.gui.MapView;
31import org.openstreetmap.josm.tools.Destroyable;
32import org.openstreetmap.josm.tools.ImageProvider;
33import org.openstreetmap.josm.tools.Utils;
34
35/**
36 * A layer encapsulates the gui component of one dataset and its representation.
37 *
38 * Some layers may display data directly imported from OSM server. Other only
39 * display background images. Some can be edited, some not. Some are static and
40 * other changes dynamically (auto-updated).
41 *
42 * Layers can be visible or not. Most actions the user can do applies only on
43 * selected layers. The available actions depend on the selected layers too.
44 *
45 * All layers are managed by the MapView. They are displayed in a list to the
46 * right of the screen.
47 *
48 * @author imi
49 */
50public abstract class Layer implements Destroyable, MapViewPaintable, ProjectionChangeListener {
51
52 public interface LayerAction {
53 boolean supportLayers(List<Layer> layers);
54
55 Component createMenuComponent();
56 }
57
58 public interface MultiLayerAction {
59 Action getMultiLayerAction(List<Layer> layers);
60 }
61
62 /**
63 * Special class that can be returned by getMenuEntries when JSeparator needs to be created
64 *
65 */
66 public static class SeparatorLayerAction extends AbstractAction implements LayerAction {
67 public static final SeparatorLayerAction INSTANCE = new SeparatorLayerAction();
68
69 @Override
70 public void actionPerformed(ActionEvent e) {
71 throw new UnsupportedOperationException();
72 }
73
74 @Override
75 public Component createMenuComponent() {
76 return new JSeparator();
77 }
78
79 @Override
80 public boolean supportLayers(List<Layer> layers) {
81 return false;
82 }
83 }
84
85 public static final String VISIBLE_PROP = Layer.class.getName() + ".visible";
86 public static final String OPACITY_PROP = Layer.class.getName() + ".opacity";
87 public static final String NAME_PROP = Layer.class.getName() + ".name";
88 public static final String FILTER_STATE_PROP = Layer.class.getName() + ".filterstate";
89
90 public static final int ICON_SIZE = 16;
91
92 /** keeps track of property change listeners */
93 protected PropertyChangeSupport propertyChangeSupport;
94
95 /**
96 * The visibility state of the layer.
97 *
98 */
99 private boolean visible = true;
100
101 /**
102 * The opacity of the layer.
103 *
104 */
105 private double opacity = 1;
106
107 /**
108 * The layer should be handled as a background layer in automatic handling
109 *
110 */
111 private boolean background;
112
113 /**
114 * The name of this layer.
115 *
116 */
117 private String name;
118
119 /**
120 * If a file is associated with this layer, this variable should be set to it.
121 */
122 private File associatedFile;
123
124 /**
125 * Create the layer and fill in the necessary components.
126 * @param name Layer name
127 */
128 public Layer(String name) {
129 this.propertyChangeSupport = new PropertyChangeSupport(this);
130 setName(name);
131 }
132
133 /**
134 * Initialization code, that depends on Main.map.mapView.
135 *
136 * It is always called in the event dispatching thread.
137 * Note that Main.map is null as long as no layer has been added, so do
138 * not execute code in the constructor, that assumes Main.map.mapView is
139 * not null. Instead override this method.
140 *
141 * This implementation provides check, if JOSM will be able to use Layer. Layers
142 * using a lot of memory, which do know in advance, how much memory they use, should
143 * override {@link #estimateMemoryUsage() estimateMemoryUsage} method and give a hint.
144 *
145 * This allows for preemptive warning message for user, instead of failing later on
146 *
147 * Remember to call {@code super.hookUpMapView()} when overriding this method
148 */
149 public void hookUpMapView() {
150 // calculate total memory needed for all layers
151 long memoryBytesRequired = 50 * 1024 * 1024; // assumed minimum JOSM memory footprint
152 if (Main.map != null && Main.map.mapView != null) {
153 for (Layer layer: Main.map.mapView.getAllLayers()) {
154 memoryBytesRequired += layer.estimateMemoryUsage();
155 }
156 if (memoryBytesRequired > Runtime.getRuntime().maxMemory()) {
157 throw new IllegalArgumentException(tr("To add another layer you need to allocate at least {0,number,#}MB memory to JOSM using -Xmx{0,number,#}M "
158 + "option (see http://forum.openstreetmap.org/viewtopic.php?id=25677).\n"
159 + "Currently you have {1,number,#}MB memory allocated for JOSM", memoryBytesRequired / 1024 / 1024, Runtime.getRuntime().maxMemory() / 1024 / 1024));
160 }
161 }
162 }
163
164 /**
165 * Paint the dataset using the engine set.
166 * @param mv The object that can translate GeoPoints to screen coordinates.
167 */
168 @Override
169 public abstract void paint(Graphics2D g, MapView mv, Bounds box);
170
171 /**
172 * Return a representative small image for this layer. The image must not
173 * be larger than 64 pixel in any dimension.
174 */
175 public abstract Icon getIcon();
176
177 /**
178 * Return a Color for this layer. Return null when no color specified.
179 * @param ignoreCustom Custom color should return null, as no default color
180 * is used. When this is true, then even for custom coloring the base
181 * color is returned - mainly for layer internal use.
182 */
183 public Color getColor(boolean ignoreCustom) {
184 return null;
185 }
186
187 /**
188 * @return A small tooltip hint about some statistics for this layer.
189 */
190 public abstract String getToolTipText();
191
192 /**
193 * Merges the given layer into this layer. Throws if the layer types are
194 * incompatible.
195 * @param from The layer that get merged into this one. After the merge,
196 * the other layer is not usable anymore and passing to one others
197 * mergeFrom should be one of the last things to do with a layer.
198 */
199 public abstract void mergeFrom(Layer from);
200
201 /**
202 * @param other The other layer that is tested to be mergable with this.
203 * @return Whether the other layer can be merged into this layer.
204 */
205 public abstract boolean isMergable(Layer other);
206
207 public abstract void visitBoundingBox(BoundingXYVisitor v);
208
209 public abstract Object getInfoComponent();
210
211 /**
212 * Determines if info dialog can be resized (false by default).
213 * @return {@code true} if the info dialog can be resized, {@code false} otherwise
214 * @since 6708
215 */
216 public boolean isInfoResizable() {
217 return false;
218 }
219
220 /**
221 * Returns list of actions. Action can implement LayerAction interface when it needs to be represented by other
222 * menu component than JMenuItem or when it supports multiple layers. Actions that support multiple layers should also
223 * have correct equals implementation.
224 *
225 * Use SeparatorLayerAction.INSTANCE instead of new JSeparator
226 *
227 */
228 public abstract Action[] getMenuEntries();
229
230 /**
231 * Called, when the layer is removed from the mapview and is going to be destroyed.
232 *
233 * This is because the Layer constructor can not add itself safely as listener
234 * to the layerlist dialog, because there may be no such dialog yet (loaded
235 * via command line parameter).
236 */
237 @Override
238 public void destroy() {
239 // Override in subclasses if needed
240 }
241
242 public File getAssociatedFile() {
243 return associatedFile;
244 }
245
246 public void setAssociatedFile(File file) {
247 associatedFile = file;
248 }
249
250 /**
251 * Replies the name of the layer
252 *
253 * @return the name of the layer
254 */
255 public String getName() {
256 return name;
257 }
258
259 /**
260 * Sets the name of the layer
261 *
262 *@param name the name. If null, the name is set to the empty string.
263 *
264 */
265 public final void setName(String name) {
266 if (name == null) {
267 name = "";
268 }
269 String oldValue = this.name;
270 this.name = name;
271 if (!this.name.equals(oldValue)) {
272 propertyChangeSupport.firePropertyChange(NAME_PROP, oldValue, this.name);
273 }
274 }
275
276 /**
277 * Replies true if this layer is a background layer
278 *
279 * @return true if this layer is a background layer
280 */
281 public boolean isBackgroundLayer() {
282 return background;
283 }
284
285 /**
286 * Sets whether this layer is a background layer
287 *
288 * @param background true, if this layer is a background layer
289 */
290 public void setBackgroundLayer(boolean background) {
291 this.background = background;
292 }
293
294 /**
295 * Sets the visibility of this layer. Emits property change event for
296 * property {@link #VISIBLE_PROP}.
297 *
298 * @param visible true, if the layer is visible; false, otherwise.
299 */
300 public void setVisible(boolean visible) {
301 boolean oldValue = isVisible();
302 this.visible = visible;
303 if (visible && opacity == 0) {
304 setOpacity(1);
305 } else if (oldValue != isVisible()) {
306 fireVisibleChanged(oldValue, isVisible());
307 }
308 }
309
310 /**
311 * Replies true if this layer is visible. False, otherwise.
312 * @return true if this layer is visible. False, otherwise.
313 */
314 public boolean isVisible() {
315 return visible && opacity != 0;
316 }
317
318 public double getOpacity() {
319 return opacity;
320 }
321
322 public void setOpacity(double opacity) {
323 if (!(opacity >= 0 && opacity <= 1))
324 throw new IllegalArgumentException("Opacity value must be between 0 and 1");
325 double oldOpacity = getOpacity();
326 boolean oldVisible = isVisible();
327 this.opacity = opacity;
328 if (!Utils.equalsEpsilon(oldOpacity, getOpacity())) {
329 fireOpacityChanged(oldOpacity, getOpacity());
330 }
331 if (oldVisible != isVisible()) {
332 fireVisibleChanged(oldVisible, isVisible());
333 }
334 }
335
336 /**
337 * Sets new state to the layer after applying {@link ImageProcessor}.
338 */
339 public void setFilterStateChanged() {
340 fireFilterStateChanged();
341 }
342
343 /**
344 * Toggles the visibility state of this layer.
345 */
346 public void toggleVisible() {
347 setVisible(!isVisible());
348 }
349
350 /**
351 * Adds a {@link PropertyChangeListener}
352 *
353 * @param listener the listener
354 */
355 public void addPropertyChangeListener(PropertyChangeListener listener) {
356 propertyChangeSupport.addPropertyChangeListener(listener);
357 }
358
359 /**
360 * Removes a {@link PropertyChangeListener}
361 *
362 * @param listener the listener
363 */
364 public void removePropertyChangeListener(PropertyChangeListener listener) {
365 propertyChangeSupport.removePropertyChangeListener(listener);
366 }
367
368 /**
369 * fires a property change for the property {@link #VISIBLE_PROP}
370 *
371 * @param oldValue the old value
372 * @param newValue the new value
373 */
374 protected void fireVisibleChanged(boolean oldValue, boolean newValue) {
375 propertyChangeSupport.firePropertyChange(VISIBLE_PROP, oldValue, newValue);
376 }
377
378 /**
379 * fires a property change for the property {@link #OPACITY_PROP}
380 *
381 * @param oldValue the old value
382 * @param newValue the new value
383 */
384 protected void fireOpacityChanged(double oldValue, double newValue) {
385 propertyChangeSupport.firePropertyChange(OPACITY_PROP, oldValue, newValue);
386 }
387
388 /**
389 * fires a property change for the property {@link #FILTER_STATE_PROP}.
390 */
391 protected void fireFilterStateChanged() {
392 propertyChangeSupport.firePropertyChange(FILTER_STATE_PROP, null, null);
393 }
394
395 /**
396 * Check changed status of layer
397 *
398 * @return True if layer was changed since last paint
399 */
400 public boolean isChanged() {
401 return true;
402 }
403
404 /**
405 * allows to check whether a projection is supported or not
406 *
407 * @return True if projection is supported for this layer
408 */
409 public boolean isProjectionSupported(Projection proj) {
410 return proj != null;
411 }
412
413 /**
414 * Specify user information about projections
415 *
416 * @return User readable text telling about supported projections
417 */
418 public String nameSupportedProjections() {
419 return tr("All projections are supported");
420 }
421
422 /**
423 * The action to save a layer
424 *
425 */
426 public static class LayerSaveAction extends AbstractAction {
427 private final transient Layer layer;
428
429 public LayerSaveAction(Layer layer) {
430 putValue(SMALL_ICON, ImageProvider.get("save"));
431 putValue(SHORT_DESCRIPTION, tr("Save the current data."));
432 putValue(NAME, tr("Save"));
433 setEnabled(true);
434 this.layer = layer;
435 }
436
437 @Override
438 public void actionPerformed(ActionEvent e) {
439 SaveAction.getInstance().doSave(layer);
440 }
441 }
442
443 public static class LayerSaveAsAction extends AbstractAction {
444 private final transient Layer layer;
445
446 public LayerSaveAsAction(Layer layer) {
447 putValue(SMALL_ICON, ImageProvider.get("save_as"));
448 putValue(SHORT_DESCRIPTION, tr("Save the current data to a new file."));
449 putValue(NAME, tr("Save As..."));
450 setEnabled(true);
451 this.layer = layer;
452 }
453
454 @Override
455 public void actionPerformed(ActionEvent e) {
456 SaveAsAction.getInstance().doSave(layer);
457 }
458 }
459
460 public static class LayerGpxExportAction extends AbstractAction {
461 private final transient Layer layer;
462
463 public LayerGpxExportAction(Layer layer) {
464 putValue(SMALL_ICON, ImageProvider.get("exportgpx"));
465 putValue(SHORT_DESCRIPTION, tr("Export the data to GPX file."));
466 putValue(NAME, tr("Export to GPX..."));
467 setEnabled(true);
468 this.layer = layer;
469 }
470
471 @Override
472 public void actionPerformed(ActionEvent e) {
473 new GpxExportAction().export(layer);
474 }
475 }
476
477 /* --------------------------------------------------------------------------------- */
478 /* interface ProjectionChangeListener */
479 /* --------------------------------------------------------------------------------- */
480 @Override
481 public void projectionChanged(Projection oldValue, Projection newValue) {
482 if (!isProjectionSupported(newValue)) {
483 JOptionPane.showMessageDialog(Main.parent,
484 tr("The layer {0} does not support the new projection {1}.\n"
485 + "Supported projections are: {2}\n"
486 + "Change the projection again or remove the layer.",
487 getName(), newValue.toCode(), nameSupportedProjections()),
488 tr("Warning"),
489 JOptionPane.WARNING_MESSAGE);
490 }
491 }
492
493 /**
494 * Initializes the layer after a successful load of data from a file
495 * @since 5459
496 */
497 public void onPostLoadFromFile() {
498 // To be overriden if needed
499 }
500
501 /**
502 * Replies the savable state of this layer (i.e if it can be saved through a "File-&gt;Save" dialog).
503 * @return true if this layer can be saved to a file
504 * @since 5459
505 */
506 public boolean isSavable() {
507 return false;
508 }
509
510 /**
511 * Checks whether it is ok to launch a save (whether we have data, there is no conflict etc.)
512 * @return <code>true</code>, if it is safe to save.
513 * @since 5459
514 */
515 public boolean checkSaveConditions() {
516 return true;
517 }
518
519 /**
520 * Creates a new "Save" dialog for this layer and makes it visible.<br>
521 * When the user has chosen a file, checks the file extension, and confirms overwrite if needed.
522 * @return The output {@code File}
523 * @see SaveActionBase#createAndOpenSaveFileChooser
524 * @since 5459
525 */
526 public File createAndOpenSaveFileChooser() {
527 return SaveActionBase.createAndOpenSaveFileChooser(tr("Save Layer"), "lay");
528 }
529
530 /**
531 * @return bytes that the tile will use. Needed for resource management
532 */
533 protected long estimateMemoryUsage() {
534 return 0;
535 }
536}
Note: See TracBrowser for help on using the repository browser.