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