001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.layer;
003
004import java.util.Collection;
005import java.util.Collections;
006import java.util.stream.Collectors;
007import java.util.stream.IntStream;
008
009import org.apache.commons.jcs3.access.CacheAccess;
010import org.openstreetmap.gui.jmapviewer.JMapViewer;
011import org.openstreetmap.gui.jmapviewer.OsmMercator;
012import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
013import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTMSTileSource;
014import org.openstreetmap.gui.jmapviewer.tilesources.ScanexTileSource;
015import org.openstreetmap.gui.jmapviewer.tilesources.TMSTileSource;
016import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;
017import org.openstreetmap.josm.data.imagery.CachedAttributionBingAerialTileSource;
018import org.openstreetmap.josm.data.imagery.ImageryInfo;
019import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
020import org.openstreetmap.josm.data.imagery.JosmTemplatedTMSTileSource;
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.tools.Logging;
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    /** override minimum/maximum zoom level with those supported by JMapViewer, as these might be used in slippymap chooser */
050    public static final int MAX_ZOOM = JMapViewer.MAX_ZOOM;
051    public static final int MIN_ZOOM = JMapViewer.MIN_ZOOM;
052
053    private static final ScaleList nativeScaleList = initNativeScaleList();
054
055    /**
056     * Create a layer based on ImageryInfo
057     * @param info description of the layer
058     */
059    public TMSLayer(ImageryInfo info) {
060        super(info);
061    }
062
063    /**
064     * Creates and returns a new TileSource instance depending on the {@link ImageryType}
065     * of the {@link ImageryInfo} object specified in the constructor.
066     *
067     * If no appropriate TileSource is found, null is returned.
068     * Currently supported ImageryType are {@link ImageryType#TMS},
069     * {@link ImageryType#BING}, {@link ImageryType#SCANEX}.
070     *
071     *
072     * @return a new TileSource instance or null if no TileSource for the ImageryInfo/ImageryType could be found.
073     * @throws IllegalArgumentException if url from imagery info is null or invalid
074     */
075    @Override
076    protected TMSTileSource getTileSource() {
077        return getTileSourceStatic(info, () -> {
078            Logging.debug("Attribution loaded, running loadAllErrorTiles");
079            this.loadAllErrorTiles(false);
080        });
081    }
082
083    @Override
084    public Collection<String> getNativeProjections() {
085        return Collections.singletonList("EPSG:3857");
086    }
087
088    /**
089     * Creates and returns a new TileSource instance depending on the {@link ImageryType}
090     * of the passed ImageryInfo object.
091     *
092     * If no appropriate TileSource is found, null is returned.
093     * Currently supported ImageryType are {@link ImageryType#TMS},
094     * {@link ImageryType#BING}, {@link ImageryType#SCANEX}.
095     *
096     * @param info imagery info
097     * @return a new TileSource instance or null if no TileSource for the ImageryInfo/ImageryType could be found.
098     * @throws IllegalArgumentException if url from imagery info is null or invalid
099     */
100    public static AbstractTMSTileSource getTileSourceStatic(ImageryInfo info) {
101        return getTileSourceStatic(info, null);
102    }
103
104    /**
105     * Creates and returns a new TileSource instance depending on the {@link ImageryType}
106     * of the passed ImageryInfo object.
107     *
108     * If no appropriate TileSource is found, null is returned.
109     * Currently supported ImageryType are {@link ImageryType#TMS},
110     * {@link ImageryType#BING}, {@link ImageryType#SCANEX}.
111     *
112     * @param info imagery info
113     * @param attributionLoadedTask task to be run once attribution is loaded, might be null, if nothing special shall happen
114     * @return a new TileSource instance or null if no TileSource for the ImageryInfo/ImageryType could be found.
115     * @throws IllegalArgumentException if url from imagery info is null or invalid
116     */
117    public static TMSTileSource getTileSourceStatic(ImageryInfo info, Runnable attributionLoadedTask) {
118        if (info.getImageryType() == ImageryType.TMS) {
119            JosmTemplatedTMSTileSource.checkUrl(info.getUrl());
120            TMSTileSource t = new JosmTemplatedTMSTileSource(info);
121            info.setAttribution(t);
122            return t;
123        } else if (info.getImageryType() == ImageryType.BING) {
124            return new CachedAttributionBingAerialTileSource(info, attributionLoadedTask);
125        } else if (info.getImageryType() == ImageryType.SCANEX) {
126            return new ScanexTileSource(info);
127        }
128        return null;
129    }
130
131    @Override
132    protected Class<? extends TileLoader> getTileLoaderClass() {
133        return TMSCachedTileLoader.class;
134    }
135
136    @Override
137    protected String getCacheName() {
138        return CACHE_REGION_NAME;
139    }
140
141    /**
142     * Returns cache for TMS region.
143     * @return cache for TMS region
144     */
145    public static CacheAccess<String, BufferedImageCacheEntry> getCache() {
146        return AbstractCachedTileSourceLayer.getCache(CACHE_REGION_NAME);
147    }
148
149    @Override
150    public ScaleList getNativeScales() {
151        return nativeScaleList;
152    }
153
154    private static ScaleList initNativeScaleList() {
155        Collection<Double> scales = IntStream.rangeClosed(AbstractTileSourceLayer.MIN_ZOOM, AbstractTileSourceLayer.MAX_ZOOM)
156                .mapToDouble(zoom -> OsmMercator.EARTH_RADIUS * Math.PI * 2 / Math.pow(2, zoom) / OsmMercator.DEFAUL_TILE_SIZE)
157                .boxed()
158                .collect(Collectors.toList());
159        return new ScaleList(scales);
160    }
161}