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