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