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 }