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