001// License: GPL. For details, see Readme.txt file.
002package org.openstreetmap.gui.jmapviewer;
003
004import java.io.IOException;
005import java.io.InputStream;
006import java.net.HttpURLConnection;
007import java.net.URL;
008import java.net.URLConnection;
009import java.util.HashMap;
010import java.util.Map;
011import java.util.Map.Entry;
012
013import org.openstreetmap.gui.jmapviewer.interfaces.TileJob;
014import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
015import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
016
017/**
018 * A {@link TileLoader} implementation that loads tiles from OSM.
019 *
020 * @author Jan Peter Stotz
021 */
022public class OsmTileLoader implements TileLoader {
023
024    /**
025     * Holds the HTTP headers. Insert e.g. User-Agent here when default should not be used.
026     */
027    public Map<String, String> headers = new HashMap<>();
028
029    public int timeoutConnect = 0;
030    public int timeoutRead = 0;
031
032    protected TileLoaderListener listener;
033
034    public OsmTileLoader(TileLoaderListener listener) {
035        headers.put("Accept", "text/html, image/png, image/jpeg, image/gif, */*");
036        this.listener = listener;
037    }
038
039    public TileJob createTileLoaderJob(final Tile tile) {
040        return new TileJob() {
041
042            InputStream input = null;
043
044            public void run() {
045                synchronized (tile) {
046                    if ((tile.isLoaded() && !tile.hasError()) || tile.isLoading())
047                        return;
048                    tile.loaded = false;
049                    tile.error = false;
050                    tile.loading = true;
051                }
052                try {
053                    URLConnection conn = loadTileFromOsm(tile);
054                    loadTileMetadata(tile, conn);
055                    if ("no-tile".equals(tile.getValue("tile-info"))) {
056                        tile.setError("No tile at this zoom level");
057                    } else {
058                        input = conn.getInputStream();
059                        try {
060                            tile.loadImage(input);
061                        } finally {
062                            input.close();
063                            input = null;
064                        }
065                    }
066                    tile.setLoaded(true);
067                    listener.tileLoadingFinished(tile, true);
068                } catch (Exception e) {
069                    tile.setError(e.getMessage());
070                    listener.tileLoadingFinished(tile, false);
071                    if (input == null) {
072                        try {
073                            System.err.println("Failed loading " + tile.getUrl() +": "
074                                    +e.getClass() + ": " + e.getMessage());
075                        } catch (IOException ioe) {
076                            ioe.printStackTrace();
077                        }
078                    }
079                } finally {
080                    tile.loading = false;
081                    tile.setLoaded(true);
082                }
083            }
084
085            public Tile getTile() {
086                return tile;
087            }
088        };
089    }
090
091    protected URLConnection loadTileFromOsm(Tile tile) throws IOException {
092        URL url;
093        url = new URL(tile.getUrl());
094        URLConnection urlConn = url.openConnection();
095        if (urlConn instanceof HttpURLConnection) {
096            prepareHttpUrlConnection((HttpURLConnection)urlConn);
097        }
098        urlConn.setReadTimeout(30000); // 30 seconds read timeout
099        return urlConn;
100    }
101
102    protected void loadTileMetadata(Tile tile, URLConnection urlConn) {
103        String str = urlConn.getHeaderField("X-VE-TILEMETA-CaptureDatesRange");
104        if (str != null) {
105            tile.putValue("capture-date", str);
106        }
107        str = urlConn.getHeaderField("X-VE-Tile-Info");
108        if (str != null) {
109            tile.putValue("tile-info", str);
110        }
111    }
112
113    protected void prepareHttpUrlConnection(HttpURLConnection urlConn) {
114        for(Entry<String, String> e : headers.entrySet()) {
115            urlConn.setRequestProperty(e.getKey(), e.getValue());
116        }
117        if(timeoutConnect != 0)
118            urlConn.setConnectTimeout(timeoutConnect);
119        if(timeoutRead != 0)
120            urlConn.setReadTimeout(timeoutRead);
121    }
122
123    @Override
124    public String toString() {
125        return getClass().getSimpleName();
126    }
127
128}