Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of 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,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.gallery3d.util;
     18 
     19 import com.android.gallery3d.common.BlobCache;
     20 
     21 import android.content.Context;
     22 import android.location.Address;
     23 import android.location.Geocoder;
     24 import android.location.Location;
     25 import android.location.LocationManager;
     26 import android.net.ConnectivityManager;
     27 import android.net.NetworkInfo;
     28 
     29 import java.io.ByteArrayInputStream;
     30 import java.io.ByteArrayOutputStream;
     31 import java.io.DataInputStream;
     32 import java.io.DataOutputStream;
     33 import java.io.IOException;
     34 import java.util.List;
     35 import java.util.Locale;
     36 
     37 public class ReverseGeocoder {
     38     private static final String TAG = "ReverseGeocoder";
     39     public static final int EARTH_RADIUS_METERS = 6378137;
     40     public static final int LAT_MIN = -90;
     41     public static final int LAT_MAX = 90;
     42     public static final int LON_MIN = -180;
     43     public static final int LON_MAX = 180;
     44     private static final int MAX_COUNTRY_NAME_LENGTH = 8;
     45     // If two points are within 20 miles of each other, use
     46     // "Around Palo Alto, CA" or "Around Mountain View, CA".
     47     // instead of directly jumping to the next level and saying
     48     // "California, US".
     49     private static final int MAX_LOCALITY_MILE_RANGE = 20;
     50 
     51     private static final String GEO_CACHE_FILE = "rev_geocoding";
     52     private static final int GEO_CACHE_MAX_ENTRIES = 1000;
     53     private static final int GEO_CACHE_MAX_BYTES = 500 * 1024;
     54     private static final int GEO_CACHE_VERSION = 0;
     55 
     56     public static class SetLatLong {
     57         // The latitude and longitude of the min latitude point.
     58         public double mMinLatLatitude = LAT_MAX;
     59         public double mMinLatLongitude;
     60         // The latitude and longitude of the max latitude point.
     61         public double mMaxLatLatitude = LAT_MIN;
     62         public double mMaxLatLongitude;
     63         // The latitude and longitude of the min longitude point.
     64         public double mMinLonLatitude;
     65         public double mMinLonLongitude = LON_MAX;
     66         // The latitude and longitude of the max longitude point.
     67         public double mMaxLonLatitude;
     68         public double mMaxLonLongitude = LON_MIN;
     69     }
     70 
     71     private Context mContext;
     72     private Geocoder mGeocoder;
     73     private BlobCache mGeoCache;
     74     private ConnectivityManager mConnectivityManager;
     75     private static Address sCurrentAddress; // last known address
     76 
     77     public ReverseGeocoder(Context context) {
     78         mContext = context;
     79         mGeocoder = new Geocoder(mContext);
     80         mGeoCache = CacheManager.getCache(context, GEO_CACHE_FILE,
     81                 GEO_CACHE_MAX_ENTRIES, GEO_CACHE_MAX_BYTES,
     82                 GEO_CACHE_VERSION);
     83         mConnectivityManager = (ConnectivityManager)
     84                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
     85     }
     86 
     87     public String computeAddress(SetLatLong set) {
     88         // The overall min and max latitudes and longitudes of the set.
     89         double setMinLatitude = set.mMinLatLatitude;
     90         double setMinLongitude = set.mMinLatLongitude;
     91         double setMaxLatitude = set.mMaxLatLatitude;
     92         double setMaxLongitude = set.mMaxLatLongitude;
     93         if (Math.abs(set.mMaxLatLatitude - set.mMinLatLatitude)
     94                 < Math.abs(set.mMaxLonLongitude - set.mMinLonLongitude)) {
     95             setMinLatitude = set.mMinLonLatitude;
     96             setMinLongitude = set.mMinLonLongitude;
     97             setMaxLatitude = set.mMaxLonLatitude;
     98             setMaxLongitude = set.mMaxLonLongitude;
     99         }
    100         Address addr1 = lookupAddress(setMinLatitude, setMinLongitude, true);
    101         Address addr2 = lookupAddress(setMaxLatitude, setMaxLongitude, true);
    102         if (addr1 == null)
    103             addr1 = addr2;
    104         if (addr2 == null)
    105             addr2 = addr1;
    106         if (addr1 == null || addr2 == null) {
    107             return null;
    108         }
    109 
    110         // Get current location, we decide the granularity of the string based
    111         // on this.
    112         LocationManager locationManager =
    113                 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
    114         Location location = null;
    115         List<String> providers = locationManager.getAllProviders();
    116         for (int i = 0; i < providers.size(); ++i) {
    117             String provider = providers.get(i);
    118             location = (provider != null) ? locationManager.getLastKnownLocation(provider) : null;
    119             if (location != null)
    120                 break;
    121         }
    122         String currentCity = "";
    123         String currentAdminArea = "";
    124         String currentCountry = Locale.getDefault().getCountry();
    125         if (location != null) {
    126             Address currentAddress = lookupAddress(
    127                     location.getLatitude(), location.getLongitude(), true);
    128             if (currentAddress == null) {
    129                 currentAddress = sCurrentAddress;
    130             } else {
    131                 sCurrentAddress = currentAddress;
    132             }
    133             if (currentAddress != null && currentAddress.getCountryCode() != null) {
    134                 currentCity = checkNull(currentAddress.getLocality());
    135                 currentCountry = checkNull(currentAddress.getCountryCode());
    136                 currentAdminArea = checkNull(currentAddress.getAdminArea());
    137             }
    138         }
    139 
    140         String closestCommonLocation = null;
    141         String addr1Locality = checkNull(addr1.getLocality());
    142         String addr2Locality = checkNull(addr2.getLocality());
    143         String addr1AdminArea = checkNull(addr1.getAdminArea());
    144         String addr2AdminArea = checkNull(addr2.getAdminArea());
    145         String addr1CountryCode = checkNull(addr1.getCountryCode());
    146         String addr2CountryCode = checkNull(addr2.getCountryCode());
    147 
    148         if (currentCity.equals(addr1Locality) || currentCity.equals(addr2Locality)) {
    149             String otherCity = currentCity;
    150             if (currentCity.equals(addr1Locality)) {
    151                 otherCity = addr2Locality;
    152                 if (otherCity.length() == 0) {
    153                     otherCity = addr2AdminArea;
    154                     if (!currentCountry.equals(addr2CountryCode)) {
    155                         otherCity += " " + addr2CountryCode;
    156                     }
    157                 }
    158                 addr2Locality = addr1Locality;
    159                 addr2AdminArea = addr1AdminArea;
    160                 addr2CountryCode = addr1CountryCode;
    161             } else {
    162                 otherCity = addr1Locality;
    163                 if (otherCity.length() == 0) {
    164                     otherCity = addr1AdminArea;
    165                     if (!currentCountry.equals(addr1CountryCode)) {
    166                         otherCity += " " + addr1CountryCode;
    167                     }
    168                 }
    169                 addr1Locality = addr2Locality;
    170                 addr1AdminArea = addr2AdminArea;
    171                 addr1CountryCode = addr2CountryCode;
    172             }
    173             closestCommonLocation = valueIfEqual(addr1.getAddressLine(0), addr2.getAddressLine(0));
    174             if (closestCommonLocation != null && !("null".equals(closestCommonLocation))) {
    175                 if (!currentCity.equals(otherCity)) {
    176                     closestCommonLocation += " - " + otherCity;
    177                 }
    178                 return closestCommonLocation;
    179             }
    180 
    181             // Compare thoroughfare (street address) next.
    182             closestCommonLocation = valueIfEqual(addr1.getThoroughfare(), addr2.getThoroughfare());
    183             if (closestCommonLocation != null && !("null".equals(closestCommonLocation))) {
    184                 return closestCommonLocation;
    185             }
    186         }
    187 
    188         // Compare the locality.
    189         closestCommonLocation = valueIfEqual(addr1Locality, addr2Locality);
    190         if (closestCommonLocation != null && !("".equals(closestCommonLocation))) {
    191             String adminArea = addr1AdminArea;
    192             String countryCode = addr1CountryCode;
    193             if (adminArea != null && adminArea.length() > 0) {
    194                 if (!countryCode.equals(currentCountry)) {
    195                     closestCommonLocation += ", " + adminArea + " " + countryCode;
    196                 } else {
    197                     closestCommonLocation += ", " + adminArea;
    198                 }
    199             }
    200             return closestCommonLocation;
    201         }
    202 
    203         // If the admin area is the same as the current location, we hide it and
    204         // instead show the city name.
    205         if (currentAdminArea.equals(addr1AdminArea) && currentAdminArea.equals(addr2AdminArea)) {
    206             if ("".equals(addr1Locality)) {
    207                 addr1Locality = addr2Locality;
    208             }
    209             if ("".equals(addr2Locality)) {
    210                 addr2Locality = addr1Locality;
    211             }
    212             if (!"".equals(addr1Locality)) {
    213                 if (addr1Locality.equals(addr2Locality)) {
    214                     closestCommonLocation = addr1Locality + ", " + currentAdminArea;
    215                 } else {
    216                     closestCommonLocation = addr1Locality + " - " + addr2Locality;
    217                 }
    218                 return closestCommonLocation;
    219             }
    220         }
    221 
    222         // Just choose one of the localities if within a MAX_LOCALITY_MILE_RANGE
    223         // mile radius.
    224         float[] distanceFloat = new float[1];
    225         Location.distanceBetween(setMinLatitude, setMinLongitude,
    226                 setMaxLatitude, setMaxLongitude, distanceFloat);
    227         int distance = (int) GalleryUtils.toMile(distanceFloat[0]);
    228         if (distance < MAX_LOCALITY_MILE_RANGE) {
    229             // Try each of the points and just return the first one to have a
    230             // valid address.
    231             closestCommonLocation = getLocalityAdminForAddress(addr1, true);
    232             if (closestCommonLocation != null) {
    233                 return closestCommonLocation;
    234             }
    235             closestCommonLocation = getLocalityAdminForAddress(addr2, true);
    236             if (closestCommonLocation != null) {
    237                 return closestCommonLocation;
    238             }
    239         }
    240 
    241         // Check the administrative area.
    242         closestCommonLocation = valueIfEqual(addr1AdminArea, addr2AdminArea);
    243         if (closestCommonLocation != null && !("".equals(closestCommonLocation))) {
    244             String countryCode = addr1CountryCode;
    245             if (!countryCode.equals(currentCountry)) {
    246                 if (countryCode != null && countryCode.length() > 0) {
    247                     closestCommonLocation += " " + countryCode;
    248                 }
    249             }
    250             return closestCommonLocation;
    251         }
    252 
    253         // Check the country codes.
    254         closestCommonLocation = valueIfEqual(addr1CountryCode, addr2CountryCode);
    255         if (closestCommonLocation != null && !("".equals(closestCommonLocation))) {
    256             return closestCommonLocation;
    257         }
    258         // There is no intersection, let's choose a nicer name.
    259         String addr1Country = addr1.getCountryName();
    260         String addr2Country = addr2.getCountryName();
    261         if (addr1Country == null)
    262             addr1Country = addr1CountryCode;
    263         if (addr2Country == null)
    264             addr2Country = addr2CountryCode;
    265         if (addr1Country == null || addr2Country == null)
    266             return null;
    267         if (addr1Country.length() > MAX_COUNTRY_NAME_LENGTH || addr2Country.length() > MAX_COUNTRY_NAME_LENGTH) {
    268             closestCommonLocation = addr1CountryCode + " - " + addr2CountryCode;
    269         } else {
    270             closestCommonLocation = addr1Country + " - " + addr2Country;
    271         }
    272         return closestCommonLocation;
    273     }
    274 
    275     private String checkNull(String locality) {
    276         if (locality == null)
    277             return "";
    278         if (locality.equals("null"))
    279             return "";
    280         return locality;
    281     }
    282 
    283     private String getLocalityAdminForAddress(final Address addr, final boolean approxLocation) {
    284         if (addr == null)
    285             return "";
    286         String localityAdminStr = addr.getLocality();
    287         if (localityAdminStr != null && !("null".equals(localityAdminStr))) {
    288             if (approxLocation) {
    289                 // TODO: Uncomment these lines as soon as we may translations
    290                 // for Res.string.around.
    291                 // localityAdminStr =
    292                 // mContext.getResources().getString(Res.string.around) + " " +
    293                 // localityAdminStr;
    294             }
    295             String adminArea = addr.getAdminArea();
    296             if (adminArea != null && adminArea.length() > 0) {
    297                 localityAdminStr += ", " + adminArea;
    298             }
    299             return localityAdminStr;
    300         }
    301         return null;
    302     }
    303 
    304     public Address lookupAddress(final double latitude, final double longitude,
    305             boolean useCache) {
    306         try {
    307             long locationKey = (long) (((latitude + LAT_MAX) * 2 * LAT_MAX
    308                     + (longitude + LON_MAX)) * EARTH_RADIUS_METERS);
    309             byte[] cachedLocation = null;
    310             if (useCache && mGeoCache != null) {
    311                 cachedLocation = mGeoCache.lookup(locationKey);
    312             }
    313             Address address = null;
    314             NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo();
    315             if (cachedLocation == null || cachedLocation.length == 0) {
    316                 if (networkInfo == null || !networkInfo.isConnected()) {
    317                     return null;
    318                 }
    319                 List<Address> addresses = mGeocoder.getFromLocation(latitude, longitude, 1);
    320                 if (!addresses.isEmpty()) {
    321                     address = addresses.get(0);
    322                     ByteArrayOutputStream bos = new ByteArrayOutputStream();
    323                     DataOutputStream dos = new DataOutputStream(bos);
    324                     Locale locale = address.getLocale();
    325                     writeUTF(dos, locale.getLanguage());
    326                     writeUTF(dos, locale.getCountry());
    327                     writeUTF(dos, locale.getVariant());
    328 
    329                     writeUTF(dos, address.getThoroughfare());
    330                     int numAddressLines = address.getMaxAddressLineIndex();
    331                     dos.writeInt(numAddressLines);
    332                     for (int i = 0; i < numAddressLines; ++i) {
    333                         writeUTF(dos, address.getAddressLine(i));
    334                     }
    335                     writeUTF(dos, address.getFeatureName());
    336                     writeUTF(dos, address.getLocality());
    337                     writeUTF(dos, address.getAdminArea());
    338                     writeUTF(dos, address.getSubAdminArea());
    339 
    340                     writeUTF(dos, address.getCountryName());
    341                     writeUTF(dos, address.getCountryCode());
    342                     writeUTF(dos, address.getPostalCode());
    343                     writeUTF(dos, address.getPhone());
    344                     writeUTF(dos, address.getUrl());
    345 
    346                     dos.flush();
    347                     if (mGeoCache != null) {
    348                         mGeoCache.insert(locationKey, bos.toByteArray());
    349                     }
    350                     dos.close();
    351                 }
    352             } else {
    353                 // Parsing the address from the byte stream.
    354                 DataInputStream dis = new DataInputStream(
    355                         new ByteArrayInputStream(cachedLocation));
    356                 String language = readUTF(dis);
    357                 String country = readUTF(dis);
    358                 String variant = readUTF(dis);
    359                 Locale locale = null;
    360                 if (language != null) {
    361                     if (country == null) {
    362                         locale = new Locale(language);
    363                     } else if (variant == null) {
    364                         locale = new Locale(language, country);
    365                     } else {
    366                         locale = new Locale(language, country, variant);
    367                     }
    368                 }
    369                 if (!locale.getLanguage().equals(Locale.getDefault().getLanguage())) {
    370                     dis.close();
    371                     return lookupAddress(latitude, longitude, false);
    372                 }
    373                 address = new Address(locale);
    374 
    375                 address.setThoroughfare(readUTF(dis));
    376                 int numAddressLines = dis.readInt();
    377                 for (int i = 0; i < numAddressLines; ++i) {
    378                     address.setAddressLine(i, readUTF(dis));
    379                 }
    380                 address.setFeatureName(readUTF(dis));
    381                 address.setLocality(readUTF(dis));
    382                 address.setAdminArea(readUTF(dis));
    383                 address.setSubAdminArea(readUTF(dis));
    384 
    385                 address.setCountryName(readUTF(dis));
    386                 address.setCountryCode(readUTF(dis));
    387                 address.setPostalCode(readUTF(dis));
    388                 address.setPhone(readUTF(dis));
    389                 address.setUrl(readUTF(dis));
    390                 dis.close();
    391             }
    392             return address;
    393         } catch (Exception e) {
    394             // Ignore.
    395         }
    396         return null;
    397     }
    398 
    399     private String valueIfEqual(String a, String b) {
    400         return (a != null && b != null && a.equalsIgnoreCase(b)) ? a : null;
    401     }
    402 
    403     public static final void writeUTF(DataOutputStream dos, String string) throws IOException {
    404         if (string == null) {
    405             dos.writeUTF("");
    406         } else {
    407             dos.writeUTF(string);
    408         }
    409     }
    410 
    411     public static final String readUTF(DataInputStream dis) throws IOException {
    412         String retVal = dis.readUTF();
    413         if (retVal.length() == 0)
    414             return null;
    415         return retVal;
    416     }
    417 }
    418