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.preferences.BooleanProperty;
028import org.openstreetmap.josm.data.preferences.IntegerProperty;
029import org.openstreetmap.josm.data.projection.Projection;
030import org.openstreetmap.josm.data.projection.Projections;
031import org.openstreetmap.josm.gui.MainApplication;
032import org.openstreetmap.josm.gui.layer.imagery.TileSourceDisplaySettings;
033import org.openstreetmap.josm.tools.CheckParameterUtil;
034import org.openstreetmap.josm.tools.Logging;
035import org.openstreetmap.josm.tools.Utils;
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 final 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(info.getImageryType() == ImageryType.WMS, "ImageryType is WMS");
068        CheckParameterUtil.ensureParameterNotNull(info.getUrl(), "info.url");
069        TemplatedWMSTileSource.checkUrl(info.getUrl());
070        this.serverProjections = new ArrayList<>(info.getServerProjections());
071    }
072
073    @Override
074    protected TileSourceDisplaySettings createDisplaySettings() {
075        return new TileSourceDisplaySettings(PREFERENCE_PREFIX);
076    }
077
078    @Override
079    public Action[] getMenuEntries() {
080        List<Action> ret = new ArrayList<>();
081        ret.addAll(Arrays.asList(super.getMenuEntries()));
082        ret.add(SeparatorLayerAction.INSTANCE);
083        ret.add(new LayerSaveAction(this));
084        ret.add(new LayerSaveAsAction(this));
085        ret.add(new BookmarkWmsAction());
086        return ret.toArray(new Action[0]);
087    }
088
089    @Override
090    protected AbstractWMSTileSource getTileSource() {
091        AbstractWMSTileSource tileSource = new TemplatedWMSTileSource(
092                info, chooseProjection(Main.getProjection()));
093        info.setAttribution(tileSource);
094        return tileSource;
095    }
096
097    /**
098     * This action will add a WMS layer menu entry with the current WMS layer
099     * URL and name extended by the current resolution.
100     * When using the menu entry again, the WMS cache will be used properly.
101     */
102    public class BookmarkWmsAction extends AbstractAction {
103        /**
104         * Constructs a new {@code BookmarkWmsAction}.
105         */
106        public BookmarkWmsAction() {
107            super(tr("Set WMS Bookmark"));
108        }
109
110        @Override
111        public void actionPerformed(ActionEvent ev) {
112            ImageryLayerInfo.addLayer(new ImageryInfo(info));
113        }
114    }
115
116    @Override
117    public Collection<String> getNativeProjections() {
118        return serverProjections;
119    }
120
121    @Override
122    public void projectionChanged(Projection oldValue, Projection newValue) {
123        super.projectionChanged(oldValue, newValue);
124        Projection tileProjection = chooseProjection(newValue);
125        if (!Objects.equals(tileSource.getTileProjection(), tileProjection)) {
126            tileSource.setTileProjection(tileProjection);
127        }
128    }
129
130    private Projection chooseProjection(Projection requested) {
131        if (serverProjections.contains(requested.toCode())) {
132            return requested;
133        } else {
134            LatLon center = MainApplication.isDisplayingMapView() ?
135                    requested.eastNorth2latlon(MainApplication.getMap().mapView.getCenter()) : null;
136            Projection firstNonNullproj = null;
137            Projection firstProjInBounds = null;
138            for (String code : serverProjections) {
139                Projection proj = Projections.getProjectionByCode(code);
140                if (proj != null) {
141                    if (firstNonNullproj == null) {
142                        firstNonNullproj = proj;
143                    }
144                    if (center != null && proj.getWorldBoundsLatLon().contains(center)) {
145                        firstProjInBounds = proj;
146                        break;
147                    }
148                }
149            }
150            if (firstProjInBounds != null) {
151                return selectProjection(firstProjInBounds);
152            } else if (firstNonNullproj != null) {
153                return selectProjection(firstNonNullproj);
154            }
155            Logging.warn(tr("Unable to find supported projection for layer {0}. Using {1}.", getName(), requested.toCode()));
156            return requested;
157        }
158    }
159
160    private Projection selectProjection(Projection proj) {
161        Logging.info(tr("Reprojecting layer {0} from {1} to {2}. For best image quality and performance,"
162                + " switch to one of the supported projections: {3}",
163                getName(), proj.toCode(), Main.getProjection().toCode(), Utils.join(", ", getNativeProjections())));
164        return proj;
165    }
166
167    @Override
168    protected Class<? extends TileLoader> getTileLoaderClass() {
169        return WMSCachedTileLoader.class;
170    }
171
172    @Override
173    protected String getCacheName() {
174        return CACHE_REGION_NAME;
175    }
176
177    /**
178     * @return cache region for WMS layer
179     */
180    public static CacheAccess<String, BufferedImageCacheEntry> getCache() {
181        return AbstractCachedTileSourceLayer.getCache(CACHE_REGION_NAME);
182    }
183}