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.Graphics2D; 012import java.awt.GridBagLayout; 013import java.awt.event.ActionEvent; 014import java.awt.font.FontRenderContext; 015import java.awt.font.LineBreakMeasurer; 016import java.awt.font.TextAttribute; 017import java.awt.font.TextLayout; 018import java.awt.image.BufferedImage; 019import java.awt.image.BufferedImageOp; 020import java.awt.image.ConvolveOp; 021import java.awt.image.Kernel; 022import java.text.AttributedCharacterIterator; 023import java.text.AttributedString; 024import java.util.Hashtable; 025import java.util.List; 026import java.util.Map; 027 028import javax.swing.AbstractAction; 029import javax.swing.Icon; 030import javax.swing.JCheckBoxMenuItem; 031import javax.swing.JComponent; 032import javax.swing.JLabel; 033import javax.swing.JMenu; 034import javax.swing.JMenuItem; 035import javax.swing.JPanel; 036import javax.swing.JPopupMenu; 037import javax.swing.JSeparator; 038 039import org.openstreetmap.josm.Main; 040import org.openstreetmap.josm.actions.ImageryAdjustAction; 041import org.openstreetmap.josm.data.ProjectionBounds; 042import org.openstreetmap.josm.data.imagery.ImageryInfo; 043import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType; 044import org.openstreetmap.josm.data.imagery.OffsetBookmark; 045import org.openstreetmap.josm.data.preferences.ColorProperty; 046import org.openstreetmap.josm.data.preferences.IntegerProperty; 047import org.openstreetmap.josm.gui.MenuScroller; 048import org.openstreetmap.josm.gui.widgets.UrlLabel; 049import org.openstreetmap.josm.tools.GBC; 050import org.openstreetmap.josm.tools.ImageProvider; 051 052public abstract class ImageryLayer extends Layer { 053 054 public static final ColorProperty PROP_FADE_COLOR = new ColorProperty(marktr("Imagery fade"), Color.white); 055 public static final IntegerProperty PROP_FADE_AMOUNT = new IntegerProperty("imagery.fade_amount", 0); 056 public static final IntegerProperty PROP_SHARPEN_LEVEL = new IntegerProperty("imagery.sharpen_level", 0); 057 058 public static Color getFadeColor() { 059 return PROP_FADE_COLOR.get(); 060 } 061 062 public static Color getFadeColorWithAlpha() { 063 Color c = PROP_FADE_COLOR.get(); 064 return new Color(c.getRed(),c.getGreen(),c.getBlue(),PROP_FADE_AMOUNT.get()*255/100); 065 } 066 067 protected final ImageryInfo info; 068 069 protected Icon icon; 070 071 protected double dx = 0.0; 072 protected double dy = 0.0; 073 074 protected int sharpenLevel; 075 076 private final ImageryAdjustAction adjustAction = new ImageryAdjustAction(this); 077 078 /** 079 * Constructs a new {@code ImageryLayer}. 080 */ 081 public ImageryLayer(ImageryInfo info) { 082 super(info.getName()); 083 this.info = info; 084 if (info.getIcon() != null) { 085 icon = new ImageProvider(info.getIcon()).setOptional(true). 086 setMaxHeight(ICON_SIZE).setMaxWidth(ICON_SIZE).get(); 087 } 088 if (icon == null) { 089 icon = ImageProvider.get("imagery_small"); 090 } 091 this.sharpenLevel = PROP_SHARPEN_LEVEL.get(); 092 } 093 094 public double getPPD() { 095 if (!Main.isDisplayingMapView()) return Main.getProjection().getDefaultZoomInPPD(); 096 ProjectionBounds bounds = Main.map.mapView.getProjectionBounds(); 097 return Main.map.mapView.getWidth() / (bounds.maxEast - bounds.minEast); 098 } 099 100 public double getDx() { 101 return dx; 102 } 103 104 public double getDy() { 105 return dy; 106 } 107 108 public void setOffset(double dx, double dy) { 109 this.dx = dx; 110 this.dy = dy; 111 } 112 113 public void displace(double dx, double dy) { 114 setOffset(this.dx += dx, this.dy += dy); 115 } 116 117 public ImageryInfo getInfo() { 118 return info; 119 } 120 121 @Override 122 public Icon getIcon() { 123 return icon; 124 } 125 126 @Override 127 public boolean isMergable(Layer other) { 128 return false; 129 } 130 131 @Override 132 public void mergeFrom(Layer from) { 133 } 134 135 @Override 136 public Object getInfoComponent() { 137 JPanel panel = new JPanel(new GridBagLayout()); 138 panel.add(new JLabel(getToolTipText()), GBC.eol()); 139 if (info != null) { 140 String url = info.getUrl(); 141 if (url != null) { 142 panel.add(new JLabel(tr("URL: ")), GBC.std().insets(0, 5, 2, 0)); 143 panel.add(new UrlLabel(url), GBC.eol().insets(2, 5, 10, 0)); 144 } 145 if (dx != 0.0 || dy != 0.0) { 146 panel.add(new JLabel(tr("Offset: ") + dx + ";" + dy), GBC.eol().insets(0, 5, 10, 0)); 147 } 148 } 149 return panel; 150 } 151 152 public static ImageryLayer create(ImageryInfo info) { 153 if (info.getImageryType() == ImageryType.WMS || info.getImageryType() == ImageryType.HTML) 154 return new WMSLayer(info); 155 else if (info.getImageryType() == ImageryType.TMS || info.getImageryType() == ImageryType.BING || info.getImageryType() == ImageryType.SCANEX) 156 return new TMSLayer(info); 157 else throw new AssertionError(); 158 } 159 160 class ApplyOffsetAction extends AbstractAction { 161 private OffsetBookmark b; 162 ApplyOffsetAction(OffsetBookmark b) { 163 super(b.name); 164 this.b = b; 165 } 166 167 @Override 168 public void actionPerformed(ActionEvent ev) { 169 setOffset(b.dx, b.dy); 170 Main.main.menu.imageryMenu.refreshOffsetMenu(); 171 Main.map.repaint(); 172 } 173 } 174 175 public class OffsetAction extends AbstractAction implements LayerAction { 176 @Override 177 public void actionPerformed(ActionEvent e) { 178 } 179 180 @Override 181 public Component createMenuComponent() { 182 return getOffsetMenuItem(); 183 } 184 185 @Override 186 public boolean supportLayers(List<Layer> layers) { 187 return false; 188 } 189 } 190 191 public JMenuItem getOffsetMenuItem() { 192 JMenu subMenu = new JMenu(trc("layer", "Offset")); 193 subMenu.setIcon(ImageProvider.get("mapmode", "adjustimg")); 194 return (JMenuItem)getOffsetMenuItem(subMenu); 195 } 196 197 public JComponent getOffsetMenuItem(JComponent subMenu) { 198 JMenuItem adjustMenuItem = new JMenuItem(adjustAction); 199 if (OffsetBookmark.allBookmarks.isEmpty()) return adjustMenuItem; 200 201 subMenu.add(adjustMenuItem); 202 subMenu.add(new JSeparator()); 203 boolean hasBookmarks = false; 204 int menuItemHeight = 0; 205 for (OffsetBookmark b : OffsetBookmark.allBookmarks) { 206 if (!b.isUsable(this)) { 207 continue; 208 } 209 JCheckBoxMenuItem item = new JCheckBoxMenuItem(new ApplyOffsetAction(b)); 210 if (b.dx == dx && b.dy == dy) { 211 item.setSelected(true); 212 } 213 subMenu.add(item); 214 menuItemHeight = item.getPreferredSize().height; 215 hasBookmarks = true; 216 } 217 if (menuItemHeight > 0) { 218 if (subMenu instanceof JMenu) { 219 MenuScroller.setScrollerFor((JMenu) subMenu); 220 } else if (subMenu instanceof JPopupMenu) { 221 MenuScroller.setScrollerFor((JPopupMenu)subMenu); 222 } 223 } 224 return hasBookmarks ? subMenu : adjustMenuItem; 225 } 226 227 public BufferedImage sharpenImage(BufferedImage img) { 228 if (sharpenLevel <= 0) return img; 229 int width = img.getWidth(null); 230 int height = img.getHeight(null); 231 BufferedImage tmp = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 232 tmp.getGraphics().drawImage(img, 0, 0, null); 233 Kernel kernel; 234 if (sharpenLevel == 1) { 235 kernel = new Kernel(3, 3, new float[] { -0.25f, -0.5f, -0.25f, -0.5f, 4, -0.5f, -0.25f, -0.5f, -0.25f}); 236 } else { 237 kernel = new Kernel(3, 3, new float[] { -0.5f, -1, -0.5f, -1, 7, -1, -0.5f, -1, -0.5f}); 238 } 239 BufferedImageOp op = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null); 240 return op.filter(tmp, null); 241 } 242 243 /** 244 * Draws a red error tile when imagery tile cannot be fetched. 245 * @param img The buffered image 246 * @param message Additional error message to display 247 */ 248 public void drawErrorTile(BufferedImage img, String message) { 249 Graphics2D g = (Graphics2D) img.getGraphics(); 250 g.setColor(Color.RED); 251 g.fillRect(0, 0, img.getWidth(), img.getHeight()); 252 g.setFont(g.getFont().deriveFont(Font.PLAIN).deriveFont(24.0f)); 253 g.setColor(Color.BLACK); 254 255 String text = tr("ERROR"); 256 g.drawString(text, (img.getWidth() - g.getFontMetrics().stringWidth(text)) / 2, g.getFontMetrics().getHeight()+5); 257 if (message != null) { 258 float drawPosY = 2.5f*g.getFontMetrics().getHeight()+10; 259 if (!message.contains(" ")) { 260 g.setFont(g.getFont().deriveFont(Font.PLAIN).deriveFont(18.0f)); 261 g.drawString(message, 5, (int)drawPosY); 262 } else { 263 // Draw message on several lines 264 Map<TextAttribute, Object> map = new Hashtable<TextAttribute, Object>(); 265 map.put(TextAttribute.FAMILY, "Serif"); 266 map.put(TextAttribute.SIZE, new Float(18.0)); 267 AttributedString vanGogh = new AttributedString(message, map); 268 // Create a new LineBreakMeasurer from the text 269 AttributedCharacterIterator paragraph = vanGogh.getIterator(); 270 int paragraphStart = paragraph.getBeginIndex(); 271 int paragraphEnd = paragraph.getEndIndex(); 272 FontRenderContext frc = g.getFontRenderContext(); 273 LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(paragraph, frc); 274 // Set break width to width of image with some margin 275 float breakWidth = img.getWidth()-10; 276 // Set position to the index of the first character in the text 277 lineMeasurer.setPosition(paragraphStart); 278 // Get lines until the entire paragraph has been displayed 279 while (lineMeasurer.getPosition() < paragraphEnd) { 280 // Retrieve next layout 281 TextLayout layout = lineMeasurer.nextLayout(breakWidth); 282 283 // Compute pen x position 284 float drawPosX = layout.isLeftToRight() ? 0 : breakWidth - layout.getAdvance(); 285 286 // Move y-coordinate by the ascent of the layout 287 drawPosY += layout.getAscent(); 288 289 // Draw the TextLayout at (drawPosX, drawPosY) 290 layout.draw(g, drawPosX, drawPosY); 291 292 // Move y-coordinate in preparation for next layout 293 drawPosY += layout.getDescent() + layout.getLeading(); 294 } 295 } 296 } 297 } 298 299 @Override 300 public void destroy() { 301 super.destroy(); 302 adjustAction.destroy(); 303 } 304}