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

Last change on this file since 17903 was 17862, checked in by simon04, 3 years ago

fix #17177 - Add support for Mapbox Vector Tile (patch by taylor.smock)

Signed-off-by: Taylor Smock <tsmock@…>

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