001// License: GPL. For details, see Readme.txt file.
002package org.openstreetmap.gui.jmapviewer.tilesources;
003
004import java.awt.Point;
005import java.io.IOException;
006import java.security.MessageDigest;
007import java.security.NoSuchAlgorithmException;
008import java.util.HashMap;
009import java.util.List;
010import java.util.Map;
011import java.util.Map.Entry;
012import java.util.Set;
013
014import org.openstreetmap.gui.jmapviewer.JMapViewer;
015import org.openstreetmap.gui.jmapviewer.OsmMercator;
016import org.openstreetmap.gui.jmapviewer.Tile;
017import org.openstreetmap.gui.jmapviewer.TileXY;
018import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
019
020/**
021 * Class generalizing all tile based tile sources
022 *
023 * @author Wiktor Niesiobędzki
024 *
025 */
026public abstract class AbstractTMSTileSource extends AbstractTileSource {
027
028    protected String name;
029    protected String baseUrl;
030    protected String id;
031    private final Map<String, Set<String>> noTileHeaders;
032    private final Map<String, Set<String>> noTileChecksums;
033    private final Map<String, String> metadataHeaders;
034    protected boolean modTileFeatures;
035    protected int tileSize;
036
037    /**
038     * Creates an instance based on TileSource information
039     *
040     * @param info description of the Tile Source
041     */
042    public AbstractTMSTileSource(TileSourceInfo info) {
043        this.name = info.getName();
044        this.baseUrl = info.getUrl();
045        if (baseUrl != null && baseUrl.endsWith("/")) {
046            baseUrl = baseUrl.substring(0, baseUrl.length()-1);
047        }
048        this.id = info.getUrl();
049        this.noTileHeaders = info.getNoTileHeaders();
050        this.noTileChecksums = info.getNoTileChecksums();
051        this.metadataHeaders = info.getMetadataHeaders();
052        this.modTileFeatures = info.isModTileFeatures();
053        this.tileSize = info.getTileSize();
054    }
055
056    /**
057     * @return default tile size to use, when not set in Imagery Preferences
058     */
059    @Override
060    public int getDefaultTileSize() {
061        return OsmMercator.DEFAUL_TILE_SIZE;
062    }
063
064    @Override
065    public String getName() {
066        return name;
067    }
068
069    @Override
070    public String getId() {
071        return id;
072    }
073
074    @Override
075    public int getMaxZoom() {
076        return JMapViewer.MAX_ZOOM;
077    }
078
079    @Override
080    public int getMinZoom() {
081        return JMapViewer.MIN_ZOOM;
082    }
083
084    /**
085     * @return image extension, used for URL creation
086     */
087    public String getExtension() {
088        return "png";
089    }
090
091    /**
092     * @param zoom level of the tile
093     * @param tilex tile number in x axis
094     * @param tiley tile number in y axis
095     * @return String containg path part of URL of the tile
096     * @throws IOException when subclass cannot return the tile URL
097     */
098    public String getTilePath(int zoom, int tilex, int tiley) throws IOException {
099        return "/" + zoom + "/" + tilex + "/" + tiley + "." + getExtension();
100    }
101
102    /**
103     * @return Base part of the URL of the tile source
104     */
105    public String getBaseUrl() {
106        return this.baseUrl;
107    }
108
109    @Override
110    public String getTileUrl(int zoom, int tilex, int tiley) throws IOException {
111        return this.getBaseUrl() + getTilePath(zoom, tilex, tiley);
112    }
113
114    @Override
115    public String toString() {
116        return getName();
117    }
118
119    /*
120     * Most tilesources use OsmMercator projection.
121     */
122    @Override
123    public int getTileSize() {
124        if (tileSize <= 0) {
125            return getDefaultTileSize();
126        }
127        return tileSize;
128    }
129
130    @Override
131    public Point latLonToXY(ICoordinate point, int zoom) {
132        return latLonToXY(point.getLat(), point.getLon(), zoom);
133    }
134
135    @Override
136    public ICoordinate xyToLatLon(Point point, int zoom) {
137        return xyToLatLon(point.x, point.y, zoom);
138    }
139
140    @Override
141    public TileXY latLonToTileXY(ICoordinate point, int zoom) {
142        return latLonToTileXY(point.getLat(), point.getLon(), zoom);
143    }
144
145    @Override
146    public ICoordinate tileXYToLatLon(TileXY xy, int zoom) {
147        return tileXYToLatLon(xy.getXIndex(), xy.getYIndex(), zoom);
148    }
149
150    @Override
151    public ICoordinate tileXYToLatLon(Tile tile) {
152        return tileXYToLatLon(tile.getXtile(), tile.getYtile(), tile.getZoom());
153    }
154
155    @Override
156    public int getTileXMax(int zoom) {
157        return getTileMax(zoom);
158    }
159
160    @Override
161    public int getTileXMin(int zoom) {
162        return 0;
163    }
164
165    @Override
166    public int getTileYMax(int zoom) {
167        return getTileMax(zoom);
168    }
169
170    @Override
171    public int getTileYMin(int zoom) {
172        return 0;
173    }
174
175    @Override
176    public boolean isNoTileAtZoom(Map<String, List<String>> headers, int statusCode, byte[] content) {
177        if (noTileHeaders != null && headers != null) {
178            for (Entry<String, Set<String>> searchEntry: noTileHeaders.entrySet()) {
179                List<String> headerVals = headers.get(searchEntry.getKey());
180                if (headerVals != null) {
181                    for (String headerValue: headerVals) {
182                        for (String val: searchEntry.getValue()) {
183                            if (headerValue.matches(val)) {
184                                return true;
185                            }
186                        }
187                    }
188                }
189            }
190        }
191        if (noTileChecksums != null && content != null) {
192            for (Entry<String, Set<String>> searchEntry: noTileChecksums.entrySet()) {
193                MessageDigest md = null;
194                try {
195                    md = MessageDigest.getInstance(searchEntry.getKey());
196                } catch (NoSuchAlgorithmException e) {
197                    break;
198                }
199                byte[] byteDigest = md.digest(content);
200                final int len = byteDigest.length;
201
202                char[] hexChars = new char[len * 2];
203                for (int i = 0, j = 0; i < len; i++) {
204                    final int v = byteDigest[i];
205                    int vn = (v & 0xf0) >> 4;
206                    hexChars[j++] = (char) (vn + (vn >= 10 ? 'a'-10 : '0'));
207                    vn = (v & 0xf);
208                    hexChars[j++] = (char) (vn + (vn >= 10 ? 'a'-10 : '0'));
209                }
210                for (String val: searchEntry.getValue()) {
211                    if (new String(hexChars).equalsIgnoreCase(val)) {
212                        return true;
213                    }
214                }
215            }
216        }
217        return super.isNoTileAtZoom(headers, statusCode, content);
218    }
219
220    @Override
221    public Map<String, String> getMetadata(Map<String, List<String>> headers) {
222        Map<String, String> ret = new HashMap<>();
223        if (metadataHeaders != null && headers != null) {
224            for (Entry<String, String> searchEntry: metadataHeaders.entrySet()) {
225                List<String> headerVals = headers.get(searchEntry.getKey());
226                if (headerVals != null) {
227                    for (String headerValue: headerVals) {
228                        ret.put(searchEntry.getValue(), headerValue);
229                    }
230                }
231            }
232        }
233        return ret;
234    }
235
236    @Override
237    public String getTileId(int zoom, int tilex, int tiley) {
238        return this.baseUrl + "/" + zoom + "/" + tilex + "/" + tiley;
239    }
240
241    @Override
242    public boolean isModTileFeatures() {
243        return modTileFeatures;
244    }
245
246    private static int getTileMax(int zoom) {
247        return (int) Math.pow(2.0, zoom) - 1;
248    }
249}