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 if (info.getCookies() != null) { 059 headers.put(COOKIE_HEADER, info.getCookies()); 060 } 061 handleTemplate(); 062 } 063 064 private void handleTemplate() { 065 // Capturing group pattern on switch values 066 Matcher m = Pattern.compile(".*"+PATTERN_SWITCH+".*").matcher(baseUrl); 067 if (m.matches()) { 068 rand = new Random(); 069 randomParts = m.group(1).split(","); 070 } 071 Pattern pattern = Pattern.compile(PATTERN_HEADER); 072 StringBuffer output = new StringBuffer(); 073 Matcher matcher = pattern.matcher(baseUrl); 074 while (matcher.find()) { 075 headers.put(matcher.group(1), matcher.group(2)); 076 matcher.appendReplacement(output, ""); 077 } 078 matcher.appendTail(output); 079 baseUrl = output.toString(); 080 } 081 082 @Override 083 public Map<String, String> getHeaders() { 084 return headers; 085 } 086 087 @Override 088 public String getTileUrl(int zoom, int tilex, int tiley) { 089 int finalZoom = zoom; 090 Matcher m = Pattern.compile(".*"+PATTERN_ZOOM+".*").matcher(this.baseUrl); 091 if (m.matches()) { 092 if (m.group(1) != null) { 093 finalZoom = Integer.parseInt(m.group(1))-zoom; 094 } 095 if (m.group(2) != null) { 096 String ofs = m.group(2); 097 if (ofs.startsWith("+")) 098 ofs = ofs.substring(1); 099 finalZoom += Integer.parseInt(ofs); 100 } 101 } 102 String r = this.baseUrl 103 .replaceAll(PATTERN_ZOOM, Integer.toString(finalZoom)) 104 .replaceAll(PATTERN_X, Integer.toString(tilex)) 105 .replaceAll(PATTERN_Y, Integer.toString(tiley)) 106 .replaceAll(PATTERN_Y_YAHOO, Integer.toString((int) Math.pow(2, zoom-1)-1-tiley)) 107 .replaceAll(PATTERN_NEG_Y, Integer.toString((int) Math.pow(2, zoom)-1-tiley)); 108 if (rand != null) { 109 r = r.replaceAll(PATTERN_SWITCH, randomParts[rand.nextInt(randomParts.length)]); 110 } 111 return r; 112 } 113 114 /** 115 * Checks if url is acceptable by this Tile Source 116 * @param url URL to check 117 */ 118 public static void checkUrl(String url) { 119 assert url != null && !"".equals(url) : "URL cannot be null or empty"; 120 Matcher m = Pattern.compile("\\{[^}]*\\}").matcher(url); 121 while (m.find()) { 122 boolean isSupportedPattern = false; 123 for (String pattern : ALL_PATTERNS) { 124 if (m.group().matches(pattern)) { 125 isSupportedPattern = true; 126 break; 127 } 128 } 129 if (!isSupportedPattern) { 130 throw new IllegalArgumentException( 131 m.group() + " is not a valid TMS argument. Please check this server URL:\n" + url); 132 } 133 } 134 } 135}