/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.gallery3d.util; import com.android.gallery3d.common.BlobCache; import android.content.Context; import android.location.Address; import android.location.Geocoder; import android.location.Location; import android.location.LocationManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.List; import java.util.Locale; public class ReverseGeocoder { private static final String TAG = "ReverseGeocoder"; public static final int EARTH_RADIUS_METERS = 6378137; public static final int LAT_MIN = -90; public static final int LAT_MAX = 90; public static final int LON_MIN = -180; public static final int LON_MAX = 180; private static final int MAX_COUNTRY_NAME_LENGTH = 8; // If two points are within 20 miles of each other, use // "Around Palo Alto, CA" or "Around Mountain View, CA". // instead of directly jumping to the next level and saying // "California, US". private static final int MAX_LOCALITY_MILE_RANGE = 20; private static final String GEO_CACHE_FILE = "rev_geocoding"; private static final int GEO_CACHE_MAX_ENTRIES = 1000; private static final int GEO_CACHE_MAX_BYTES = 500 * 1024; private static final int GEO_CACHE_VERSION = 0; public static class SetLatLong { // The latitude and longitude of the min latitude point. public double mMinLatLatitude = LAT_MAX; public double mMinLatLongitude; // The latitude and longitude of the max latitude point. public double mMaxLatLatitude = LAT_MIN; public double mMaxLatLongitude; // The latitude and longitude of the min longitude point. public double mMinLonLatitude; public double mMinLonLongitude = LON_MAX; // The latitude and longitude of the max longitude point. public double mMaxLonLatitude; public double mMaxLonLongitude = LON_MIN; } private Context mContext; private Geocoder mGeocoder; private BlobCache mGeoCache; private ConnectivityManager mConnectivityManager; private static Address sCurrentAddress; // last known address public ReverseGeocoder(Context context) { mContext = context; mGeocoder = new Geocoder(mContext); mGeoCache = CacheManager.getCache(context, GEO_CACHE_FILE, GEO_CACHE_MAX_ENTRIES, GEO_CACHE_MAX_BYTES, GEO_CACHE_VERSION); mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); } public String computeAddress(SetLatLong set) { // The overall min and max latitudes and longitudes of the set. double setMinLatitude = set.mMinLatLatitude; double setMinLongitude = set.mMinLatLongitude; double setMaxLatitude = set.mMaxLatLatitude; double setMaxLongitude = set.mMaxLatLongitude; if (Math.abs(set.mMaxLatLatitude - set.mMinLatLatitude) < Math.abs(set.mMaxLonLongitude - set.mMinLonLongitude)) { setMinLatitude = set.mMinLonLatitude; setMinLongitude = set.mMinLonLongitude; setMaxLatitude = set.mMaxLonLatitude; setMaxLongitude = set.mMaxLonLongitude; } Address addr1 = lookupAddress(setMinLatitude, setMinLongitude, true); Address addr2 = lookupAddress(setMaxLatitude, setMaxLongitude, true); if (addr1 == null) addr1 = addr2; if (addr2 == null) addr2 = addr1; if (addr1 == null || addr2 == null) { return null; } // Get current location, we decide the granularity of the string based // on this. LocationManager locationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); Location location = null; List<String> providers = locationManager.getAllProviders(); for (int i = 0; i < providers.size(); ++i) { String provider = providers.get(i); location = (provider != null) ? locationManager.getLastKnownLocation(provider) : null; if (location != null) break; } String currentCity = ""; String currentAdminArea = ""; String currentCountry = Locale.getDefault().getCountry(); if (location != null) { Address currentAddress = lookupAddress( location.getLatitude(), location.getLongitude(), true); if (currentAddress == null) { currentAddress = sCurrentAddress; } else { sCurrentAddress = currentAddress; } if (currentAddress != null && currentAddress.getCountryCode() != null) { currentCity = checkNull(currentAddress.getLocality()); currentCountry = checkNull(currentAddress.getCountryCode()); currentAdminArea = checkNull(currentAddress.getAdminArea()); } } String closestCommonLocation = null; String addr1Locality = checkNull(addr1.getLocality()); String addr2Locality = checkNull(addr2.getLocality()); String addr1AdminArea = checkNull(addr1.getAdminArea()); String addr2AdminArea = checkNull(addr2.getAdminArea()); String addr1CountryCode = checkNull(addr1.getCountryCode()); String addr2CountryCode = checkNull(addr2.getCountryCode()); if (currentCity.equals(addr1Locality) || currentCity.equals(addr2Locality)) { String otherCity = currentCity; if (currentCity.equals(addr1Locality)) { otherCity = addr2Locality; if (otherCity.length() == 0) { otherCity = addr2AdminArea; if (!currentCountry.equals(addr2CountryCode)) { otherCity += " " + addr2CountryCode; } } addr2Locality = addr1Locality; addr2AdminArea = addr1AdminArea; addr2CountryCode = addr1CountryCode; } else { otherCity = addr1Locality; if (otherCity.length() == 0) { otherCity = addr1AdminArea; if (!currentCountry.equals(addr1CountryCode)) { otherCity += " " + addr1CountryCode; } } addr1Locality = addr2Locality; addr1AdminArea = addr2AdminArea; addr1CountryCode = addr2CountryCode; } closestCommonLocation = valueIfEqual(addr1.getAddressLine(0), addr2.getAddressLine(0)); if (closestCommonLocation != null && !("null".equals(closestCommonLocation))) { if (!currentCity.equals(otherCity)) { closestCommonLocation += " - " + otherCity; } return closestCommonLocation; } // Compare thoroughfare (street address) next. closestCommonLocation = valueIfEqual(addr1.getThoroughfare(), addr2.getThoroughfare()); if (closestCommonLocation != null && !("null".equals(closestCommonLocation))) { return closestCommonLocation; } } // Compare the locality. closestCommonLocation = valueIfEqual(addr1Locality, addr2Locality); if (closestCommonLocation != null && !("".equals(closestCommonLocation))) { String adminArea = addr1AdminArea; String countryCode = addr1CountryCode; if (adminArea != null && adminArea.length() > 0) { if (!countryCode.equals(currentCountry)) { closestCommonLocation += ", " + adminArea + " " + countryCode; } else { closestCommonLocation += ", " + adminArea; } } return closestCommonLocation; } // If the admin area is the same as the current location, we hide it and // instead show the city name. if (currentAdminArea.equals(addr1AdminArea) && currentAdminArea.equals(addr2AdminArea)) { if ("".equals(addr1Locality)) { addr1Locality = addr2Locality; } if ("".equals(addr2Locality)) { addr2Locality = addr1Locality; } if (!"".equals(addr1Locality)) { if (addr1Locality.equals(addr2Locality)) { closestCommonLocation = addr1Locality + ", " + currentAdminArea; } else { closestCommonLocation = addr1Locality + " - " + addr2Locality; } return closestCommonLocation; } } // Just choose one of the localities if within a MAX_LOCALITY_MILE_RANGE // mile radius. float[] distanceFloat = new float[1]; Location.distanceBetween(setMinLatitude, setMinLongitude, setMaxLatitude, setMaxLongitude, distanceFloat); int distance = (int) GalleryUtils.toMile(distanceFloat[0]); if (distance < MAX_LOCALITY_MILE_RANGE) { // Try each of the points and just return the first one to have a // valid address. closestCommonLocation = getLocalityAdminForAddress(addr1, true); if (closestCommonLocation != null) { return closestCommonLocation; } closestCommonLocation = getLocalityAdminForAddress(addr2, true); if (closestCommonLocation != null) { return closestCommonLocation; } } // Check the administrative area. closestCommonLocation = valueIfEqual(addr1AdminArea, addr2AdminArea); if (closestCommonLocation != null && !("".equals(closestCommonLocation))) { String countryCode = addr1CountryCode; if (!countryCode.equals(currentCountry)) { if (countryCode != null && countryCode.length() > 0) { closestCommonLocation += " " + countryCode; } } return closestCommonLocation; } // Check the country codes. closestCommonLocation = valueIfEqual(addr1CountryCode, addr2CountryCode); if (closestCommonLocation != null && !("".equals(closestCommonLocation))) { return closestCommonLocation; } // There is no intersection, let's choose a nicer name. String addr1Country = addr1.getCountryName(); String addr2Country = addr2.getCountryName(); if (addr1Country == null) addr1Country = addr1CountryCode; if (addr2Country == null) addr2Country = addr2CountryCode; if (addr1Country == null || addr2Country == null) return null; if (addr1Country.length() > MAX_COUNTRY_NAME_LENGTH || addr2Country.length() > MAX_COUNTRY_NAME_LENGTH) { closestCommonLocation = addr1CountryCode + " - " + addr2CountryCode; } else { closestCommonLocation = addr1Country + " - " + addr2Country; } return closestCommonLocation; } private String checkNull(String locality) { if (locality == null) return ""; if (locality.equals("null")) return ""; return locality; } private String getLocalityAdminForAddress(final Address addr, final boolean approxLocation) { if (addr == null) return ""; String localityAdminStr = addr.getLocality(); if (localityAdminStr != null && !("null".equals(localityAdminStr))) { if (approxLocation) { // TODO: Uncomment these lines as soon as we may translations // for Res.string.around. // localityAdminStr = // mContext.getResources().getString(Res.string.around) + " " + // localityAdminStr; } String adminArea = addr.getAdminArea(); if (adminArea != null && adminArea.length() > 0) { localityAdminStr += ", " + adminArea; } return localityAdminStr; } return null; } public Address lookupAddress(final double latitude, final double longitude, boolean useCache) { try { long locationKey = (long) (((latitude + LAT_MAX) * 2 * LAT_MAX + (longitude + LON_MAX)) * EARTH_RADIUS_METERS); byte[] cachedLocation = null; if (useCache && mGeoCache != null) { cachedLocation = mGeoCache.lookup(locationKey); } Address address = null; NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo(); if (cachedLocation == null || cachedLocation.length == 0) { if (networkInfo == null || !networkInfo.isConnected()) { return null; } List<Address> addresses = mGeocoder.getFromLocation(latitude, longitude, 1); if (!addresses.isEmpty()) { address = addresses.get(0); ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); Locale locale = address.getLocale(); writeUTF(dos, locale.getLanguage()); writeUTF(dos, locale.getCountry()); writeUTF(dos, locale.getVariant()); writeUTF(dos, address.getThoroughfare()); int numAddressLines = address.getMaxAddressLineIndex(); dos.writeInt(numAddressLines); for (int i = 0; i < numAddressLines; ++i) { writeUTF(dos, address.getAddressLine(i)); } writeUTF(dos, address.getFeatureName()); writeUTF(dos, address.getLocality()); writeUTF(dos, address.getAdminArea()); writeUTF(dos, address.getSubAdminArea()); writeUTF(dos, address.getCountryName()); writeUTF(dos, address.getCountryCode()); writeUTF(dos, address.getPostalCode()); writeUTF(dos, address.getPhone()); writeUTF(dos, address.getUrl()); dos.flush(); if (mGeoCache != null) { mGeoCache.insert(locationKey, bos.toByteArray()); } dos.close(); } } else { // Parsing the address from the byte stream. DataInputStream dis = new DataInputStream( new ByteArrayInputStream(cachedLocation)); String language = readUTF(dis); String country = readUTF(dis); String variant = readUTF(dis); Locale locale = null; if (language != null) { if (country == null) { locale = new Locale(language); } else if (variant == null) { locale = new Locale(language, country); } else { locale = new Locale(language, country, variant); } } if (!locale.getLanguage().equals(Locale.getDefault().getLanguage())) { dis.close(); return lookupAddress(latitude, longitude, false); } address = new Address(locale); address.setThoroughfare(readUTF(dis)); int numAddressLines = dis.readInt(); for (int i = 0; i < numAddressLines; ++i) { address.setAddressLine(i, readUTF(dis)); } address.setFeatureName(readUTF(dis)); address.setLocality(readUTF(dis)); address.setAdminArea(readUTF(dis)); address.setSubAdminArea(readUTF(dis)); address.setCountryName(readUTF(dis)); address.setCountryCode(readUTF(dis)); address.setPostalCode(readUTF(dis)); address.setPhone(readUTF(dis)); address.setUrl(readUTF(dis)); dis.close(); } return address; } catch (Exception e) { // Ignore. } return null; } private String valueIfEqual(String a, String b) { return (a != null && b != null && a.equalsIgnoreCase(b)) ? a : null; } public static final void writeUTF(DataOutputStream dos, String string) throws IOException { if (string == null) { dos.writeUTF(""); } else { dos.writeUTF(string); } } public static final String readUTF(DataInputStream dis) throws IOException { String retVal = dis.readUTF(); if (retVal.length() == 0) return null; return retVal; } }