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