aboutsummaryrefslogtreecommitdiffstats
path: root/main/src/cgeo/geocaching/location
diff options
context:
space:
mode:
Diffstat (limited to 'main/src/cgeo/geocaching/location')
-rw-r--r--main/src/cgeo/geocaching/location/AndroidGeocoder.java59
-rw-r--r--main/src/cgeo/geocaching/location/GCGeocoder.java65
-rw-r--r--main/src/cgeo/geocaching/location/Geocoder.java61
-rw-r--r--main/src/cgeo/geocaching/location/GeopointParser.java2
-rw-r--r--main/src/cgeo/geocaching/location/MapQuestGeocoder.java117
-rw-r--r--main/src/cgeo/geocaching/location/Viewport.java5
6 files changed, 245 insertions, 64 deletions
diff --git a/main/src/cgeo/geocaching/location/AndroidGeocoder.java b/main/src/cgeo/geocaching/location/AndroidGeocoder.java
new file mode 100644
index 0000000..98ea285
--- /dev/null
+++ b/main/src/cgeo/geocaching/location/AndroidGeocoder.java
@@ -0,0 +1,59 @@
+package cgeo.geocaching.location;
+
+import cgeo.geocaching.utils.Log;
+import cgeo.geocaching.utils.RxUtils;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.eclipse.jdt.annotation.NonNull;
+
+import rx.Observable;
+import rx.functions.Func0;
+
+import android.content.Context;
+import android.location.Address;
+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;
+
+ public AndroidGeocoder(final Context context) {
+ geocoder = new Geocoder(context, Locale.getDefault());
+ }
+
+ /**
+ * Retrieve addresses from a textual location using Android geocoding API. The work happens on the network
+ * scheduler.
+ *
+ * @param keyword
+ * the location
+ * @return an observable containing zero or more locations
+ *
+ * @see Geocoder#getFromLocationName(String, int)
+ */
+ public Observable<Address> getFromLocationName(@NonNull final String keyword) {
+ 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);
+ } catch (final Exception e) {
+ Log.i("Unable to use Android geocoder: " + e.getMessage());
+ return Observable.error(e);
+ }
+ }
+ }).subscribeOn(RxUtils.networkScheduler);
+ }
+
+}
diff --git a/main/src/cgeo/geocaching/location/GCGeocoder.java b/main/src/cgeo/geocaching/location/GCGeocoder.java
new file mode 100644
index 0000000..549044f
--- /dev/null
+++ b/main/src/cgeo/geocaching/location/GCGeocoder.java
@@ -0,0 +1,65 @@
+package cgeo.geocaching.location;
+
+import cgeo.geocaching.network.Network;
+import cgeo.geocaching.network.Parameters;
+import cgeo.geocaching.settings.Settings;
+import cgeo.geocaching.utils.Log;
+import cgeo.geocaching.utils.RxUtils;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jdt.annotation.NonNull;
+
+import rx.Observable;
+import rx.functions.Func0;
+
+import android.location.Address;
+
+import java.util.Locale;
+
+public class GCGeocoder {
+
+ private GCGeocoder() {
+ // Do not instantiate
+ }
+
+ /**
+ * Retrieve addresses from a textual location using geocaching.com geocoding API. The work happens on the network
+ * scheduler.
+ *
+ * @param address
+ * the location
+ * @return an observable containing zero or more locations
+ *
+ * @see android.location.Geocoder#getFromLocationName(String, int)
+ */
+ public static Observable<Address> getFromLocationName(@NonNull final String address) {
+ return Observable.defer(new Func0<Observable<Address>>() {
+ @Override
+ public Observable<Address> call() {
+ if (!Settings.isGCConnectorActive()) {
+ return Observable.error(new RuntimeException("geocaching.com connector is not active"));
+ }
+ final ObjectNode response = Network.requestJSON("https://www.geocaching.com/api/geocode", new Parameters("q", address));
+ if (response == null || !StringUtils.equalsIgnoreCase(response.path("status").asText(), "success")) {
+ return Observable.error(new RuntimeException("unable to use geocaching.com geocoder"));
+ }
+
+ final JsonNode data = response.path("data");
+ final Address geocodedAddress = new Address(Locale.getDefault());
+ try {
+ geocodedAddress.setLatitude(data.get("lat").asDouble());
+ geocodedAddress.setLongitude(data.get("lng").asDouble());
+ geocodedAddress.setAddressLine(0, address);
+ return Observable.just(geocodedAddress);
+ } catch (final Exception e) {
+ Log.e("unable to decode answer from geocaching.com geocoder", e);
+ return Observable.error(e);
+ }
+ }
+ }).subscribeOn(RxUtils.networkScheduler);
+ }
+
+}
diff --git a/main/src/cgeo/geocaching/location/Geocoder.java b/main/src/cgeo/geocaching/location/Geocoder.java
deleted file mode 100644
index 1582daa..0000000
--- a/main/src/cgeo/geocaching/location/Geocoder.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package cgeo.geocaching.location;
-
-import cgeo.geocaching.utils.Log;
-
-import org.apache.commons.lang3.StringUtils;
-import org.eclipse.jdt.annotation.NonNull;
-
-import android.content.Context;
-import android.location.Address;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Encapsulation of the Android {@link android.location.Geocoder} with default error handling. All methods of this class
- * are blocking and will do network lookups.
- *
- */
-public class Geocoder {
- private final android.location.Geocoder geocoder;
-
- public Geocoder(final Context context) {
- geocoder = new android.location.Geocoder(context, Locale.getDefault());
- }
-
- /**
- * @param keyword
- * @return
- *
- * @see android.location.Geocoder#getFromLocationName(String, int)
- */
- public @NonNull List<Address> getFromLocationName(final String keyword) {
- try {
- return geocoder.getFromLocationName(keyword, 20);
- } catch (final Exception e) {
- handleException(e);
- return Collections.emptyList();
- }
- }
-
- public @NonNull List<Address> getFromLocation(final Geopoint coords) {
- try {
- return geocoder.getFromLocation(coords.getLatitude(), coords.getLongitude(), 20);
- } catch (final IOException e) {
- handleException(e);
- return Collections.emptyList();
- }
- }
-
- private static void handleException(final Exception e) {
- // non Google devices come without the geocoder
- if (StringUtils.containsIgnoreCase(e.getMessage(), "Service not Available")) {
- Log.i("No geocoder available");
- }
- else {
- Log.e("Geocoder", e);
- }
- }
-}
diff --git a/main/src/cgeo/geocaching/location/GeopointParser.java b/main/src/cgeo/geocaching/location/GeopointParser.java
index e73e787..a6b8e45 100644
--- a/main/src/cgeo/geocaching/location/GeopointParser.java
+++ b/main/src/cgeo/geocaching/location/GeopointParser.java
@@ -122,7 +122,7 @@ class GeopointParser {
// Nothing found with "N 52...", try to match string as decimal degree parts (i.e. multiple doubles)
try {
- final String[] items = StringUtils.split(text.trim());
+ final String[] items = StringUtils.split(StringUtils.trimToEmpty(text));
if (items.length > 0 && items.length <= 2) {
final int index = (latlon == LatLon.LON ? items.length - 1 : 0);
final String textPart = items[index];
diff --git a/main/src/cgeo/geocaching/location/MapQuestGeocoder.java b/main/src/cgeo/geocaching/location/MapQuestGeocoder.java
new file mode 100644
index 0000000..537ae40
--- /dev/null
+++ b/main/src/cgeo/geocaching/location/MapQuestGeocoder.java
@@ -0,0 +1,117 @@
+package cgeo.geocaching.location;
+
+import cgeo.geocaching.network.Network;
+import cgeo.geocaching.network.Parameters;
+import cgeo.geocaching.utils.Log;
+import cgeo.geocaching.utils.RxUtils;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jdt.annotation.NonNull;
+
+import rx.Observable;
+import rx.Observable.OnSubscribe;
+import rx.Subscriber;
+import rx.functions.Func0;
+
+import android.location.Address;
+
+import java.util.Locale;
+
+public class MapQuestGeocoder {
+
+ private static final String MAPQUEST_KEY = "Fmjtd|luurn1u2n9,bs=o5-9wynua";
+
+ private MapQuestGeocoder() {
+ // Do not instantiate
+ }
+
+ /**
+ * Retrieve addresses from a textual location using MapQuest geocoding API. The work happens on the network
+ * scheduler.
+ *
+ * @param address
+ * the location
+ * @return an observable containing zero or more locations
+ *
+ * @see android.location.Geocoder#getFromLocationName(String, int)
+ */
+ public static Observable<Address> getFromLocationName(@NonNull final String address) {
+ 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"));
+ if (response == null) {
+ Log.w("MapQuest decoder error: no response");
+ return Observable.error(new RuntimeException("no answer from MapQuest geocoder"));
+ }
+ final int statusCode = response.path("info").path("statuscode").asInt(-1);
+ if (statusCode != 0) {
+ Log.w("MapQuest decoder error: statuscode is not 0");
+ return Observable.error(new RuntimeException("no correct answer from MapQuest geocoder"));
+ }
+ return Observable.create(new OnSubscribe<Address>() {
+ @Override
+ public void call(final Subscriber<? super Address> subscriber) {
+ try {
+ for (final JsonNode address: response.get("results").get(0).get("locations")) {
+ subscriber.onNext(mapquestToAddress(address));
+ }
+ subscriber.onCompleted();
+ } catch (final Exception e) {
+ Log.e("Error decoding MapQuest address", e);
+ subscriber.onError(e);
+ }
+ }
+ });
+ }
+ }).subscribeOn(RxUtils.networkScheduler);
+ }
+
+ private static Address mapquestToAddress(final JsonNode mapquestAddress) {
+ final Address address = new Address(Locale.getDefault());
+ for (int i = 1; i <= 6; i++) {
+ final String adminAreaName = "adminArea" + i;
+ setComponent(address, mapquestAddress, adminAreaName, mapquestAddress.path(adminAreaName + "Type").asText());
+ }
+ setComponent(address, mapquestAddress, "postalCode", "PostalCode");
+ int index = 0;
+ for (final String addressComponent: new String[]{ mapquestAddress.path("street").asText(), address.getSubLocality(), address.getLocality(),
+ address.getPostalCode(), address.getSubAdminArea(), address.getAdminArea(), address.getCountryCode() }) {
+ if (StringUtils.isNotBlank(addressComponent)) {
+ address.setAddressLine(index++, addressComponent);
+ }
+ }
+ address.setLatitude(mapquestAddress.get("latLng").get("lat").asDouble());
+ address.setLongitude(mapquestAddress.get("latLng").get("lng").asDouble());
+ return address;
+ }
+
+ private static void setComponent(final Address address, final JsonNode mapquestAddress, final String adminArea, final String adminAreaType) {
+ final String content = StringUtils.trimToNull(mapquestAddress.path(adminArea).asText());
+ switch (adminAreaType) {
+ case "City":
+ address.setLocality(content);
+ break;
+ case "Neighborhood":
+ address.setSubLocality(content);
+ break;
+ case "PostalCode":
+ address.setPostalCode(content);
+ break;
+ case "State":
+ address.setAdminArea(content);
+ break;
+ case "County":
+ address.setSubAdminArea(content);
+ break;
+ case "Country":
+ address.setCountryCode(content);
+ break;
+ }
+ }
+
+}
diff --git a/main/src/cgeo/geocaching/location/Viewport.java b/main/src/cgeo/geocaching/location/Viewport.java
index e482828..b885336 100644
--- a/main/src/cgeo/geocaching/location/Viewport.java
+++ b/main/src/cgeo/geocaching/location/Viewport.java
@@ -87,7 +87,7 @@ public final class Viewport {
*/
public int count(final @NonNull Collection<? extends ICoordinates> points) {
int total = 0;
- for (ICoordinates point: points) {
+ for (final ICoordinates point: points) {
if (point != null && contains(point)) {
total += 1;
}
@@ -102,7 +102,7 @@ public final class Viewport {
/**
* Check whether another viewport is fully included into the current one.
- *
+ *
* @param vp
* the other viewport
* @return true if the viewport is fully included into this one, false otherwise
@@ -118,6 +118,7 @@ public final class Viewport {
* the database table to use as prefix, or null if no prefix is required
* @return the string without the "where" keyword
*/
+ @NonNull
public StringBuilder sqlWhere(@Nullable final String dbTable) {
final String prefix = dbTable == null ? "" : (dbTable + ".");
return new StringBuilder(prefix).append("latitude >= ").append(getLatitudeMin()).append(" and ")