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

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

PMD - Strict Exceptions

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