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