// License: GPL. For details, see Readme.txt file.
package org.openstreetmap.gui.jmapviewer.tilesources;
import java.awt.Point;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.openstreetmap.gui.jmapviewer.OsmMercator;
import org.openstreetmap.gui.jmapviewer.Tile;
import org.openstreetmap.gui.jmapviewer.TileXY;
import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
/**
* Class generalizing all tile based tile sources
*
* @author Wiktor Niesiobędzki
*
*/
public abstract class AbstractTMSTileSource extends AbstractTileSource {
protected String name;
protected String baseUrl;
protected String id;
private final Map<String, Set<String>> noTileHeaders;
private final Map<String, Set<String>> noTileChecksums;
private final Map<String, String> metadataHeaders;
protected int tileSize;
/**
* Creates an instance based on TileSource information
*
* @param info description of the Tile Source
*/
public AbstractTMSTileSource(TileSourceInfo info) {
this.name = info.getName();
this.baseUrl = info.getUrl();
if (baseUrl != null && baseUrl.endsWith("/")) {
baseUrl = baseUrl.substring(0, baseUrl.length()-1);
}
this.id = info.getUrl();
this.noTileHeaders = info.getNoTileHeaders();
this.noTileChecksums = info.getNoTileChecksums();
this.metadataHeaders = info.getMetadataHeaders();
this.tileSize = info.getTileSize();
}
/**
* @return default tile size to use, when not set in Imagery Preferences
*/
@Override
public int getDefaultTileSize() {
return OsmMercator.DEFAUL_TILE_SIZE;
}
@Override
public String getName() {
return name;
}
@Override
public String getId() {
return id;
}
@Override
public int getMaxZoom() {
return 21;
}
@Override
public int getMinZoom() {
return 0;
}
/**
* @return image extension, used for URL creation
*/
public String getExtension() {
return "png";
}
/**
* @param zoom level of the tile
* @param tilex tile number in x axis
* @param tiley tile number in y axis
* @return String containg path part of URL of the tile
* @throws IOException when subclass cannot return the tile URL
*/
public String getTilePath(int zoom, int tilex, int tiley) throws IOException {
return "/" + zoom + "/" + tilex + "/" + tiley + "." + getExtension();
}
/**
* @return Base part of the URL of the tile source
*/
public String getBaseUrl() {
return this.baseUrl;
}
@Override
public String getTileUrl(int zoom, int tilex, int tiley) throws IOException {
return this.getBaseUrl() + getTilePath(zoom, tilex, tiley);
}
@Override
public String toString() {
return getName();
}
/*
* Most tilesources use OsmMercator projection.
*/
@Override
public int getTileSize() {
if (tileSize <= 0) {
return getDefaultTileSize();
}
return tileSize;
}
@Override
public Point latLonToXY(ICoordinate point, int zoom) {
return latLonToXY(point.getLat(), point.getLon(), zoom);
}
@Override
public ICoordinate xyToLatLon(Point point, int zoom) {
return xyToLatLon(point.x, point.y, zoom);
}
@Override
public TileXY latLonToTileXY(ICoordinate point, int zoom) {
return latLonToTileXY(point.getLat(), point.getLon(), zoom);
}
@Override
public ICoordinate tileXYToLatLon(TileXY xy, int zoom) {
return tileXYToLatLon(xy.getXIndex(), xy.getYIndex(), zoom);
}
@Override
public ICoordinate tileXYToLatLon(Tile tile) {
return tileXYToLatLon(tile.getXtile(), tile.getYtile(), tile.getZoom());
}
@Override
public int getTileXMax(int zoom) {
return getTileMax(zoom);
}
@Override
public int getTileXMin(int zoom) {
return 0;
}
@Override
public int getTileYMax(int zoom) {
return getTileMax(zoom);
}
@Override
public int getTileYMin(int zoom) {
return 0;
}
@Override
public boolean isNoTileAtZoom(Map<String, List<String>> headers, int statusCode, byte[] content) {
if (noTileHeaders != null && headers != null) {
for (Entry<String, Set<String>> searchEntry: noTileHeaders.entrySet()) {
List<String> headerVals = headers.get(searchEntry.getKey());
if (headerVals != null) {
for (String headerValue: headerVals) {
for (String val: searchEntry.getValue()) {
if (headerValue.matches(val)) {
return true;
}
}
}
}
}
}
if (noTileChecksums != null && content != null) {
for (Entry<String, Set<String>> searchEntry: noTileChecksums.entrySet()) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance(searchEntry.getKey());
} catch (NoSuchAlgorithmException e) {
break;
}
byte[] byteDigest = md.digest(content);
final int len = byteDigest.length;
char[] hexChars = new char[len * 2];
for (int i = 0, j = 0; i < len; i++) {
final int v = byteDigest[i];
int vn = (v & 0xf0) >> 4;
hexChars[j++] = (char) (vn + (vn >= 10 ? 'a'-10 : '0'));
vn = (v & 0xf);
hexChars[j++] = (char) (vn + (vn >= 10 ? 'a'-10 : '0'));
}
for (String val: searchEntry.getValue()) {
if (new String(hexChars).equalsIgnoreCase(val)) {
return true;
}
}
}
}
return super.isNoTileAtZoom(headers, statusCode, content);
}
@Override
public Map<String, String> getMetadata(Map<String, List<String>> headers) {
Map<String, String> ret = new HashMap<>();
if (metadataHeaders != null && headers != null) {
for (Entry<String, String> searchEntry: metadataHeaders.entrySet()) {
List<String> headerVals = headers.get(searchEntry.getKey());
if (headerVals != null) {
for (String headerValue: headerVals) {
ret.put(searchEntry.getValue(), headerValue);
}
}
}
}
return ret;
}
@Override
public String getTileId(int zoom, int tilex, int tiley) {
return this.baseUrl + "/" + zoom + "/" + tilex + "/" + tiley;
}
private static int getTileMax(int zoom) {
return (int) Math.pow(2.0, zoom) - 1;
}
}