001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.bbox;
003
004import java.util.ArrayList;
005import java.util.Collections;
006import java.util.HashMap;
007import java.util.List;
008import java.util.Map;
009import java.util.concurrent.TimeUnit;
010import java.util.stream.Collectors;
011
012import javax.swing.JOptionPane;
013
014import org.openstreetmap.gui.jmapviewer.JMapViewer;
015import org.openstreetmap.gui.jmapviewer.MemoryTileCache;
016import org.openstreetmap.gui.jmapviewer.OsmTileLoader;
017import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
018import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
019import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource;
020import org.openstreetmap.josm.data.Version;
021import org.openstreetmap.josm.data.imagery.ImageryInfo;
022import org.openstreetmap.josm.data.imagery.ImageryLayerInfo;
023import org.openstreetmap.josm.data.imagery.TMSCachedTileLoader;
024import org.openstreetmap.josm.data.imagery.TileLoaderFactory;
025import org.openstreetmap.josm.data.preferences.StringProperty;
026import org.openstreetmap.josm.gui.MainApplication;
027import org.openstreetmap.josm.gui.Notification;
028import org.openstreetmap.josm.gui.layer.AbstractCachedTileSourceLayer;
029import org.openstreetmap.josm.gui.layer.ImageryLayer;
030import org.openstreetmap.josm.gui.layer.TMSLayer;
031import org.openstreetmap.josm.tools.Logging;
032import org.openstreetmap.josm.tools.Utils;
033
034/**
035 * An extension of {@link JMapViewer} that implements JOSM-specific tile loading mechanisms.
036 * @since 15145
037 */
038public class JosmMapViewer extends JMapViewer {
039
040    /**
041     * A list of tile sources that can be used for displaying the map.
042     */
043    @FunctionalInterface
044    public interface TileSourceProvider {
045        /**
046         * Gets the tile sources that can be displayed
047         * @return The tile sources
048         */
049        List<TileSource> getTileSources();
050    }
051
052    /**
053     * TileSource provider.
054     */
055    public abstract static class AbstractImageryInfoBasedTileSourceProvider implements TileSourceProvider {
056        /**
057         * Returns the list of imagery infos backing tile sources.
058         * @return the list of imagery infos backing tile sources
059         */
060        public abstract List<ImageryInfo> getImageryInfos();
061
062        @Override
063        public List<TileSource> getTileSources() {
064            if (!TMSLayer.PROP_ADD_TO_SLIPPYMAP_CHOOSER.get()) return Collections.<TileSource>emptyList();
065            return imageryInfosToTileSources(getImageryInfos());
066        }
067    }
068
069    static List<TileSource> imageryInfosToTileSources(List<ImageryInfo> imageryInfos) {
070        List<TileSource> sources = new ArrayList<>();
071        for (ImageryInfo info : imageryInfos) {
072            try {
073                TileSource source = TMSLayer.getTileSourceStatic(info);
074                if (source != null) {
075                    sources.add(source);
076                }
077            } catch (IllegalArgumentException ex) {
078                Logging.trace(ex);
079                Logging.warn(ex.getMessage());
080                if (!Utils.isEmpty(ex.getMessage())) {
081                    new Notification(ex.getMessage()).setIcon(JOptionPane.WARNING_MESSAGE).show();
082                }
083            }
084        }
085        return sources;
086    }
087
088    /**
089     * TileSource provider - providing default OSM tile source
090     */
091    public static class DefaultOsmTileSourceProvider implements TileSourceProvider {
092
093        protected static final StringProperty DEFAULT_OSM_TILE_URL = new StringProperty(
094                "default.osm.tile.source.url", "https://{switch:a,b,c}.tile.openstreetmap.org/{zoom}/{x}/{y}.png");
095
096        @Override
097        public List<TileSource> getTileSources() {
098            List<TileSource> result = imageryInfosToTileSources(ImageryLayerInfo.instance.getLayers().stream()
099                   .filter(l -> l.getUrl().equals(DEFAULT_OSM_TILE_URL.get())).collect(Collectors.toList()));
100            if (result.isEmpty()) {
101                result.add(new OsmTileSource.Mapnik());
102            }
103            return result;
104        }
105
106        /**
107         * Returns the default OSM tile source.
108         * @return the default OSM tile source
109         */
110        public static TileSource get() {
111            return new DefaultOsmTileSourceProvider().getTileSources().get(0);
112        }
113    }
114
115    /**
116     * TileSource provider - providing sources from imagery sources menu
117     */
118    public static class TMSTileSourceProvider extends AbstractImageryInfoBasedTileSourceProvider {
119        @Override
120        public List<ImageryInfo> getImageryInfos() {
121            return ImageryLayerInfo.instance.getLayers();
122        }
123    }
124
125    /**
126     * TileSource provider - providing sources from current layers
127     */
128    public static class CurrentLayersTileSourceProvider extends AbstractImageryInfoBasedTileSourceProvider {
129        @Override
130        public List<ImageryInfo> getImageryInfos() {
131            return MainApplication.getLayerManager().getLayers().stream().filter(
132                layer -> layer instanceof ImageryLayer
133            ).map(
134                layer -> ((ImageryLayer) layer).getInfo()
135            ).collect(Collectors.toList());
136        }
137    }
138
139    protected final transient TileLoader cachedLoader;
140    protected final transient OsmTileLoader uncachedLoader;
141
142    /**
143     * Constructs a new {@code JosmMapViewer}.
144     */
145    public JosmMapViewer() {
146        Map<String, String> headers = new HashMap<>();
147        headers.put("User-Agent", Version.getInstance().getFullAgentString());
148
149        TileLoaderFactory cachedLoaderFactory = AbstractCachedTileSourceLayer.getTileLoaderFactory("TMS", TMSCachedTileLoader.class);
150        if (cachedLoaderFactory != null) {
151            cachedLoader = cachedLoaderFactory.makeTileLoader(this, headers, TimeUnit.HOURS.toSeconds(1));
152        } else {
153            cachedLoader = null;
154        }
155
156        uncachedLoader = new OsmTileLoader(this);
157        uncachedLoader.headers.putAll(headers);
158        setFileCacheEnabled(true);
159    }
160
161    /**
162     * Enables the disk tile cache.
163     * @param enabled true to enable, false to disable
164     */
165    public final void setFileCacheEnabled(boolean enabled) {
166        if (enabled && cachedLoader != null) {
167            setTileLoader(cachedLoader);
168        } else {
169            setTileLoader(uncachedLoader);
170        }
171    }
172
173    /**
174     * Sets the maximum number of tiles that may be held in memory
175     * @param tiles The maximum number of tiles.
176     */
177    public final void setMaxTilesInMemory(int tiles) {
178        ((MemoryTileCache) getTileCache()).setCacheSize(tiles);
179    }
180}