blob: 3df4c49334b8943257790a9912b6c9d42b03d272 [file] [log] [blame]
Owen Lina2fba682011-08-17 22:07:43 +08001/*
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
17package com.android.gallery3d.util;
18
Owen Lina2fba682011-08-17 22:07:43 +080019import android.content.Context;
20import android.location.Address;
21import android.location.Geocoder;
22import android.location.Location;
23import android.location.LocationManager;
24import android.net.ConnectivityManager;
25import android.net.NetworkInfo;
26
Owen Lin73a04ff2012-03-14 17:27:24 +080027import com.android.gallery3d.common.BlobCache;
28
Owen Lina2fba682011-08-17 22:07:43 +080029import java.io.ByteArrayInputStream;
30import java.io.ByteArrayOutputStream;
31import java.io.DataInputStream;
32import java.io.DataOutputStream;
33import java.io.IOException;
34import java.util.List;
35import java.util.Locale;
36
37public 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
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
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
275 private String checkNull(String locality) {
276 if (locality == null)
277 return "";
278 if (locality.equals("null"))
279 return "";
280 return locality;
281 }
282
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
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
399 private String valueIfEqual(String a, String b) {
400 return (a != null && b != null && a.equalsIgnoreCase(b)) ? a : null;
401 }
402
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
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}