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 }