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 *
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 passed ImageryInfo object.
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     * @param info imagery info
070     * @return a new TileSource instance or null if no TileSource for the ImageryInfo/ImageryType could be found.
071     * @throws IllegalArgumentException if url from imagery info is null or invalid
072     */
073    @Override
074    protected TMSTileSource getTileSource(ImageryInfo info) throws IllegalArgumentException {
075        return getTileSourceStatic(info, new Runnable() {
076            @Override
077            public void run() {
078                Main.debug("Attribution loaded, running loadAllErrorTiles");
079                TMSLayer.this.loadAllErrorTiles(false);
080            }
081        });
082    }
083
084    @Override
085    public final boolean isProjectionSupported(Projection proj) {
086        return "EPSG:3857".equals(proj.toCode()) || "EPSG:4326".equals(proj.toCode());
087    }
088
089    @Override
090    public final String nameSupportedProjections() {
091        return tr("EPSG:4326 and Mercator projection are supported");
092    }
093
094    /**
095     * Creates and returns a new TileSource instance depending on the {@link ImageryType}
096     * of the passed ImageryInfo object.
097     *
098     * If no appropriate TileSource is found, null is returned.
099     * Currently supported ImageryType are {@link ImageryType#TMS},
100     * {@link ImageryType#BING}, {@link ImageryType#SCANEX}.
101     *
102     * @param info imagery info
103     * @return a new TileSource instance or null if no TileSource for the ImageryInfo/ImageryType could be found.
104     * @throws IllegalArgumentException if url from imagery info is null or invalid
105     */
106    public static AbstractTMSTileSource getTileSourceStatic(ImageryInfo info) throws IllegalArgumentException {
107        return getTileSourceStatic(info, null);
108    }
109
110    /**
111     * Creates and returns a new TileSource instance depending on the {@link ImageryType}
112     * of the passed ImageryInfo object.
113     *
114     * If no appropriate TileSource is found, null is returned.
115     * Currently supported ImageryType are {@link ImageryType#TMS},
116     * {@link ImageryType#BING}, {@link ImageryType#SCANEX}.
117     *
118     * @param info imagery info
119     * @param attributionLoadedTask task to be run once attribution is loaded, might be null, if nothing special shall happen
120     * @return a new TileSource instance or null if no TileSource for the ImageryInfo/ImageryType could be found.
121     * @throws IllegalArgumentException if url from imagery info is null or invalid
122     */
123    public static TMSTileSource getTileSourceStatic(ImageryInfo info, Runnable attributionLoadedTask) throws IllegalArgumentException {
124        if (info.getImageryType() == ImageryType.TMS) {
125            TemplatedTMSTileSource.checkUrl(info.getUrl());
126            TMSTileSource t = new TemplatedTMSTileSource(info);
127            info.setAttribution(t);
128            return t;
129        } else if (info.getImageryType() == ImageryType.BING) {
130            return new CachedAttributionBingAerialTileSource(info, attributionLoadedTask);
131        } else if (info.getImageryType() == ImageryType.SCANEX) {
132            return new ScanexTileSource(info);
133        }
134        return null;
135    }
136
137    @Override
138    protected Class<? extends TileLoader> getTileLoaderClass() {
139        return TMSCachedTileLoader.class;
140    }
141
142    @Override
143    protected String getCacheName() {
144        return CACHE_REGION_NAME;
145    }
146
147    /**
148     * @return cache for TMS region
149     */
150    public static CacheAccess<String, BufferedImageCacheEntry> getCache() {
151        return AbstractCachedTileSourceLayer.getCache(CACHE_REGION_NAME);
152    }
153
154    @Override
155    public ScaleList getNativeScales() {
156        return nativeScaleList;
157    }
158
159    private static ScaleList initNativeScaleList() {
160        Collection<Double> scales = new ArrayList<>(AbstractTileSourceLayer.MAX_ZOOM);
161        for (int zoom = AbstractTileSourceLayer.MIN_ZOOM; zoom <= AbstractTileSourceLayer.MAX_ZOOM; zoom++) {
162            double scale = OsmMercator.EARTH_RADIUS * Math.PI * 2 / Math.pow(2, zoom) / OsmMercator.DEFAUL_TILE_SIZE;
163            scales.add(scale);
164        }
165        return new ScaleList(scales);
166    }
167 }