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

Last change on this file since 8018 was 7005, checked in by Don-vip, 10 years ago

see #8465 - use diamond operator where applicable

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