1 | // License: GPL. For details, see LICENSE file.
|
---|
2 | package org.openstreetmap.josm.io.imagery;
|
---|
3 |
|
---|
4 | import java.awt.image.BufferedImage;
|
---|
5 | import java.io.BufferedReader;
|
---|
6 | import java.io.ByteArrayInputStream;
|
---|
7 | import java.io.ByteArrayOutputStream;
|
---|
8 | import java.io.IOException;
|
---|
9 | import java.io.InputStream;
|
---|
10 | import java.io.InputStreamReader;
|
---|
11 | import java.net.HttpURLConnection;
|
---|
12 | import java.net.MalformedURLException;
|
---|
13 | import java.net.URL;
|
---|
14 | import java.net.URLConnection;
|
---|
15 | import java.text.DecimalFormat;
|
---|
16 | import java.text.DecimalFormatSymbols;
|
---|
17 | import java.text.NumberFormat;
|
---|
18 | import java.util.HashMap;
|
---|
19 | import java.util.Locale;
|
---|
20 | import java.util.Map;
|
---|
21 | import java.util.Map.Entry;
|
---|
22 | import java.util.regex.Matcher;
|
---|
23 | import java.util.regex.Pattern;
|
---|
24 |
|
---|
25 | import javax.imageio.ImageIO;
|
---|
26 |
|
---|
27 | import org.openstreetmap.josm.Main;
|
---|
28 | import org.openstreetmap.josm.data.Version;
|
---|
29 | import org.openstreetmap.josm.data.coor.EastNorth;
|
---|
30 | import org.openstreetmap.josm.data.coor.LatLon;
|
---|
31 | import org.openstreetmap.josm.data.imagery.GeorefImage.State;
|
---|
32 | import org.openstreetmap.josm.data.imagery.ImageryInfo;
|
---|
33 | import org.openstreetmap.josm.gui.MapView;
|
---|
34 | import org.openstreetmap.josm.gui.layer.WMSLayer;
|
---|
35 | import org.openstreetmap.josm.io.OsmTransferException;
|
---|
36 | import org.openstreetmap.josm.io.ProgressInputStream;
|
---|
37 | import org.openstreetmap.josm.tools.Utils;
|
---|
38 |
|
---|
39 |
|
---|
40 | public class WMSGrabber extends Grabber {
|
---|
41 |
|
---|
42 | protected String baseURL;
|
---|
43 | private ImageryInfo info;
|
---|
44 | private Map<String, String> props = new HashMap<String, String>();
|
---|
45 |
|
---|
46 | public WMSGrabber(MapView mv, WMSLayer layer, boolean localOnly) {
|
---|
47 | super(mv, layer, localOnly);
|
---|
48 | this.info = layer.getInfo();
|
---|
49 | this.baseURL = info.getUrl();
|
---|
50 | if(layer.getInfo().getCookies() != null && !layer.getInfo().getCookies().equals("")) {
|
---|
51 | props.put("Cookie", layer.getInfo().getCookies());
|
---|
52 | }
|
---|
53 | props.put("User-Agent", Main.pref.get("imagery.wms.user_agent", Version.getInstance().getAgentString()));
|
---|
54 | Pattern pattern = Pattern.compile("\\{header\\(([^,]+),([^}]+)\\)\\}");
|
---|
55 | StringBuffer output = new StringBuffer();
|
---|
56 | Matcher matcher = pattern.matcher(this.baseURL);
|
---|
57 | while (matcher.find()) {
|
---|
58 | props.put(matcher.group(1),matcher.group(2));
|
---|
59 | matcher.appendReplacement(output, "");
|
---|
60 | }
|
---|
61 | matcher.appendTail(output);
|
---|
62 | this.baseURL = output.toString();
|
---|
63 | }
|
---|
64 |
|
---|
65 | @Override
|
---|
66 | void fetch(WMSRequest request, int attempt) throws Exception{
|
---|
67 | URL url = null;
|
---|
68 | try {
|
---|
69 | url = getURL(
|
---|
70 | b.minEast, b.minNorth,
|
---|
71 | b.maxEast, b.maxNorth,
|
---|
72 | width(), height());
|
---|
73 | request.finish(State.IMAGE, grab(request, url, attempt));
|
---|
74 |
|
---|
75 | } catch(Exception e) {
|
---|
76 | e.printStackTrace();
|
---|
77 | throw new Exception(e.getMessage() + "\nImage couldn't be fetched: " + (url != null ? url.toString() : ""));
|
---|
78 | }
|
---|
79 | }
|
---|
80 |
|
---|
81 | public static final NumberFormat latLonFormat = new DecimalFormat("###0.0000000",
|
---|
82 | new DecimalFormatSymbols(Locale.US));
|
---|
83 |
|
---|
84 | protected URL getURL(double w, double s,double e,double n,
|
---|
85 | int wi, int ht) throws MalformedURLException {
|
---|
86 | String myProj = Main.getProjection().toCode();
|
---|
87 | if (!info.getServerProjections().contains(myProj) && "EPSG:3857".equals(Main.getProjection().toCode())) {
|
---|
88 | LatLon sw = Main.getProjection().eastNorth2latlon(new EastNorth(w, s));
|
---|
89 | LatLon ne = Main.getProjection().eastNorth2latlon(new EastNorth(e, n));
|
---|
90 | myProj = "EPSG:4326";
|
---|
91 | s = sw.lat();
|
---|
92 | w = sw.lon();
|
---|
93 | n = ne.lat();
|
---|
94 | e = ne.lon();
|
---|
95 | }
|
---|
96 | if (myProj.equals("EPSG:4326") && !info.getServerProjections().contains(myProj) && info.getServerProjections().contains("CRS:84")) {
|
---|
97 | myProj = "CRS:84";
|
---|
98 | }
|
---|
99 |
|
---|
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 = Utils.openHttpConnection(url);
|
---|
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 | }
|
---|