001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.imagery; 003 004import static org.openstreetmap.josm.data.imagery.ImageryPatterns.PATTERN_PARAM; 005 006import java.text.DecimalFormat; 007import java.text.DecimalFormatSymbols; 008import java.text.NumberFormat; 009import java.util.Locale; 010import java.util.Map; 011import java.util.Set; 012import java.util.TreeSet; 013import java.util.concurrent.ConcurrentHashMap; 014import java.util.regex.Matcher; 015 016import org.openstreetmap.gui.jmapviewer.interfaces.TemplatedTileSource; 017import org.openstreetmap.josm.data.coor.EastNorth; 018import org.openstreetmap.josm.data.projection.Projection; 019import org.openstreetmap.josm.gui.layer.WMSLayer; 020import org.openstreetmap.josm.tools.Utils; 021 022/** 023 * Tile Source handling WMS providers 024 * 025 * @author Wiktor Niesiobędzki 026 * @since 8526 027 */ 028public class TemplatedWMSTileSource extends AbstractWMSTileSource implements TemplatedTileSource { 029 030 private static final NumberFormat LATLON_FORMAT = new DecimalFormat("###0.0000000", new DecimalFormatSymbols(Locale.US)); 031 032 private final Set<String> serverProjections; 033 private final Map<String, String> headers = new ConcurrentHashMap<>(); 034 private final String date; 035 private final boolean belowWMS130; 036 037 /** 038 * Creates a tile source based on imagery info 039 * @param info imagery info 040 * @param tileProjection the tile projection 041 */ 042 public TemplatedWMSTileSource(ImageryInfo info, Projection tileProjection) { 043 super(info, tileProjection); 044 this.serverProjections = new TreeSet<>(info.getServerProjections()); 045 this.headers.putAll(info.getCustomHttpHeaders()); 046 this.date = info.getDate(); 047 this.baseUrl = ImageryPatterns.handleHeaderTemplate(baseUrl, headers); 048 initProjection(); 049 // Bounding box coordinates have to be switched for WMS 1.3.0 EPSG:4326. 050 // 051 // Background: 052 // 053 // bbox=x_min,y_min,x_max,y_max 054 // 055 // SRS=... is WMS 1.1.1 056 // CRS=... is WMS 1.3.0 057 // 058 // The difference: 059 // For SRS x is east-west and y is north-south 060 // For CRS x and y are as specified by the EPSG 061 // E.g. [1] lists lat as first coordinate axis and lot as second, so it is switched for EPSG:4326. 062 // For most other EPSG code there seems to be no difference. 063 // CHECKSTYLE.OFF: LineLength 064 // [1] https://www.epsg-registry.org/report.htm?type=selection&entity=urn:ogc:def:crs:EPSG::4326&reportDetail=short&style=urn:uuid:report-style:default-with-code&style_name=OGP%20Default%20With%20Code&title=EPSG:4326 065 // CHECKSTYLE.ON: LineLength 066 belowWMS130 = !baseUrl.toLowerCase(Locale.US).contains("crs="); 067 } 068 069 @Override 070 public int getDefaultTileSize() { 071 return WMSLayer.PROP_IMAGE_SIZE.get(); 072 } 073 074 @Override 075 public String getTileUrl(int zoom, int tilex, int tiley) { 076 String myProjCode = getServerCRS(); 077 078 EastNorth nw = getTileEastNorth(tilex, tiley, zoom); 079 EastNorth se = getTileEastNorth(tilex + 1, tiley + 1, zoom); 080 081 double w = nw.getX(); 082 double n = nw.getY(); 083 084 double s = se.getY(); 085 double e = se.getX(); 086 087 if ("EPSG:4326".equals(myProjCode) && !serverProjections.contains(myProjCode) && serverProjections.contains("CRS:84")) { 088 myProjCode = "CRS:84"; 089 } 090 091 // Using StringBuffer and generic PATTERN_PARAM matcher gives 2x performance improvement over replaceAll 092 StringBuffer url = new StringBuffer(baseUrl.length()); 093 Matcher matcher = PATTERN_PARAM.matcher(baseUrl); 094 while (matcher.find()) { 095 String replacement; 096 switch (matcher.group(1)) { 097 case "proj": 098 replacement = myProjCode; 099 break; 100 case "wkid": 101 replacement = myProjCode.startsWith("EPSG:") ? myProjCode.substring(5) : myProjCode; 102 break; 103 case "bbox": 104 replacement = getBbox(zoom, tilex, tiley, !belowWMS130 && getTileProjection().switchXY()); 105 break; 106 case "w": 107 replacement = LATLON_FORMAT.format(w); 108 break; 109 case "s": 110 replacement = LATLON_FORMAT.format(s); 111 break; 112 case "e": 113 replacement = LATLON_FORMAT.format(e); 114 break; 115 case "n": 116 replacement = LATLON_FORMAT.format(n); 117 break; 118 case "width": 119 case "height": 120 replacement = String.valueOf(getTileSize()); 121 break; 122 case "time": 123 replacement = Utils.encodeUrl(date); 124 break; 125 default: 126 replacement = '{' + matcher.group(1) + '}'; 127 } 128 matcher.appendReplacement(url, replacement); 129 } 130 matcher.appendTail(url); 131 return url.toString().replace(" ", "%20"); 132 } 133 134 @Override 135 public String getTileId(int zoom, int tilex, int tiley) { 136 return getTileUrl(zoom, tilex, tiley); 137 } 138 139 @Override 140 public Map<String, String> getHeaders() { 141 return headers; 142 } 143 144 /** 145 * Checks if url is acceptable by this Tile Source 146 * @param url URL to check 147 */ 148 public static void checkUrl(String url) { 149 ImageryPatterns.checkWmsUrlPatterns(url); 150 } 151}