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.Collection; 010import java.util.List; 011import java.util.Objects; 012 013import javax.swing.AbstractAction; 014import javax.swing.Action; 015 016import org.apache.commons.jcs.access.CacheAccess; 017import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader; 018import org.openstreetmap.josm.Main; 019import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry; 020import org.openstreetmap.josm.data.coor.LatLon; 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.imagery.WMSEndpointTileSource; 028import org.openstreetmap.josm.data.preferences.BooleanProperty; 029import org.openstreetmap.josm.data.preferences.IntegerProperty; 030import org.openstreetmap.josm.data.projection.Projection; 031import org.openstreetmap.josm.data.projection.Projections; 032import org.openstreetmap.josm.gui.MainApplication; 033import org.openstreetmap.josm.gui.layer.imagery.TileSourceDisplaySettings; 034import org.openstreetmap.josm.tools.CheckParameterUtil; 035import org.openstreetmap.josm.tools.Logging; 036import org.openstreetmap.josm.tools.Utils; 037 038/** 039 * This is a layer that grabs the current screen from an WMS server. The data 040 * fetched this way is tiled and managed to the disc to reduce server load. 041 * 042 */ 043public class WMSLayer extends AbstractCachedTileSourceLayer<AbstractWMSTileSource> { 044 private static final String PREFERENCE_PREFIX = "imagery.wms"; 045 /** 046 * Registers all setting properties 047 */ 048 static { 049 new TileSourceDisplaySettings(PREFERENCE_PREFIX); 050 } 051 052 /** default tile size for WMS Layer */ 053 public static final IntegerProperty PROP_IMAGE_SIZE = new IntegerProperty(PREFERENCE_PREFIX + ".imageSize", 512); 054 055 /** should WMS layer autozoom in default mode */ 056 public static final BooleanProperty PROP_DEFAULT_AUTOZOOM = new BooleanProperty(PREFERENCE_PREFIX + ".default_autozoom", true); 057 058 private static final String CACHE_REGION_NAME = "WMS"; 059 060 private List<String> serverProjections; 061 062 /** 063 * Constructs a new {@code WMSLayer}. 064 * @param info ImageryInfo description of the layer 065 */ 066 public WMSLayer(ImageryInfo info) { 067 super(info); 068 CheckParameterUtil.ensureThat( 069 info.getImageryType() == ImageryType.WMS || info.getImageryType() == ImageryType.WMS_ENDPOINT, "ImageryType is WMS"); 070 CheckParameterUtil.ensureParameterNotNull(info.getUrl(), "info.url"); 071 if (info.getImageryType() == ImageryType.WMS) { 072 TemplatedWMSTileSource.checkUrl(info.getUrl()); 073 074 } 075 this.serverProjections = new ArrayList<>(info.getServerProjections()); 076 } 077 078 @Override 079 protected TileSourceDisplaySettings createDisplaySettings() { 080 return new TileSourceDisplaySettings(PREFERENCE_PREFIX); 081 } 082 083 @Override 084 public Action[] getMenuEntries() { 085 List<Action> ret = new ArrayList<>(); 086 ret.addAll(Arrays.asList(super.getMenuEntries())); 087 ret.add(SeparatorLayerAction.INSTANCE); 088 ret.add(new LayerSaveAction(this)); 089 ret.add(new LayerSaveAsAction(this)); 090 ret.add(new BookmarkWmsAction()); 091 return ret.toArray(new Action[0]); 092 } 093 094 @Override 095 protected AbstractWMSTileSource getTileSource() { 096 AbstractWMSTileSource tileSource; 097 if (info.getImageryType() == ImageryType.WMS) { 098 tileSource = new TemplatedWMSTileSource(info, chooseProjection(Main.getProjection())); 099 } else { 100 /* 101 * Chicken-and-egg problem. We want to create tile source, but supported projections we can get only 102 * from this tile source. So create tilesource first with dummy Main.getProjection(), and then update 103 * once we update server projections. 104 * 105 * Thus: 106 * * it is not required to provide projections for wms_endpoint imagery types 107 * * we always use current definitions returned by server 108 */ 109 WMSEndpointTileSource endpointTileSource = new WMSEndpointTileSource(info, Main.getProjection()); 110 this.serverProjections = endpointTileSource.getServerProjections(); 111 endpointTileSource.setTileProjection(chooseProjection(Main.getProjection())); 112 tileSource = endpointTileSource; 113 } 114 info.setAttribution(tileSource); 115 return tileSource; 116 } 117 118 /** 119 * This action will add a WMS layer menu entry with the current WMS layer 120 * URL and name extended by the current resolution. 121 * When using the menu entry again, the WMS cache will be used properly. 122 */ 123 public class BookmarkWmsAction extends AbstractAction { 124 /** 125 * Constructs a new {@code BookmarkWmsAction}. 126 */ 127 public BookmarkWmsAction() { 128 super(tr("Set WMS Bookmark")); 129 } 130 131 @Override 132 public void actionPerformed(ActionEvent ev) { 133 ImageryLayerInfo.addLayer(new ImageryInfo(info)); 134 } 135 } 136 137 @Override 138 public Collection<String> getNativeProjections() { 139 return serverProjections; 140 } 141 142 @Override 143 public void projectionChanged(Projection oldValue, Projection newValue) { 144 super.projectionChanged(oldValue, newValue); 145 Projection tileProjection = chooseProjection(newValue); 146 if (!Objects.equals(tileSource.getTileProjection(), tileProjection)) { 147 tileSource.setTileProjection(tileProjection); 148 } 149 } 150 151 private Projection chooseProjection(Projection requested) { 152 if (serverProjections.contains(requested.toCode())) { 153 return requested; 154 } else { 155 LatLon center = MainApplication.isDisplayingMapView() ? 156 requested.eastNorth2latlon(MainApplication.getMap().mapView.getCenter()) : null; 157 Projection firstNonNullproj = null; 158 Projection firstProjInBounds = null; 159 for (String code : serverProjections) { 160 Projection proj = Projections.getProjectionByCode(code); 161 if (proj != null) { 162 if (firstNonNullproj == null) { 163 firstNonNullproj = proj; 164 } 165 if (center != null && proj.getWorldBoundsLatLon().contains(center)) { 166 firstProjInBounds = proj; 167 break; 168 } 169 } 170 } 171 if (firstProjInBounds != null) { 172 return selectProjection(firstProjInBounds); 173 } else if (firstNonNullproj != null) { 174 return selectProjection(firstNonNullproj); 175 } 176 Logging.warn(tr("Unable to find supported projection for layer {0}. Using {1}.", getName(), requested.toCode())); 177 return requested; 178 } 179 } 180 181 private Projection selectProjection(Projection proj) { 182 Logging.info(tr("Reprojecting layer {0} from {1} to {2}. For best image quality and performance," 183 + " switch to one of the supported projections: {3}", 184 getName(), proj.toCode(), Main.getProjection().toCode(), Utils.join(", ", getNativeProjections()))); 185 return proj; 186 } 187 188 @Override 189 protected Class<? extends TileLoader> getTileLoaderClass() { 190 return WMSCachedTileLoader.class; 191 } 192 193 @Override 194 protected String getCacheName() { 195 return CACHE_REGION_NAME; 196 } 197 198 /** 199 * @return cache region for WMS layer 200 */ 201 public static CacheAccess<String, BufferedImageCacheEntry> getCache() { 202 return AbstractCachedTileSourceLayer.getCache(CACHE_REGION_NAME); 203 } 204}