• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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