001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.layer;
003    
004    import static org.openstreetmap.josm.tools.I18n.marktr;
005    import static org.openstreetmap.josm.tools.I18n.tr;
006    import static org.openstreetmap.josm.tools.I18n.trc;
007    
008    import java.awt.Color;
009    import java.awt.Component;
010    import java.awt.Font;
011    import java.awt.Graphics;
012    import java.awt.Toolkit;
013    import java.awt.event.ActionEvent;
014    import java.awt.image.BufferedImage;
015    import java.awt.image.BufferedImageOp;
016    import java.awt.image.ConvolveOp;
017    import java.awt.image.Kernel;
018    import java.util.List;
019    
020    import javax.swing.AbstractAction;
021    import javax.swing.Icon;
022    import javax.swing.JCheckBoxMenuItem;
023    import javax.swing.JComponent;
024    import javax.swing.JMenu;
025    import javax.swing.JMenuItem;
026    import javax.swing.JPopupMenu;
027    import javax.swing.JSeparator;
028    import javax.swing.SwingUtilities;
029    
030    import org.openstreetmap.josm.Main;
031    import org.openstreetmap.josm.actions.ImageryAdjustAction;
032    import org.openstreetmap.josm.data.ProjectionBounds;
033    import org.openstreetmap.josm.data.coor.EastNorth;
034    import org.openstreetmap.josm.data.imagery.ImageryInfo;
035    import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
036    import org.openstreetmap.josm.data.imagery.OffsetBookmark;
037    import org.openstreetmap.josm.data.preferences.ColorProperty;
038    import org.openstreetmap.josm.data.preferences.IntegerProperty;
039    import org.openstreetmap.josm.gui.MenuScroller;
040    import org.openstreetmap.josm.io.imagery.OffsetServer;
041    import org.openstreetmap.josm.io.imagery.OsmosnimkiOffsetServer;
042    import org.openstreetmap.josm.tools.ImageProvider;
043    
044    public 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        protected boolean offsetServerSupported;
069        protected boolean offsetServerUsed;
070        protected OffsetServerThread offsetServerThread;
071    
072        private final ImageryAdjustAction adjustAction = new ImageryAdjustAction(this);
073        private final AbstractAction useServerOffsetAction = new AbstractAction(tr("(use server offset)")) {
074            @Override
075            public void actionPerformed(ActionEvent e) {
076                enableOffsetServer(true);
077            }
078        };
079    
080        protected OffsetServerThread createoffsetServerThread() {
081            return new OffsetServerThread(new OsmosnimkiOffsetServer(
082                    OsmosnimkiOffsetServer.PROP_SERVER_URL.get()));
083        }
084    
085        public ImageryLayer(ImageryInfo info) {
086            super(info.getName());
087            this.info = info;
088            if (info.getIcon() != null) {
089                icon = new ImageProvider(info.getIcon()).setOptional(true).
090                        setMaxHeight(ICON_SIZE).setMaxWidth(ICON_SIZE).get();
091            }
092            if (icon == null) {
093                icon = ImageProvider.get("imagery_small");
094            }
095            this.sharpenLevel = PROP_SHARPEN_LEVEL.get();
096            if (OffsetServer.PROP_SERVER_ENABLED.get()) {
097                offsetServerThread = createoffsetServerThread();
098                offsetServerThread.start();
099            }
100        }
101    
102        public double getPPD(){
103            if (Main.map == null || Main.map.mapView == null) return Main.getProjection().getDefaultZoomInPPD();
104            ProjectionBounds bounds = Main.map.mapView.getProjectionBounds();
105            return Main.map.mapView.getWidth() / (bounds.maxEast - bounds.minEast);
106        }
107    
108        public double getDx() {
109            return dx;
110        }
111    
112        public double getDy() {
113            return dy;
114        }
115    
116        public void setOffset(double dx, double dy) {
117            this.dx = dx;
118            this.dy = dy;
119        }
120    
121        public void displace(double dx, double dy) {
122            setOffset(this.dx += dx, this.dy += dy);
123        }
124    
125        public ImageryInfo getInfo() {
126            return info;
127        }
128    
129        @Override
130        public Icon getIcon() {
131            return icon;
132        }
133    
134        @Override
135        public boolean isMergable(Layer other) {
136            return false;
137        }
138    
139        @Override
140        public void mergeFrom(Layer from) {
141        }
142    
143        @Override
144        public Object getInfoComponent() {
145            return getToolTipText();
146        }
147    
148        public static ImageryLayer create(ImageryInfo info) {
149            if (info.getImageryType() == ImageryType.WMS || info.getImageryType() == ImageryType.HTML)
150                return new WMSLayer(info);
151            else if (info.getImageryType() == ImageryType.TMS || info.getImageryType() == ImageryType.BING || info.getImageryType() == ImageryType.SCANEX)
152                return new TMSLayer(info);
153            else throw new AssertionError();
154        }
155    
156        class ApplyOffsetAction extends AbstractAction {
157            private OffsetBookmark b;
158            ApplyOffsetAction(OffsetBookmark b) {
159                super(b.name);
160                this.b = b;
161            }
162    
163            @Override
164            public void actionPerformed(ActionEvent ev) {
165                setOffset(b.dx, b.dy);
166                enableOffsetServer(false);
167                Main.main.menu.imageryMenu.refreshOffsetMenu();
168                Main.map.repaint();
169            }
170        }
171    
172        public class OffsetAction extends AbstractAction implements LayerAction {
173            @Override
174            public void actionPerformed(ActionEvent e) {
175            }
176    
177            @Override
178            public Component createMenuComponent() {
179                return getOffsetMenuItem();
180            }
181    
182            @Override
183            public boolean supportLayers(List<Layer> layers) {
184                return false;
185            }
186        }
187    
188        public void enableOffsetServer(boolean enable) {
189            offsetServerUsed = enable;
190            if (offsetServerUsed && !offsetServerThread.isAlive()) {
191                offsetServerThread = createoffsetServerThread();
192                offsetServerThread.start();
193            }
194        }
195    
196        public JMenuItem getOffsetMenuItem() {
197            JMenu subMenu = new JMenu(trc("layer", "Offset"));
198            subMenu.setIcon(ImageProvider.get("mapmode", "adjustimg"));
199            return (JMenuItem)getOffsetMenuItem(subMenu);
200        }
201    
202        public JComponent getOffsetMenuItem(JComponent subMenu) {
203            JMenuItem adjustMenuItem = new JMenuItem(adjustAction);
204            if (OffsetBookmark.allBookmarks.isEmpty() && !offsetServerSupported) return adjustMenuItem;
205    
206            subMenu.add(adjustMenuItem);
207            if (offsetServerSupported) {
208                JCheckBoxMenuItem item = new JCheckBoxMenuItem(useServerOffsetAction);
209                if (offsetServerUsed) {
210                    item.setSelected(true);
211                }
212                subMenu.add(item);
213            }
214            subMenu.add(new JSeparator());
215            boolean hasBookmarks = false;
216            int menuItemHeight = 0;
217            for (OffsetBookmark b : OffsetBookmark.allBookmarks) {
218                if (!b.isUsable(this)) {
219                    continue;
220                }
221                JCheckBoxMenuItem item = new JCheckBoxMenuItem(new ApplyOffsetAction(b));
222                if (b.dx == dx && b.dy == dy && !offsetServerUsed) {
223                    item.setSelected(true);
224                }
225                subMenu.add(item);
226                menuItemHeight = item.getPreferredSize().height;
227                hasBookmarks = true;
228            }
229            if (menuItemHeight > 0) {
230                int scrollcount = (Toolkit.getDefaultToolkit().getScreenSize().height / menuItemHeight) - 1;
231                if (subMenu instanceof JMenu) {
232                    MenuScroller.setScrollerFor((JMenu) subMenu, scrollcount);
233                } else if (subMenu instanceof JPopupMenu) {
234                    MenuScroller.setScrollerFor((JPopupMenu)subMenu, scrollcount);
235                }
236            }
237            return (hasBookmarks || offsetServerSupported) ? subMenu : adjustMenuItem;
238        }
239    
240        public BufferedImage sharpenImage(BufferedImage img) {
241            if (sharpenLevel <= 0) return img;
242            int width = img.getWidth(null);
243            int height = img.getHeight(null);
244            BufferedImage tmp = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
245            tmp.getGraphics().drawImage(img, 0, 0, null);
246            Kernel kernel;
247            if (sharpenLevel == 1) {
248                kernel = new Kernel(3, 3, new float[] { -0.25f, -0.5f, -0.25f, -0.5f, 4, -0.5f, -0.25f, -0.5f, -0.25f});
249            } else {
250                kernel = new Kernel(3, 3, new float[] { -0.5f, -1, -0.5f, -1, 7, -1, -0.5f, -1, -0.5f});
251            }
252            BufferedImageOp op = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
253            return op.filter(tmp, null);
254        }
255    
256        public void drawErrorTile(BufferedImage img) {
257            Graphics g = img.getGraphics();
258            g.setColor(Color.RED);
259            g.fillRect(0, 0, img.getWidth(), img.getHeight());
260            g.setFont(g.getFont().deriveFont(Font.PLAIN).deriveFont(36.0f));
261            g.setColor(Color.BLACK);
262    
263            String text = tr("ERROR");
264            g.drawString(text, (img.getWidth() + g.getFontMetrics().stringWidth(text)) / 2, img.getHeight()/2);
265        }
266    
267        protected class OffsetServerThread extends Thread {
268            OffsetServer offsetServer;
269            EastNorth oldCenter = new EastNorth(Double.NaN, Double.NaN);
270    
271            public OffsetServerThread(OffsetServer offsetServer) {
272                this.offsetServer = offsetServer;
273                setDaemon(true);
274            }
275    
276            private void updateOffset() {
277                if (Main.map == null || Main.map.mapView == null) return;
278                EastNorth center = Main.map.mapView.getCenter();
279                if (center.equals(oldCenter)) return;
280                oldCenter = center;
281    
282                EastNorth offset = offsetServer.getOffset(getInfo(), center);
283                if (offset != null) {
284                    setOffset(offset.east(),offset.north());
285                }
286            }
287    
288            @Override
289            public void run() {
290                if (!offsetServerSupported) {
291                    if (!offsetServer.isLayerSupported(info)) return;
292                    offsetServerSupported = true;
293                }
294                offsetServerUsed = true;
295                SwingUtilities.invokeLater(new Runnable() {
296                    @Override
297                    public void run() {
298                        Main.main.menu.imageryMenu.refreshOffsetMenu();
299                    }
300                });
301                try {
302                    while (offsetServerUsed) {
303                        updateOffset();
304                        Thread.sleep(1000);
305                    }
306                } catch (InterruptedException e) {
307                }
308                offsetServerUsed = false;
309            }
310        }
311    
312        /* (non-Javadoc)
313         * @see org.openstreetmap.josm.gui.layer.Layer#destroy()
314         */
315        @Override
316        public void destroy() {
317            adjustAction.destroy();
318        }
319    }