001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.layer; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.event.ActionEvent; 007import java.util.ArrayList; 008import java.util.Arrays; 009import java.util.List; 010import java.util.Set; 011import java.util.TreeSet; 012 013import javax.swing.AbstractAction; 014import javax.swing.Action; 015import javax.swing.JOptionPane; 016 017import org.apache.commons.jcs.access.CacheAccess; 018import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader; 019import org.openstreetmap.josm.Main; 020import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry; 021import org.openstreetmap.josm.data.imagery.AbstractWMSTileSource; 022import org.openstreetmap.josm.data.imagery.ImageryInfo; 023import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType; 024import org.openstreetmap.josm.data.imagery.ImageryLayerInfo; 025import org.openstreetmap.josm.data.imagery.TemplatedWMSTileSource; 026import org.openstreetmap.josm.data.imagery.WMSCachedTileLoader; 027import org.openstreetmap.josm.data.preferences.BooleanProperty; 028import org.openstreetmap.josm.data.preferences.IntegerProperty; 029import org.openstreetmap.josm.data.projection.Projection; 030import org.openstreetmap.josm.gui.ExtendedDialog; 031import org.openstreetmap.josm.gui.layer.imagery.TileSourceDisplaySettings; 032import org.openstreetmap.josm.tools.CheckParameterUtil; 033 034/** 035 * This is a layer that grabs the current screen from an WMS server. The data 036 * fetched this way is tiled and managed to the disc to reduce server load. 037 * 038 */ 039public class WMSLayer extends AbstractCachedTileSourceLayer<AbstractWMSTileSource> { 040 private static final String PREFERENCE_PREFIX = "imagery.wms"; 041 /** 042 * Registers all setting properties 043 */ 044 static { 045 new TileSourceDisplaySettings(PREFERENCE_PREFIX); 046 } 047 048 /** default tile size for WMS Layer */ 049 public static final IntegerProperty PROP_IMAGE_SIZE = new IntegerProperty(PREFERENCE_PREFIX + ".imageSize", 512); 050 051 /** should WMS layer autozoom in default mode */ 052 public static final BooleanProperty PROP_DEFAULT_AUTOZOOM = new BooleanProperty(PREFERENCE_PREFIX + ".default_autozoom", true); 053 054 private static final String CACHE_REGION_NAME = "WMS"; 055 056 private final Set<String> supportedProjections; 057 058 /** 059 * Constructs a new {@code WMSLayer}. 060 * @param info ImageryInfo description of the layer 061 */ 062 public WMSLayer(ImageryInfo info) { 063 super(info); 064 CheckParameterUtil.ensureThat(info.getImageryType() == ImageryType.WMS, "ImageryType is WMS"); 065 CheckParameterUtil.ensureParameterNotNull(info.getUrl(), "info.url"); 066 TemplatedWMSTileSource.checkUrl(info.getUrl()); 067 this.supportedProjections = new TreeSet<>(info.getServerProjections()); 068 } 069 070 @Override 071 protected TileSourceDisplaySettings createDisplaySettings() { 072 return new TileSourceDisplaySettings(PREFERENCE_PREFIX); 073 } 074 075 @Override 076 public Action[] getMenuEntries() { 077 List<Action> ret = new ArrayList<>(); 078 ret.addAll(Arrays.asList(super.getMenuEntries())); 079 ret.add(SeparatorLayerAction.INSTANCE); 080 ret.add(new LayerSaveAction(this)); 081 ret.add(new LayerSaveAsAction(this)); 082 ret.add(new BookmarkWmsAction()); 083 return ret.toArray(new Action[ret.size()]); 084 } 085 086 @Override 087 protected AbstractWMSTileSource getTileSource() { 088 AbstractWMSTileSource tileSource = new TemplatedWMSTileSource(info); 089 info.setAttribution(tileSource); 090 return tileSource; 091 } 092 093 /** 094 * This action will add a WMS layer menu entry with the current WMS layer 095 * URL and name extended by the current resolution. 096 * When using the menu entry again, the WMS cache will be used properly. 097 */ 098 public class BookmarkWmsAction extends AbstractAction { 099 /** 100 * Constructs a new {@code BookmarkWmsAction}. 101 */ 102 public BookmarkWmsAction() { 103 super(tr("Set WMS Bookmark")); 104 } 105 106 @Override 107 public void actionPerformed(ActionEvent ev) { 108 ImageryLayerInfo.addLayer(new ImageryInfo(info)); 109 } 110 } 111 112 @Override 113 public boolean isProjectionSupported(Projection proj) { 114 return supportedProjections == null || supportedProjections.isEmpty() || supportedProjections.contains(proj.toCode()) || 115 (info.isEpsg4326To3857Supported() && supportedProjections.contains("EPSG:4326") 116 && "EPSG:3857".equals(Main.getProjection().toCode())); 117 } 118 119 @Override 120 public String nameSupportedProjections() { 121 StringBuilder ret = new StringBuilder(); 122 for (String e: supportedProjections) { 123 ret.append(e).append(", "); 124 } 125 String appendix = ""; 126 127 if (isReprojectionPossible()) { 128 appendix = ". <p>" + tr("JOSM will use EPSG:4326 to query the server, but results may vary " 129 + "depending on the WMS server") + "</p>"; 130 } 131 return ret.substring(0, ret.length()-2) + appendix; 132 } 133 134 @Override 135 public void projectionChanged(Projection oldValue, Projection newValue) { 136 // do not call super - we need custom warning dialog 137 138 if (!isProjectionSupported(newValue)) { 139 String message = 140 "<html><body><p>" + tr("The layer {0} does not support the new projection {1}.", getName(), newValue.toCode()) + 141 "<p style='width: 450px; position: absolute; margin: 0px;'>" + 142 tr("Supported projections are: {0}", nameSupportedProjections()) + "</p>" + 143 "<p>" + tr("Change the projection again or remove the layer."); 144 145 ExtendedDialog warningDialog = new ExtendedDialog(Main.parent, tr("Warning"), new String[]{tr("OK")}). 146 setContent(message). 147 setIcon(JOptionPane.WARNING_MESSAGE); 148 149 if (isReprojectionPossible()) { 150 warningDialog.toggleEnable("imagery.wms.projectionSupportWarnings." + tileSource.getBaseUrl()); 151 } 152 warningDialog.showDialog(); 153 } 154 155 if (!newValue.equals(oldValue)) { 156 tileSource.initProjection(newValue); 157 } 158 } 159 160 @Override 161 protected Class<? extends TileLoader> getTileLoaderClass() { 162 return WMSCachedTileLoader.class; 163 } 164 165 @Override 166 protected String getCacheName() { 167 return CACHE_REGION_NAME; 168 } 169 170 /** 171 * @return cache region for WMS layer 172 */ 173 public static CacheAccess<String, BufferedImageCacheEntry> getCache() { 174 return AbstractCachedTileSourceLayer.getCache(CACHE_REGION_NAME); 175 } 176 177 private boolean isReprojectionPossible() { 178 return supportedProjections.contains("EPSG:4326") && "EPSG:3857".equals(Main.getProjection().toCode()); 179 } 180}