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

Last change on this file since 11971 was 11847, checked in by Don-vip, 7 years ago

fix #14618 - add robustness to OsmUrlToBounds.parseDouble

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