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

Last change on this file since 9190 was 9062, checked in by Don-vip, 8 years ago

Sonar - squid:S1941 - Variables should not be declared before they are relevant

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