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. This SDK does not generate transactions on your account and is available free to use.
Navigation SDK is only available in the U.S. at this time. If you would like to use Navigation SDK outside of the U.S. please contact your account manager or customer support
The following APIs are often used with the Navigation SDK, but note these do incur a transaction cost to use:
Create a new Android project; note that the minimum Android SDK version supported by the MapQuest NavSDK is API level 16.
Add the following to the top of your app’s 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"
}
}
}
Also add the following items under the dependencies
section of the build.gradle
file for the app:
dependencies {
implementation('com.mapquest:navigation:3.4.0') // the MapQuest Navigation SDK
implementation "com.google.android.gms:play-services-location:15.0.1"
implementation('com.mapquest:mapping-android-sdk:2.0.10') { // for Mapping SDK MapView; displays route to navigate
exclude group: 'com.android.support' // (note: in your app, you might omit this 'exclude')
}
implementation('com.mapquest:searchahead:1.3.0) // 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.
Note that in addition to the implementation notes provided in each section below, it will often prove helpful to refer to the MapQuest Navigation SDK Reference Sample Application, which provides a complete working example upon which you can base your own host application.
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.
// Set up start and destination for the route
Coordinate nyc = new Coordinate(40.7326808, -73.9843407);
List<Coordinate> 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<Route> 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.
Moreover, you can also request walking directions for routes as an alternative to driving routes, by specifying
the RouteType
with an option of PEDESTRIAN
. See the javadocs
for further details.New in 3.5.0
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.
Our Navigation SDK uses a default radius of 50m and which is optimized for most destinations; however in some
cases you may wish to customize the arrival radius of a destination to better match its size profile. For example a stadium
may be better handled with a large 1000m radius. Simply create the destination : new Destination(@NonNull
Coordinate coordinate, @Nullable String mqId, @Nullable Double arrivalAcceptanceRadius)
and pass that to
the requestRoutes
method.New in 3.4.0
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.
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(@NonNull Destination destination, 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();
}
}
Note that your host application can further inspect the Destination
object passed to the above
handler to provide
the user more details about the waypoint or destination reached; especially if the destination was specified as an
MQID
.
Often you may need to provide a quick ETA (estimated time to arrival) and traffic information before you even start a navigation session. This may be useful as a dashboard for ETA to different points (such as home/work) or as users inquire about points of interest on the map or in your application. The route summary service provides a very quick response providing you with the ETA, Traffic, and Length to a specific destination without the heavy network bandwidth used for a whole route request.
The main advantages of using the route summary call over getting a route are: performance, optimized data delivery, and ease of use.
To get a route summary is very similar to getting a full route request. The differences are that you don't provide any route options and only one destination is supported.
// Set up start and destination for the route
Coordinate nyc = new Coordinate(40.7326808, -73.9843407);
Destination boston = new Destination(new Coordinate(42.355097, -71.055464), nil);
mRouteService.getRouteSummary(nyc, boston, new RouteSummaryResponseListener() {
@Override
public void onRouteSummaryRetrieved(@NonNull EstimatedTimeOfArrival estimatedTimeOfArrival, Route.TrafficOverview trafficConditions, int realTimeInSeconds, double routeLength) {
SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("h:mm a", Locale.ROOT);
Toast.makeText(self, "Route Summary" + " " + TIME_FORMAT.format(new Date(estimatedTimeOfArrival.getTime())) + " " + trafficConditions.name(), Toast.LENGTH_LONG).show();
}
@Override
public void onRequestFailed(@Nullable Integer httpStatusCode, @Nullable IOException exception) {}
@Override
public void onRequestMade() {}
});
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.
The Navigation SDK Reference Sample
Application provides an example implementation using the Google Fuze location
provider library, GoogleLocationProviderAdapter
-- refer to the documentation
for more details on the LocationProviderAdapter
and the implementation of GoogleLocationProviderAdapter
.
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.
The MapQuest Navigation SDK is designed to collect location data during active navigation sessions in order to improve the quality of our routes and traffic data. Verizon/Mapquest believes the user's privacy is of the highest importance. In order to safeguard user privacy, your application is required to request explicit consent from the user before navigation will proceed. There is currently no approved use case whereby Verizon data collected by this SDK may be shared with a third party.
Note that Traffic Data Collection is enabled by default in the MapQuest Navigation SDK, as of version 3.3. If the particulars of your application require that user location information is not used by MapQuest to improve its traffic and navigation services, you should inquire with your MapQuest Account Representative for details on how to disable this functionality.New in 3.3.0
In any case, your host application -- when using v3.3 or later of the MapQuest Navigation SDK -- must now present
the user with a "Location Tracking Consent" dialog, and must call the method setUserLocationTrackingConsentStatus
before it will be able to start navigating any route using the NavigationManager
.
This status flag has three possible values, as defined in the enum class
UserLocationTrackingConsentStatus
:
Per the above, if you attempt to call startNavigation
while the NavigationManager is in the awaiting
consent state, navigation will not start and you will receive an error that consent has not been set.
Therefore, before the very first navigation session, you must present the user with a consent dialog, and set this
flag accordingly. You may, of course, persist the user's response and set it for future navigation sessions; i.e.
the user need only agree to the terms once.
Our recommended in-app disclosure language for the consent dialog is as follows:
Allow collection of location information even when the app is not in use. In addition to use for in-app features, location helps improve other products and services provided by the Verizon family of companies.
Here are some example code-snippets from the sample app, which demonstrate how you might display such a dialog, set the user-consent flag, and pass it along (through a Navigation Activity) to the foreground-service -- where the NavigationManager is initialized. For example, here's a method which might be invoked when the user presses the "Start" button to begin navigating:
@OnClick(R.id.start)
protected void startNavigationActivity() {
final SharedPreferences sharedPreferences = getSharedPreferences(SHARED_PREFERENCE_NAME, MODE_PRIVATE);
if (sharedPreferences.contains(USER_TRACKING_CONSENT_KEY)) {
//
// user has already specified their consent (or not) to location tracking,
// so start navigating the currently selected route...
//
startNavigation(mSelectedRoute, sharedPreferences.getBoolean(USER_TRACKING_CONSENT_KEY, false));
} else {
//
// present the user with a consent dialog per location tracking, and persist this decision
//
new AlertDialog.Builder(this)
.setTitle(R.string.user_tracking_consent_dialog_title)
.setMessage(R.string.user_tracking_consent_dialog_message)
.setCancelable(false)
.setPositiveButton(R.string.user_tracking_consent_dialog_positive_button_text,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
sharedPreferences.edit()
.putBoolean(USER_TRACKING_CONSENT_KEY, true)
.apply();
startNavigation(mSelectedRoute, true);
}
})
.setNegativeButton(R.string.user_tracking_consent_dialog_negative_button_text,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
sharedPreferences.edit()
.putBoolean(USER_TRACKING_CONSENT_KEY, false)
.apply();
startNavigation(mSelectedRoute, false);
}
})
.show();
}
}
...where the implementation of the method startNavigation
used above would simply invoke your host
application's NavigationActivity
with the selected route, and with the boolean value expressing the
user's consent
decision.
For example:
private void startNavigation(Route route, boolean userAllowedTracking) {
NavigationActivity.start(getApplicationContext(), route, userAllowedTracking);
}
Then, in this example, via means of an intent extra, the NavigationActivity
passes the
user-consent value along to the bound foreground-service, NavigationNotificationService
-- which
manages the NavigationManager
instance -- and therefore is ultimately responsible for setting the
consent status with this value via setUserLocationTrackingConsentStatus
, like so:
private NavigationManager createNavigationManager(LocationProviderAdapter locationProviderAdapter) {
mNavigationManager = new NavigationManager.Builder(this, BuildConfig.API_KEY, locationProviderAdapter).build();
UserLocationTrackingConsentStatus userLocationTrackingConsentStatus = mUserConsentGranted ?
UserLocationTrackingConsentStatus.GRANTED : UserLocationTrackingConsentStatus.DENIED;
mNavigationManager.setUserLocationTrackingConsentStatus(userLocationTrackingConsentStatus);
return mNavigationManager;
}
Finally, in the NavigationActivity
, once the NavigationManager instance has been initialized by the
(connected) Service, we can simply invoke the actual startNavigation
method -- with the current route
to be navigated:
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
mNotificationService = NavigationNotificationService.fromBinder(binder);
mNotificationService.setRoute(mRoute);
mNavigationManager = mNotificationService.getNavigationManager();
addNavigationListeners(mNavigationManager);
mNavigationManager.startNavigation(mRoute);
// ...
}
Refer to the complete implementation in the sample app for further details on all of the above.