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.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.preferences.BooleanProperty;
027import org.openstreetmap.josm.data.preferences.IntegerProperty;
028import org.openstreetmap.josm.data.projection.Projection;
029import org.openstreetmap.josm.data.projection.Projections;
030import org.openstreetmap.josm.gui.layer.imagery.TileSourceDisplaySettings;
031import org.openstreetmap.josm.tools.CheckParameterUtil;
032import org.openstreetmap.josm.tools.Logging;
033import org.openstreetmap.josm.tools.Utils;
034
035/**
036 * This is a layer that grabs the current screen from an WMS server. The data
037 * fetched this way is tiled and managed to the disc to reduce server load.
038 *
039 */
040public class WMSLayer extends AbstractCachedTileSourceLayer<AbstractWMSTileSource> {
041    private static final String PREFERENCE_PREFIX = "imagery.wms";
042    /**
043     * Registers all setting properties
044     */
045    static {
046        new TileSourceDisplaySettings(PREFERENCE_PREFIX);
047    }
048
049    /** default tile size for WMS Layer */
050    public static final IntegerProperty PROP_IMAGE_SIZE = new IntegerProperty(PREFERENCE_PREFIX + ".imageSize", 512);
051
052    /** should WMS layer autozoom in default mode */
053    public static final BooleanProperty PROP_DEFAULT_AUTOZOOM = new BooleanProperty(PREFERENCE_PREFIX + ".default_autozoom", true);
054
055    private static final String CACHE_REGION_NAME = "WMS";
056
057    private final List<String> serverProjections;
058
059    /**
060     * Constructs a new {@code WMSLayer}.
061     * @param info ImageryInfo description of the layer
062     */
063    public WMSLayer(ImageryInfo info) {
064        super(info);
065        CheckParameterUtil.ensureThat(info.getImageryType() == ImageryType.WMS, "ImageryType is WMS");
066        CheckParameterUtil.ensureParameterNotNull(info.getUrl(), "info.url");
067        TemplatedWMSTileSource.checkUrl(info.getUrl());
068        this.serverProjections = new ArrayList<>(info.getServerProjections());
069    }
070
071    @Override
072    protected TileSourceDisplaySettings createDisplaySettings() {
073        return new TileSourceDisplaySettings(PREFERENCE_PREFIX);
074    }
075
076    @Override
077    public Action[] getMenuEntries() {
078        List<Action> ret = new ArrayList<>();
079        ret.addAll(Arrays.asList(super.getMenuEntries()));
080        ret.add(SeparatorLayerAction.INSTANCE);
081        ret.add(new LayerSaveAction(this));
082        ret.add(new LayerSaveAsAction(this));
083        ret.add(new BookmarkWmsAction());
084        return ret.toArray(new Action[0]);
085    }
086
087    @Override
088    protected AbstractWMSTileSource getTileSource() {
089        AbstractWMSTileSource tileSource = new TemplatedWMSTileSource(
090                info, chooseProjection(Main.getProjection()));
091        info.setAttribution(tileSource);
092        return tileSource;
093    }
094
095    /**
096     * This action will add a WMS layer menu entry with the current WMS layer
097     * URL and name extended by the current resolution.
098     * When using the menu entry again, the WMS cache will be used properly.
099     */
100    public class BookmarkWmsAction extends AbstractAction {
101        /**
102         * Constructs a new {@code BookmarkWmsAction}.
103         */
104        public BookmarkWmsAction() {
105            super(tr("Set WMS Bookmark"));
106        }
107
108        @Override
109        public void actionPerformed(ActionEvent ev) {
110            ImageryLayerInfo.addLayer(new ImageryInfo(info));
111        }
112    }
113
114    @Override
115    public Collection<String> getNativeProjections() {
116        return serverProjections;
117    }
118
119    @Override
120    public void projectionChanged(Projection oldValue, Projection newValue) {
121        super.projectionChanged(oldValue, newValue);
122        Projection tileProjection = chooseProjection(newValue);
123        if (!Objects.equals(tileSource.getTileProjection(), tileProjection)) {
124            tileSource.setTileProjection(tileProjection);
125        }
126    }
127
128    private Projection chooseProjection(Projection requested) {
129        if (serverProjections.contains(requested.toCode())) {
130            return requested;
131        } else {
132            for (String code : serverProjections) {
133                Projection proj = Projections.getProjectionByCode(code);
134                if (proj != null) {
135                    Logging.info(tr("Reprojecting layer {0} from {1} to {2}. For best image quality and performance,"
136                            + " switch to one of the supported projections: {3}",
137                            getName(), proj.toCode(), Main.getProjection().toCode(), Utils.join(", ", getNativeProjections())));
138                    return proj;
139                }
140            }
141            Logging.warn(tr("Unable to find supported projection for layer {0}. Using {1}.", getName(), requested.toCode()));
142            return requested;
143        }
144    }
145
146    @Override
147    protected Class<? extends TileLoader> getTileLoaderClass() {
148        return WMSCachedTileLoader.class;
149    }
150
151    @Override
152    protected String getCacheName() {
153        return CACHE_REGION_NAME;
154    }
155
156    /**
157     * @return cache region for WMS layer
158     */
159    public static CacheAccess<String, BufferedImageCacheEntry> getCache() {
160        return AbstractCachedTileSourceLayer.getCache(CACHE_REGION_NAME);
161    }
162}