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

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

code refactoring for unit tests / headless mode

  • Property svn:eol-style set to native
File size: 10.5 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.Dimension;
7import java.util.HashMap;
8import java.util.Map;
9
10import org.openstreetmap.josm.Main;
11import org.openstreetmap.josm.data.Bounds;
12import org.openstreetmap.josm.data.coor.LatLon;
13import org.openstreetmap.josm.data.projection.Ellipsoid;
14import org.openstreetmap.josm.gui.util.GuiHelper;
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 Dimension screenSize = GuiHelper.getScreenSize();
194 int height = screenSize.height;
195 int width = screenSize.width;
196 if (Main.isDisplayingMapView()) {
197 height = Main.map.mapView.getHeight();
198 width = Main.map.mapView.getWidth();
199 }
200 double scale = (1 << zoom) * tileSizeInPixels / (2 * Math.PI * Ellipsoid.WGS84.a);
201 double deltaX = width / 2.0 / scale;
202 double deltaY = height / 2.0 / scale;
203 double x = Math.toRadians(lon) * Ellipsoid.WGS84.a;
204 double y = mercatorY(lat);
205 return new Bounds(
206 invMercatorY(y - deltaY), Math.toDegrees(x - deltaX) / Ellipsoid.WGS84.a,
207 invMercatorY(y + deltaY), Math.toDegrees(x + deltaX) / Ellipsoid.WGS84.a);
208 }
209
210 public static double mercatorY(double lat) {
211 return Math.log(Math.tan(Math.PI/4 + Math.toRadians(lat)/2)) * Ellipsoid.WGS84.a;
212 }
213
214 public static double invMercatorY(double north) {
215 return Math.toDegrees(Math.atan(Math.sinh(north / Ellipsoid.WGS84.a)));
216 }
217
218 public static Pair<Double, Double> getTileOfLatLon(double lat, double lon, double zoom) {
219 double x = Math.floor((lon + 180) / 360 * Math.pow(2.0, zoom));
220 double y = Math.floor((1 - Math.log(Math.tan(Math.toRadians(lat)) + 1 / Math.cos(Math.toRadians(lat))) / Math.PI)
221 / 2 * Math.pow(2.0, zoom));
222 return new Pair<>(x, y);
223 }
224
225 public static LatLon getLatLonOfTile(double x, double y, double zoom) {
226 double lon = x / Math.pow(2.0, zoom) * 360.0 - 180;
227 double lat = Math.toDegrees(Math.atan(Math.sinh(Math.PI - (2.0 * Math.PI * y) / Math.pow(2.0, zoom))));
228 return new LatLon(lat, lon);
229 }
230
231 /**
232 * Return OSM Zoom level for a given area
233 *
234 * @param b bounds of the area
235 * @return matching zoom level for area
236 */
237 public static int getZoom(Bounds b) {
238 // convert to mercator (for calculation of zoom only)
239 double latMin = Math.log(Math.tan(Math.PI/4.0+b.getMinLat()/180.0*Math.PI/2.0))*180.0/Math.PI;
240 double latMax = Math.log(Math.tan(Math.PI/4.0+b.getMaxLat()/180.0*Math.PI/2.0))*180.0/Math.PI;
241 double size = Math.max(Math.abs(latMax-latMin), Math.abs(b.getMaxLon()-b.getMinLon()));
242 int zoom = 0;
243 while (zoom <= 20) {
244 if (size >= 180) {
245 break;
246 }
247 size *= 2;
248 zoom++;
249 }
250 return zoom;
251 }
252
253 /**
254 * Return OSM URL for given area.
255 *
256 * @param b bounds of the area
257 * @return link to display that area in OSM map
258 */
259 public static String getURL(Bounds b) {
260 return getURL(b.getCenter(), getZoom(b));
261 }
262
263 /**
264 * Return OSM URL for given position and zoom.
265 *
266 * @param pos center position of area
267 * @param zoom zoom depth of display
268 * @return link to display that area in OSM map
269 */
270 public static String getURL(LatLon pos, int zoom) {
271 return getURL(pos.lat(), pos.lon(), zoom);
272 }
273
274 /**
275 * Return OSM URL for given lat/lon and zoom.
276 *
277 * @param dlat center latitude of area
278 * @param dlon center longitude of area
279 * @param zoom zoom depth of display
280 * @return link to display that area in OSM map
281 *
282 * @since 6453
283 */
284 public static String getURL(double dlat, double dlon, int zoom) {
285 // Truncate lat and lon to something more sensible
286 int decimals = (int) Math.pow(10, zoom / 3d);
287 double lat = Math.round(dlat * decimals);
288 lat /= decimals;
289 double lon = Math.round(dlon * decimals);
290 lon /= decimals;
291 return Main.getOSMWebsite() + "/#map="+zoom+'/'+lat+'/'+lon;
292 }
293}
Note: See TracBrowser for help on using the repository browser.