// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.layer; import static org.openstreetmap.josm.tools.I18n.tr; import static org.openstreetmap.josm.tools.I18n.trc; import java.awt.Component; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JSeparator; import javax.swing.JTextField; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.ProjectionBounds; import org.openstreetmap.josm.data.imagery.ImageryInfo; import org.openstreetmap.josm.data.imagery.OffsetBookmark; import org.openstreetmap.josm.data.preferences.IntegerProperty; import org.openstreetmap.josm.gui.MenuScroller; import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings; import org.openstreetmap.josm.gui.layer.imagery.TileSourceDisplaySettings; import org.openstreetmap.josm.tools.GBC; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.ImageProvider.ImageSizes; import org.openstreetmap.josm.tools.Utils; public abstract class ImageryLayer extends Layer { public static final IntegerProperty PROP_SHARPEN_LEVEL = new IntegerProperty("imagery.sharpen_level", 0); private final List<ImageProcessor> imageProcessors = new ArrayList<>(); protected final ImageryInfo info; protected Icon icon; private final ImageryFilterSettings filterSettings = new ImageryFilterSettings(); /** * Constructs a new {@code ImageryLayer}. * @param info imagery info */ public ImageryLayer(ImageryInfo info) { super(info.getName()); this.info = info; if (info.getIcon() != null) { icon = new ImageProvider(info.getIcon()).setOptional(true). setMaxSize(ImageSizes.LAYER).get(); } if (icon == null) { icon = ImageProvider.get("imagery_small"); } for (ImageProcessor processor : filterSettings.getProcessors()) { addImageProcessor(processor); } filterSettings.setSharpenLevel(1 + PROP_SHARPEN_LEVEL.get() / 2f); } public double getPPD() { if (!Main.isDisplayingMapView()) return Main.getProjection().getDefaultZoomInPPD(); ProjectionBounds bounds = Main.map.mapView.getProjectionBounds(); return Main.map.mapView.getWidth() / (bounds.maxEast - bounds.minEast); } /** * Gets the x displacement of this layer. * To be removed end of 2016 * @return The x displacement. * @deprecated Use {@link TileSourceDisplaySettings#getDx()} */ @Deprecated public double getDx() { // moved to AbstractTileSourceLayer/TileSourceDisplaySettings. Remains until all actions migrate. return 0; } /** * Gets the y displacement of this layer. * To be removed end of 2016 * @return The y displacement. * @deprecated Use {@link TileSourceDisplaySettings#getDy()} */ @Deprecated public double getDy() { // moved to AbstractTileSourceLayer/TileSourceDisplaySettings. Remains until all actions migrate. return 0; } /** * Sets the displacement offset of this layer. The layer is automatically invalidated. * To be removed end of 2016 * @param dx The x offset * @param dy The y offset * @deprecated Use {@link TileSourceDisplaySettings} */ @Deprecated public void setOffset(double dx, double dy) { // moved to AbstractTileSourceLayer/TileSourceDisplaySettings. Remains until all actions migrate. } /** * To be removed end of 2016 * @param dx deprecated * @param dy deprecated * @deprecated Use {@link TileSourceDisplaySettings} */ @Deprecated public void displace(double dx, double dy) { // moved to AbstractTileSourceLayer/TileSourceDisplaySettings. Remains until all actions migrate. } /** * Returns imagery info. * @return imagery info */ public ImageryInfo getInfo() { return info; } @Override public Icon getIcon() { return icon; } @Override public boolean isMergable(Layer other) { return false; } @Override public void mergeFrom(Layer from) { } @Override public Object getInfoComponent() { JPanel panel = new JPanel(new GridBagLayout()); panel.add(new JLabel(getToolTipText()), GBC.eol()); if (info != null) { List<List<String>> content = new ArrayList<>(); content.add(Arrays.asList(tr("Name"), info.getName())); content.add(Arrays.asList(tr("Type"), info.getImageryType().getTypeString().toUpperCase(Locale.ENGLISH))); content.add(Arrays.asList(tr("URL"), info.getUrl())); content.add(Arrays.asList(tr("Id"), info.getId() == null ? "-" : info.getId())); if (info.getMinZoom() != 0) { content.add(Arrays.asList(tr("Min. zoom"), Integer.toString(info.getMinZoom()))); } if (info.getMaxZoom() != 0) { content.add(Arrays.asList(tr("Max. zoom"), Integer.toString(info.getMaxZoom()))); } if (info.getDescription() != null) { content.add(Arrays.asList(tr("Description"), info.getDescription())); } for (List<String> entry: content) { panel.add(new JLabel(entry.get(0) + ':'), GBC.std()); panel.add(GBC.glue(5, 0), GBC.std()); panel.add(createTextField(entry.get(1)), GBC.eol().fill(GBC.HORIZONTAL)); } } return panel; } protected JTextField createTextField(String text) { JTextField ret = new JTextField(text); ret.setEditable(false); ret.setBorder(BorderFactory.createEmptyBorder()); return ret; } public static ImageryLayer create(ImageryInfo info) { switch(info.getImageryType()) { case WMS: return new WMSLayer(info); case WMTS: return new WMTSLayer(info); case TMS: case BING: case SCANEX: return new TMSLayer(info); default: throw new AssertionError(tr("Unsupported imagery type: {0}", info.getImageryType())); } } class ApplyOffsetAction extends AbstractAction { private final transient OffsetBookmark b; ApplyOffsetAction(OffsetBookmark b) { super(b.name); this.b = b; } @Override public void actionPerformed(ActionEvent ev) { setOffset(b.dx, b.dy); Main.main.menu.imageryMenu.refreshOffsetMenu(); Main.map.repaint(); } } public class OffsetAction extends AbstractAction implements LayerAction { @Override public void actionPerformed(ActionEvent e) { // Do nothing } @Override public Component createMenuComponent() { return getOffsetMenuItem(); } @Override public boolean supportLayers(List<Layer> layers) { return false; } } public JMenuItem getOffsetMenuItem() { JMenu subMenu = new JMenu(trc("layer", "Offset")); subMenu.setIcon(ImageProvider.get("mapmode", "adjustimg")); return (JMenuItem) getOffsetMenuItem(subMenu); } public JComponent getOffsetMenuItem(JComponent subMenu) { JMenuItem adjustMenuItem = new JMenuItem(getAdjustAction()); List<OffsetBookmark> allBookmarks = OffsetBookmark.getBookmarks(); if (allBookmarks.isEmpty()) return adjustMenuItem; subMenu.add(adjustMenuItem); subMenu.add(new JSeparator()); boolean hasBookmarks = false; int menuItemHeight = 0; for (OffsetBookmark b : allBookmarks) { if (!b.isUsable(this)) { continue; } JCheckBoxMenuItem item = new JCheckBoxMenuItem(new ApplyOffsetAction(b)); if (Utils.equalsEpsilon(b.dx, getDx()) && Utils.equalsEpsilon(b.dy, getDy())) { item.setSelected(true); } subMenu.add(item); menuItemHeight = item.getPreferredSize().height; hasBookmarks = true; } if (menuItemHeight > 0) { if (subMenu instanceof JMenu) { MenuScroller.setScrollerFor((JMenu) subMenu); } else if (subMenu instanceof JPopupMenu) { MenuScroller.setScrollerFor((JPopupMenu) subMenu); } } return hasBookmarks ? subMenu : adjustMenuItem; } protected abstract Action getAdjustAction(); /** * Gets the settings for the filter that is applied to this layer. * @return The filter settings. * @since 10547 */ public ImageryFilterSettings getFilterSettings() { return filterSettings; } /** * This method adds the {@link ImageProcessor} to this Layer if it is not {@code null}. * * @param processor that processes the image * * @return true if processor was added, false otherwise */ public boolean addImageProcessor(ImageProcessor processor) { return processor != null && imageProcessors.add(processor); } /** * This method removes given {@link ImageProcessor} from this layer * * @param processor which is needed to be removed * * @return true if processor was removed */ public boolean removeImageProcessor(ImageProcessor processor) { return imageProcessors.remove(processor); } /** * Wraps a {@link BufferedImageOp} to be used as {@link ImageProcessor}. * @param op the {@link BufferedImageOp} * @param inPlace true to apply filter in place, i.e., not create a new {@link BufferedImage} for the result * (the {@code op} needs to support this!) * @return the {@link ImageProcessor} wrapper */ public static ImageProcessor createImageProcessor(final BufferedImageOp op, final boolean inPlace) { return image -> op.filter(image, inPlace ? image : null); } /** * This method gets all {@link ImageProcessor}s of the layer * * @return list of image processors without removed one */ public List<ImageProcessor> getImageProcessors() { return imageProcessors; } /** * Applies all the chosen {@link ImageProcessor}s to the image * * @param img - image which should be changed * * @return the new changed image */ public BufferedImage applyImageProcessors(BufferedImage img) { for (ImageProcessor processor : imageProcessors) { img = processor.process(img); } return img; } @Override public String toString() { return getClass().getSimpleName() + " [info=" + info + ']'; } }