PageRenderTime 51ms CodeModel.GetById 27ms app.highlight 20ms RepoModel.GetById 1ms app.codeStats 0ms

/actionslib/src/com/google/android/marvin/commands/impls/StreetLocator.java

http://eyes-free.googlecode.com/
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}