com.mapbox.api.geocoding.v5.MapboxGeocoding Maven / Gradle / Ivy
package com.mapbox.api.geocoding.v5;
import androidx.annotation.FloatRange;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.auto.value.AutoValue;
import com.google.gson.GsonBuilder;
import com.mapbox.api.geocoding.v5.GeocodingCriteria.GeocodingTypeCriteria;
import com.mapbox.api.geocoding.v5.models.GeocodingAdapterFactory;
import com.mapbox.api.geocoding.v5.models.GeocodingResponse;
import com.mapbox.core.MapboxService;
import com.mapbox.core.constants.Constants;
import com.mapbox.core.exceptions.ServicesException;
import com.mapbox.core.utils.ApiCallHelper;
import com.mapbox.core.utils.MapboxUtils;
import com.mapbox.core.utils.TextUtils;
import com.mapbox.geojson.BoundingBox;
import com.mapbox.geojson.GeometryAdapterFactory;
import com.mapbox.geojson.Point;
import com.mapbox.geojson.gson.BoundingBoxTypeAdapter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* This class gives you access to both Mapbox forward and reverse geocoding.
*
* Forward geocoding lets you convert location text into geographic coordinates, turning
* {@code 2 Lincoln Memorial Circle NW} into a {@link Point} with the coordinates
* {@code -77.050, 38.889}.
*
* Reverse geocoding turns geographic coordinates into place names, turning {@code -77.050, 38.889}
* into {@code 2 Lincoln Memorial Circle NW}. These place names can vary from specific addresses to
* states and countries that contain the given coordinates.
*
* Batch Geocoding
* The {@link #mode()} must be set to
* {@link GeocodingCriteria#MODE_PLACES_PERMANENT}.
* For more information about batch geocoding, contact Mapbox sales.
*
* Batch requests have the same parameters as normal requests, but can include more than one query
* by using {@link MapboxGeocoding.Builder#query(String)} and separating queries with the {@code ;}
* character.
*
* With the {@link GeocodingCriteria#MODE_PLACES_PERMANENT} mode, you can make up to 50 forward or
* reverse geocoding queries in a single request. The response is a list of individual
* {@link GeocodingResponse}s. Each query in a batch request counts individually against your
* account's rate limits.
*
* @see Android
* Geocoding documentation
* @since 1.0.0
*/
@AutoValue
public abstract class MapboxGeocoding extends MapboxService {
private Call> batchCall;
protected MapboxGeocoding() {
super(GeocodingService.class);
}
@Override
protected GsonBuilder getGsonBuilder() {
return new GsonBuilder()
.registerTypeAdapterFactory(GeocodingAdapterFactory.create())
.registerTypeAdapterFactory(GeometryAdapterFactory.create())
.registerTypeAdapterFactory(SingleElementSafeListTypeAdapter.FACTORY)
.registerTypeAdapter(BoundingBox.class, new BoundingBoxTypeAdapter());
}
@Override
protected Call initializeCall() {
if (mode().contains(GeocodingCriteria.MODE_PLACES_PERMANENT)) {
throw new IllegalArgumentException("Use getBatchCall() for batch calls.");
}
return getService().getCall(
ApiCallHelper.getHeaderUserAgent(clientAppName()),
mode(),
query(),
accessToken(),
country(),
proximity(),
geocodingTypes(),
autocomplete(),
bbox(),
limit(),
languages(),
reverseMode(),
fuzzyMatch());
}
private Call> getBatchCall() {
// No need to recreate it
if (batchCall != null) {
return batchCall;
}
if (mode().equals(GeocodingCriteria.MODE_PLACES)) {
throw new ServicesException(
"Use getCall() for non-batch calls or set the mode to `permanent` for batch requests."
);
}
batchCall = getService().getBatchCall(
ApiCallHelper.getHeaderUserAgent(clientAppName()),
mode(),
query(),
accessToken(),
country(),
proximity(),
geocodingTypes(),
autocomplete(),
bbox(),
limit(),
languages(),
reverseMode(),
fuzzyMatch());
return batchCall;
}
/**
* Wrapper method for Retrofits {@link Call#execute()} call returning a batch response specific to
* the Geocoding API.
*
* @return the Geocoding v5 batch response once the call completes successfully
* @throws IOException Signals that an I/O exception of some sort has occurred.
* @since 1.0.0
*/
public Response> executeBatchCall() throws IOException {
return getBatchCall().execute();
}
/**
* Wrapper method for Retrofits {@link Call#enqueue(Callback)} call returning a batch response
* specific to the Geocoding batch API. Use this method to make a geocoding request on the Main
* Thread.
*
* @param callback a {@link Callback} which is used once the {@link GeocodingResponse} is created.
* @since 1.0.0
*/
public void enqueueBatchCall(Callback> callback) {
getBatchCall().enqueue(callback);
}
/**
* Wrapper method for Retrofits {@link Call#cancel()} call, important to manually cancel call if
* the user dismisses the calling activity or no longer needs the returned results.
*
* @since 1.0.0
*/
public void cancelBatchCall() {
getBatchCall().cancel();
}
/**
* Wrapper method for Retrofits {@link Call#clone()} call, useful for getting call information.
*
* @return cloned call
* @since 1.0.0
*/
public Call> cloneBatchCall() {
return getBatchCall().clone();
}
@NonNull
abstract String query();
@NonNull
abstract String mode();
@NonNull
abstract String accessToken();
@NonNull
@Override
protected abstract String baseUrl();
@Nullable
abstract String country();
@Nullable
abstract String proximity();
@Nullable
abstract String geocodingTypes();
@Nullable
abstract Boolean autocomplete();
@Nullable
abstract String bbox();
@Nullable
abstract String limit();
@Nullable
abstract String languages();
@Nullable
abstract String reverseMode();
@Nullable
abstract Boolean fuzzyMatch();
@Nullable
abstract String clientAppName();
/**
* Build a new {@link MapboxGeocoding} object with the initial values set for
* {@link #baseUrl()} and {@link #mode()}.
*
* @return a {@link Builder} object for creating this object
* @since 3.0.0
*/
public static Builder builder() {
return new AutoValue_MapboxGeocoding.Builder()
.baseUrl(Constants.BASE_API_URL)
.mode(GeocodingCriteria.MODE_PLACES);
}
/**
* This builder is used to create a new request to the Mapbox Geocoding API. At a bare minimum,
* your request must include an access token and a query of some kind. All other fields can
* be left alone in order to use the default behaviour of the API.
*
* By default, the geocoding mode is set to mapbox.places.
* The mode can be changed to mapbox.places-permanent
* to enable batch and permanent geocoding. For more information about
* mapbox.places-permanent, contact Mapbox sales.
*
* Note to contributors: All optional booleans in this builder use the object {@code Boolean}
* rather than the primitive to allow for unset (null) values.
*
*
* @since 1.0.0
*/
@AutoValue.Builder
public abstract static class Builder {
private List countries = new ArrayList<>();
private List intersectionStreets = new ArrayList<>();
/**
* Perform a reverse geocode on the provided {@link Point}. Only one point can be passed in as
* the query and isn't guaranteed to return a result. If you
* want to do a batch reverse Geocode, you can use the {@link #query(String)} method
* separating them with a semicolon. For more information about batch geocoding, contact Mapbox sales.
*
* @param point a GeoJSON point which matches to coordinate you'd like to reverse geocode
* @return this builder for chaining options together
* @since 3.0.0
*/
public Builder query(@NonNull Point point) {
query(String.format(Locale.US, "%s,%s",
TextUtils.formatCoordinate(point.longitude()),
TextUtils.formatCoordinate(point.latitude())));
return this;
}
/**
* This method can be used for performing a forward geocode on a string representing a address
* or POI. If you want to perform a batch geocode, separate your
* queries with a semicolon. For more information about batch geocoding,
* contact Mapbox sales.
*
* @param query a String containing the text you'd like to forward geocode
* @return this builder for chaining options together
* @since 3.0.0
*/
public abstract Builder query(@NonNull String query);
/**
* This sets the kind of geocoding result you desire, either ephemeral geocoding or batch
* geocoding.
*
* To access batch geocoding, contact Mapbox sales.
* If you do not have access to batch geocoding, it will return
* an error code rather than a successful result.
*
* Options avaliable to pass in include, {@link GeocodingCriteria#MODE_PLACES} for a ephemeral
* geocoding result (default) or {@link GeocodingCriteria#MODE_PLACES_PERMANENT} for
* batch and permanent geocoding.
*
*
* @param mode mapbox.places or mapbox.places-permanent for batch and permanent geocoding
* @return this builder for chaining options together
* @since 1.0.0
*/
public abstract Builder mode(@NonNull @GeocodingCriteria.GeocodingModeCriteria String mode);
/**
* Bias local results base on a provided {@link Point}. This oftentimes increases accuracy in
* the returned results.
*
* @param proximity a point defining the proximity you'd like to bias the results around
* @return this builder for chaining options together
* @since 1.0.0
*/
public Builder proximity(@NonNull Point proximity) {
proximity(String.format(Locale.US, "%s,%s",
TextUtils.formatCoordinate(proximity.longitude()), proximity.latitude()));
return this;
}
abstract Builder proximity(String proximity);
/**
* This optionally can be set to filter the results returned back after making your forward or
* reverse geocoding request. A null value can't be passed in and only values defined in
* {@link GeocodingTypeCriteria} are allowed.
*
* Note that {@link GeocodingCriteria#TYPE_POI_LANDMARK} returns a subset of the results
* returned by {@link GeocodingCriteria#TYPE_POI}. More than one type can be specified.
*
*
* @param geocodingTypes optionally filter the result types by one or more defined types inside
* the {@link GeocodingTypeCriteria}
* @return this builder for chaining options together
* @since 1.0.0
*/
public Builder geocodingTypes(@NonNull @GeocodingTypeCriteria String... geocodingTypes) {
geocodingTypes(TextUtils.join(",", geocodingTypes));
return this;
}
abstract Builder geocodingTypes(String geocodingTypes);
/**
* Add a single country locale to restrict the results. This method can be called as many times
* as needed inorder to add multiple countries.
*
* @param country limit geocoding results to one
* @return this builder for chaining options together
* @since 3.0.0
*/
public Builder country(Locale country) {
countries.add(country.getCountry());
return this;
}
/**
* Limit results to one or more countries. Options are ISO 3166 alpha 2 country codes separated
* by commas.
*
* @param country limit geocoding results to one
* @return this builder for chaining options together
* @since 3.0.0
*/
public Builder country(String... country) {
countries.addAll(Arrays.asList(country));
return this;
}
/**
* Limit results to one or more countries. Options are ISO 3166 alpha 2 country codes separated
* by commas.
*
* @param country limit geocoding results to one
* @return this builder for chaining options together
* @since 3.0.0
*/
public abstract Builder country(String country);
/**
* This controls whether autocomplete results are included. Autocomplete results can partially
* match the query: for example, searching for {@code washingto} could include washington even
* though only the prefix matches. Autocomplete is useful for offering fast, type-ahead results
* in user interfaces.
*
* If your queries represent complete addresses or place names, you can disable this behavior
* and exclude partial matches by setting this to false, the defaults true.
*
* @param autocomplete optionally set whether to allow returned results to attempt prediction of
* the full words prior to the user completing the search terms
* @return this builder for chaining options together
* @since 1.0.0
*/
public abstract Builder autocomplete(Boolean autocomplete);
/**
* Limit the results to a defined bounding box. Unlike {@link #proximity()}, this will strictly
* limit results to within the bounding box only. If simple biasing is desired rather than a
* strict region, use proximity instead.
*
* @param bbox the bounding box as a {@link BoundingBox}
* @return this builder for chaining options together
* @since 4.7.0
*/
public Builder bbox(BoundingBox bbox) {
bbox(bbox.southwest().longitude(), bbox.southwest().latitude(),
bbox.northeast().longitude(), bbox.northeast().latitude());
return this;
}
/**
* Limit the results to a defined bounding box. Unlike {@link #proximity()}, this will strictly
* limit results to within the bounding box only. If simple biasing is desired rather than a
* strict region, use proximity instead.
*
* @param northeast the northeast corner of the bounding box as a {@link Point}
* @param southwest the southwest corner of the bounding box as a {@link Point}
* @return this builder for chaining options together
* @since 1.0.0
*/
public Builder bbox(Point southwest, Point northeast) {
bbox(southwest.longitude(), southwest.latitude(),
northeast.longitude(), northeast.latitude());
return this;
}
/**
* Limit the results to a defined bounding box. Unlike {@link #proximity()}, this will strictly
* limit results to within the bounding box only. If simple biasing is desired rather than a
* strict region, use proximity instead.
*
* @param minX the minX of bounding box when maps facing north
* @param minY the minY of bounding box when maps facing north
* @param maxX the maxX of bounding box when maps facing north
* @param maxY the maxY of bounding box when maps facing north
* @return this builder for chaining options together
* @since 1.0.0
*/
public Builder bbox(@FloatRange(from = -180, to = 180) double minX,
@FloatRange(from = -90, to = 90) double minY,
@FloatRange(from = -180, to = 180) double maxX,
@FloatRange(from = -90, to = 90) double maxY) {
bbox(String.format(Locale.US, "%s,%s,%s,%s",
TextUtils.formatCoordinate(minX),
TextUtils.formatCoordinate(minY),
TextUtils.formatCoordinate(maxX),
TextUtils.formatCoordinate(maxY))
);
return this;
}
/**
* Limit the results to a defined bounding box. Unlike {@link #proximity()}, this will strictly
* limit results to within the bounding box only. If simple biasing is desired rather than a
* strict region, use proximity instead.
*
* @param bbox a String defining the bounding box for biasing results ordered in
* {@code minX,minY,maxX,maxY}
* @return this builder for chaining options together
* @since 1.0.0
*/
public abstract Builder bbox(@NonNull String bbox);
/**
* This optionally specifies the maximum number of results to return. For forward geocoding, the
* default is 5 and the maximum is 10. For reverse geocoding, the default is 1 and the maximum
* is 5. If a limit other than 1 is used for reverse geocoding, a single types option must also
* be specified.
*
* @param limit the number of returned results
* @return this builder for chaining options together
* @since 2.0.0
*/
public Builder limit(@IntRange(from = 1, to = 10) int limit) {
limit(String.valueOf(limit));
return this;
}
abstract Builder limit(String limit);
/**
* This optionally specifies the desired response language for user queries. For forward
* geocodes, results that match the requested language are favored over results in other
* languages. If more than one language tag is supplied, text in all requested languages will be
* returned. For forward geocodes with more than one language tag, only the first language will
* be used to weight results.
*
* Any valid IETF language tag can be submitted, and a best effort will be made to return
* results in the requested language or languages, falling back first to similar and then to
* common languages in the event that text is not available in the requested language. In the
* event a fallback language is used, the language field will have a different value than the
* one requested.
*
* Translation availability varies by language and region, for a full list of supported regions,
* see the link provided below.
*
* @param languages one or more locale's specifying the language you'd like results to support
* @return this builder for chaining options together
* @see Supported languages
*
* @since 2.0.0
*/
public Builder languages(Locale... languages) {
String[] languageStrings = new String[languages.length];
for (int i = 0; i < languages.length; i++) {
languageStrings[i] = languages[i].getLanguage();
}
languages(TextUtils.join(",", languageStrings));
return this;
}
/**
* This optionally specifies the desired response language for user queries. For forward
* geocodes, results that match the requested language are favored over results in other
* languages. If more than one language tag is supplied, text in all requested languages will be
* returned. For forward geocodes with more than one language tag, only the first language will
* be used to weight results.
*
* Any valid IETF language tag can be submitted, and a best effort will be made to return
* results in the requested language or languages, falling back first to similar and then to
* common languages in the event that text is not available in the requested language. In the
* event a fallback language is used, the language field will have a different value than the
* one requested.
*
* Translation availability varies by language and region, for a full list of supported regions,
* see the link provided below.
*
* @param languages a String specifying the language or languages you'd like results to support
* @return this builder for chaining options together
* @see Supported languages
*
* @since 2.0.0
*/
public abstract Builder languages(String languages);
/**
* Set the factors that are used to sort nearby results.
* Options avaliable to pass in include, {@link GeocodingCriteria#REVERSE_MODE_DISTANCE} for
* nearest feature result (default) or {@link GeocodingCriteria#REVERSE_MODE_SCORE}
* the notability of features within approximately 1 kilometer of the queried point
* along with proximity.
*
* @param reverseMode limit geocoding results based on the reverseMode
* @return this builder for chaining options together
* @since 3.3.0
*/
public abstract Builder reverseMode(
@Nullable @GeocodingCriteria.GeocodingReverseModeCriteria String reverseMode);
/**
* Specify whether the Geocoding API should attempt approximate, as well as exact,
* matching when performing searches (true, default), or whether it should opt out
* of this behavior and only attempt exact matching (false). For example, the default
* setting might return Washington, DC for a query of wahsington
, even
* though the query was misspelled.
*
* @param fuzzyMatch optionally set whether to allow the geocoding API to attempt
* exact matching or not.
* @return this builder for chaining options together
* @since 4.9.0
*/
public abstract Builder fuzzyMatch(Boolean fuzzyMatch);
/**
* Required to call when this is being built. If no access token provided,
* {@link ServicesException} will be thrown.
*
* @param accessToken Mapbox access token, You must have a Mapbox account inorder to use
* the Geocoding API
* @return this builder for chaining options together
* @since 1.0.0
*/
public abstract Builder accessToken(@NonNull String accessToken);
/**
* Base package name or other simple string identifier. Used inside the calls user agent header.
*
* @param clientAppName base package name or other simple string identifier
* @return this builder for chaining options together
* @since 1.0.0
*/
public abstract Builder clientAppName(@NonNull String clientAppName);
/**
* Optionally change the APIs base URL to something other then the default Mapbox one.
*
* @param baseUrl base url used as end point
* @return this builder for chaining options together
* @since 1.0.0
*/
public abstract Builder baseUrl(@NonNull String baseUrl);
abstract MapboxGeocoding autoBuild();
/**
* Specify the two street names for intersection search.
*
* @param streetOneName First street name of the intersection
* @param streetTwoName Second street name of the intersection
* @return this builder for chaining options together
* @since 4.9.0
*/
public Builder intersectionStreets(@NonNull String streetOneName,
@NonNull String streetTwoName) {
intersectionStreets.add(streetOneName);
intersectionStreets.add(streetTwoName);
return this;
}
/**
* Build a new {@link MapboxGeocoding} object.
*
* @return a new {@link MapboxGeocoding} using the provided values in this builder
* @since 3.0.0
*/
public MapboxGeocoding build() {
if (!countries.isEmpty()) {
country(TextUtils.join(",", countries.toArray()));
}
if (intersectionStreets.size() == 2) {
query(TextUtils.join(" and ", intersectionStreets.toArray()));
geocodingTypes(GeocodingCriteria.TYPE_ADDRESS);
}
// Generate build so that we can check that values are valid.
MapboxGeocoding geocoding = autoBuild();
if (!MapboxUtils.isAccessTokenValid(geocoding.accessToken())) {
throw new ServicesException("Using Mapbox Services requires setting a valid access token.");
}
if (geocoding.query().isEmpty()) {
throw new ServicesException("A query with at least one character or digit is required.");
}
if (geocoding.reverseMode() != null
&& geocoding.limit() != null && !geocoding.limit().equals("1")) {
throw new ServicesException("Limit must be combined with a single type parameter");
}
if (intersectionStreets.size() == 2) {
if (!(geocoding.mode().equals(GeocodingCriteria.MODE_PLACES)
|| geocoding.mode().equals(GeocodingCriteria.MODE_PLACES_PERMANENT))) {
throw new ServicesException("Geocoding mode must be GeocodingCriteria.MODE_PLACES "
+ "or GeocodingCriteria.MODE_PLACES_PERMANENT for intersection search.");
}
if (TextUtils.isEmpty(geocoding.geocodingTypes())
|| !geocoding.geocodingTypes().equals(GeocodingCriteria.TYPE_ADDRESS)) {
throw new ServicesException("Geocoding type must be set to Geocoding "
+ "Criteria.TYPE_ADDRESS for intersection search.");
}
if (TextUtils.isEmpty(geocoding.proximity())) {
throw new ServicesException("Geocoding proximity must be set for intersection search.");
}
}
return geocoding;
}
}
}