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

Last change on this file since 12538 was 12382, checked in by michael2402, 7 years ago

More documentation for the tools package

  • 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.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") || url.contains("/#")) {
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('#');
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+(url.contains("#map=") ? "#map=".length() : "#".length()), 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 /**
216 * Compute the bounds for a given lat/lon position and the zoom level
217 * @param lat The latitude
218 * @param lon The longitude
219 * @param zoom The current zoom level
220 * @return The bounds the OSM server would display
221 */
222 public static Bounds positionToBounds(final double lat, final double lon, final int zoom) {
223 final Dimension screenSize = getScreenSize();
224 double scale = (1 << zoom) * TILE_SIZE_IN_PIXELS / (2 * Math.PI * Ellipsoid.WGS84.a);
225 double deltaX = screenSize.getWidth() / 2.0 / scale;
226 double deltaY = screenSize.getHeight() / 2.0 / scale;
227 final Projection mercator = Projections.getProjectionByCode("EPSG:3857");
228 final EastNorth projected = mercator.latlon2eastNorth(new LatLon(lat, lon));
229 return new Bounds(
230 mercator.eastNorth2latlon(projected.add(-deltaX, -deltaY)),
231 mercator.eastNorth2latlon(projected.add(deltaX, deltaY)));
232 }
233
234 /**
235 * Return OSM Zoom level for a given area
236 *
237 * @param b bounds of the area
238 * @return matching zoom level for area
239 */
240 public static int getZoom(Bounds b) {
241 final Projection mercator = Projections.getProjectionByCode("EPSG:3857");
242 final EastNorth min = mercator.latlon2eastNorth(b.getMin());
243 final EastNorth max = mercator.latlon2eastNorth(b.getMax());
244 final double deltaX = max.getX() - min.getX();
245 final double scale = getScreenSize().getWidth() / deltaX;
246 final double x = scale * (2 * Math.PI * Ellipsoid.WGS84.a) / TILE_SIZE_IN_PIXELS;
247 return (int) Math.round(Math.log(x) / Math.log(2));
248 }
249
250 /**
251 * Return OSM URL for given area.
252 *
253 * @param b bounds of the area
254 * @return link to display that area in OSM map
255 */
256 public static String getURL(Bounds b) {
257 return getURL(b.getCenter(), getZoom(b));
258 }
259
260 /**
261 * Return OSM URL for given position and zoom.
262 *
263 * @param pos center position of area
264 * @param zoom zoom depth of display
265 * @return link to display that area in OSM map
266 */
267 public static String getURL(LatLon pos, int zoom) {
268 return getURL(pos.lat(), pos.lon(), zoom);
269 }
270
271 /**
272 * Return OSM URL for given lat/lon and zoom.
273 *
274 * @param dlat center latitude of area
275 * @param dlon center longitude of area
276 * @param zoom zoom depth of display
277 * @return link to display that area in OSM map
278 *
279 * @since 6453
280 */
281 public static String getURL(double dlat, double dlon, int zoom) {
282 // Truncate lat and lon to something more sensible
283 int decimals = (int) Math.pow(10, zoom / 3d);
284 double lat = Math.round(dlat * decimals);
285 lat /= decimals;
286 double lon = Math.round(dlon * decimals);
287 lon /= decimals;
288 return Main.getOSMWebsite() + "/#map="+zoom+'/'+lat+'/'+lon;
289 }
290}
Note: See TracBrowser for help on using the repository browser.