/compass/src/com/google/marvin/compass/StreetLocator.java

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