001 // License: GPL. Copyright 2007 by Immanuel Scholz and others 002 package org.openstreetmap.josm.tools; 003 004 import java.awt.Toolkit; 005 import java.io.UnsupportedEncodingException; 006 import java.net.URLDecoder; 007 import java.util.HashMap; 008 import java.util.Map; 009 010 import org.openstreetmap.josm.Main; 011 import org.openstreetmap.josm.data.Bounds; 012 import org.openstreetmap.josm.data.coor.LatLon; 013 014 public class OsmUrlToBounds { 015 private static final String SHORTLINK_PREFIX = "http://osm.org/go/"; 016 017 public static Bounds parse(String url) { 018 try { 019 // a percent sign indicates an encoded URL (RFC 1738). 020 if (url.contains("%")) { 021 url = URLDecoder.decode(url, "UTF-8"); 022 } 023 } catch (UnsupportedEncodingException x) { 024 } catch (IllegalArgumentException x) { 025 } 026 Bounds b = parseShortLink(url); 027 if (b != null) 028 return b; 029 int i = url.indexOf('?'); 030 if (i == -1) 031 return null; 032 String[] args = url.substring(i+1).split("&"); 033 HashMap<String, String> map = new HashMap<String, String>(); 034 for (String arg : args) { 035 int eq = arg.indexOf('='); 036 if (eq != -1) { 037 map.put(arg.substring(0, eq), arg.substring(eq + 1)); 038 } 039 } 040 041 try { 042 if (map.containsKey("bbox")) { 043 String bbox[] = map.get("bbox").split(","); 044 b = new Bounds( 045 new LatLon(Double.parseDouble(bbox[1]), Double.parseDouble(bbox[0])), 046 new LatLon(Double.parseDouble(bbox[3]), Double.parseDouble(bbox[2]))); 047 } else if (map.containsKey("minlat")) { 048 String s = map.get("minlat"); 049 Double minlat = Double.parseDouble(s); 050 s = map.get("minlon"); 051 Double minlon = Double.parseDouble(s); 052 s = map.get("maxlat"); 053 Double maxlat = Double.parseDouble(s); 054 s = map.get("maxlon"); 055 Double maxlon = Double.parseDouble(s); 056 b = new Bounds(new LatLon(minlat, minlon), new LatLon(maxlat, maxlon)); 057 } else { 058 String z = map.get("zoom"); 059 b = positionToBounds(parseDouble(map, "lat"), 060 parseDouble(map, "lon"), 061 z == null ? 18 : Integer.parseInt(z)); 062 } 063 } catch (NumberFormatException x) { 064 x.printStackTrace(); 065 } catch (NullPointerException x) { 066 x.printStackTrace(); 067 } catch (ArrayIndexOutOfBoundsException x) { 068 x.printStackTrace(); 069 } 070 return b; 071 } 072 073 private static double parseDouble(HashMap<String, String> map, String key) { 074 if (map.containsKey(key)) 075 return Double.parseDouble(map.get(key)); 076 return Double.parseDouble(map.get("m"+key)); 077 } 078 079 private static final char[] SHORTLINK_CHARS = { 080 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 081 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 082 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 083 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 084 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 085 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 086 'w', 'x', 'y', 'z', '0', '1', '2', '3', 087 '4', '5', '6', '7', '8', '9', '_', '@' 088 }; 089 090 /** 091 * p 092 * 093 * @param url string for parsing 094 * 095 * @return Bounds if shortlink, null otherwise 096 * 097 * @see http://trac.openstreetmap.org/browser/sites/rails_port/lib/short_link.rb 098 */ 099 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.isDisplayingMapView()) { 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 }