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