OSDN Git Service

Updates to 3D gallery source.
[android-x86/packages-apps-Gallery2.git] / src / com / cooliris / media / ReverseGeocoder.java
1 package com.cooliris.media;
2
3 import java.io.BufferedInputStream;
4 import java.io.BufferedOutputStream;
5 import java.io.ByteArrayInputStream;
6 import java.io.ByteArrayOutputStream;
7 import java.io.DataInputStream;
8 import java.io.DataOutputStream;
9 import java.io.IOException;
10 import java.util.List;
11 import java.util.Locale;
12
13 import android.content.Context;
14 import android.location.Address;
15 import android.location.Geocoder;
16 import android.os.Process;
17
18 public final class ReverseGeocoder extends Thread {
19     private static final int MAX_COUNTRY_NAME_LENGTH = 8;
20     // If two points are within 50 miles of each other, use "Around Palo Alto, CA" or "Around Mountain View, CA".
21     // instead of directly jumping to the next level and saying "California, US".
22     private static final int MAX_LOCALITY_MILE_RANGE = 50;
23     private static final Deque<MediaSet> sQueue = new Deque<MediaSet>();
24     private static final DiskCache sGeoCache = new DiskCache("geocoder-cache");
25     private static final String TAG = "ReverseGeocoder";
26
27     private Geocoder mGeocoder;
28     private final Context mContext;
29
30     public ReverseGeocoder(Context context) {
31         super(TAG);
32         mContext = context;
33         start();
34     }
35
36     public void enqueue(MediaSet set) {
37         Deque<MediaSet> inQueue = sQueue;
38         synchronized (inQueue) {
39             inQueue.addLast(set);
40             inQueue.notify();
41         }
42     }
43
44     @Override
45     public void run() {
46         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
47         Deque<MediaSet> queue = sQueue;
48         mGeocoder = new Geocoder(mContext);
49         queue.clear();
50         try {
51             for (;;) {
52                 // Wait for the next request.
53                 MediaSet set;
54                 synchronized (queue) {
55                     while ((set = queue.pollFirst()) == null) {
56                         queue.wait();
57                     }
58                 }
59                 // Process the request.
60                 process(set);
61             }
62         } catch (InterruptedException e) {
63             // Terminate the thread.
64         }
65     }
66
67     public void flushCache() {
68         sGeoCache.flush();
69     }
70
71     public void shutdown() {
72         flushCache();
73         this.interrupt();
74     }
75
76     private boolean process(final MediaSet set) {
77         if (!set.mLatLongDetermined) {
78             // No latitude, longitude information available.
79             set.mReverseGeocodedLocationComputed = true;
80             return false;
81         }
82         set.mReverseGeocodedLocation = computeMostGranularCommonLocation(set);
83         set.mReverseGeocodedLocationComputed = true;
84         return true;
85     }
86
87     protected String computeMostGranularCommonLocation(final MediaSet set) {
88         // The overall min and max latitudes and longitudes of the set.
89         double setMinLatitude = set.mMinLatLatitude;
90         double setMinLongitude = set.mMinLonLongitude;
91         double setMaxLatitude = set.mMaxLatLatitude;
92         double setMaxLongitude = set.mMaxLonLongitude;
93         Address addr1 = lookupAddress(setMinLatitude, setMinLongitude);
94         Address addr2 = lookupAddress(setMaxLatitude, setMaxLongitude);
95         if (addr1 == null || addr2 == null) {
96             return null;
97         }
98
99         // Look at the first line of the address.
100         String closestCommonLocation = valueIfEqual(addr1.getAddressLine(0), addr2.getAddressLine(0));
101         if (closestCommonLocation != null && !("null".equals(closestCommonLocation))) {
102             return closestCommonLocation;
103         }
104
105         // Compare thoroughfare (street address) next.
106         closestCommonLocation = valueIfEqual(addr1.getThoroughfare(), addr2.getThoroughfare());
107         if (closestCommonLocation != null && !("null".equals(closestCommonLocation))) {
108             return closestCommonLocation;
109         }
110
111         // Feature names can sometimes be useful like "Golden Gate Bridge" but can also be
112         // degenerate for street address (like just the house number).
113         closestCommonLocation = valueIfEqual(addr1.getFeatureName(), addr2.getFeatureName());
114         if (closestCommonLocation != null && !("null".equals(closestCommonLocation))) {
115             try {
116                 Integer.parseInt(closestCommonLocation);
117                 closestCommonLocation = null;
118             } catch (final NumberFormatException nfe) {
119                 // The feature name is not an integer, allow and continue.
120                 return closestCommonLocation;
121             }
122         }
123
124         // Compare the locality.
125         closestCommonLocation = valueIfEqual(addr1.getLocality(), addr2.getLocality());
126         if (closestCommonLocation != null && !("null".equals(closestCommonLocation))) {
127             String adminArea = addr1.getAdminArea();
128             if (adminArea != null && adminArea.length() > 0) {
129                 closestCommonLocation += ", " + adminArea;
130             }
131             return closestCommonLocation;
132         }
133
134         // Just choose one of the localities if within a 50 mile radius.
135         int distance = (int) LocationMediaFilter.toMile(LocationMediaFilter.distanceBetween(setMinLatitude, setMinLongitude,
136                 setMaxLatitude, setMaxLongitude));
137         if (distance < MAX_LOCALITY_MILE_RANGE) {
138             // Try each of the points and just return the first one to have a valid address.
139             Address minLatAddress = lookupAddress(setMinLatitude, set.mMinLatLongitude);
140             closestCommonLocation = getLocalityAdminForAddress(minLatAddress, true);
141             if (closestCommonLocation != null) {
142                 return closestCommonLocation;
143             }
144             Address minLonAddress = lookupAddress(set.mMinLonLatitude, setMinLongitude);
145             closestCommonLocation = getLocalityAdminForAddress(minLonAddress, true);
146             if (closestCommonLocation != null) {
147                 return closestCommonLocation;
148             }
149             Address maxLatAddress = lookupAddress(setMaxLatitude, set.mMaxLatLongitude);
150             closestCommonLocation = getLocalityAdminForAddress(maxLatAddress, true);
151             if (closestCommonLocation != null) {
152                 return closestCommonLocation;
153             }
154             Address maxLonAddress = lookupAddress(set.mMaxLonLatitude, setMaxLongitude);
155             closestCommonLocation = getLocalityAdminForAddress(maxLonAddress, true);
156             if (closestCommonLocation != null) {
157                 return closestCommonLocation;
158             }
159         }
160
161         // Check the administrative area.
162         closestCommonLocation = valueIfEqual(addr1.getAdminArea(), addr2.getAdminArea());
163         if (closestCommonLocation != null && !("null".equals(closestCommonLocation))) {
164             String countryCode = addr1.getCountryCode();
165             if (countryCode != null && countryCode.length() > 0) {
166                 closestCommonLocation += ", " + countryCode;
167             }
168             return closestCommonLocation;
169         }
170
171         // Check the country codes.
172         closestCommonLocation = valueIfEqual(addr1.getCountryCode(), addr2.getCountryCode());
173         if (closestCommonLocation != null && !("null".equals(closestCommonLocation))) {
174             return closestCommonLocation;
175         }
176         // There is no intersection, let's choose a nicer name.
177         String addr1Country = addr1.getCountryName();
178         String addr2Country = addr2.getCountryName();
179         if (addr1Country.length() > MAX_COUNTRY_NAME_LENGTH || addr2Country.length() > MAX_COUNTRY_NAME_LENGTH) {
180             closestCommonLocation = addr1.getCountryCode() + " - " + addr2.getCountryCode();
181         } else {
182             closestCommonLocation = addr1Country + " - " + addr2Country;
183         }
184         return closestCommonLocation;
185     }
186
187     protected String getReverseGeocodedLocation(final double latitude, final double longitude, final int desiredNumDetails) {
188         String location = null;
189         int numDetails = 0;
190         Address addr = lookupAddress(latitude, longitude);
191
192         if (addr != null) {
193             // Look at the first line of the address, thorough fare and feature name in order and pick one.
194             location = addr.getAddressLine(0);
195             if (location != null && !("null".equals(location))) {
196                 numDetails++;
197             } else {
198                 location = addr.getThoroughfare();
199                 if (location != null && !("null".equals(location))) {
200                     numDetails++;
201                 } else {
202                     location = addr.getFeatureName();
203                     if (location != null && !("null".equals(location))) {
204                         numDetails++;
205                     }
206                 }
207             }
208
209             if (numDetails == desiredNumDetails) {
210                 return location;
211             }
212
213             String locality = addr.getLocality();
214             if (locality != null && !("null".equals(locality))) {
215                 if (location != null && location.length() > 0) {
216                     location += ", " + locality;
217                 } else {
218                     location = locality;
219                 }
220                 numDetails++;
221             }
222
223             if (numDetails == desiredNumDetails) {
224                 return location;
225             }
226
227             String adminArea = addr.getAdminArea();
228             if (adminArea != null && !("null".equals(adminArea))) {
229                 if (location != null && location.length() > 0) {
230                     location += ", " + adminArea;
231                 } else {
232                     location = adminArea;
233                 }
234                 numDetails++;
235             }
236
237             if (numDetails == desiredNumDetails) {
238                 return location;
239             }
240
241             String countryCode = addr.getCountryCode();
242             if (countryCode != null && !("null".equals(countryCode))) {
243                 if (location != null && location.length() > 0) {
244                     location += ", " + countryCode;
245                 } else {
246                     location = addr.getCountryName();
247                 }
248             }
249         }
250
251         return location;
252     }
253
254     private String getLocalityAdminForAddress(final Address addr, final boolean approxLocation) {
255         if (addr == null)
256             return "";
257         String localityAdminStr = addr.getLocality();
258         if (localityAdminStr != null && !("null".equals(localityAdminStr))) {
259             if (approxLocation) {
260                 // TODO: Uncomment these lines as soon as we may translations for R.string.around.
261                 // localityAdminStr = mContext.getResources().getString(R.string.around) + " " + localityAdminStr;
262             }
263             String adminArea = addr.getAdminArea();
264             if (adminArea != null && adminArea.length() > 0) {
265                 localityAdminStr += ", " + adminArea;
266             }
267             return localityAdminStr;
268         }
269         return null;
270     }
271
272     private Address lookupAddress(final double latitude, final double longitude) {
273         try {
274             long locationKey = (long) (((latitude + LocationMediaFilter.LAT_MAX) * 2 * LocationMediaFilter.LAT_MAX + (longitude + LocationMediaFilter.LON_MAX)) * LocationMediaFilter.EARTH_RADIUS_METERS);
275             byte[] cachedLocation = sGeoCache.get(locationKey, 0);
276             Address address = null;
277             if (cachedLocation == null || cachedLocation.length == 0) {
278                 List<Address> addresses = mGeocoder.getFromLocation(latitude, longitude, 1);
279                 if (!addresses.isEmpty()) {
280                     address = addresses.get(0);
281                     ByteArrayOutputStream bos = new ByteArrayOutputStream();
282                     DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(bos, 256));
283                     Locale locale = address.getLocale();
284                     Utils.writeUTF(dos, locale.getLanguage());
285                     Utils.writeUTF(dos, locale.getCountry());
286                     Utils.writeUTF(dos, locale.getVariant());
287
288                     Utils.writeUTF(dos, address.getThoroughfare());
289                     int numAddressLines = address.getMaxAddressLineIndex();
290                     dos.writeInt(numAddressLines);
291                     for (int i = 0; i < numAddressLines; ++i) {
292                         Utils.writeUTF(dos, address.getAddressLine(i));
293                     }
294                     Utils.writeUTF(dos, address.getFeatureName());
295                     Utils.writeUTF(dos, address.getLocality());
296                     Utils.writeUTF(dos, address.getAdminArea());
297                     Utils.writeUTF(dos, address.getSubAdminArea());
298
299                     Utils.writeUTF(dos, address.getCountryName());
300                     Utils.writeUTF(dos, address.getCountryCode());
301                     Utils.writeUTF(dos, address.getPostalCode());
302                     Utils.writeUTF(dos, address.getPhone());
303                     Utils.writeUTF(dos, address.getUrl());
304
305                     dos.flush();
306                     sGeoCache.put(locationKey, bos.toByteArray());
307                     dos.close();
308                 }
309             } else {
310                 // Parsing the address from the byte stream.
311                 DataInputStream dis = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(cachedLocation), 256));
312                 String language = Utils.readUTF(dis);
313                 String country = Utils.readUTF(dis);
314                 String variant = Utils.readUTF(dis);
315                 Locale locale = null;
316                 if (language != null) {
317                     if (country == null) {
318                         locale = new Locale(language);
319                     } else if (variant == null) {
320                         locale = new Locale(language, country);
321                     } else {
322                         locale = new Locale(language, country, variant);
323                     }
324                 }
325                 if (!locale.getLanguage().equals(Locale.getDefault().getLanguage())) {
326                     sGeoCache.delete(locationKey);
327                     dis.close();
328                     return lookupAddress(latitude, longitude);
329                 }
330                 address = new Address(locale);
331
332                 address.setThoroughfare(Utils.readUTF(dis));
333                 int numAddressLines = dis.readInt();
334                 for (int i = 0; i < numAddressLines; ++i) {
335                     address.setAddressLine(i, Utils.readUTF(dis));
336                 }
337                 address.setFeatureName(Utils.readUTF(dis));
338                 address.setLocality(Utils.readUTF(dis));
339                 address.setAdminArea(Utils.readUTF(dis));
340                 address.setSubAdminArea(Utils.readUTF(dis));
341
342                 address.setCountryName(Utils.readUTF(dis));
343                 address.setCountryCode(Utils.readUTF(dis));
344                 address.setPostalCode(Utils.readUTF(dis));
345                 address.setPhone(Utils.readUTF(dis));
346                 address.setUrl(Utils.readUTF(dis));
347                 dis.close();
348             }
349             return address;
350
351         } catch (IOException e) {
352             // Ignore.
353         }
354         return null;
355     }
356
357     private String valueIfEqual(String a, String b) {
358         return (a != null && b != null && a.equalsIgnoreCase(b)) ? a : null;
359     }
360 }