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