001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.layer;
003
004import static org.openstreetmap.josm.tools.I18n.marktr;
005import static org.openstreetmap.josm.tools.I18n.tr;
006import static org.openstreetmap.josm.tools.I18n.trc;
007
008import java.awt.Color;
009import java.awt.Component;
010import java.awt.Font;
011import java.awt.Graphics;
012import java.awt.GridBagLayout;
013import java.awt.event.ActionEvent;
014import java.awt.image.BufferedImage;
015import java.awt.image.BufferedImageOp;
016import java.awt.image.ConvolveOp;
017import java.awt.image.Kernel;
018import java.util.List;
019
020import javax.swing.AbstractAction;
021import javax.swing.Icon;
022import javax.swing.JCheckBoxMenuItem;
023import javax.swing.JComponent;
024import javax.swing.JLabel;
025import javax.swing.JMenu;
026import javax.swing.JMenuItem;
027import javax.swing.JPanel;
028import javax.swing.JPopupMenu;
029import javax.swing.JSeparator;
030
031import org.openstreetmap.josm.Main;
032import org.openstreetmap.josm.actions.ImageryAdjustAction;
033import org.openstreetmap.josm.data.ProjectionBounds;
034import org.openstreetmap.josm.data.imagery.ImageryInfo;
035import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
036import org.openstreetmap.josm.data.imagery.OffsetBookmark;
037import org.openstreetmap.josm.data.preferences.ColorProperty;
038import org.openstreetmap.josm.data.preferences.IntegerProperty;
039import org.openstreetmap.josm.gui.MenuScroller;
040import org.openstreetmap.josm.gui.widgets.UrlLabel;
041import org.openstreetmap.josm.tools.GBC;
042import org.openstreetmap.josm.tools.ImageProvider;
043
044public abstract class ImageryLayer extends Layer {
045
046    public static final ColorProperty PROP_FADE_COLOR = new ColorProperty(marktr("Imagery fade"), Color.white);
047    public static final IntegerProperty PROP_FADE_AMOUNT = new IntegerProperty("imagery.fade_amount", 0);
048    public static final IntegerProperty PROP_SHARPEN_LEVEL = new IntegerProperty("imagery.sharpen_level", 0);
049
050    public static Color getFadeColor() {
051        return PROP_FADE_COLOR.get();
052    }
053
054    public static Color getFadeColorWithAlpha() {
055        Color c = PROP_FADE_COLOR.get();
056        return new Color(c.getRed(),c.getGreen(),c.getBlue(),PROP_FADE_AMOUNT.get()*255/100);
057    }
058
059    protected final ImageryInfo info;
060
061    protected Icon icon;
062
063    protected double dx = 0.0;
064    protected double dy = 0.0;
065
066    protected int sharpenLevel;
067
068    private final ImageryAdjustAction adjustAction = new ImageryAdjustAction(this);
069
070    public ImageryLayer(ImageryInfo info) {
071        super(info.getName());
072        this.info = info;
073        if (info.getIcon() != null) {
074            icon = new ImageProvider(info.getIcon()).setOptional(true).
075                    setMaxHeight(ICON_SIZE).setMaxWidth(ICON_SIZE).get();
076        }
077        if (icon == null) {
078            icon = ImageProvider.get("imagery_small");
079        }
080        this.sharpenLevel = PROP_SHARPEN_LEVEL.get();
081    }
082
083    public double getPPD(){
084        if (!Main.isDisplayingMapView()) return Main.getProjection().getDefaultZoomInPPD();
085        ProjectionBounds bounds = Main.map.mapView.getProjectionBounds();
086        return Main.map.mapView.getWidth() / (bounds.maxEast - bounds.minEast);
087    }
088
089    public double getDx() {
090        return dx;
091    }
092
093    public double getDy() {
094        return dy;
095    }
096
097    public void setOffset(double dx, double dy) {
098        this.dx = dx;
099        this.dy = dy;
100    }
101
102    public void displace(double dx, double dy) {
103        setOffset(this.dx += dx, this.dy += dy);
104    }
105
106    public ImageryInfo getInfo() {
107        return info;
108    }
109
110    @Override
111    public Icon getIcon() {
112        return icon;
113    }
114
115    @Override
116    public boolean isMergable(Layer other) {
117        return false;
118    }
119
120    @Override
121    public void mergeFrom(Layer from) {
122    }
123
124    @Override
125    public Object getInfoComponent() {
126        JPanel panel = new JPanel(new GridBagLayout());
127        panel.add(new JLabel(getToolTipText()), GBC.eol());
128        if (info != null) {
129            String url = info.getUrl();
130            if (url != null) {
131                panel.add(new JLabel(tr("URL: ")), GBC.std().insets(0, 5, 2, 0));
132                panel.add(new UrlLabel(url), GBC.eol().insets(2, 5, 10, 0));
133            }
134            if (dx != 0.0 || dy != 0.0) {
135                panel.add(new JLabel(tr("Offset: ") + dx + ";" + dy), GBC.eol().insets(0, 5, 10, 0));
136            }
137        }
138        return panel;
139    }
140
141    public static ImageryLayer create(ImageryInfo info) {
142        if (info.getImageryType() == ImageryType.WMS || info.getImageryType() == ImageryType.HTML)
143            return new WMSLayer(info);
144        else if (info.getImageryType() == ImageryType.TMS || info.getImageryType() == ImageryType.BING || info.getImageryType() == ImageryType.SCANEX)
145            return new TMSLayer(info);
146        else throw new AssertionError();
147    }
148
149    class ApplyOffsetAction extends AbstractAction {
150        private OffsetBookmark b;
151        ApplyOffsetAction(OffsetBookmark b) {
152            super(b.name);
153            this.b = b;
154        }
155
156        @Override
157        public void actionPerformed(ActionEvent ev) {
158            setOffset(b.dx, b.dy);
159            Main.main.menu.imageryMenu.refreshOffsetMenu();
160            Main.map.repaint();
161        }
162    }
163
164    public class OffsetAction extends AbstractAction implements LayerAction {
165        @Override
166        public void actionPerformed(ActionEvent e) {
167        }
168
169        @Override
170        public Component createMenuComponent() {
171            return getOffsetMenuItem();
172        }
173
174        @Override
175        public boolean supportLayers(List<Layer> layers) {
176            return false;
177        }
178    }
179
180    public JMenuItem getOffsetMenuItem() {
181        JMenu subMenu = new JMenu(trc("layer", "Offset"));
182        subMenu.setIcon(ImageProvider.get("mapmode", "adjustimg"));
183        return (JMenuItem)getOffsetMenuItem(subMenu);
184    }
185
186    public JComponent getOffsetMenuItem(JComponent subMenu) {
187        JMenuItem adjustMenuItem = new JMenuItem(adjustAction);
188        if (OffsetBookmark.allBookmarks.isEmpty()) return adjustMenuItem;
189
190        subMenu.add(adjustMenuItem);
191        subMenu.add(new JSeparator());
192        boolean hasBookmarks = false;
193        int menuItemHeight = 0;
194        for (OffsetBookmark b : OffsetBookmark.allBookmarks) {
195            if (!b.isUsable(this)) {
196                continue;
197            }
198            JCheckBoxMenuItem item = new JCheckBoxMenuItem(new ApplyOffsetAction(b));
199            if (b.dx == dx && b.dy == dy) {
200                item.setSelected(true);
201            }
202            subMenu.add(item);
203            menuItemHeight = item.getPreferredSize().height;
204            hasBookmarks = true;
205        }
206        if (menuItemHeight > 0) {
207            int scrollcount = MenuScroller.computeScrollCount(subMenu, menuItemHeight);
208            if (subMenu instanceof JMenu) {
209                MenuScroller.setScrollerFor((JMenu) subMenu, scrollcount);
210            } else if (subMenu instanceof JPopupMenu) {
211                MenuScroller.setScrollerFor((JPopupMenu)subMenu, scrollcount);
212            }
213        }
214        return hasBookmarks ? subMenu : adjustMenuItem;
215    }
216
217    public BufferedImage sharpenImage(BufferedImage img) {
218        if (sharpenLevel <= 0) return img;
219        int width = img.getWidth(null);
220        int height = img.getHeight(null);
221        BufferedImage tmp = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
222        tmp.getGraphics().drawImage(img, 0, 0, null);
223        Kernel kernel;
224        if (sharpenLevel == 1) {
225            kernel = new Kernel(3, 3, new float[] { -0.25f, -0.5f, -0.25f, -0.5f, 4, -0.5f, -0.25f, -0.5f, -0.25f});
226        } else {
227            kernel = new Kernel(3, 3, new float[] { -0.5f, -1, -0.5f, -1, 7, -1, -0.5f, -1, -0.5f});
228        }
229        BufferedImageOp op = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
230        return op.filter(tmp, null);
231    }
232
233    public void drawErrorTile(BufferedImage img) {
234        Graphics g = img.getGraphics();
235        g.setColor(Color.RED);
236        g.fillRect(0, 0, img.getWidth(), img.getHeight());
237        g.setFont(g.getFont().deriveFont(Font.PLAIN).deriveFont(36.0f));
238        g.setColor(Color.BLACK);
239
240        String text = tr("ERROR");
241        g.drawString(text, (img.getWidth() + g.getFontMetrics().stringWidth(text)) / 2, img.getHeight()/2);
242    }
243
244    /* (non-Javadoc)
245     * @see org.openstreetmap.josm.gui.layer.Layer#destroy()
246     */
247    @Override
248    public void destroy() {
249        super.destroy();
250        adjustAction.destroy();
251    }
252}