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