001    // License: GPL. Copyright 2007 by Immanuel Scholz and others
002    package org.openstreetmap.josm.tools;
003    
004    import java.awt.Toolkit;
005    import java.io.UnsupportedEncodingException;
006    import java.net.URLDecoder;
007    import java.util.HashMap;
008    import java.util.Map;
009    
010    import org.openstreetmap.josm.Main;
011    import org.openstreetmap.josm.data.Bounds;
012    import org.openstreetmap.josm.data.coor.LatLon;
013    
014    public class OsmUrlToBounds {
015        private static final String SHORTLINK_PREFIX = "http://osm.org/go/";
016    
017        public static Bounds parse(String url) {
018            try {
019                // a percent sign indicates an encoded URL (RFC 1738).
020                if (url.contains("%")) {
021                    url = URLDecoder.decode(url, "UTF-8");
022                }
023            } catch (UnsupportedEncodingException x) {
024            } catch (IllegalArgumentException x) {
025            }
026            Bounds b = parseShortLink(url);
027            if (b != null)
028                return b;
029            int i = url.indexOf('?');
030            if (i == -1)
031                return null;
032            String[] args = url.substring(i+1).split("&");
033            HashMap<String, String> map = new HashMap<String, String>();
034            for (String arg : args) {
035                int eq = arg.indexOf('=');
036                if (eq != -1) {
037                    map.put(arg.substring(0, eq), arg.substring(eq + 1));
038                }
039            }
040    
041            try {
042                if (map.containsKey("bbox")) {
043                    String bbox[] = map.get("bbox").split(",");
044                    b = new Bounds(
045                            new LatLon(Double.parseDouble(bbox[1]), Double.parseDouble(bbox[0])),
046                            new LatLon(Double.parseDouble(bbox[3]), Double.parseDouble(bbox[2])));
047                } else if (map.containsKey("minlat")) {
048                    String s = map.get("minlat");
049                    Double minlat = Double.parseDouble(s);
050                    s = map.get("minlon");
051                    Double minlon = Double.parseDouble(s);
052                    s = map.get("maxlat");
053                    Double maxlat = Double.parseDouble(s);
054                    s = map.get("maxlon");
055                    Double maxlon = Double.parseDouble(s);
056                    b = new Bounds(new LatLon(minlat, minlon), new LatLon(maxlat, maxlon));
057                } else {
058                    String z = map.get("zoom");
059                    b = positionToBounds(parseDouble(map, "lat"),
060                            parseDouble(map, "lon"),
061                            z == null ? 18 : Integer.parseInt(z));
062                }
063            } catch (NumberFormatException x) {
064                x.printStackTrace();
065            } catch (NullPointerException x) {
066                x.printStackTrace();
067            } catch (ArrayIndexOutOfBoundsException x) {
068                x.printStackTrace();
069            }
070            return b;
071        }
072    
073        private static double parseDouble(HashMap<String, String> map, String key) {
074            if (map.containsKey(key))
075                return Double.parseDouble(map.get(key));
076            return Double.parseDouble(map.get("m"+key));
077        }
078    
079        private static final char[] SHORTLINK_CHARS = {
080            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
081            'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
082            'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
083            'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
084            'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
085            'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
086            'w', 'x', 'y', 'z', '0', '1', '2', '3',
087            '4', '5', '6', '7', '8', '9', '_', '@'
088        };
089    
090        /**
091         * p
092         *
093         * @param url string for parsing
094         *
095         * @return Bounds if shortlink, null otherwise
096         *
097         * @see http://trac.openstreetmap.org/browser/sites/rails_port/lib/short_link.rb
098         */
099        private static Bounds parseShortLink(final String url) {
100            if (!url.startsWith(SHORTLINK_PREFIX))
101                return null;
102            final String shortLink = url.substring(SHORTLINK_PREFIX.length());
103    
104            final Map<Character, Integer> array = new HashMap<Character, Integer>();
105    
106            for (int i=0; i<SHORTLINK_CHARS.length; ++i) {
107                array.put(SHORTLINK_CHARS[i], i);
108            }
109    
110            // long is necessary (need 32 bit positive value is needed)
111            long x = 0;
112            long y = 0;
113            int zoom = 0;
114            int zoomOffset = 0;
115    
116            for (final char ch : shortLink.toCharArray()) {
117                if (array.containsKey(ch)) {
118                    int val = array.get(ch);
119                    for (int i=0; i<3; ++i) {
120                        x <<= 1;
121                        if ((val & 32) != 0) {
122                            x |= 1;
123                        }
124                        val <<= 1;
125    
126                        y <<= 1;
127                        if ((val & 32) != 0) {
128                            y |= 1;
129                        }
130                        val <<= 1;
131                    }
132                    zoom += 3;
133                } else {
134                    zoomOffset--;
135                }
136            }
137    
138            x <<= 32 - zoom;
139            y <<= 32 - zoom;
140    
141            // 2**32 == 4294967296
142            return positionToBounds(y * 180.0 / 4294967296.0 - 90.0,
143                    x * 360.0 / 4294967296.0 - 180.0,
144                    // TODO: -2 was not in ruby code
145                    zoom - 8 - (zoomOffset % 3) - 2);
146        }
147    
148        public static final double R = 6378137.0;
149    
150        public static Bounds positionToBounds(final double lat, final double lon, final int zoom) {
151            int tileSizeInPixels = 256;
152            int height = Toolkit.getDefaultToolkit().getScreenSize().height;
153            int width = Toolkit.getDefaultToolkit().getScreenSize().width;
154            if (Main.isDisplayingMapView()) {
155                height = Main.map.mapView.getHeight();
156                width = Main.map.mapView.getWidth();
157            }
158            double scale = (1 << zoom) * tileSizeInPixels / (2 * Math.PI * R);
159            double deltaX = width / 2.0 / scale;
160            double deltaY = height / 2.0 / scale;
161            double x = Math.toRadians(lon) * R;
162            double y = mercatorY(lat);
163            return new Bounds(invMercatorY(y - deltaY), Math.toDegrees(x - deltaX) / R, invMercatorY(y + deltaY), Math.toDegrees(x + deltaX) / R);
164        }
165    
166        public static double mercatorY(double lat) {
167            return Math.log(Math.tan(Math.PI/4 + Math.toRadians(lat)/2)) * R;
168        }
169    
170        public static double invMercatorY(double north) {
171            return Math.toDegrees(Math.atan(Math.sinh(north / R)));
172        }
173    
174        public static Pair<Double, Double> getTileOfLatLon(double lat, double lon, double zoom) {
175            double x = Math.floor((lon + 180) / 360 * Math.pow(2.0, zoom));
176            double y = Math.floor((1 - Math.log(Math.tan(Math.toRadians(lat)) + 1 / Math.cos(Math.toRadians(lat))) / Math.PI)
177                    / 2 * Math.pow(2.0, zoom));
178            return new Pair<Double, Double>(x, y);
179        }
180    
181        public static LatLon getLatLonOfTile(double x, double y, double zoom) {
182            double lon = x / Math.pow(2.0, zoom) * 360.0 - 180;
183            double lat = Math.toDegrees(Math.atan(Math.sinh(Math.PI - (2.0 * Math.PI * y) / Math.pow(2.0, zoom))));
184            return new LatLon(lat, lon);
185        }
186    
187        static public int getZoom(Bounds b) {
188            // convert to mercator (for calculation of zoom only)
189            double latMin = Math.log(Math.tan(Math.PI/4.0+b.getMin().lat()/180.0*Math.PI/2.0))*180.0/Math.PI;
190            double latMax = Math.log(Math.tan(Math.PI/4.0+b.getMax().lat()/180.0*Math.PI/2.0))*180.0/Math.PI;
191            double size = Math.max(Math.abs(latMax-latMin), Math.abs(b.getMax().lon()-b.getMin().lon()));
192            int zoom = 0;
193            while (zoom <= 20) {
194                if (size >= 180) {
195                    break;
196                }
197                size *= 2;
198                zoom++;
199            }
200            return zoom;
201        }
202    
203        static public String getURL(Bounds b) {
204            return getURL(b.getCenter(), getZoom(b));
205        }
206    
207        static public String getURL(LatLon pos, int zoom) {
208            // Truncate lat and lon to something more sensible
209            int decimals = (int) Math.pow(10, (zoom / 3));
210            double lat = (Math.round(pos.lat() * decimals));
211            lat /= decimals;
212            double lon = (Math.round(pos.lon() * decimals));
213            lon /= decimals;
214            return "http://www.openstreetmap.org/?lat="+lat+"&lon="+lon+"&zoom="+zoom;
215        }
216    }