source: josm/trunk/src/org/openstreetmap/josm/gui/layer/ImageryLayer.java@ 13733

Last change on this file since 13733 was 13733, checked in by wiktorn, 8 years ago

Imagery definition refactor

Extend imagery definitions by:

  • allowing setting default layers for WMS_ENDPOINT and WMTS
  • allowing setting minimum expires time for tile for this imagery
  • allowing setting custom headers that will be sent for all requests

(get map, get capabilities) for this imagery

Additional changes in code:

  • use TileJobOptions to pass miscellaneous options to loaders
  • refactor WMSImagery to use SAX parser

See: #15981, #7953, #16224, #15940, #16249

  • Property svn:eol-style set to native
File size: 12.7 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;
5import static org.openstreetmap.josm.tools.I18n.trc;
6
7import java.awt.Component;
8import java.awt.GridBagLayout;
9import java.awt.event.ActionEvent;
10import java.awt.image.BufferedImage;
11import java.awt.image.BufferedImageOp;
12import java.util.ArrayList;
13import java.util.Arrays;
14import java.util.List;
15import java.util.Locale;
16
17import javax.swing.AbstractAction;
18import javax.swing.Action;
19import javax.swing.BorderFactory;
20import javax.swing.Icon;
21import javax.swing.JCheckBoxMenuItem;
22import javax.swing.JComponent;
23import javax.swing.JLabel;
24import javax.swing.JMenu;
25import javax.swing.JMenuItem;
26import javax.swing.JPanel;
27import javax.swing.JPopupMenu;
28import javax.swing.JSeparator;
29import javax.swing.JTextField;
30
31import org.openstreetmap.josm.Main;
32import org.openstreetmap.josm.data.ProjectionBounds;
33import org.openstreetmap.josm.data.imagery.ImageryInfo;
34import org.openstreetmap.josm.data.imagery.OffsetBookmark;
35import org.openstreetmap.josm.data.preferences.IntegerProperty;
36import org.openstreetmap.josm.gui.MainApplication;
37import org.openstreetmap.josm.gui.MapView;
38import org.openstreetmap.josm.gui.MenuScroller;
39import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings;
40import org.openstreetmap.josm.gui.layer.imagery.TileSourceDisplaySettings;
41import org.openstreetmap.josm.gui.widgets.UrlLabel;
42import org.openstreetmap.josm.tools.GBC;
43import org.openstreetmap.josm.tools.ImageProcessor;
44import org.openstreetmap.josm.tools.ImageProvider;
45import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
46
47/**
48 * Abstract base class for background imagery layers ({@link WMSLayer}, {@link TMSLayer}, {@link WMTSLayer}).
49 *
50 * Handles some common tasks, like image filters, image processors, etc.
51 */
52public abstract class ImageryLayer extends Layer {
53
54 /**
55 * The default value for the sharpen filter for each imagery layer.
56 */
57 public static final IntegerProperty PROP_SHARPEN_LEVEL = new IntegerProperty("imagery.sharpen_level", 0);
58
59 private final List<ImageProcessor> imageProcessors = new ArrayList<>();
60
61 protected final ImageryInfo info;
62
63 protected Icon icon;
64
65 private final ImageryFilterSettings filterSettings = new ImageryFilterSettings();
66
67 /**
68 * Constructs a new {@code ImageryLayer}.
69 * @param info imagery info
70 */
71 public ImageryLayer(ImageryInfo info) {
72 super(info.getName());
73 this.info = info;
74 if (info.getIcon() != null) {
75 icon = new ImageProvider(info.getIcon()).setOptional(true).
76 setMaxSize(ImageSizes.LAYER).get();
77 }
78 if (icon == null) {
79 icon = ImageProvider.get("imagery_small");
80 }
81 for (ImageProcessor processor : filterSettings.getProcessors()) {
82 addImageProcessor(processor);
83 }
84 filterSettings.setSharpenLevel(1 + PROP_SHARPEN_LEVEL.get() / 2f);
85 }
86
87 public double getPPD() {
88 if (!MainApplication.isDisplayingMapView())
89 return Main.getProjection().getDefaultZoomInPPD();
90 MapView mapView = MainApplication.getMap().mapView;
91 ProjectionBounds bounds = mapView.getProjectionBounds();
92 return mapView.getWidth() / (bounds.maxEast - bounds.minEast);
93 }
94
95 /**
96 * Gets the x displacement of this layer.
97 * To be removed end of 2016
98 * @return The x displacement.
99 * @deprecated Use {@link TileSourceDisplaySettings#getDx()}
100 */
101 @Deprecated
102 public double getDx() {
103 // moved to AbstractTileSourceLayer/TileSourceDisplaySettings. Remains until all actions migrate.
104 return 0;
105 }
106
107 /**
108 * Gets the y displacement of this layer.
109 * To be removed end of 2016
110 * @return The y displacement.
111 * @deprecated Use {@link TileSourceDisplaySettings#getDy()}
112 */
113 @Deprecated
114 public double getDy() {
115 // moved to AbstractTileSourceLayer/TileSourceDisplaySettings. Remains until all actions migrate.
116 return 0;
117 }
118
119 /**
120 * Sets the displacement offset of this layer. The layer is automatically invalidated.
121 * To be removed end of 2016
122 * @param offset the offset bookmark
123 * @deprecated Use {@link TileSourceDisplaySettings}
124 */
125 @Deprecated
126 public void setOffset(OffsetBookmark offset) {
127 // moved to AbstractTileSourceLayer/TileSourceDisplaySettings. Remains until all actions migrate.
128 }
129
130 /**
131 * Returns imagery info.
132 * @return imagery info
133 */
134 public ImageryInfo getInfo() {
135 return info;
136 }
137
138 @Override
139 public Icon getIcon() {
140 return icon;
141 }
142
143 @Override
144 public boolean isMergable(Layer other) {
145 return false;
146 }
147
148 @Override
149 public void mergeFrom(Layer from) {
150 }
151
152 @Override
153 public Object getInfoComponent() {
154 JPanel panel = new JPanel(new GridBagLayout());
155 panel.add(new JLabel(getToolTipText()), GBC.eol());
156 if (info != null) {
157 List<List<String>> content = new ArrayList<>();
158 content.add(Arrays.asList(tr("Name"), info.getName()));
159 content.add(Arrays.asList(tr("Type"), info.getImageryType().getTypeString().toUpperCase(Locale.ENGLISH)));
160 content.add(Arrays.asList(tr("URL"), info.getUrl()));
161 content.add(Arrays.asList(tr("Id"), info.getId() == null ? "-" : info.getId()));
162 if (info.getMinZoom() != 0) {
163 content.add(Arrays.asList(tr("Min. zoom"), Integer.toString(info.getMinZoom())));
164 }
165 if (info.getMaxZoom() != 0) {
166 content.add(Arrays.asList(tr("Max. zoom"), Integer.toString(info.getMaxZoom())));
167 }
168 if (info.getDescription() != null) {
169 content.add(Arrays.asList(tr("Description"), info.getDescription()));
170 }
171 for (List<String> entry: content) {
172 panel.add(new JLabel(entry.get(0) + ':'), GBC.std());
173 panel.add(GBC.glue(5, 0), GBC.std());
174 panel.add(createTextField(entry.get(1)), GBC.eol().fill(GBC.HORIZONTAL));
175 }
176 }
177 return panel;
178 }
179
180 protected JComponent createTextField(String text) {
181 if (text != null && text.matches("https?://.*")) {
182 return new UrlLabel(text);
183 }
184 JTextField ret = new JTextField(text);
185 ret.setEditable(false);
186 ret.setBorder(BorderFactory.createEmptyBorder());
187 return ret;
188 }
189
190 /**
191 * Create a new imagery layer
192 * @param info The imagery info to use as base
193 * @return The created layer
194 */
195 public static ImageryLayer create(ImageryInfo info) {
196 switch(info.getImageryType()) {
197 case WMS:
198 case WMS_ENDPOINT:
199 return new WMSLayer(info);
200 case WMTS:
201 return new WMTSLayer(info);
202 case TMS:
203 case BING:
204 case SCANEX:
205 return new TMSLayer(info);
206 default:
207 throw new AssertionError(tr("Unsupported imagery type: {0}", info.getImageryType()));
208 }
209 }
210
211 private static class ApplyOffsetAction extends AbstractAction {
212 private final transient OffsetMenuEntry menuEntry;
213
214 ApplyOffsetAction(OffsetMenuEntry menuEntry) {
215 super(menuEntry.getLabel());
216 this.menuEntry = menuEntry;
217 }
218
219 @Override
220 public void actionPerformed(ActionEvent ev) {
221 menuEntry.actionPerformed();
222 //TODO: Use some form of listeners for this.
223 MainApplication.getMenu().imageryMenu.refreshOffsetMenu();
224 }
225 }
226
227 public class OffsetAction extends AbstractAction implements LayerAction {
228 @Override
229 public void actionPerformed(ActionEvent e) {
230 // Do nothing
231 }
232
233 @Override
234 public Component createMenuComponent() {
235 return getOffsetMenuItem();
236 }
237
238 @Override
239 public boolean supportLayers(List<Layer> layers) {
240 return false;
241 }
242 }
243
244 /**
245 * Create the menu item that should be added to the offset menu.
246 * It may have a sub menu of e.g. bookmarks added to it.
247 * @return The menu item to add to the imagery menu.
248 */
249 public JMenuItem getOffsetMenuItem() {
250 JMenu subMenu = new JMenu(trc("layer", "Offset"));
251 subMenu.setIcon(ImageProvider.get("mapmode", "adjustimg"));
252 return (JMenuItem) getOffsetMenuItem(subMenu);
253 }
254
255 /**
256 * Create the submenu or the menu item to set the offset of the layer.
257 *
258 * If only one menu item for this layer exists, it is returned by this method.
259 *
260 * If there are multiple, this method appends them to the subMenu and then returns the reference to the subMenu.
261 * @param subMenu The subMenu to use
262 * @return A single menu item to adjust the layer or the passed subMenu to which the menu items were appended.
263 */
264 public JComponent getOffsetMenuItem(JComponent subMenu) {
265 JMenuItem adjustMenuItem = new JMenuItem(getAdjustAction());
266 List<OffsetMenuEntry> usableBookmarks = getOffsetMenuEntries();
267 if (usableBookmarks.isEmpty()) {
268 return adjustMenuItem;
269 }
270
271 subMenu.add(adjustMenuItem);
272 subMenu.add(new JSeparator());
273 int menuItemHeight = 0;
274 for (OffsetMenuEntry b : usableBookmarks) {
275 JCheckBoxMenuItem item = new JCheckBoxMenuItem(new ApplyOffsetAction(b));
276 item.setSelected(b.isActive());
277 subMenu.add(item);
278 menuItemHeight = item.getPreferredSize().height;
279 }
280 if (menuItemHeight > 0) {
281 if (subMenu instanceof JMenu) {
282 MenuScroller.setScrollerFor((JMenu) subMenu);
283 } else if (subMenu instanceof JPopupMenu) {
284 MenuScroller.setScrollerFor((JPopupMenu) subMenu);
285 }
286 }
287 return subMenu;
288 }
289
290 protected abstract Action getAdjustAction();
291
292 protected abstract List<OffsetMenuEntry> getOffsetMenuEntries();
293
294 /**
295 * Gets the settings for the filter that is applied to this layer.
296 * @return The filter settings.
297 * @since 10547
298 */
299 public ImageryFilterSettings getFilterSettings() {
300 return filterSettings;
301 }
302
303 /**
304 * This method adds the {@link ImageProcessor} to this Layer if it is not {@code null}.
305 *
306 * @param processor that processes the image
307 *
308 * @return true if processor was added, false otherwise
309 */
310 public boolean addImageProcessor(ImageProcessor processor) {
311 return processor != null && imageProcessors.add(processor);
312 }
313
314 /**
315 * This method removes given {@link ImageProcessor} from this layer
316 *
317 * @param processor which is needed to be removed
318 *
319 * @return true if processor was removed
320 */
321 public boolean removeImageProcessor(ImageProcessor processor) {
322 return imageProcessors.remove(processor);
323 }
324
325 /**
326 * Wraps a {@link BufferedImageOp} to be used as {@link ImageProcessor}.
327 * @param op the {@link BufferedImageOp}
328 * @param inPlace true to apply filter in place, i.e., not create a new {@link BufferedImage} for the result
329 * (the {@code op} needs to support this!)
330 * @return the {@link ImageProcessor} wrapper
331 */
332 public static ImageProcessor createImageProcessor(final BufferedImageOp op, final boolean inPlace) {
333 return image -> op.filter(image, inPlace ? image : null);
334 }
335
336 /**
337 * This method gets all {@link ImageProcessor}s of the layer
338 *
339 * @return list of image processors without removed one
340 */
341 public List<ImageProcessor> getImageProcessors() {
342 return imageProcessors;
343 }
344
345 /**
346 * Applies all the chosen {@link ImageProcessor}s to the image
347 *
348 * @param img - image which should be changed
349 *
350 * @return the new changed image
351 */
352 public BufferedImage applyImageProcessors(BufferedImage img) {
353 for (ImageProcessor processor : imageProcessors) {
354 img = processor.process(img);
355 }
356 return img;
357 }
358
359 /**
360 * An additional menu entry in the imagery offset menu.
361 * @author Michael Zangl
362 * @see ImageryLayer#getOffsetMenuEntries()
363 * @since 13243
364 */
365 public interface OffsetMenuEntry {
366 /**
367 * Get the label to use for this menu item
368 * @return The label to display in the menu.
369 */
370 String getLabel();
371
372 /**
373 * Test whether this bookmark is currently active
374 * @return <code>true</code> if it is active
375 */
376 boolean isActive();
377
378 /**
379 * Load this bookmark
380 */
381 void actionPerformed();
382 }
383
384 @Override
385 public String toString() {
386 return getClass().getSimpleName() + " [info=" + info + ']';
387 }
388}
Note: See TracBrowser for help on using the repository browser.