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

Last change on this file since 11320 was 11302, checked in by simon04, 7 years ago

OsmUrlToBounds: Make getZoom return the previously parsed zoom level

Might be a regression of r4706.

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