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 }