aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--main/src/cgeo/geocaching/MainActivity.java36
-rw-r--r--main/src/cgeo/geocaching/location/AndroidGeocoder.java45
-rw-r--r--main/src/cgeo/geocaching/location/MapQuestGeocoder.java22
-rw-r--r--tests/src/cgeo/geocaching/location/GeocoderTest.java32
4 files changed, 89 insertions, 46 deletions
diff --git a/main/src/cgeo/geocaching/MainActivity.java b/main/src/cgeo/geocaching/MainActivity.java
index de80f5c..399de83 100644
--- a/main/src/cgeo/geocaching/MainActivity.java
+++ b/main/src/cgeo/geocaching/MainActivity.java
@@ -11,7 +11,9 @@ import cgeo.geocaching.enumerations.CacheType;
import cgeo.geocaching.enumerations.StatusCode;
import cgeo.geocaching.list.PseudoList;
import cgeo.geocaching.list.StoredList;
+import cgeo.geocaching.location.AndroidGeocoder;
import cgeo.geocaching.location.Geopoint;
+import cgeo.geocaching.location.MapQuestGeocoder;
import cgeo.geocaching.location.Units;
import cgeo.geocaching.maps.CGeoMap;
import cgeo.geocaching.network.Network;
@@ -37,12 +39,11 @@ import com.google.zxing.integration.android.IntentResult;
import org.apache.commons.lang3.StringUtils;
import rx.Observable;
-import rx.Observable.OnSubscribe;
-import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.android.view.ViewObservable;
import rx.functions.Action0;
import rx.functions.Action1;
+import rx.functions.Func1;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
@@ -54,7 +55,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.location.Address;
-import android.location.Geocoder;
import android.net.ConnectivityManager;
import android.os.Bundle;
import android.os.Handler;
@@ -75,7 +75,6 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-import java.util.Locale;
public class MainActivity extends AbstractActionBarActivity {
@InjectView(R.id.nav_satellites) protected TextView navSatellites;
@@ -572,30 +571,21 @@ public class MainActivity extends AbstractActionBarActivity {
navAccuracy.setText(null);
}
+ final Geopoint currentCoords = geo.getCoords();
if (Settings.isShowAddress()) {
if (addCoords == null) {
navLocation.setText(R.string.loc_no_addr);
}
- if (addCoords == null || (geo.getCoords().distanceTo(addCoords) > 0.5)) {
- final Observable<String> address = Observable.create(new OnSubscribe<String>() {
+ if (addCoords == null || (currentCoords.distanceTo(addCoords) > 0.5)) {
+ addCoords = currentCoords;
+ final Observable<String> address = (new AndroidGeocoder(MainActivity.this).getFromLocation(currentCoords)
+ .onErrorResumeNext(MapQuestGeocoder.getFromLocation(currentCoords))).map(new Func1<Address, String>() {
@Override
- public void call(final Subscriber<? super String> subscriber) {
- try {
- addCoords = geo.getCoords();
- final Geocoder geocoder = new Geocoder(MainActivity.this, Locale.getDefault());
- final List<Address> addresses = geocoder.getFromLocation(addCoords.getLatitude(), addCoords.getLongitude(), 1);
- if (!addresses.isEmpty()) {
- subscriber.onNext(formatAddress(addresses.get(0)));
- }
- subscriber.onCompleted();
- } catch (final Exception e) {
- subscriber.onError(e);
- }
+ public String call(final Address address) {
+ return formatAddress(address);
}
- });
- ViewObservable.bindView(navLocation, address.onErrorResumeNext(Observable.just(geo.getCoords().toString())))
- .subscribeOn(RxUtils.networkScheduler)
- .subscribe(new Action1<String>() {
+ }).onErrorResumeNext(Observable.just(currentCoords.toString()));
+ ViewObservable.bindView(navLocation, address).subscribe(new Action1<String>() {
@Override
public void call(final String address) {
navLocation.setText(address);
@@ -603,7 +593,7 @@ public class MainActivity extends AbstractActionBarActivity {
});
}
} else {
- navLocation.setText(geo.getCoords().toString());
+ navLocation.setText(currentCoords.toString());
}
}
}
diff --git a/main/src/cgeo/geocaching/location/AndroidGeocoder.java b/main/src/cgeo/geocaching/location/AndroidGeocoder.java
index 98ea285..d0d07e8 100644
--- a/main/src/cgeo/geocaching/location/AndroidGeocoder.java
+++ b/main/src/cgeo/geocaching/location/AndroidGeocoder.java
@@ -16,11 +16,6 @@ import android.location.Geocoder;
import java.util.List;
import java.util.Locale;
-/**
- * Encapsulation of the Android {@link Geocoder} with default error handling. All methods of this class
- * are blocking and will do network lookups.
- *
- */
public class AndroidGeocoder {
private final Geocoder geocoder;
@@ -39,21 +34,49 @@ public class AndroidGeocoder {
* @see Geocoder#getFromLocationName(String, int)
*/
public Observable<Address> getFromLocationName(@NonNull final String keyword) {
+ if (!Geocoder.isPresent()) {
+ return Observable.error(new RuntimeException("no Android geocoder"));
+ }
return Observable.defer(new Func0<Observable<Address>>() {
@Override
public Observable<Address> call() {
try {
- final List<Address> addresses = geocoder.getFromLocationName(keyword, 20);
- if (CollectionUtils.isEmpty(addresses)) {
- return Observable.error(new RuntimeException("no result from Android geocoder"));
- }
- return Observable.from(addresses);
+ return addressesToObservable(geocoder.getFromLocationName(keyword, 20));
} catch (final Exception e) {
- Log.i("Unable to use Android geocoder: " + e.getMessage());
+ Log.i("Unable to use Android reverse geocoder: " + e.getMessage());
return Observable.error(e);
}
}
}).subscribeOn(RxUtils.networkScheduler);
}
+ /**
+ * Retrieve the physical address for coordinates. The work happens on the network scheduler.
+ *
+ * @param coords the coordinates
+ * @return an observable containing one location or an error
+ */
+ public Observable<Address> getFromLocation(@NonNull final Geopoint coords) {
+ if (!Geocoder.isPresent()) {
+ return Observable.error(new RuntimeException("no Android reverse geocoder"));
+ }
+ return Observable.defer(new Func0<Observable<Address>>() {
+ @Override
+ public Observable<Address> call() {
+ try {
+ return addressesToObservable(geocoder.getFromLocation(coords.getLatitude(), coords.getLongitude(), 1));
+ } catch (final Exception e) {
+ Log.i("Unable to use Android reverse geocoder: " + e.getMessage());
+ return Observable.error(e);
+ }
+ }
+ }).subscribeOn(RxUtils.networkScheduler).first();
+ }
+
+ private static Observable<Address> addressesToObservable(final List<Address> addresses) {
+ return CollectionUtils.isEmpty(addresses) ?
+ Observable.<Address>error(new RuntimeException("no result from Android geocoder")) :
+ Observable.from(addresses);
+ }
+
}
diff --git a/main/src/cgeo/geocaching/location/MapQuestGeocoder.java b/main/src/cgeo/geocaching/location/MapQuestGeocoder.java
index 537ae40..cc4a6b5 100644
--- a/main/src/cgeo/geocaching/location/MapQuestGeocoder.java
+++ b/main/src/cgeo/geocaching/location/MapQuestGeocoder.java
@@ -39,11 +39,25 @@ public class MapQuestGeocoder {
* @see android.location.Geocoder#getFromLocationName(String, int)
*/
public static Observable<Address> getFromLocationName(@NonNull final String address) {
+ return get("address", new Parameters("location", address, "maxResults", "20", "thumbMaps", "false"));
+ }
+
+ /**
+ * Retrieve the physical address for coordinates. The work happens on the network scheduler.
+ *
+ * @param coords the coordinates
+ * @return an observable containing one location or an error
+ */
+ public static Observable<Address> getFromLocation(@NonNull final Geopoint coords) {
+ return get("reverse", new Parameters("location", String.format(Locale.US, "%f,%f", coords.getLatitude(), coords.getLongitude()))).first();
+ }
+
+ private static Observable<Address> get(@NonNull final String method, @NonNull final Parameters parameters) {
return Observable.defer(new Func0<Observable<Address>>() {
@Override
public Observable<Address> call() {
- final ObjectNode response = Network.requestJSON("https://www.mapquestapi.com/geocoding/v1/address",
- new Parameters("key", MAPQUEST_KEY, "location", address, "maxResults", "20", "thumbMaps", "false"));
+ final ObjectNode response = Network.requestJSON("https://www.mapquestapi.com/geocoding/v1/" + method,
+ parameters.put("key", MAPQUEST_KEY));
if (response == null) {
Log.w("MapQuest decoder error: no response");
return Observable.error(new RuntimeException("no answer from MapQuest geocoder"));
@@ -110,6 +124,10 @@ public class MapQuestGeocoder {
break;
case "Country":
address.setCountryCode(content);
+ address.setCountryName(new Locale("", content).getDisplayCountry());
+ break;
+ // Make checkers happy
+ default:
break;
}
}
diff --git a/tests/src/cgeo/geocaching/location/GeocoderTest.java b/tests/src/cgeo/geocaching/location/GeocoderTest.java
index f53c074..0cc1a79 100644
--- a/tests/src/cgeo/geocaching/location/GeocoderTest.java
+++ b/tests/src/cgeo/geocaching/location/GeocoderTest.java
@@ -4,45 +4,57 @@ import static org.assertj.core.api.Assertions.assertThat;
import cgeo.CGeoTestCase;
import cgeo.geocaching.CgeoApplication;
+import cgeo.geocaching.utils.Log;
import org.apache.commons.lang3.StringUtils;
import org.assertj.core.data.Offset;
import rx.Observable;
-import android.annotation.TargetApi;
import android.location.Address;
import android.location.Geocoder;
-import android.os.Build;
public class GeocoderTest extends CGeoTestCase {
private static final String TEST_ADDRESS = "46 rue Barrault, Paris, France";
private static final double TEST_LATITUDE = 48.82677;
private static final double TEST_LONGITUDE = 2.34644;
+ private static final Geopoint TEST_COORDS = new Geopoint(TEST_LATITUDE, TEST_LONGITUDE);
private static final Offset<Double> TEST_OFFSET = Offset.offset(0.00050);
- @TargetApi(Build.VERSION_CODES.GINGERBREAD)
public static void testAndroidGeocoder() {
// Some emulators don't have access to Google Android geocoder
if (Geocoder.isPresent()) {
- testGeocoder(new AndroidGeocoder(CgeoApplication.getInstance()).getFromLocationName(TEST_ADDRESS), "Android");
+ final AndroidGeocoder geocoder = new AndroidGeocoder(CgeoApplication.getInstance());
+ testGeocoder(geocoder.getFromLocationName(TEST_ADDRESS), "Android", true);
+ testGeocoder(geocoder.getFromLocation(TEST_COORDS), "Android reverse", true);
+ } else {
+ Log.i("not testing absent Android geocoder");
}
}
public static void testGCGeocoder() {
- testGeocoder(GCGeocoder.getFromLocationName(TEST_ADDRESS), "GC");
+ testGeocoder(GCGeocoder.getFromLocationName(TEST_ADDRESS), "GC", false);
}
public static void testMapQuestGeocoder() {
- testGeocoder(MapQuestGeocoder.getFromLocationName(TEST_ADDRESS), "MapQuest");
+ testGeocoder(MapQuestGeocoder.getFromLocationName(TEST_ADDRESS), "MapQuest", true);
+ testGeocoder(MapQuestGeocoder.getFromLocation(TEST_COORDS), "MapQuest reverse", true);
}
- public static void testGeocoder(final Observable<Address> addressObservable, final String geocoder) {
+ public static void testGeocoder(final Observable<Address> addressObservable, final String geocoder, final boolean withAddress) {
final Address address = addressObservable.toBlocking().first();
- assertThat(address.getLatitude()).as("latitude for " + geocoder + " geocoder").isCloseTo(TEST_LATITUDE, TEST_OFFSET);
- assertThat(address.getLongitude()).as("longitude for " + geocoder + " geocoder").isCloseTo(TEST_LONGITUDE, TEST_OFFSET);
- assertThat(StringUtils.lowerCase(address.getAddressLine(0))).as("street address for " + geocoder + " geocoder").startsWith("46 rue barrault");
+ assertThat(address.getLatitude()).as(describe("latitude", geocoder)).isCloseTo(TEST_LATITUDE, TEST_OFFSET);
+ assertThat(address.getLongitude()).as(describe("longitude", geocoder)).isCloseTo(TEST_LONGITUDE, TEST_OFFSET);
+ if (withAddress) {
+ assertThat(StringUtils.lowerCase(address.getAddressLine(0))).as(describe("street address", geocoder)).startsWith("46 rue barrault");
+ assertThat(address.getLocality()).as(describe("locality", geocoder)).isEqualTo("Paris");
+ assertThat(address.getCountryName()).as(describe("country name", geocoder)).isEqualTo("France");
+ }
+ }
+
+ private static String describe(final String field, final String geocoder) {
+ return new StringBuilder(field).append(" for ").append(geocoder).append(" .geocoder").toString();
}
}