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}