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.GridBagLayout; 011import java.awt.event.ActionEvent; 012import java.awt.image.BufferedImage; 013import java.awt.image.BufferedImageOp; 014import java.util.ArrayList; 015import java.util.List; 016 017import javax.swing.AbstractAction; 018import javax.swing.Action; 019import javax.swing.Icon; 020import javax.swing.JCheckBoxMenuItem; 021import javax.swing.JComponent; 022import javax.swing.JLabel; 023import javax.swing.JMenu; 024import javax.swing.JMenuItem; 025import javax.swing.JPanel; 026import javax.swing.JPopupMenu; 027import javax.swing.JSeparator; 028 029import org.openstreetmap.josm.Main; 030import org.openstreetmap.josm.data.ProjectionBounds; 031import org.openstreetmap.josm.data.imagery.ImageryInfo; 032import org.openstreetmap.josm.data.imagery.OffsetBookmark; 033import org.openstreetmap.josm.data.preferences.ColorProperty; 034import org.openstreetmap.josm.data.preferences.IntegerProperty; 035import org.openstreetmap.josm.gui.MenuScroller; 036import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings; 037import org.openstreetmap.josm.gui.layer.imagery.TileSourceDisplaySettings; 038import org.openstreetmap.josm.gui.widgets.UrlLabel; 039import org.openstreetmap.josm.tools.GBC; 040import org.openstreetmap.josm.tools.ImageProvider; 041import org.openstreetmap.josm.tools.ImageProvider.ImageSizes; 042import org.openstreetmap.josm.tools.Utils; 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 private final List<ImageProcessor> imageProcessors = new ArrayList<>(); 051 052 public static Color getFadeColor() { 053 return PROP_FADE_COLOR.get(); 054 } 055 056 public static Color getFadeColorWithAlpha() { 057 Color c = PROP_FADE_COLOR.get(); 058 return new Color(c.getRed(), c.getGreen(), c.getBlue(), PROP_FADE_AMOUNT.get()*255/100); 059 } 060 061 protected final ImageryInfo info; 062 063 protected Icon icon; 064 065 private final ImageryFilterSettings filterSettings = new ImageryFilterSettings(); 066 067 /** 068 * Constructs a new {@code ImageryLayer}. 069 * @param info imagery info 070 */ 071 public ImageryLayer(ImageryInfo info) { 072 super(info.getName()); 073 this.info = info; 074 if (info.getIcon() != null) { 075 icon = new ImageProvider(info.getIcon()).setOptional(true). 076 setMaxSize(ImageSizes.LAYER).get(); 077 } 078 if (icon == null) { 079 icon = ImageProvider.get("imagery_small"); 080 } 081 for (ImageProcessor processor : filterSettings.getProcessors()) { 082 addImageProcessor(processor); 083 } 084 filterSettings.setSharpenLevel(1 + PROP_SHARPEN_LEVEL.get() / 2f); 085 } 086 087 public double getPPD() { 088 if (!Main.isDisplayingMapView()) 089 return Main.getProjection().getDefaultZoomInPPD(); 090 ProjectionBounds bounds = Main.map.mapView.getProjectionBounds(); 091 return Main.map.mapView.getWidth() / (bounds.maxEast - bounds.minEast); 092 } 093 094 /** 095 * Gets the x displacement of this layer. 096 * To be removed end of 2016 097 * @return The x displacement. 098 * @deprecated Use {@link TileSourceDisplaySettings#getDx()} 099 */ 100 @Deprecated 101 public double getDx() { 102 // moved to AbstractTileSourceLayer/TileSourceDisplaySettings. Remains until all actions migrate. 103 return 0; 104 } 105 106 /** 107 * Gets the y displacement of this layer. 108 * To be removed end of 2016 109 * @return The y displacement. 110 * @deprecated Use {@link TileSourceDisplaySettings#getDy()} 111 */ 112 @Deprecated 113 public double getDy() { 114 // moved to AbstractTileSourceLayer/TileSourceDisplaySettings. Remains until all actions migrate. 115 return 0; 116 } 117 118 /** 119 * Sets the displacement offset of this layer. The layer is automatically invalidated. 120 * To be removed end of 2016 121 * @param dx The x offset 122 * @param dy The y offset 123 * @deprecated Use {@link TileSourceDisplaySettings} 124 */ 125 @Deprecated 126 public void setOffset(double dx, double dy) { 127 // moved to AbstractTileSourceLayer/TileSourceDisplaySettings. Remains until all actions migrate. 128 } 129 130 /** 131 * To be removed end of 2016 132 * @param dx deprecated 133 * @param dy deprecated 134 * @deprecated Use {@link TileSourceDisplaySettings} 135 */ 136 @Deprecated 137 public void displace(double dx, double dy) { 138 // moved to AbstractTileSourceLayer/TileSourceDisplaySettings. Remains until all actions migrate. 139 } 140 141 /** 142 * Returns imagery info. 143 * @return imagery info 144 */ 145 public ImageryInfo getInfo() { 146 return info; 147 } 148 149 @Override 150 public Icon getIcon() { 151 return icon; 152 } 153 154 @Override 155 public boolean isMergable(Layer other) { 156 return false; 157 } 158 159 @Override 160 public void mergeFrom(Layer from) { 161 } 162 163 @Override 164 public Object getInfoComponent() { 165 JPanel panel = new JPanel(new GridBagLayout()); 166 panel.add(new JLabel(getToolTipText()), GBC.eol()); 167 if (info != null) { 168 String url = info.getUrl(); 169 if (url != null) { 170 panel.add(new JLabel(tr("URL: ")), GBC.std().insets(0, 5, 2, 0)); 171 panel.add(new UrlLabel(url), GBC.eol().insets(2, 5, 10, 0)); 172 } 173 } 174 return panel; 175 } 176 177 public static ImageryLayer create(ImageryInfo info) { 178 switch(info.getImageryType()) { 179 case WMS: 180 case HTML: 181 return new WMSLayer(info); 182 case WMTS: 183 return new WMTSLayer(info); 184 case TMS: 185 case BING: 186 case SCANEX: 187 return new TMSLayer(info); 188 default: 189 throw new AssertionError(tr("Unsupported imagery type: {0}", info.getImageryType())); 190 } 191 } 192 193 class ApplyOffsetAction extends AbstractAction { 194 private final transient OffsetBookmark b; 195 196 ApplyOffsetAction(OffsetBookmark b) { 197 super(b.name); 198 this.b = b; 199 } 200 201 @Override 202 public void actionPerformed(ActionEvent ev) { 203 setOffset(b.dx, b.dy); 204 Main.main.menu.imageryMenu.refreshOffsetMenu(); 205 Main.map.repaint(); 206 } 207 } 208 209 public class OffsetAction extends AbstractAction implements LayerAction { 210 @Override 211 public void actionPerformed(ActionEvent e) { 212 // Do nothing 213 } 214 215 @Override 216 public Component createMenuComponent() { 217 return getOffsetMenuItem(); 218 } 219 220 @Override 221 public boolean supportLayers(List<Layer> layers) { 222 return false; 223 } 224 } 225 226 public JMenuItem getOffsetMenuItem() { 227 JMenu subMenu = new JMenu(trc("layer", "Offset")); 228 subMenu.setIcon(ImageProvider.get("mapmode", "adjustimg")); 229 return (JMenuItem) getOffsetMenuItem(subMenu); 230 } 231 232 public JComponent getOffsetMenuItem(JComponent subMenu) { 233 JMenuItem adjustMenuItem = new JMenuItem(getAdjustAction()); 234 if (OffsetBookmark.allBookmarks.isEmpty()) return adjustMenuItem; 235 236 subMenu.add(adjustMenuItem); 237 subMenu.add(new JSeparator()); 238 boolean hasBookmarks = false; 239 int menuItemHeight = 0; 240 for (OffsetBookmark b : OffsetBookmark.allBookmarks) { 241 if (!b.isUsable(this)) { 242 continue; 243 } 244 JCheckBoxMenuItem item = new JCheckBoxMenuItem(new ApplyOffsetAction(b)); 245 if (Utils.equalsEpsilon(b.dx, getDx()) && Utils.equalsEpsilon(b.dy, getDy())) { 246 item.setSelected(true); 247 } 248 subMenu.add(item); 249 menuItemHeight = item.getPreferredSize().height; 250 hasBookmarks = true; 251 } 252 if (menuItemHeight > 0) { 253 if (subMenu instanceof JMenu) { 254 MenuScroller.setScrollerFor((JMenu) subMenu); 255 } else if (subMenu instanceof JPopupMenu) { 256 MenuScroller.setScrollerFor((JPopupMenu) subMenu); 257 } 258 } 259 return hasBookmarks ? subMenu : adjustMenuItem; 260 } 261 262 protected abstract Action getAdjustAction(); 263 264 /** 265 * Gets the settings for the filter that is applied to this layer. 266 * @return The filter settings. 267 * @since 10547 268 */ 269 public ImageryFilterSettings getFilterSettings() { 270 return filterSettings; 271 } 272 273 /** 274 * This method adds the {@link ImageProcessor} to this Layer if it is not {@code null}. 275 * 276 * @param processor that processes the image 277 * 278 * @return true if processor was added, false otherwise 279 */ 280 public boolean addImageProcessor(ImageProcessor processor) { 281 return processor != null && imageProcessors.add(processor); 282 } 283 284 /** 285 * This method removes given {@link ImageProcessor} from this layer 286 * 287 * @param processor which is needed to be removed 288 * 289 * @return true if processor was removed 290 */ 291 public boolean removeImageProcessor(ImageProcessor processor) { 292 return imageProcessors.remove(processor); 293 } 294 295 /** 296 * Wraps a {@link BufferedImageOp} to be used as {@link ImageProcessor}. 297 * @param op the {@link BufferedImageOp} 298 * @param inPlace true to apply filter in place, i.e., not create a new {@link BufferedImage} for the result 299 * (the {@code op} needs to support this!) 300 * @return the {@link ImageProcessor} wrapper 301 */ 302 public static ImageProcessor createImageProcessor(final BufferedImageOp op, final boolean inPlace) { 303 return image -> op.filter(image, inPlace ? image : null); 304 } 305 306 /** 307 * This method gets all {@link ImageProcessor}s of the layer 308 * 309 * @return list of image processors without removed one 310 */ 311 public List<ImageProcessor> getImageProcessors() { 312 return imageProcessors; 313 } 314 315 /** 316 * Applies all the chosen {@link ImageProcessor}s to the image 317 * 318 * @param img - image which should be changed 319 * 320 * @return the new changed image 321 */ 322 public BufferedImage applyImageProcessors(BufferedImage img) { 323 for (ImageProcessor processor : imageProcessors) { 324 img = processor.process(img); 325 } 326 return img; 327 } 328 329 @Override 330 public String toString() { 331 return getClass().getSimpleName() + " [info=" + info + ']'; 332 } 333}