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