001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.io.imagery; 003 004import java.awt.image.BufferedImage; 005import java.io.BufferedReader; 006import java.io.ByteArrayInputStream; 007import java.io.ByteArrayOutputStream; 008import java.io.IOException; 009import java.io.InputStream; 010import java.io.InputStreamReader; 011import java.net.HttpURLConnection; 012import java.net.MalformedURLException; 013import java.net.URL; 014import java.net.URLConnection; 015import java.text.DecimalFormat; 016import java.text.DecimalFormatSymbols; 017import java.text.NumberFormat; 018import java.util.HashMap; 019import java.util.Locale; 020import java.util.Map; 021import java.util.Map.Entry; 022import java.util.regex.Matcher; 023import java.util.regex.Pattern; 024 025import javax.imageio.ImageIO; 026 027import org.openstreetmap.josm.Main; 028import org.openstreetmap.josm.data.coor.EastNorth; 029import org.openstreetmap.josm.data.coor.LatLon; 030import org.openstreetmap.josm.data.imagery.GeorefImage.State; 031import org.openstreetmap.josm.data.imagery.ImageryInfo; 032import org.openstreetmap.josm.gui.MapView; 033import org.openstreetmap.josm.gui.layer.WMSLayer; 034import org.openstreetmap.josm.io.OsmTransferException; 035import org.openstreetmap.josm.io.ProgressInputStream; 036import org.openstreetmap.josm.tools.Utils; 037 038 039public class WMSGrabber extends Grabber { 040 041 protected String baseURL; 042 private ImageryInfo info; 043 private Map<String, String> props = new HashMap<String, String>(); 044 045 public WMSGrabber(MapView mv, WMSLayer layer, boolean localOnly) { 046 super(mv, layer, localOnly); 047 this.info = layer.getInfo(); 048 this.baseURL = info.getUrl(); 049 if(layer.getInfo().getCookies() != null && !layer.getInfo().getCookies().isEmpty()) { 050 props.put("Cookie", layer.getInfo().getCookies()); 051 } 052 Pattern pattern = Pattern.compile("\\{header\\(([^,]+),([^}]+)\\)\\}"); 053 StringBuffer output = new StringBuffer(); 054 Matcher matcher = pattern.matcher(this.baseURL); 055 while (matcher.find()) { 056 props.put(matcher.group(1),matcher.group(2)); 057 matcher.appendReplacement(output, ""); 058 } 059 matcher.appendTail(output); 060 this.baseURL = output.toString(); 061 } 062 063 @Override 064 void fetch(WMSRequest request, int attempt) throws Exception{ 065 URL url = null; 066 try { 067 url = getURL( 068 b.minEast, b.minNorth, 069 b.maxEast, b.maxNorth, 070 width(), height()); 071 request.finish(State.IMAGE, grab(request, url, attempt)); 072 073 } catch(Exception e) { 074 e.printStackTrace(); 075 throw new Exception(e.getMessage() + "\nImage couldn't be fetched: " + (url != null ? url.toString() : "")); 076 } 077 } 078 079 public static final NumberFormat latLonFormat = new DecimalFormat("###0.0000000", 080 new DecimalFormatSymbols(Locale.US)); 081 082 protected URL getURL(double w, double s,double e,double n, 083 int wi, int ht) throws MalformedURLException { 084 String myProj = Main.getProjection().toCode(); 085 if (!info.getServerProjections().contains(myProj) && "EPSG:3857".equals(Main.getProjection().toCode())) { 086 LatLon sw = Main.getProjection().eastNorth2latlon(new EastNorth(w, s)); 087 LatLon ne = Main.getProjection().eastNorth2latlon(new EastNorth(e, n)); 088 myProj = "EPSG:4326"; 089 s = sw.lat(); 090 w = sw.lon(); 091 n = ne.lat(); 092 e = ne.lon(); 093 } 094 if (myProj.equals("EPSG:4326") && !info.getServerProjections().contains(myProj) && info.getServerProjections().contains("CRS:84")) { 095 myProj = "CRS:84"; 096 } 097 098 // Bounding box coordinates have to be switched for WMS 1.3.0 EPSG:4326. 099 // 100 // Background: 101 // 102 // bbox=x_min,y_min,x_max,y_max 103 // 104 // SRS=... is WMS 1.1.1 105 // CRS=... is WMS 1.3.0 106 // 107 // The difference: 108 // For SRS x is east-west and y is north-south 109 // For CRS x and y are as specified by the EPSG 110 // E.g. [1] lists lat as first coordinate axis and lot as second, so it is switched for EPSG:4326. 111 // For most other EPSG code there seems to be no difference. 112 // [1] http://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 113 boolean switchLatLon = false; 114 if (baseURL.toLowerCase().contains("crs=epsg:4326")) { 115 switchLatLon = true; 116 } else if (baseURL.toLowerCase().contains("crs=") && myProj.equals("EPSG:4326")) { 117 switchLatLon = true; 118 } 119 String bbox; 120 if (switchLatLon) { 121 bbox = String.format("%s,%s,%s,%s", latLonFormat.format(s), latLonFormat.format(w), latLonFormat.format(n), latLonFormat.format(e)); 122 } else { 123 bbox = String.format("%s,%s,%s,%s", latLonFormat.format(w), latLonFormat.format(s), latLonFormat.format(e), latLonFormat.format(n)); 124 } 125 return new URL(baseURL.replaceAll("\\{proj(\\([^})]+\\))?\\}", myProj) 126 .replaceAll("\\{bbox\\}", bbox) 127 .replaceAll("\\{w\\}", latLonFormat.format(w)) 128 .replaceAll("\\{s\\}", latLonFormat.format(s)) 129 .replaceAll("\\{e\\}", latLonFormat.format(e)) 130 .replaceAll("\\{n\\}", latLonFormat.format(n)) 131 .replaceAll("\\{width\\}", String.valueOf(wi)) 132 .replaceAll("\\{height\\}", String.valueOf(ht)) 133 .replace(" ", "%20")); 134 } 135 136 @Override 137 public boolean loadFromCache(WMSRequest request) { 138 BufferedImage cached = layer.cache.getExactMatch(Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth); 139 140 if (cached != null) { 141 request.finish(State.IMAGE, cached); 142 return true; 143 } else if (request.isAllowPartialCacheMatch()) { 144 BufferedImage partialMatch = layer.cache.getPartialMatch(Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth); 145 if (partialMatch != null) { 146 request.finish(State.PARTLY_IN_CACHE, partialMatch); 147 return true; 148 } 149 } 150 151 if((!request.isReal() && !layer.hasAutoDownload())){ 152 request.finish(State.NOT_IN_CACHE, null); 153 return true; 154 } 155 156 return false; 157 } 158 159 protected BufferedImage grab(WMSRequest request, URL url, int attempt) throws IOException, OsmTransferException { 160 Main.info("Grabbing WMS " + (attempt > 1? "(attempt " + attempt + ") ":"") + url); 161 162 HttpURLConnection conn = Utils.openHttpConnection(url); 163 for(Entry<String, String> e : props.entrySet()) { 164 conn.setRequestProperty(e.getKey(), e.getValue()); 165 } 166 conn.setConnectTimeout(Main.pref.getInteger("socket.timeout.connect",15) * 1000); 167 conn.setReadTimeout(Main.pref.getInteger("socket.timeout.read", 30) * 1000); 168 169 String contentType = conn.getHeaderField("Content-Type"); 170 if( conn.getResponseCode() != 200 171 || contentType != null && !contentType.startsWith("image") ) 172 throw new IOException(readException(conn)); 173 174 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 175 InputStream is = new ProgressInputStream(conn, null); 176 try { 177 Utils.copyStream(is, baos); 178 } finally { 179 Utils.close(is); 180 } 181 182 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); 183 BufferedImage img = layer.normalizeImage(ImageIO.read(bais)); 184 bais.reset(); 185 layer.cache.saveToCache(layer.isOverlapEnabled()?img:null, bais, Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth); 186 return img; 187 } 188 189 protected String readException(URLConnection conn) throws IOException { 190 StringBuilder exception = new StringBuilder(); 191 InputStream in = conn.getInputStream(); 192 BufferedReader br = new BufferedReader(new InputStreamReader(in)); 193 try { 194 String line = null; 195 while( (line = br.readLine()) != null) { 196 // filter non-ASCII characters and control characters 197 exception.append(line.replaceAll("[^\\p{Print}]", "")); 198 exception.append('\n'); 199 } 200 return exception.toString(); 201 } finally { 202 Utils.close(br); 203 } 204 } 205}