The MapQuest Navigation SDK enables turn-by-turn GPS in any iOS or Android application. Developers now have the ability to customize their UI and gain insight into the navigation experience as users drive.
The SDK provides:
build.gradle
file
-- such that it includes the maven URL for the MapQuest artifacts repository:
allprojects {
repositories {
jcenter()
maven {
url "https://maven.google.com"
}
maven {
url "http://artifactory.cloud.mapquest.com/artifactory/et-android-binaries"
}
}
}
dependencies
section of the build.gradle
file for the app:
dependencies {
compile('com.mapquest:navigation:3.2.2') // the MapQuest Navigation SDK
compile('com.mapzen.android:lost:3.0.4') // used for GPS Location Acquisition (see notes)
compile('com.mapquest:mapping-android-sdk:2.0.1') { // for Mapping SDK MapView; displays route to navigate
exclude group: 'com.android.support' // (note: in your app, you might omit this 'exclude')
}
compile('com.mapquest:search-ahead-v3:1.2.9') // for the MapQuest "Search Ahead" feature (optional)
}
In order to use MapQuest APIs and SDKs you will need a MapQuest key. We use this key to associate your requests to APIs with your account. You can find your existing keys or create a new one on the Applications page.
If you don't have a MapQuest Developer Account you can Sign Up Here.
Once you have a MapQuest Key, per above, create a new file named mapquest.properties
in the app
directory
of your project, containing the following single-line entry:
api_key=[PUT_YOUR_API_KEY_HERE]
Then, add the following items under the `android` section of the build.gradle
file for the app:
applicationVariants.all { variant ->
variant.buildConfigField "String", "API_KEY", getApiKey() // Provides key for the Navigation SDK
variant.resValue "string", "API_KEY", getApiKey() // Provides key for the MapView used in app layouts
}
Per the above, also add the following method (at the top-level) to the build.gradle
for the app -- to define the getApiKey()
method:
def getApiKey() {
def props = new Properties()
file("mapquest.properties").withInputStream { props.load(it) }
return "\"" + props.getProperty("api_key") + "\""
}
Before adding any of the example code outlined below, try building your app thus far -- to ensure that the above setup and library dependencies are indeed correct.
In general, developers using the Navigation SDK will first create a query against the RouteService
,
and then call the method startNavigation
on the returned route -- using an instance of the NavigationManager
which captures callbacks for navigation-related events. Events are triggered when:
Here's a simple example of how to create a route from New York City to Boston, and then start a turn-by-turn navigation session.
First, we create an instance of the RouteService
, using our API_KEY
, like so:
mRouteService = new RouteService.Builder().build(getApplicationContext(), BuildConfig.API_KEY);
Additionally, we'll also need an instance of NavigationManager
, which we will use to navigate the route
selected after it has been retrieved -- note that it requires a LocationProviderAdapter
(discussed further below):
mNavigationManager = new NavigationManager.Builder(this, BuildConfig.API_KEY)
.withLocationProviderAdapter(mApp.getLocationProviderAdapter())
.build();
Now, we can define a start and destination(s) for the route --
and query the RouteService
for the possible route(s) between these locations -- specifying various RouteOptions
as desired -- for example to allow (or disallow) highway routes, toll-roads, ferries, etc.
Note also that language
can now also be specified as an option;
specify the IETF language tag (with locale) as the string argument, e.g. "en_US" for English in the US; or "es_US" for Spanish in the US.New in 3.2.0
// Set up start and destination for the route
Coordinate nyc = new Coordinate(40.7326808, -73.9843407);
List boston = Arrays.asList(new Coordinate(42.355097, -71.055464));
// Set up route options
RouteOptions routeOptions = new RouteOptions.Builder()
.maxRoutes(3)
.systemOfMeasurementForDisplayText(SystemOfMeasurement.UNITED_STATES_CUSTOMARY) // or specify METRIC
.language("en_US") // NOTE: alternately, specify "es_US" for Spanish in the US
.highways(RouteOptionType.ALLOW)
.tolls(RouteOptionType.ALLOW)
.ferries(RouteOptionType.DISALLOW)
.internationalBorders(RouteOptionType.DISALLOW)
.unpaved(RouteOptionType.DISALLOW)
.seasonalClosures(RouteOptionType.AVOID)
.build();
mRouteService.requestRoutes(nyc, boston, routeOptions, new RoutesResponseListener() {
@Override
public void onRoutesRetrieved(List routes) {
if (routes.size() > 0) {
mNavigationManager.startNavigation((Route) routes.get(0));
}
}
@Override
public void onRequestFailed(@Nullable Integer httpStatusCode, @Nullable IOException exception) {}
@Override
public void onRequestMade() {}
});
In the example onRoutesRetrieved
callback, above, note that we use our
NavigationManager
to startNavigation
on, say, the first Route
that was retrieved --
though in a real application we might first "render" the resulting routes on a map-view, and allow the user to select
the one they wish to navigate.
Also, note that the system of measurement used for any prompts or other "display text" in the returned routes can also be specified using
systemOfMeasurementForDisplayText
; which can be set to either UNITED_STATES_CUSTOMARY
or METRIC
.
However, do note that all distance values in the route data itself are always returned in meters (metric units); i.e. this option
affects the units for text values only.
Refer to the Navigation SDK Reference Sample Application code to see a complete example of how to
leverage the MapQuest Android SDK (MapView) to draw the routes returned from the RouteService
.
Now that you have the basics in place -- using the RouteService
and using the NavigationManager
to start navigation
along a selected route -- the next step is to leverage one or more of the available callbacks provided by various listener interfaces,
provided in the SDK package: com.mapquest.navigation.listener
.
Simply define an implementation of a given listener interface, and implement the desired functionality for each callback method of interest.
For example, if we want to simply update a message in the UI to inform the user that navigation has started or stopped, we can implement a
NavigationStartStopListener
, and add it to our NavigationManager
instance, like so:
mNavigationManager.addAndNotifyNavigationStartStopListener(new NavigationStartStopListener() {
@Override
public void onNavigationStarted() {
Toast.makeText(mApp, "Navigation Started...", Toast.LENGTH_SHORT).show();
}
@Override
public void onNavigationStopped(@NonNull RouteStoppedReason routeStoppedReason) {
Toast.makeText(mApp, "Navigation Stopped.", Toast.LENGTH_SHORT).show();
}
});
Another commonly used listener is the EtaResponseListener
, used to update the UI when the Estimated Time of Arrival has changed while navigating a route.
For example, you could add one like so:
mNavigationManager.addEtaResponseListener(new EtaResponseListener() {
@Override
public void onEtaUpdate(@NonNull EstimatedTimeOfArrival estimatedTimeOfArrival) {
// TODO: update your ETA (text) view here...
}
});
The complete set of available listeners are described in detail in the Navigation SDK API Docs, and include:
Upon reaching a destination, the NavigationProgressListener
provides an method which notifies this listener of the RouteLeg
that was completed, and whether the destination was
the final destination.
If you implement this listener method, you are required to call the DestinationAcceptanceHandler
provided in order to either continue to the next destination or finish navigation.New in 3.2.0
Note that by default, navigation automatically stops when the final destination has been reached. For intermediate destinations, you may want to effectively pause navigation; and provide some kind of user-interface (e.g. an alert-dialog) to allow the user to resume navigating once they continue driving towards the next route-stop.
Refer to the Sample App for an example implementation of how to handle the onDestinationReached
callback;
here's the relevant snippet, as supplied in the example NavigationActivity
:
@Override
public void onDestinationReached(boolean isFinalDestination, @NonNull RouteLeg routeLegCompleted, @NonNull final DestinationAcceptanceHandler destinationAcceptanceHandler) {
if (!isFinalDestination) {
final AlertDialog alertDialog = new AlertDialog.Builder(NavigationActivity.this)
.setTitle("Arrived at Waypoint")
.setMessage("Intermediate destination reached. Press 'Proceed' to proceed to the next stop...")
.setPositiveButton("Proceed", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
destinationAcceptanceHandler.confirmArrival(true);
updateDirectionsList();
// allow user to skip ahead only if not on final leg of route
showSkipLegButton(!isNavigatingFinalRouteLeg());
}
})
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
destinationAcceptanceHandler.confirmArrival(false);
}
})
.show();
} else {
clearMarkup();
final AlertDialog alertDialog = new AlertDialog.Builder(NavigationActivity.this)
.setTitle("Done Navigating")
.setMessage("Final destination reached")
.setPositiveButton("OK", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
destinationAcceptanceHandler.confirmArrival(true);
finish(); // OK, done with NavigationActivity...
}
}).setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
destinationAcceptanceHandler.confirmArrival(false);
}
})
.show();
}
}
When navigating a route using the NavigationManager
, the user's location updates are provided to the SDK
by a location provider. The abstract class LocationProviderAdapter
in the SDK provides a generic "wrapper" for any
location-provider of your choice -- for example, a native Google location service, a "mocked" location-provider
(e.g. used during testing), or one of the commonly used 3rd-party location libraries such as
Mapzen Lost.
The Navigation SDK Reference Sample Application provides an example implementation using the Mapzen Lost location
provider library, MapzenLocationProviderAdapter
-- refer to the documentation
for more details on the LocationProviderAdapter
and the implementation of MapzenLocationProviderAdapter
.
Normally, the Navigation SDK can be used only while the application is in the foreground. However, if developers want to have the navigation experience continue while the app has been backgrounded -- and also to persist through various Android OS actions (e.g. if the app being killed to free memory, or per Android Oreo location-update restrictions), some extra setup is required. To handle this use-case correctly, a Foreground Service should be created, started, and bound to your application.
In the sample app provided, NavigationNotificationService
is an example Android Service which
maintains an instance of NavigationManager
. It is used to ensure navigation continues in the case
that the app is backgrounded and reclaimed by the OS. Activities in the sample app can bind to the service
and retrieve the navigation manager.
In addition, note that the example NavigationNotificationService is also responsible for building and updating various notifications that occur during navigation -- which appear, of course, in the system Notification Drawer -- and which are typically shown from an application running in the background.
Note that the MapQuest Navigation SDK is not shipped with an integrated TTS (text-to-speech) implementation, but instead allows the developer to specify their own TTS engine per their application’s specific requirements.
This is accomplished by means of a simple interface,PromptListener
, which can be added to the NavigationManager
to handle each prompt event.
Nevertheless, an example implementation of TTS is provided as part of the sample app,
which uses Android’s native TextToSpeech Engine -- see the TextToSpeechPromptListener
class.
This class delegates to the example TextToSpeechManager
which handles all aspects of TTS playback,
including “overlapping” prompt-events, managing the “audio focus”, and Android API backwards-compatibility concerns.