| 1 | // License: GPL. Copyright 2007 by Immanuel Scholz and others |
|---|
| 2 | package org.openstreetmap.josm.tools; |
|---|
| 3 | |
|---|
| 4 | import java.awt.Toolkit; |
|---|
| 5 | import java.io.UnsupportedEncodingException; |
|---|
| 6 | import java.net.URLDecoder; |
|---|
| 7 | import java.util.HashMap; |
|---|
| 8 | import java.util.Map; |
|---|
| 9 | |
|---|
| 10 | import org.openstreetmap.josm.Main; |
|---|
| 11 | import org.openstreetmap.josm.data.Bounds; |
|---|
| 12 | import org.openstreetmap.josm.data.coor.LatLon; |
|---|
| 13 | |
|---|
| 14 | public class OsmUrlToBounds { |
|---|
| 15 | private static final String SHORTLINK_PREFIX = "http://osm.org/go/"; |
|---|
| 16 | |
|---|
| 17 | public static Bounds parse(String url) { |
|---|
| 18 | try { |
|---|
| 19 | // a percent sign indicates an encoded URL (RFC 1738). |
|---|
| 20 | if (url.contains("%")) { |
|---|
| 21 | url = URLDecoder.decode(url, "UTF-8"); |
|---|
| 22 | } |
|---|
| 23 | } catch (UnsupportedEncodingException x) { |
|---|
| 24 | } catch (IllegalArgumentException x) { |
|---|
| 25 | } |
|---|
| 26 | Bounds b = parseShortLink(url); |
|---|
| 27 | if (b != null) |
|---|
| 28 | return b; |
|---|
| 29 | int i = url.indexOf('?'); |
|---|
| 30 | if (i == -1) |
|---|
| 31 | return null; |
|---|
| 32 | String[] args = url.substring(i+1).split("&"); |
|---|
| 33 | HashMap<String, String> map = new HashMap<String, String>(); |
|---|
| 34 | for (String arg : args) { |
|---|
| 35 | int eq = arg.indexOf('='); |
|---|
| 36 | if (eq != -1) { |
|---|
| 37 | map.put(arg.substring(0, eq), arg.substring(eq + 1)); |
|---|
| 38 | } |
|---|
| 39 | } |
|---|
| 40 | |
|---|
| 41 | try { |
|---|
| 42 | if (map.containsKey("bbox")) { |
|---|
| 43 | String bbox[] = map.get("bbox").split(","); |
|---|
| 44 | b = new Bounds( |
|---|
| 45 | new LatLon(Double.parseDouble(bbox[1]), Double.parseDouble(bbox[0])), |
|---|
| 46 | new LatLon(Double.parseDouble(bbox[3]), Double.parseDouble(bbox[2]))); |
|---|
| 47 | } else if (map.containsKey("minlat")) { |
|---|
| 48 | String s = map.get("minlat"); |
|---|
| 49 | Double minlat = Double.parseDouble(s); |
|---|
| 50 | s = map.get("minlon"); |
|---|
| 51 | Double minlon = Double.parseDouble(s); |
|---|
| 52 | s = map.get("maxlat"); |
|---|
| 53 | Double maxlat = Double.parseDouble(s); |
|---|
| 54 | s = map.get("maxlon"); |
|---|
| 55 | Double maxlon = Double.parseDouble(s); |
|---|
| 56 | b = new Bounds(new LatLon(minlat, minlon), new LatLon(maxlat, maxlon)); |
|---|
| 57 | } else { |
|---|
| 58 | String z = map.get("zoom"); |
|---|
| 59 | b = positionToBounds(parseDouble(map, "lat"), |
|---|
| 60 | parseDouble(map, "lon"), |
|---|
| 61 | z == null ? 18 : Integer.parseInt(z)); |
|---|
| 62 | } |
|---|
| 63 | } catch (NumberFormatException x) { |
|---|
| 64 | x.printStackTrace(); |
|---|
| 65 | } catch (NullPointerException x) { |
|---|
| 66 | x.printStackTrace(); |
|---|
| 67 | } catch (ArrayIndexOutOfBoundsException x) { |
|---|
| 68 | x.printStackTrace(); |
|---|
| 69 | } |
|---|
| 70 | return b; |
|---|
| 71 | } |
|---|
| 72 | |
|---|
| 73 | private static double parseDouble(HashMap<String, String> map, String key) { |
|---|
| 74 | if (map.containsKey(key)) |
|---|
| 75 | return Double.parseDouble(map.get(key)); |
|---|
| 76 | return Double.parseDouble(map.get("m"+key)); |
|---|
| 77 | } |
|---|
| 78 | |
|---|
| 79 | private static final char[] SHORTLINK_CHARS = { |
|---|
| 80 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', |
|---|
| 81 | 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', |
|---|
| 82 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', |
|---|
| 83 | 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', |
|---|
| 84 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', |
|---|
| 85 | 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', |
|---|
| 86 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', |
|---|
| 87 | '4', '5', '6', '7', '8', '9', '_', '@' |
|---|
| 88 | }; |
|---|
| 89 | |
|---|
| 90 | /** |
|---|
| 91 | * p |
|---|
| 92 | * |
|---|
| 93 | * @param url string for parsing |
|---|
| 94 | * |
|---|
| 95 | * @return Bounds if shortlink, null otherwise |
|---|
| 96 | * |
|---|
| 97 | * @see http://trac.openstreetmap.org/browser/sites/rails_port/lib/short_link.rb |
|---|
| 98 | */ |
|---|
| 99 | private static Bounds parseShortLink(final String url) { |
|---|
| 100 | if (!url.startsWith(SHORTLINK_PREFIX)) |
|---|
| 101 | return null; |
|---|
| 102 | final String shortLink = url.substring(SHORTLINK_PREFIX.length()); |
|---|
| 103 | |
|---|
| 104 | final Map<Character, Integer> array = new HashMap<Character, Integer>(); |
|---|
| 105 | |
|---|
| 106 | for (int i=0; i<SHORTLINK_CHARS.length; ++i) { |
|---|
| 107 | array.put(SHORTLINK_CHARS[i], i); |
|---|
| 108 | } |
|---|
| 109 | |
|---|
| 110 | // long is necessary (need 32 bit positive value is needed) |
|---|
| 111 | long x = 0; |
|---|
| 112 | long y = 0; |
|---|
| 113 | int zoom = 0; |
|---|
| 114 | int zoomOffset = 0; |
|---|
| 115 | |
|---|
| 116 | for (final char ch : shortLink.toCharArray()) { |
|---|
| 117 | if (array.containsKey(ch)) { |
|---|
| 118 | int val = array.get(ch); |
|---|
| 119 | for (int i=0; i<3; ++i) { |
|---|
| 120 | x <<= 1; |
|---|
| 121 | if ((val & 32) != 0) { |
|---|
| 122 | x |= 1; |
|---|
| 123 | } |
|---|
| 124 | val <<= 1; |
|---|
| 125 | |
|---|
| 126 | y <<= 1; |
|---|
| 127 | if ((val & 32) != 0) { |
|---|
| 128 | y |= 1; |
|---|
| 129 | } |
|---|
| 130 | val <<= 1; |
|---|
| 131 | } |
|---|
| 132 | zoom += 3; |
|---|
| 133 | } else { |
|---|
| 134 | zoomOffset--; |
|---|
| 135 | } |
|---|
| 136 | } |
|---|
| 137 | |
|---|
| 138 | x <<= 32 - zoom; |
|---|
| 139 | y <<= 32 - zoom; |
|---|
| 140 | |
|---|
| 141 | // 2**32 == 4294967296 |
|---|
| 142 | return positionToBounds(y * 180.0 / 4294967296.0 - 90.0, |
|---|
| 143 | x * 360.0 / 4294967296.0 - 180.0, |
|---|
| 144 | // TODO: -2 was not in ruby code |
|---|
| 145 | zoom - 8 - (zoomOffset % 3) - 2); |
|---|
| 146 | } |
|---|
| 147 | |
|---|
| 148 | public static final double R = 6378137.0; |
|---|
| 149 | |
|---|
| 150 | public static Bounds positionToBounds(final double lat, final double lon, final int zoom) { |
|---|
| 151 | int tileSizeInPixels = 256; |
|---|
| 152 | int height = Toolkit.getDefaultToolkit().getScreenSize().height; |
|---|
| 153 | int width = Toolkit.getDefaultToolkit().getScreenSize().width; |
|---|
| 154 | if (Main.map != null && Main.map.mapView != null) { |
|---|
| 155 | height = Main.map.mapView.getHeight(); |
|---|
| 156 | width = Main.map.mapView.getWidth(); |
|---|
| 157 | } |
|---|
| 158 | double scale = (1 << zoom) * tileSizeInPixels / (2 * Math.PI * R); |
|---|
| 159 | double deltaX = width / 2.0 / scale; |
|---|
| 160 | double deltaY = height / 2.0 / scale; |
|---|
| 161 | double x = Math.toRadians(lon) * R; |
|---|
| 162 | double y = mercatorY(lat); |
|---|
| 163 | return new Bounds(invMercatorY(y - deltaY), Math.toDegrees(x - deltaX) / R, invMercatorY(y + deltaY), Math.toDegrees(x + deltaX) / R); |
|---|
| 164 | } |
|---|
| 165 | |
|---|
| 166 | public static double mercatorY(double lat) { |
|---|
| 167 | return Math.log(Math.tan(Math.PI/4 + Math.toRadians(lat)/2)) * R; |
|---|
| 168 | } |
|---|
| 169 | |
|---|
| 170 | public static double invMercatorY(double north) { |
|---|
| 171 | return Math.toDegrees(Math.atan(Math.sinh(north / R))); |
|---|
| 172 | } |
|---|
| 173 | |
|---|
| 174 | public static Pair<Double, Double> getTileOfLatLon(double lat, double lon, double zoom) { |
|---|
| 175 | double x = Math.floor((lon + 180) / 360 * Math.pow(2.0, zoom)); |
|---|
| 176 | double y = Math.floor((1 - Math.log(Math.tan(Math.toRadians(lat)) + 1 / Math.cos(Math.toRadians(lat))) / Math.PI) |
|---|
| 177 | / 2 * Math.pow(2.0, zoom)); |
|---|
| 178 | return new Pair<Double, Double>(x, y); |
|---|
| 179 | } |
|---|
| 180 | |
|---|
| 181 | public static LatLon getLatLonOfTile(double x, double y, double zoom) { |
|---|
| 182 | double lon = x / Math.pow(2.0, zoom) * 360.0 - 180; |
|---|
| 183 | double lat = Math.toDegrees(Math.atan(Math.sinh(Math.PI - (2.0 * Math.PI * y) / Math.pow(2.0, zoom)))); |
|---|
| 184 | return new LatLon(lat, lon); |
|---|
| 185 | } |
|---|
| 186 | |
|---|
| 187 | static public int getZoom(Bounds b) { |
|---|
| 188 | // convert to mercator (for calculation of zoom only) |
|---|
| 189 | double latMin = Math.log(Math.tan(Math.PI/4.0+b.getMin().lat()/180.0*Math.PI/2.0))*180.0/Math.PI; |
|---|
| 190 | double latMax = Math.log(Math.tan(Math.PI/4.0+b.getMax().lat()/180.0*Math.PI/2.0))*180.0/Math.PI; |
|---|
| 191 | double size = Math.max(Math.abs(latMax-latMin), Math.abs(b.getMax().lon()-b.getMin().lon())); |
|---|
| 192 | int zoom = 0; |
|---|
| 193 | while (zoom <= 20) { |
|---|
| 194 | if (size >= 180) { |
|---|
| 195 | break; |
|---|
| 196 | } |
|---|
| 197 | size *= 2; |
|---|
| 198 | zoom++; |
|---|
| 199 | } |
|---|
| 200 | return zoom; |
|---|
| 201 | } |
|---|
| 202 | |
|---|
| 203 | static public String getURL(Bounds b) { |
|---|
| 204 | return getURL(b.getCenter(), getZoom(b)); |
|---|
| 205 | } |
|---|
| 206 | |
|---|
| 207 | static public String getURL(LatLon pos, int zoom) { |
|---|
| 208 | // Truncate lat and lon to something more sensible |
|---|
| 209 | int decimals = (int) Math.pow(10, (zoom / 3)); |
|---|
| 210 | double lat = (Math.round(pos.lat() * decimals)); |
|---|
| 211 | lat /= decimals; |
|---|
| 212 | double lon = (Math.round(pos.lon() * decimals)); |
|---|
| 213 | lon /= decimals; |
|---|
| 214 | return "http://www.openstreetmap.org/?lat="+lat+"&lon="+lon+"&zoom="+zoom; |
|---|
| 215 | } |
|---|
| 216 | } |
|---|