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.util.ArrayList;
007import java.util.Collection;
008
009import org.apache.commons.jcs.access.CacheAccess;
010import org.openstreetmap.gui.jmapviewer.OsmMercator;
011import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
012import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTMSTileSource;
013import org.openstreetmap.gui.jmapviewer.tilesources.ScanexTileSource;
014import org.openstreetmap.gui.jmapviewer.tilesources.TMSTileSource;
015import org.openstreetmap.gui.jmapviewer.tilesources.TemplatedTMSTileSource;
016import org.openstreetmap.josm.Main;
017import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;
018import org.openstreetmap.josm.data.imagery.CachedAttributionBingAerialTileSource;
019import org.openstreetmap.josm.data.imagery.ImageryInfo;
020import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
021import org.openstreetmap.josm.data.imagery.TMSCachedTileLoader;
022import org.openstreetmap.josm.data.preferences.BooleanProperty;
023import org.openstreetmap.josm.data.preferences.IntegerProperty;
024import org.openstreetmap.josm.data.projection.Projection;
025
026/**
027 * Class that displays a slippy map layer.
028 *
029 * @author Frederik Ramm
030 * @author LuVar <lubomir.varga@freemap.sk>
031 * @author Dave Hansen <dave@sr71.net>
032 * @author Upliner <upliner@gmail.com>
033 * @since 3715
034 */
035public class TMSLayer extends AbstractCachedTileSourceLayer<TMSTileSource> implements NativeScaleLayer {
036    private static final String CACHE_REGION_NAME = "TMS";
037
038    private static final String PREFERENCE_PREFIX = "imagery.tms";
039
040    /** minimum zoom level for TMS layer */
041    public static final IntegerProperty PROP_MIN_ZOOM_LVL = new IntegerProperty(PREFERENCE_PREFIX + ".min_zoom_lvl",
042            AbstractTileSourceLayer.PROP_MIN_ZOOM_LVL.get());
043    /** maximum zoom level for TMS layer */
044    public static final IntegerProperty PROP_MAX_ZOOM_LVL = new IntegerProperty(PREFERENCE_PREFIX + ".max_zoom_lvl",
045            AbstractTileSourceLayer.PROP_MAX_ZOOM_LVL.get());
046    /** shall TMS layers be added to download dialog */
047    public static final BooleanProperty PROP_ADD_TO_SLIPPYMAP_CHOOSER = new BooleanProperty(PREFERENCE_PREFIX + ".add_to_slippymap_chooser",
048            true);
049
050    private static final ScaleList nativeScaleList = initNativeScaleList();
051
052    /**
053     * Create a layer based on ImageryInfo
054     * @param info description of the layer
055     */
056    public TMSLayer(ImageryInfo info) {
057        super(info);
058    }
059
060    /**
061     * Creates and returns a new TileSource instance depending on the {@link ImageryType}
062     * of the {@link ImageryInfo} object specified in the constructor.
063     *
064     * If no appropriate TileSource is found, null is returned.
065     * Currently supported ImageryType are {@link ImageryType#TMS},
066     * {@link ImageryType#BING}, {@link ImageryType#SCANEX}.
067     *
068     *
069     * @return a new TileSource instance or null if no TileSource for the ImageryInfo/ImageryType could be found.
070     * @throws IllegalArgumentException if url from imagery info is null or invalid
071     */
072    @Override
073    protected TMSTileSource getTileSource() {
074        return getTileSourceStatic(info, () -> {
075            Main.debug("Attribution loaded, running loadAllErrorTiles");
076            this.loadAllErrorTiles(false);
077        });
078    }
079
080    @Override
081    public final boolean isProjectionSupported(Projection proj) {
082        return "EPSG:3857".equals(proj.toCode()) || "EPSG:4326".equals(proj.toCode());
083    }
084
085    @Override
086    public final String nameSupportedProjections() {
087        return tr("EPSG:4326 and Mercator projection are supported");
088    }
089
090    /**
091     * Creates and returns a new TileSource instance depending on the {@link ImageryType}
092     * of the passed ImageryInfo object.
093     *
094     * If no appropriate TileSource is found, null is returned.
095     * Currently supported ImageryType are {@link ImageryType#TMS},
096     * {@link ImageryType#BING}, {@link ImageryType#SCANEX}.
097     *
098     * @param info imagery info
099     * @return a new TileSource instance or null if no TileSource for the ImageryInfo/ImageryType could be found.
100     * @throws IllegalArgumentException if url from imagery info is null or invalid
101     */
102    public static AbstractTMSTileSource getTileSourceStatic(ImageryInfo info) {
103        return getTileSourceStatic(info, null);
104    }
105
106    /**
107     * Creates and returns a new TileSource instance depending on the {@link ImageryType}
108     * of the passed ImageryInfo object.
109     *
110     * If no appropriate TileSource is found, null is returned.
111     * Currently supported ImageryType are {@link ImageryType#TMS},
112     * {@link ImageryType#BING}, {@link ImageryType#SCANEX}.
113     *
114     * @param info imagery info
115     * @param attributionLoadedTask task to be run once attribution is loaded, might be null, if nothing special shall happen
116     * @return a new TileSource instance or null if no TileSource for the ImageryInfo/ImageryType could be found.
117     * @throws IllegalArgumentException if url from imagery info is null or invalid
118     */
119    public static TMSTileSource getTileSourceStatic(ImageryInfo info, Runnable attributionLoadedTask) {
120        if (info.getImageryType() == ImageryType.TMS) {
121            TemplatedTMSTileSource.checkUrl(info.getUrl());
122            TMSTileSource t = new TemplatedTMSTileSource(info);
123            info.setAttribution(t);
124            return t;
125        } else if (info.getImageryType() == ImageryType.BING) {
126            return new CachedAttributionBingAerialTileSource(info, attributionLoadedTask);
127        } else if (info.getImageryType() == ImageryType.SCANEX) {
128            return new ScanexTileSource(info);
129        }
130        return null;
131    }
132
133    @Override
134    protected Class<? extends TileLoader> getTileLoaderClass() {
135        return TMSCachedTileLoader.class;
136    }
137
138    @Override
139    protected String getCacheName() {
140        return CACHE_REGION_NAME;
141    }
142
143    /**
144     * @return cache for TMS region
145     */
146    public static CacheAccess<String, BufferedImageCacheEntry> getCache() {
147        return AbstractCachedTileSourceLayer.getCache(CACHE_REGION_NAME);
148    }
149
150    @Override
151    public ScaleList getNativeScales() {
152        return nativeScaleList;
153    }
154
155    private static ScaleList initNativeScaleList() {
156        Collection<Double> scales = new ArrayList<>(AbstractTileSourceLayer.MAX_ZOOM);
157        for (int zoom = AbstractTileSourceLayer.MIN_ZOOM; zoom <= AbstractTileSourceLayer.MAX_ZOOM; zoom++) {
158            double scale = OsmMercator.EARTH_RADIUS * Math.PI * 2 / Math.pow(2, zoom) / OsmMercator.DEFAUL_TILE_SIZE;
159            scales.add(scale);
160        }
161        return new ScaleList(scales);
162    }
163 }