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 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 ReverseGeocoder(Context context)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 computeAddress(SetLatLong set)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 checkNull(String locality)275 private String checkNull(String locality) { 276 if (locality == null) 277 return ""; 278 if (locality.equals("null")) 279 return ""; 280 return locality; 281 } 282 getLocalityAdminForAddress(final Address addr, final boolean approxLocation)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 lookupAddress(final double latitude, final double longitude, boolean useCache)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 valueIfEqual(String a, String b)399 private String valueIfEqual(String a, String b) { 400 return (a != null && b != null && a.equalsIgnoreCase(b)) ? a : null; 401 } 402 writeUTF(DataOutputStream dos, String string)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 readUTF(DataInputStream dis)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