001// License: GPL. For details, see Readme.txt file. 002package org.openstreetmap.gui.jmapviewer.tilesources; 003 004import java.util.HashMap; 005import java.util.Map; 006import java.util.Random; 007import java.util.regex.Matcher; 008import java.util.regex.Pattern; 009 010import org.openstreetmap.gui.jmapviewer.interfaces.TemplatedTileSource; 011 012/** 013 * Handles templated TMS Tile Source. Templated means, that some patterns within 014 * URL gets substituted. 015 * 016 * Supported parameters 017 * {zoom} - substituted with zoom level 018 * {z} - as above 019 * {NUMBER-zoom} - substituted with result of equation "NUMBER - zoom", 020 * eg. {20-zoom} for zoom level 15 will result in 5 in this place 021 * {zoom+number} - substituted with result of equation "zoom + number", 022 * eg. {zoom+5} for zoom level 15 will result in 20. 023 * {x} - substituted with X tile number 024 * {y} - substituted with Y tile number 025 * {!y} - substituted with Yahoo Y tile number 026 * {-y} - substituted with reversed Y tile number 027 * {switch:VAL_A,VAL_B,VAL_C,...} - substituted with one of VAL_A, VAL_B, VAL_C. Usually 028 * used to specify many tile servers 029 * {header:(HEADER_NAME,HEADER_VALUE)} - sets the headers to be sent to tile server 030 */ 031public class TemplatedTMSTileSource extends TMSTileSource implements TemplatedTileSource { 032 033 private Random rand; 034 private String[] randomParts; 035 private final Map<String, String> headers = new HashMap<>(); 036 037 // CHECKSTYLE.OFF: SingleSpaceSeparator 038 private static final String COOKIE_HEADER = "Cookie"; 039 private static final String PATTERN_ZOOM = "\\{(?:(\\d+)-)?z(?:oom)?([+-]\\d+)?\\}"; 040 private static final String PATTERN_X = "\\{x\\}"; 041 private static final String PATTERN_Y = "\\{y\\}"; 042 private static final String PATTERN_Y_YAHOO = "\\{!y\\}"; 043 private static final String PATTERN_NEG_Y = "\\{-y\\}"; 044 private static final String PATTERN_SWITCH = "\\{switch:([^}]+)\\}"; 045 private static final String PATTERN_HEADER = "\\{header\\(([^,]+),([^}]+)\\)\\}"; 046 // CHECKSTYLE.ON: SingleSpaceSeparator 047 048 private static final String[] ALL_PATTERNS = { 049 PATTERN_HEADER, PATTERN_ZOOM, PATTERN_X, PATTERN_Y, PATTERN_Y_YAHOO, PATTERN_NEG_Y, PATTERN_SWITCH 050 }; 051 052 /** 053 * Creates Templated TMS Tile Source based on ImageryInfo 054 * @param info imagery info 055 */ 056 public TemplatedTMSTileSource(TileSourceInfo info) { 057 super(info); 058 String cookies = info.getCookies(); 059 if (cookies != null && !cookies.isEmpty()) { 060 headers.put(COOKIE_HEADER, cookies); 061 } 062 handleTemplate(); 063 } 064 065 private void handleTemplate() { 066 // Capturing group pattern on switch values 067 Matcher m = Pattern.compile(".*"+PATTERN_SWITCH+".*").matcher(baseUrl); 068 if (m.matches()) { 069 rand = new Random(); 070 randomParts = m.group(1).split(","); 071 } 072 Pattern pattern = Pattern.compile(PATTERN_HEADER); 073 StringBuffer output = new StringBuffer(); 074 Matcher matcher = pattern.matcher(baseUrl); 075 while (matcher.find()) { 076 headers.put(matcher.group(1), matcher.group(2)); 077 matcher.appendReplacement(output, ""); 078 } 079 matcher.appendTail(output); 080 baseUrl = output.toString(); 081 } 082 083 @Override 084 public Map<String, String> getHeaders() { 085 return headers; 086 } 087 088 @Override 089 public String getTileUrl(int zoom, int tilex, int tiley) { 090 int finalZoom = zoom; 091 Matcher m = Pattern.compile(".*"+PATTERN_ZOOM+".*").matcher(this.baseUrl); 092 if (m.matches()) { 093 if (m.group(1) != null) { 094 finalZoom = Integer.parseInt(m.group(1))-zoom; 095 } 096 if (m.group(2) != null) { 097 String ofs = m.group(2); 098 if (ofs.startsWith("+")) 099 ofs = ofs.substring(1); 100 finalZoom += Integer.parseInt(ofs); 101 } 102 } 103 String r = this.baseUrl 104 .replaceAll(PATTERN_ZOOM, Integer.toString(finalZoom)) 105 .replaceAll(PATTERN_X, Integer.toString(tilex)) 106 .replaceAll(PATTERN_Y, Integer.toString(tiley)) 107 .replaceAll(PATTERN_Y_YAHOO, Integer.toString((int) Math.pow(2, zoom-1)-1-tiley)) 108 .replaceAll(PATTERN_NEG_Y, Integer.toString((int) Math.pow(2, zoom)-1-tiley)); 109 if (rand != null) { 110 r = r.replaceAll(PATTERN_SWITCH, randomParts[rand.nextInt(randomParts.length)]); 111 } 112 return r; 113 } 114 115 /** 116 * Checks if url is acceptable by this Tile Source 117 * @param url URL to check 118 */ 119 public static void checkUrl(String url) { 120 assert url != null && !"".equals(url) : "URL cannot be null or empty"; 121 Matcher m = Pattern.compile("\\{[^}]*\\}").matcher(url); 122 while (m.find()) { 123 boolean isSupportedPattern = false; 124 for (String pattern : ALL_PATTERNS) { 125 if (m.group().matches(pattern)) { 126 isSupportedPattern = true; 127 break; 128 } 129 } 130 if (!isSupportedPattern) { 131 throw new IllegalArgumentException( 132 m.group() + " is not a valid TMS argument. Please check this server URL:\n" + url); 133 } 134 } 135 } 136}