source: josm/trunk/src/org/openstreetmap/josm/io/imagery/WMSGrabber.java@ 7425

Last change on this file since 7425 was 7425, checked in by Don-vip, 10 years ago

fix #3916 - WMS: Improve exception handling. Proper message is now displayed on tiles.

  • Property svn:eol-style set to native
File size: 12.6 KB
RevLine 
[3719]1// License: GPL. For details, see LICENSE file.
[3715]2package org.openstreetmap.josm.io.imagery;
3
4import java.awt.image.BufferedImage;
5import java.io.BufferedReader;
[4065]6import java.io.ByteArrayInputStream;
7import java.io.ByteArrayOutputStream;
[3715]8import java.io.IOException;
9import java.io.InputStream;
10import java.io.InputStreamReader;
[7425]11import java.io.StringReader;
[3715]12import java.net.HttpURLConnection;
13import java.net.MalformedURLException;
14import java.net.URL;
15import java.net.URLConnection;
[7082]16import java.nio.charset.StandardCharsets;
[3715]17import java.text.DecimalFormat;
18import java.text.DecimalFormatSymbols;
19import java.text.NumberFormat;
[7425]20import java.util.ArrayList;
[4745]21import java.util.HashMap;
[7425]22import java.util.List;
[3715]23import java.util.Locale;
[4228]24import java.util.Map;
[4745]25import java.util.Map.Entry;
[3715]26import java.util.regex.Matcher;
27import java.util.regex.Pattern;
28
[7425]29import javax.xml.parsers.DocumentBuilder;
30import javax.xml.parsers.DocumentBuilderFactory;
31import javax.xml.parsers.ParserConfigurationException;
32
[3715]33import org.openstreetmap.josm.Main;
[7425]34import org.openstreetmap.josm.data.ProjectionBounds;
[3715]35import org.openstreetmap.josm.data.coor.EastNorth;
36import org.openstreetmap.josm.data.coor.LatLon;
[3747]37import org.openstreetmap.josm.data.imagery.GeorefImage.State;
[3715]38import org.openstreetmap.josm.data.imagery.ImageryInfo;
39import org.openstreetmap.josm.gui.MapView;
40import org.openstreetmap.josm.gui.layer.WMSLayer;
41import org.openstreetmap.josm.io.OsmTransferException;
42import org.openstreetmap.josm.io.ProgressInputStream;
[7132]43import org.openstreetmap.josm.tools.ImageProvider;
[4065]44import org.openstreetmap.josm.tools.Utils;
[7425]45import org.w3c.dom.Document;
46import org.w3c.dom.NodeList;
47import org.xml.sax.InputSource;
48import org.xml.sax.SAXException;
[3715]49
[7425]50/**
51 * WMS grabber, fetching tiles from WMS server.
52 * @since 3715
53 */
54public class WMSGrabber implements Runnable {
[3715]55
[7425]56 protected final MapView mv;
57 protected final WMSLayer layer;
58 private final boolean localOnly;
59
60 protected ProjectionBounds b;
61 protected volatile boolean canceled;
62
[3715]63 protected String baseURL;
[4432]64 private ImageryInfo info;
[7005]65 private Map<String, String> props = new HashMap<>();
[3715]66
[7425]67 /**
68 * Constructs a new {@code WMSGrabber}.
69 * @param mv Map view
70 * @param layer WMS layer
71 */
[4745]72 public WMSGrabber(MapView mv, WMSLayer layer, boolean localOnly) {
[7425]73 this.mv = mv;
74 this.layer = layer;
75 this.localOnly = localOnly;
[4432]76 this.info = layer.getInfo();
77 this.baseURL = info.getUrl();
[7425]78 if (layer.getInfo().getCookies() != null && !layer.getInfo().getCookies().isEmpty()) {
[4228]79 props.put("Cookie", layer.getInfo().getCookies());
80 }
81 Pattern pattern = Pattern.compile("\\{header\\(([^,]+),([^}]+)\\)\\}");
82 StringBuffer output = new StringBuffer();
83 Matcher matcher = pattern.matcher(this.baseURL);
84 while (matcher.find()) {
85 props.put(matcher.group(1),matcher.group(2));
86 matcher.appendReplacement(output, "");
87 }
88 matcher.appendTail(output);
89 this.baseURL = output.toString();
[3715]90 }
91
[7425]92 int width() {
93 return layer.getBaseImageWidth();
94 }
95
96 int height() {
97 return layer.getBaseImageHeight();
98 }
99
[3715]100 @Override
[7425]101 public void run() {
102 while (true) {
103 if (canceled)
104 return;
105 WMSRequest request = layer.getRequest(localOnly);
106 if (request == null)
107 return;
108 this.b = layer.getBounds(request);
109 if (request.isPrecacheOnly()) {
110 if (!layer.cache.hasExactMatch(Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth)) {
111 attempt(request);
112 } else if (Main.isDebugEnabled()) {
113 Main.debug("Ignoring "+request+" (precache only + exact match)");
114 }
115 } else if (!loadFromCache(request)){
116 attempt(request);
117 } else if (Main.isDebugEnabled()) {
118 Main.debug("Ignoring "+request+" (loaded from cache)");
119 }
120 layer.finishRequest(request);
121 }
122 }
123
124 protected void attempt(WMSRequest request){ // try to fetch the image
125 int maxTries = 5; // n tries for every image
126 for (int i = 1; i <= maxTries; i++) {
127 if (canceled)
128 return;
129 try {
130 if (!request.isPrecacheOnly() && !layer.requestIsVisible(request))
131 return;
132 fetch(request, i);
133 break; // break out of the retry loop
134 } catch (IOException e) {
135 try { // sleep some time and then ask the server again
136 Thread.sleep(random(1000, 2000));
137 } catch (InterruptedException e1) {
138 Main.debug("InterruptedException in "+getClass().getSimpleName()+" during WMS request");
139 }
140 if (i == maxTries) {
141 Main.error(e);
142 request.finish(State.FAILED, null, null);
143 }
144 } catch (WMSException e) {
145 // Fail fast in case of WMS Service exception: useless to retry:
146 // either the URL is wrong or the server suffers huge problems
147 Main.error("WMS service exception while requesting "+e.getUrl()+":\n"+e.getMessage().trim());
148 request.finish(State.FAILED, null, e);
149 break; // break out of the retry loop
150 }
151 }
152 }
153
154 public static int random(int min, int max) {
155 return (int)(Math.random() * ((max+1)-min) ) + min;
156 }
157
158 public final void cancel() {
159 canceled = true;
160 }
161
162 private void fetch(WMSRequest request, int attempt) throws IOException, WMSException {
[3715]163 URL url = null;
164 try {
165 url = getURL(
[4065]166 b.minEast, b.minNorth,
167 b.maxEast, b.maxNorth,
[3715]168 width(), height());
[7425]169 request.finish(State.IMAGE, grab(request, url, attempt), null);
[3715]170
[7425]171 } catch (IOException | OsmTransferException e) {
[6643]172 Main.error(e);
[7425]173 throw new IOException(e.getMessage() + "\nImage couldn't be fetched: " + (url != null ? url.toString() : ""), e);
[3715]174 }
175 }
176
[7425]177 public static final NumberFormat latLonFormat = new DecimalFormat("###0.0000000", new DecimalFormatSymbols(Locale.US));
[3715]178
179 protected URL getURL(double w, double s,double e,double n,
180 int wi, int ht) throws MalformedURLException {
[4126]181 String myProj = Main.getProjection().toCode();
[5017]182 if (!info.getServerProjections().contains(myProj) && "EPSG:3857".equals(Main.getProjection().toCode())) {
[4126]183 LatLon sw = Main.getProjection().eastNorth2latlon(new EastNorth(w, s));
184 LatLon ne = Main.getProjection().eastNorth2latlon(new EastNorth(e, n));
[3715]185 myProj = "EPSG:4326";
186 s = sw.lat();
187 w = sw.lon();
188 n = ne.lat();
189 e = ne.lon();
190 }
[7012]191 if ("EPSG:4326".equals(myProj) && !info.getServerProjections().contains(myProj) && info.getServerProjections().contains("CRS:84")) {
[4857]192 myProj = "CRS:84";
193 }
[3715]194
[5017]195 // Bounding box coordinates have to be switched for WMS 1.3.0 EPSG:4326.
[4857]196 //
197 // Background:
198 //
199 // bbox=x_min,y_min,x_max,y_max
200 //
201 // SRS=... is WMS 1.1.1
202 // CRS=... is WMS 1.3.0
203 //
204 // The difference:
205 // For SRS x is east-west and y is north-south
206 // For CRS x and y are as specified by the EPSG
207 // E.g. [1] lists lat as first coordinate axis and lot as second, so it is switched for EPSG:4326.
208 // For most other EPSG code there seems to be no difference.
[6920]209 // [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
[4857]210 boolean switchLatLon = false;
211 if (baseURL.toLowerCase().contains("crs=epsg:4326")) {
212 switchLatLon = true;
[7012]213 } else if (baseURL.toLowerCase().contains("crs=") && "EPSG:4326".equals(myProj)) {
[4857]214 switchLatLon = true;
215 }
216 String bbox;
217 if (switchLatLon) {
218 bbox = String.format("%s,%s,%s,%s", latLonFormat.format(s), latLonFormat.format(w), latLonFormat.format(n), latLonFormat.format(e));
219 } else {
220 bbox = String.format("%s,%s,%s,%s", latLonFormat.format(w), latLonFormat.format(s), latLonFormat.format(e), latLonFormat.format(n));
221 }
[4432]222 return new URL(baseURL.replaceAll("\\{proj(\\([^})]+\\))?\\}", myProj)
[4857]223 .replaceAll("\\{bbox\\}", bbox)
224 .replaceAll("\\{w\\}", latLonFormat.format(w))
225 .replaceAll("\\{s\\}", latLonFormat.format(s))
226 .replaceAll("\\{e\\}", latLonFormat.format(e))
227 .replaceAll("\\{n\\}", latLonFormat.format(n))
228 .replaceAll("\\{width\\}", String.valueOf(wi))
229 .replaceAll("\\{height\\}", String.valueOf(ht))
230 .replace(" ", "%20"));
[3715]231 }
232
233 public boolean loadFromCache(WMSRequest request) {
[7132]234 BufferedImage cached = layer.cache.getExactMatch(
235 Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth);
[4065]236
237 if (cached != null) {
[7425]238 request.finish(State.IMAGE, cached, null);
[4065]239 return true;
240 } else if (request.isAllowPartialCacheMatch()) {
[7132]241 BufferedImage partialMatch = layer.cache.getPartialMatch(
242 Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth);
[4065]243 if (partialMatch != null) {
[7425]244 request.finish(State.PARTLY_IN_CACHE, partialMatch, null);
[3715]245 return true;
246 }
[4065]247 }
248
[7425]249 if ((!request.isReal() && !layer.hasAutoDownload())){
250 request.finish(State.NOT_IN_CACHE, null, null);
[3715]251 return true;
252 }
[4065]253
[3715]254 return false;
255 }
256
[7425]257 protected BufferedImage grab(WMSRequest request, URL url, int attempt) throws WMSException, IOException, OsmTransferException {
[6248]258 Main.info("Grabbing WMS " + (attempt > 1? "(attempt " + attempt + ") ":"") + url);
[3715]259
[5587]260 HttpURLConnection conn = Utils.openHttpConnection(url);
[7425]261 for (Entry<String, String> e : props.entrySet()) {
[4228]262 conn.setRequestProperty(e.getKey(), e.getValue());
[3715]263 }
[4172]264 conn.setConnectTimeout(Main.pref.getInteger("socket.timeout.connect",15) * 1000);
265 conn.setReadTimeout(Main.pref.getInteger("socket.timeout.read", 30) * 1000);
[3715]266
267 String contentType = conn.getHeaderField("Content-Type");
[7425]268 if (conn.getResponseCode() != 200
269 || contentType != null && !contentType.startsWith("image") ) {
270 String xml = readException(conn);
271 try {
272 DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
273 InputSource is = new InputSource(new StringReader(xml));
274 Document doc = db.parse(is);
275 NodeList nodes = doc.getElementsByTagName("ServiceException");
276 List<String> exceptions = new ArrayList<>(nodes.getLength());
277 for (int i = 0; i < nodes.getLength(); i++) {
278 exceptions.add(nodes.item(i).getTextContent());
279 }
280 throw new WMSException(request, url, exceptions);
281 } catch (SAXException | ParserConfigurationException ex) {
282 throw new IOException(xml, ex);
283 }
284 }
[3715]285
[4065]286 ByteArrayOutputStream baos = new ByteArrayOutputStream();
[7033]287 try (InputStream is = new ProgressInputStream(conn, null)) {
[4065]288 Utils.copyStream(is, baos);
289 }
[3715]290
[4065]291 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
[7132]292 BufferedImage img = layer.normalizeImage(ImageProvider.read(bais, true, WMSLayer.PROP_ALPHA_CHANNEL.get()));
[4065]293 bais.reset();
[4745]294 layer.cache.saveToCache(layer.isOverlapEnabled()?img:null, bais, Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth);
[3715]295 return img;
296 }
297
298 protected String readException(URLConnection conn) throws IOException {
299 StringBuilder exception = new StringBuilder();
300 InputStream in = conn.getInputStream();
[7082]301 try (BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
[3747]302 String line = null;
303 while( (line = br.readLine()) != null) {
304 // filter non-ASCII characters and control characters
305 exception.append(line.replaceAll("[^\\p{Print}]", ""));
306 exception.append('\n');
307 }
308 return exception.toString();
[3715]309 }
310 }
311}
Note: See TracBrowser for help on using the repository browser.