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}