/actionslib/src/com/google/android/marvin/commands/impls/StreetLocator.java
Java | 436 lines | 260 code | 32 blank | 144 comment | 25 complexity | 1f16b80bf032f4dc017eb6cb6f1255e9 MD5 | raw file
1/* 2 * Copyright (C) 2008 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package com.google.android.marvin.commands.impls; 18 19import org.json.JSONException; 20import org.json.JSONObject; 21 22import android.location.Location; 23import android.location.LocationManager; 24import android.util.Log; 25 26import java.io.BufferedReader; 27import java.io.IOException; 28import java.io.InputStream; 29import java.io.InputStreamReader; 30import java.net.HttpURLConnection; 31import java.net.MalformedURLException; 32import java.net.URL; 33import java.util.HashSet; 34 35/** 36 * This class implements methods to get street address from lat-lon using the 37 * reverse geocoding API through HTTP 38 * 39 * @author chaitanyag@google.com (Chaitanya Gharpure) 40 */ 41public class StreetLocator { 42 public interface StreetLocatorListener { 43 public void onIntersectionLocated(String[] streetnames); 44 45 public void onAddressLocated(String address); 46 47 public void onFrontBackLocated(String[] streetsFront, String[] streetsBack); 48 } 49 50 private StreetLocatorListener cb; 51 52 private static final String ENCODING = "UTF-8"; 53 54 // URL for obtaining navigation directions 55 private static final String URL_NAV_STRING = "http://maps.google.com/maps/nav?"; 56 57 // URL for obtaining reverse geocoded location 58 private static final String URL_GEO_STRING = "http://maps.google.com/maps/geo?"; 59 60 public StreetLocator(StreetLocatorListener callback) { 61 cb = callback; 62 } 63 64 /** 65 * Queries the map server and obtains the street names at the specified 66 * location. This is done by obtaining street name at specified location, 67 * and at locations X meters to the N, S, E, and W of the specified 68 * location. 69 * 70 * @param lat The latitude in degrees 71 * @param lon The longitude in degrees 72 */ 73 public void getStreetIntersectionAsync(double lat, double lon) { 74 final double latitude = lat; 75 final double longitude = lon; 76 class IntersectionThread implements Runnable { 77 public void run() { 78 cb.onIntersectionLocated(getStreetIntersection(latitude, longitude)); 79 } 80 } 81 (new Thread(new IntersectionThread())).start(); 82 } 83 84 /** 85 * Queries the map server and obtains the street names at the specified 86 * location. This is done by obtaining street name at specified location, 87 * and at locations X meters to the N, S, E, and W of the specified 88 * location. 89 * 90 * @param lat The latitude in degrees 91 * @param lon The longitude in degrees 92 */ 93 public void getStreetsInFrontAndBackAsync(double lat, double lon, double heading) { 94 final double latitude = lat; 95 final double longitude = lon; 96 final double direction = heading; 97 class IntersectionThread implements Runnable { 98 public void run() { 99 getStreetsInFrontAndBack(latitude, longitude, direction); 100 } 101 } 102 (new Thread(new IntersectionThread())).start(); 103 } 104 105 /** 106 * Queries the map server and obtains the reverse geocoded address of the 107 * specified location. 108 * 109 * @param lat The latitude in degrees 110 * @param lon The longitude in degrees 111 */ 112 public void getAddressAsync(double lat, double lon) { 113 final double latitude = lat; 114 final double longitude = lon; 115 class AddressThread implements Runnable { 116 public void run() { 117 String address = getAddress(latitude, longitude); 118 if (address != null) { 119 cb.onAddressLocated(address); 120 } 121 } 122 } 123 (new Thread(new AddressThread())).start(); 124 } 125 126 /** 127 * Queries the map server and obtains the street names at the specified 128 * location. This is done by obtaining street name at specified location, 129 * and at locations X meters to the N, S, E, and W of the specified 130 * location. 131 * 132 * @param lat The latitude in degrees 133 * @param lon The longitude in degrees 134 * @return Returns the string array containing street names 135 */ 136 public String[] getStreetIntersection(double lat, double lon) { 137 HashSet<String> streets = new HashSet<String>(); 138 try { 139 for (int i = 0; i < 5; i++) { 140 // Find street address at lat-lon x meters to the N, S, E and W 141 // of 142 // the given lat-lon 143 String street = parseStreetName(getResult(makeNavURL(lat, lon, lat, lon))); 144 if (street != null) { 145 streets.add(street); 146 } 147 // get points 150m away, towards N, E, S, and W 148 if (i < 4) { 149 Location nextLoc = endLocation(lat, lon, i * 90, 15); 150 lat = nextLoc.getLatitude(); 151 lon = nextLoc.getLongitude(); 152 } 153 } 154 } catch (MalformedURLException mue) { 155 Log.d("Locator", "Malformed URL: " + mue.getMessage()); 156 } catch (IOException e) { 157 Log.d("Locator", "Error reading from Map server: " + e.toString()); 158 } catch (JSONException e) { 159 Log.d("Locator", "Error in JSON response: " + e.getMessage()); 160 } 161 String[] st = new String[streets.size()]; 162 int i = 0; 163 for (String s : streets) { 164 st[i++] = s; 165 } 166 return st; 167 } 168 169 /** 170 * Queries the map server and obtains the street names at the specified 171 * location. This is done by obtaining street name at specified location, 172 * and at locations X meters to the N, S, E, and W of the specified 173 * location. 174 * 175 * @param lat The latitude in degrees 176 * @param lon The longitude in degrees 177 */ 178 public void getStreetsInFrontAndBack(double lat, double lon, double heading) { 179 HashSet<String> streetsFront = new HashSet<String>(); 180 HashSet<String> streetsBack = new HashSet<String>(); 181 double searchDistance = 15; // 15m (? - is there really a factor of 10 182 // here) 183 184 try { 185 // Get the current street 186 String street = parseStreetName(getResult(makeNavURL(lat, lon, lat, lon))); 187 if (street != null) { 188 streetsFront.add(street); 189 streetsBack.add(street); 190 } 191 Log.i("Current street", "lat: " + Double.toString(lat) + ", lon: " 192 + Double.toString(lon)); 193 194 // Get the street in front of the current street 195 Location nextLoc = endLocation(lat, lon, heading, searchDistance); 196 lat = nextLoc.getLatitude(); 197 lon = nextLoc.getLongitude(); 198 street = parseStreetName(getResult(makeNavURL(lat, lon, lat, lon))); 199 if (street != null) { 200 streetsFront.add(street); 201 } 202 Log 203 .i("Front street", "lat: " + Double.toString(lat) + ", lon: " 204 + Double.toString(lon)); 205 206 // Get the street behind the current street 207 heading = heading + 180; 208 if (heading >= 360) { 209 heading = heading - 360; 210 } 211 nextLoc = endLocation(lat, lon, heading, searchDistance); 212 lat = nextLoc.getLatitude(); 213 lon = nextLoc.getLongitude(); 214 street = parseStreetName(getResult(makeNavURL(lat, lon, lat, lon))); 215 if (street != null) { 216 streetsBack.add(street); 217 } 218 Log.i("Back street", "lat: " + Double.toString(lat) + ", lon: " + Double.toString(lon)); 219 220 String[] sf = new String[streetsFront.size()]; 221 int i = 0; 222 for (String s : streetsFront) { 223 sf[i++] = s; 224 } 225 226 String[] sb = new String[streetsBack.size()]; 227 i = 0; 228 for (String s : streetsBack) { 229 sb[i++] = s; 230 } 231 cb.onFrontBackLocated(sf, sb); 232 } catch (MalformedURLException e) { 233 // TODO Auto-generated catch block 234 e.printStackTrace(); 235 } catch (JSONException e) { 236 // TODO Auto-generated catch block 237 e.printStackTrace(); 238 } catch (IOException e) { 239 // TODO Auto-generated catch block 240 e.printStackTrace(); 241 } 242 } 243 244 /** 245 * Queries the map server and obtains the reverse geocoded address of the 246 * specified location. 247 * 248 * @param lat The latitude in degrees 249 * @param lon The longitude in degrees 250 * @return Returns the reverse geocoded address 251 */ 252 public String getAddress(double lat, double lon) { 253 try { 254 String resp = getResult(makeGeoURL(lat, lon)); 255 JSONObject jsonObj = new JSONObject(resp); 256 int code = jsonObj.getJSONObject("Status").getInt("code"); 257 if (code == 200) { 258 return extendShorts(jsonObj.getJSONArray("Placemark").getJSONObject(0).getString( 259 "address")); 260 } 261 } catch (MalformedURLException mue) { 262 Log.d("Locator", "Malformed URL: " + mue.getMessage()); 263 } catch (IOException e) { 264 Log.d("Locator", "Error reading from Map server: " + e.toString()); 265 } catch (JSONException e) { 266 Log.d("Locator", "Error in JSON response: " + e.getMessage()); 267 } 268 return null; 269 } 270 271 /** 272 * Parses the JSON response to extract the street name. 273 * 274 * @param resp The String representation of the JSON response 275 * @return Returns the street name 276 * @throws JSONException 277 */ 278 private String parseStreetName(String resp) throws JSONException { 279 JSONObject jsonObj = new JSONObject(resp); 280 int code = jsonObj.getJSONObject("Status").getInt("code"); 281 if (code == 200) { 282 return extendShorts(jsonObj.getJSONArray("Placemark").getJSONObject(0).getString( 283 "address")); 284 } 285 return null; 286 } 287 288 /** 289 * Sends a request to the specified URL and obtains the result from the 290 * sever. 291 * 292 * @param url The URL to connect to 293 * @return the server response 294 * @throws IOException 295 */ 296 private String getResult(URL url) throws IOException { 297 Log.d("Locator", url.toString()); 298 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 299 conn.setDoInput(true); 300 conn.setDoOutput(true); 301 InputStream is = conn.getInputStream(); 302 String result = toString(is); 303 return result; 304 } 305 306 /** 307 * Prepares the URL to connect to navigation server, from the specified 308 * start and end location coordinates 309 * 310 * @param lat1 Start location latitude in degrees 311 * @param lon1 Start location longitude in degrees 312 * @param lat2 End location latitude in degrees 313 * @param lon2 End location longitude in degrees 314 * @return a well-formed URL 315 * @throws MalformedURLException 316 */ 317 private URL makeNavURL(double lat1, double lon1, double lat2, double lon2) 318 throws MalformedURLException { 319 StringBuilder url = new StringBuilder(); 320 url.append(URL_NAV_STRING).append("hl=EN&gl=EN&output=js&oe=utf8&q=from%3A").append(lat1) 321 .append(",").append(lon1).append("+to%3A").append(lat2).append(",").append(lon2); 322 return new URL(url.toString()); 323 } 324 325 /** 326 * Prepares the URL to connect to the reverse geocoding server from the 327 * specified location coordinates. 328 * 329 * @param lat latitude in degrees of the location to reverse geocode 330 * @param lon longitude in degrees of the location to reverse geocode 331 * @return URL The Geo URL created based on the given lat/lon 332 * @throws MalformedURLException 333 */ 334 private URL makeGeoURL(double lat, double lon) throws MalformedURLException { 335 StringBuilder url = new StringBuilder(); 336 url.append(URL_GEO_STRING).append("q=").append(lat).append(",").append(lon); 337 return new URL(url.toString()); 338 } 339 340 /** 341 * Reads an InputStream and returns its contents as a String. 342 * 343 * @param inputStream The InputStream to read from. 344 * @return The contents of the InputStream as a String. 345 */ 346 private static String toString(InputStream inputStream) throws IOException { 347 StringBuilder outputBuilder = new StringBuilder(); 348 String string; 349 if (inputStream != null) { 350 BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, ENCODING)); 351 while (null != (string = reader.readLine())) { 352 outputBuilder.append(string).append('\n'); 353 } 354 } 355 return outputBuilder.toString(); 356 } 357 358 /** 359 * Replaces the short forms in the address by their longer forms, so that 360 * TTS speaks the addresses properly 361 * 362 * @param addr The address from which to replace short forms 363 * @return the modified address string 364 */ 365 private String extendShorts(String addr) { 366 addr = addr.replace("St,", "Street"); 367 addr = addr.replace("St.", "Street"); 368 addr = addr.replace("Rd", "Road"); 369 addr = addr.replace("Fwy", "Freeway"); 370 addr = addr.replace("Pkwy", "Parkway"); 371 addr = addr.replace("Blvd", "Boulevard"); 372 addr = addr.replace("Expy", "Expressway"); 373 addr = addr.replace("Ave", "Avenue"); 374 addr = addr.replace("Dr", "Drive"); 375 return addr; 376 } 377 378 /** 379 * Computes the new location at a particular direction and distance from the 380 * specified location using the inverse Vincenti formula. 381 * 382 * @param lat1 latitude of source location in degrees 383 * @param lon1 longitude of source location in degrees 384 * @param brng Direction in degrees wrt source location 385 * @param dist Distance from the source location 386 * @return the new location 387 */ 388 private Location endLocation(double lat1, double lon1, double brng, double dist) { 389 double a = 6378137, b = 6356752.3142, f = 1 / 298.257223563; 390 double s = dist; 391 double alpha1 = Math.toRadians(brng); 392 double sinAlpha1 = Math.sin(alpha1), cosAlpha1 = Math.cos(alpha1); 393 394 double tanU1 = (1 - f) * Math.tan(Math.toRadians(lat1)); 395 double cosU1 = 1 / Math.sqrt((1 + tanU1 * tanU1)), sinU1 = tanU1 * cosU1; 396 double sigma1 = Math.atan2(tanU1, cosAlpha1); 397 double sinAlpha = cosU1 * sinAlpha1; 398 double cosSqAlpha = 1 - sinAlpha * sinAlpha; 399 double uSq = cosSqAlpha * (a * a - b * b) / (b * b); 400 double A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq))); 401 double B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq))); 402 403 double sigma = s / (b * A), sigmaP = 2 * Math.PI; 404 double cos2SigmaM = 0, sinSigma = 0, deltaSigma = 0, cosSigma = 0; 405 while (Math.abs(sigma - sigmaP) > 1e-12) { 406 cos2SigmaM = Math.cos(2 * sigma1 + sigma); 407 sinSigma = Math.sin(sigma); 408 cosSigma = Math.cos(sigma); 409 deltaSigma = B 410 * sinSigma 411 * (cos2SigmaM + B 412 / 4 413 * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) - B / 6 * cos2SigmaM 414 * (-3 + 4 * sinSigma * sinSigma) 415 * (-3 + 4 * cos2SigmaM * cos2SigmaM))); 416 sigmaP = sigma; 417 sigma = s / (b * A) + deltaSigma; 418 } 419 double tmp = sinU1 * sinSigma - cosU1 * cosSigma * cosAlpha1; 420 double lat2 = Math.atan2(sinU1 * cosSigma + cosU1 * sinSigma * cosAlpha1, (1 - f) 421 * Math.sqrt(sinAlpha * sinAlpha + tmp * tmp)); 422 double lambda = Math.atan2(sinSigma * sinAlpha1, cosU1 * cosSigma - sinU1 * sinSigma 423 * cosAlpha1); 424 double C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha)); 425 double L = lambda 426 - (1 - C) 427 * f 428 * sinAlpha 429 * (sigma + C * sinSigma 430 * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM))); 431 Location l = new Location(LocationManager.GPS_PROVIDER); 432 l.setLatitude(Math.toDegrees(lat2)); 433 l.setLongitude(lon1 + Math.toDegrees(L)); 434 return l; 435 } 436}