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

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

fix javadoc errors/warnings

  • 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.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;
14import org.openstreetmap.josm.data.projection.Ellipsoid;
15
16public final class OsmUrlToBounds {
17 private static final String SHORTLINK_PREFIX = "http://osm.org/go/";
18
19 private OsmUrlToBounds() {
20 // Hide default constructor for utils classes
21 }
22
23 public static Bounds parse(String url) {
24 try {
25 // a percent sign indicates an encoded URL (RFC 1738).
26 if (url.contains("%")) {
27 url = Utils.decodeUrl(url);
28 }
29 } catch (IllegalArgumentException x) {
30 Main.error(x);
31 }
32 Bounds b = parseShortLink(url);
33 if (b != null)
34 return b;
35 int i = url.indexOf("#map");
36 if (i >= 0) {
37 // probably it's a URL following the new scheme?
38 return parseHashURLs(url);
39 }
40 i = url.indexOf('?');
41 if (i == -1) {
42 return null;
43 }
44 String[] args = url.substring(i+1).split("&");
45 Map<String, String> map = new HashMap<>();
46 for (String arg : args) {
47 int eq = arg.indexOf('=');
48 if (eq != -1) {
49 map.put(arg.substring(0, eq), arg.substring(eq + 1));
50 }
51 }
52
53 try {
54 if (map.containsKey("bbox")) {
55 String[] bbox = map.get("bbox").split(",");
56 b = new Bounds(
57 Double.parseDouble(bbox[1]), Double.parseDouble(bbox[0]),
58 Double.parseDouble(bbox[3]), Double.parseDouble(bbox[2]));
59 } else if (map.containsKey("minlat")) {
60 double minlat = Double.parseDouble(map.get("minlat"));
61 double minlon = Double.parseDouble(map.get("minlon"));
62 double maxlat = Double.parseDouble(map.get("maxlat"));
63 double maxlon = Double.parseDouble(map.get("maxlon"));
64 b = new Bounds(minlat, minlon, maxlat, maxlon);
65 } else {
66 String z = map.get("zoom");
67 b = positionToBounds(parseDouble(map, "lat"),
68 parseDouble(map, "lon"),
69 z == null ? 18 : Integer.parseInt(z));
70 }
71 } catch (NumberFormatException | NullPointerException | ArrayIndexOutOfBoundsException x) {
72 Main.error(x);
73 }
74 return b;
75 }
76
77 /**
78 * Openstreetmap.org changed it's URL scheme in August 2013, which breaks the URL parsing.
79 * The following function, called by the old parse function if necessary, provides parsing new URLs
80 * the new URLs follow the scheme https://www.openstreetmap.org/#map=18/51.71873/8.76164&amp;layers=CN
81 * @param url string for parsing
82 * @return Bounds if hashurl, {@code null} otherwise
83 */
84 private static Bounds parseHashURLs(String url) {
85 int startIndex = url.indexOf("#map=");
86 if (startIndex == -1) return null;
87 int endIndex = url.indexOf('&', startIndex);
88 if (endIndex == -1) endIndex = url.length();
89 String coordPart = url.substring(startIndex+5, endIndex);
90 String[] parts = coordPart.split("/");
91 if (parts.length < 3) {
92 Main.warn(tr("URL does not contain {0}/{1}/{2}", tr("zoom"), tr("latitude"), tr("longitude")));
93 return null;
94 }
95 int zoom;
96 try {
97 zoom = Integer.parseInt(parts[0]);
98 } catch (NumberFormatException e) {
99 Main.warn(tr("URL does not contain valid {0}", tr("zoom")), e);
100 return null;
101 }
102 double lat, lon;
103 try {
104 lat = Double.parseDouble(parts[1]);
105 } catch (NumberFormatException e) {
106 Main.warn(tr("URL does not contain valid {0}", tr("latitude")), e);
107 return null;
108 }
109 try {
110 lon = Double.parseDouble(parts[2]);
111 } catch (NumberFormatException e) {
112 Main.warn(tr("URL does not contain valid {0}", tr("longitude")), e);
113 return null;
114 }
115 return positionToBounds(lat, lon, zoom);
116 }
117
118 private static double parseDouble(Map<String, String> map, String key) {
119 if (map.containsKey(key))
120 return Double.parseDouble(map.get(key));
121 return Double.parseDouble(map.get('m'+key));
122 }
123
124 private static final char[] SHORTLINK_CHARS = {
125 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
126 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
127 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
128 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
129 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
130 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
131 'w', 'x', 'y', 'z', '0', '1', '2', '3',
132 '4', '5', '6', '7', '8', '9', '_', '@'
133 };
134
135 /**
136 * Parse OSM short link
137 *
138 * @param url string for parsing
139 * @return Bounds if shortlink, null otherwise
140 * @see <a href="http://trac.openstreetmap.org/browser/sites/rails_port/lib/short_link.rb">short_link.rb</a>
141 */
142 private static Bounds parseShortLink(final String url) {
143 if (!url.startsWith(SHORTLINK_PREFIX))
144 return null;
145 final String shortLink = url.substring(SHORTLINK_PREFIX.length());
146
147 final Map<Character, Integer> array = new HashMap<>();
148
149 for (int i = 0; i < SHORTLINK_CHARS.length; ++i) {
150 array.put(SHORTLINK_CHARS[i], i);
151 }
152
153 // long is necessary (need 32 bit positive value is needed)
154 long x = 0;
155 long y = 0;
156 int zoom = 0;
157 int zoomOffset = 0;
158
159 for (final char ch : shortLink.toCharArray()) {
160 if (array.containsKey(ch)) {
161 int val = array.get(ch);
162 for (int i = 0; i < 3; ++i) {
163 x <<= 1;
164 if ((val & 32) != 0) {
165 x |= 1;
166 }
167 val <<= 1;
168
169 y <<= 1;
170 if ((val & 32) != 0) {
171 y |= 1;
172 }
173 val <<= 1;
174 }
175 zoom += 3;
176 } else {
177 zoomOffset--;
178 }
179 }
180
181 x <<= 32 - zoom;
182 y <<= 32 - zoom;
183
184 // 2**32 == 4294967296
185 return positionToBounds(y * 180.0 / 4294967296.0 - 90.0,
186 x * 360.0 / 4294967296.0 - 180.0,
187 // TODO: -2 was not in ruby code
188 zoom - 8 - (zoomOffset % 3) - 2);
189 }
190
191 public static Bounds positionToBounds(final double lat, final double lon, final int zoom) {
192 int tileSizeInPixels = 256;
193 int height;
194 int width;
195 try {
196 height = Toolkit.getDefaultToolkit().getScreenSize().height;
197 width = Toolkit.getDefaultToolkit().getScreenSize().width;
198 if (Main.isDisplayingMapView()) {
199 height = Main.map.mapView.getHeight();
200 width = Main.map.mapView.getWidth();
201 }
202 } catch (HeadlessException he) {
203 // in headless mode, when running tests
204 height = 480;
205 width = 640;
206 }
207 double scale = (1 << zoom) * tileSizeInPixels / (2 * Math.PI * Ellipsoid.WGS84.a);
208 double deltaX = width / 2.0 / scale;
209 double deltaY = height / 2.0 / scale;
210 double x = Math.toRadians(lon) * Ellipsoid.WGS84.a;
211 double y = mercatorY(lat);
212 return new Bounds(
213 invMercatorY(y - deltaY), Math.toDegrees(x - deltaX) / Ellipsoid.WGS84.a,
214 invMercatorY(y + deltaY), Math.toDegrees(x + deltaX) / Ellipsoid.WGS84.a);
215 }
216
217 public static double mercatorY(double lat) {
218 return Math.log(Math.tan(Math.PI/4 + Math.toRadians(lat)/2)) * Ellipsoid.WGS84.a;
219 }
220
221 public static double invMercatorY(double north) {
222 return Math.toDegrees(Math.atan(Math.sinh(north / Ellipsoid.WGS84.a)));
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.