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    }