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