source: josm/trunk/src/org/openstreetmap/josm/tools/OsmUrlToBounds.java@ 6610

Last change on this file since 6610 was 6514, checked in by simon04, 10 years ago

fix #9467 - IllegalArgumentException: URL does not contain valid zoom

  • Property svn:eol-style set to native
File size: 10.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.HeadlessException;
7import java.awt.Toolkit;
8import java.io.UnsupportedEncodingException;
9import java.net.URLDecoder;
10import java.util.HashMap;
11import java.util.Map;
12
13import org.openstreetmap.josm.Main;
14import org.openstreetmap.josm.data.Bounds;
15import org.openstreetmap.josm.data.coor.LatLon;
16
17public final class OsmUrlToBounds {
18 private static final String SHORTLINK_PREFIX = "http://osm.org/go/";
19
20 private OsmUrlToBounds() {
21 // Hide default constructor for utils classes
22 }
23
24 public static Bounds parse(String url) throws IllegalArgumentException {
25 try {
26 // a percent sign indicates an encoded URL (RFC 1738).
27 if (url.contains("%")) {
28 url = URLDecoder.decode(url, "UTF-8");
29 }
30 } catch (UnsupportedEncodingException x) {
31 Main.error(x);
32 } catch (IllegalArgumentException x) {
33 Main.error(x);
34 }
35 Bounds b = parseShortLink(url);
36 if (b != null)
37 return b;
38 int i = url.indexOf("#map");
39 if (i >= 0) {
40 // probably it's a URL following the new scheme?
41 return parseHashURLs(url);
42 }
43 i = url.indexOf('?');
44 if (i == -1) {
45 return null;
46 }
47 String[] args = url.substring(i+1).split("&");
48 HashMap<String, String> map = new HashMap<String, String>();
49 for (String arg : args) {
50 int eq = arg.indexOf('=');
51 if (eq != -1) {
52 map.put(arg.substring(0, eq), arg.substring(eq + 1));
53 }
54 }
55
56 try {
57 if (map.containsKey("bbox")) {
58 String[] bbox = map.get("bbox").split(",");
59 b = new Bounds(
60 Double.parseDouble(bbox[1]), Double.parseDouble(bbox[0]),
61 Double.parseDouble(bbox[3]), Double.parseDouble(bbox[2]));
62 } else if (map.containsKey("minlat")) {
63 double minlat = Double.parseDouble(map.get("minlat"));
64 double minlon = Double.parseDouble(map.get("minlon"));
65 double maxlat = Double.parseDouble(map.get("maxlat"));
66 double maxlon = Double.parseDouble(map.get("maxlon"));
67 b = new Bounds(minlat, minlon, maxlat, maxlon);
68 } else {
69 String z = map.get("zoom");
70 b = positionToBounds(parseDouble(map, "lat"),
71 parseDouble(map, "lon"),
72 z == null ? 18 : Integer.parseInt(z));
73 }
74 } catch (NumberFormatException x) {
75 x.printStackTrace();
76 } catch (NullPointerException x) {
77 x.printStackTrace();
78 } catch (ArrayIndexOutOfBoundsException x) {
79 x.printStackTrace();
80 }
81 return b;
82 }
83
84 /**
85 * Openstreetmap.org changed it's URL scheme in August 2013, which breaks the URL parsing.
86 * The following function, called by the old parse function if necessary, provides parsing new URLs
87 * the new URLs follow the scheme http://www.openstreetmap.org/#map=18/51.71873/8.76164&layers=CN
88 * @param url string for parsing
89 * @return Bounds if hashurl, {@code null} otherwise
90 */
91 private static Bounds parseHashURLs(String url) throws IllegalArgumentException {
92 int startIndex = url.indexOf("#map=");
93 if (startIndex == -1) return null;
94 int endIndex = url.indexOf('&', startIndex);
95 if (endIndex == -1) endIndex = url.length();
96 String coordPart = url.substring(startIndex+5, endIndex);
97 String[] parts = coordPart.split("/");
98 if (parts.length < 3) {
99 Main.warn(tr("URL does not contain {0}/{1}/{2}", tr("zoom"), tr("latitude"), tr("longitude")));
100 return null;
101 }
102 int zoom;
103 double lat, lon;
104 try {
105 zoom = Integer.parseInt(parts[0]);
106 } catch (NumberFormatException e) {
107 Main.warn(tr("URL does not contain valid {0}", tr("zoom")), e);
108 return null;
109 }
110 try {
111 lat = Double.parseDouble(parts[1]);
112 } catch (NumberFormatException e) {
113 Main.warn(tr("URL does not contain valid {0}", tr("latitude")), e);
114 return null;
115 }
116 try {
117 lon = Double.parseDouble(parts[2]);
118 } catch (NumberFormatException e) {
119 Main.warn(tr("URL does not contain valid {0}", tr("longitude")), e);
120 return null;
121 }
122 return positionToBounds(lat, lon, zoom);
123 }
124
125 private static double parseDouble(Map<String, String> map, String key) {
126 if (map.containsKey(key))
127 return Double.parseDouble(map.get(key));
128 return Double.parseDouble(map.get("m"+key));
129 }
130
131 private static final char[] SHORTLINK_CHARS = {
132 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
133 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
134 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
135 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
136 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
137 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
138 'w', 'x', 'y', 'z', '0', '1', '2', '3',
139 '4', '5', '6', '7', '8', '9', '_', '@'
140 };
141
142 /**
143 * Parse OSM short link
144 *
145 * @param url string for parsing
146 * @return Bounds if shortlink, null otherwise
147 * @see <a href="http://trac.openstreetmap.org/browser/sites/rails_port/lib/short_link.rb">short_link.rb</a>
148 */
149 private static Bounds parseShortLink(final String url) {
150 if (!url.startsWith(SHORTLINK_PREFIX))
151 return null;
152 final String shortLink = url.substring(SHORTLINK_PREFIX.length());
153
154 final Map<Character, Integer> array = new HashMap<Character, Integer>();
155
156 for (int i=0; i<SHORTLINK_CHARS.length; ++i) {
157 array.put(SHORTLINK_CHARS[i], i);
158 }
159
160 // long is necessary (need 32 bit positive value is needed)
161 long x = 0;
162 long y = 0;
163 int zoom = 0;
164 int zoomOffset = 0;
165
166 for (final char ch : shortLink.toCharArray()) {
167 if (array.containsKey(ch)) {
168 int val = array.get(ch);
169 for (int i=0; i<3; ++i) {
170 x <<= 1;
171 if ((val & 32) != 0) {
172 x |= 1;
173 }
174 val <<= 1;
175
176 y <<= 1;
177 if ((val & 32) != 0) {
178 y |= 1;
179 }
180 val <<= 1;
181 }
182 zoom += 3;
183 } else {
184 zoomOffset--;
185 }
186 }
187
188 x <<= 32 - zoom;
189 y <<= 32 - zoom;
190
191 // 2**32 == 4294967296
192 return positionToBounds(y * 180.0 / 4294967296.0 - 90.0,
193 x * 360.0 / 4294967296.0 - 180.0,
194 // TODO: -2 was not in ruby code
195 zoom - 8 - (zoomOffset % 3) - 2);
196 }
197
198 /** radius of the earth */
199 public static final double R = 6378137.0;
200
201 public static Bounds positionToBounds(final double lat, final double lon, final int zoom) {
202 int tileSizeInPixels = 256;
203 int height;
204 int width;
205 try {
206 height = Toolkit.getDefaultToolkit().getScreenSize().height;
207 width = Toolkit.getDefaultToolkit().getScreenSize().width;
208 if (Main.isDisplayingMapView()) {
209 height = Main.map.mapView.getHeight();
210 width = Main.map.mapView.getWidth();
211 }
212 } catch (HeadlessException he) {
213 // in headless mode, when running tests
214 height = 480;
215 width = 640;
216 }
217 double scale = (1 << zoom) * tileSizeInPixels / (2 * Math.PI * R);
218 double deltaX = width / 2.0 / scale;
219 double deltaY = height / 2.0 / scale;
220 double x = Math.toRadians(lon) * R;
221 double y = mercatorY(lat);
222 return new Bounds(invMercatorY(y - deltaY), Math.toDegrees(x - deltaX) / R, invMercatorY(y + deltaY), Math.toDegrees(x + deltaX) / R);
223 }
224
225 public static double mercatorY(double lat) {
226 return Math.log(Math.tan(Math.PI/4 + Math.toRadians(lat)/2)) * R;
227 }
228
229 public static double invMercatorY(double north) {
230 return Math.toDegrees(Math.atan(Math.sinh(north / R)));
231 }
232
233 public static Pair<Double, Double> getTileOfLatLon(double lat, double lon, double zoom) {
234 double x = Math.floor((lon + 180) / 360 * Math.pow(2.0, zoom));
235 double y = Math.floor((1 - Math.log(Math.tan(Math.toRadians(lat)) + 1 / Math.cos(Math.toRadians(lat))) / Math.PI)
236 / 2 * Math.pow(2.0, zoom));
237 return new Pair<Double, Double>(x, y);
238 }
239
240 public static LatLon getLatLonOfTile(double x, double y, double zoom) {
241 double lon = x / Math.pow(2.0, zoom) * 360.0 - 180;
242 double lat = Math.toDegrees(Math.atan(Math.sinh(Math.PI - (2.0 * Math.PI * y) / Math.pow(2.0, zoom))));
243 return new LatLon(lat, lon);
244 }
245
246 /**
247 * Return OSM Zoom level for a given area
248 *
249 * @param b bounds of the area
250 * @return matching zoom level for area
251 */
252 static public int getZoom(Bounds b) {
253 // convert to mercator (for calculation of zoom only)
254 double latMin = Math.log(Math.tan(Math.PI/4.0+b.getMinLat()/180.0*Math.PI/2.0))*180.0/Math.PI;
255 double latMax = Math.log(Math.tan(Math.PI/4.0+b.getMaxLat()/180.0*Math.PI/2.0))*180.0/Math.PI;
256 double size = Math.max(Math.abs(latMax-latMin), Math.abs(b.getMaxLon()-b.getMinLon()));
257 int zoom = 0;
258 while (zoom <= 20) {
259 if (size >= 180) {
260 break;
261 }
262 size *= 2;
263 zoom++;
264 }
265 return zoom;
266 }
267
268 /**
269 * Return OSM URL for given area.
270 *
271 * @param b bounds of the area
272 * @return link to display that area in OSM map
273 */
274 public static String getURL(Bounds b) {
275 return getURL(b.getCenter(), getZoom(b));
276 }
277
278 /**
279 * Return OSM URL for given position and zoom.
280 *
281 * @param pos center position of area
282 * @param zoom zoom depth of display
283 * @return link to display that area in OSM map
284 */
285 public static String getURL(LatLon pos, int zoom) {
286 return getURL(pos.lat(), pos.lon(), zoom);
287 }
288
289 /**
290 * Return OSM URL for given lat/lon and zoom.
291 *
292 * @param dlat center latitude of area
293 * @param dlon center longitude of area
294 * @param zoom zoom depth of display
295 * @return link to display that area in OSM map
296 *
297 * @since 6453
298 */
299 public static String getURL(double dlat, double dlon, int zoom) {
300 // Truncate lat and lon to something more sensible
301 int decimals = (int) Math.pow(10, (zoom / 3));
302 double lat = (Math.round(dlat * decimals));
303 lat /= decimals;
304 double lon = (Math.round(dlon * decimals));
305 lon /= decimals;
306 return Main.OSM_WEBSITE + "/#map="+zoom+"/"+lat+"/"+lon;
307 }
308}
Note: See TracBrowser for help on using the repository browser.