package cgeo.geocaching.address;
import cgeo.geocaching.location.Geopoint;
import cgeo.geocaching.network.Network;
import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.utils.Log;
import android.location.Address;
import android.support.annotation.NonNull;
import java.util.Locale;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.Single;
import io.reactivex.functions.Function;
import org.apache.commons.lang3.StringUtils;
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 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 Single<Address> getFromLocation(@NonNull final Geopoint coords) {
return get("reverse", new Parameters("location", String.format(Locale.US, "%f,%f", coords.getLatitude(), coords.getLongitude()))).firstOrError();
}
private static Observable<Address> get(@NonNull final String method, @NonNull final Parameters parameters) {
return Network.requestJSON("https://open.mapquestapi.com/geocoding/v1/" + method,
parameters.put("key", MAPQUEST_KEY))
.flatMapObservable(new Function<ObjectNode, Observable<Address>>() {
@Override
public Observable<Address> apply(final ObjectNode response) {
final int statusCode = response.path("info").path("statuscode").asInt(-1);
if (statusCode != 0) {
Log.w("MapQuest decoder error: statuscode is not 0");
throw new IllegalStateException("no correct answer from MapQuest geocoder");
}
return Observable.create(new ObservableOnSubscribe<Address>() {
@Override
public void subscribe(final ObservableEmitter<Address> emitter) throws Exception {
try {
for (final JsonNode address : response.get("results").get(0).get("locations")) {
emitter.onNext(mapquestToAddress(address));
}
emitter.onComplete();
} catch (final Exception e) {
Log.e("Error decoding MapQuest address", e);
emitter.onError(e);
}
}
});
}
});
}
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);
}
}
final JsonNode latLng = mapquestAddress.get("latLng");
address.setLatitude(latLng.get("lat").asDouble());
address.setLongitude(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);
address.setCountryName(new Locale("", content).getDisplayCountry());
break;
// Make checkers happy
default:
break;
}
}
}