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

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

see #15182 - deprecate Main.map and Main.isDisplayingMapView(). Replacements: gui.MainApplication.getMap() / gui.MainApplication.isDisplayingMapView()

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