Add Turn-by-Turn Navigation with Mapbox Directions API
Mapbox Directions API provides a variety of navigation features for driving, cycling and walking, including turn-by-turn directions, traffic-aware routing, and ETA calculation. And with Mapbox, all your functionality is overlayed on one of their many beautiful vector maps.
In this tutorial, we’ll use the Mapbox Directions BLOCK to implement turn-by-turn navigation to build directions into your real-time app. For example, if you’re building a taxi or ridesharing application, the BLOCK enables you to stream geolocation coordinates data between users (like a driver and passenger), then provide navigation to connect them. Or for a delivery app, you can send drop off locations to a fleet of trucks based on their current coordinates and guide them there.
Our simple direction demo will look like this:
Getting Started with Mapbox Direction API
First, you’ll need a Mapbox developer account to take advantage of the mapping APIs, including Directions.
- Go to the Mapbox signup form.
- Go to the Mapbox studio page and configure your services.
- Look at the “Access token” pane of the dashboard and make note of the credential to update the BLOCK below.
Overall, it’s a pretty quick process. And free to get started, which is a bonus!
Setting up the BLOCK
Next up is BLOCKS, which ties together your real-time functionality (via PubNub), with the Mapbox Directions API. In a nutshell, BLOCKS lets you execute functions on your data in-motion, in this case, tap the Mapbox API to calculate your directions and publish them to the end-user.
First, go to the application instance on the PubNub Admin Dashboard.
Create a new BLOCK.
Paste in the BLOCK code from the next section and update the client token with the Mapbox client token from the previous steps above.
Start the BLOCK, and test it using the “publish message” button and payload on the left-hand side of the screen. That’s all it takes to create your serverless code running in the cloud!
Diving into the Code – the BLOCK
You’ll want to grab the 27 lines of BLOCK JavaScript and save them to a file called pubnub_mapbox_directions_block.js
. It’s available as a Gist on GitHub for your convenience.
First up, we declare our dependency on xhr and query (for HTTP requests) and create a function to handle incoming messages.
export default request => { let xhr = require('xhr'); let query = require('codec/query_string');
Next, we set up variables for accessing the service (the API url and client token).
let clientToken = 'YOUR_CLIENT_TOKEN'; let apiUrl = 'https://api.mapbox.com/directions/v5';
Next, we set up the HTTP params for the directions API URL (note: in this demo, the client browser requests the URL, we are just constructing that URL in the BLOCK).
We use a GET request for the URL. We use the JSON content of the message as the source of most important parameters, and hard-code the steps parameter to true (for now).
let profile = request.message.profile; let lat1 = request.message.lat1; let lng1 = request.message.lng1; let lat2 = request.message.lat2; let lng2 = request.message.lng2; let queryParams = { steps:true, access_token: clientToken, };
Finally, we construct the directions URL with the given data and decorate the message with a directions
attribute containing the API request URL. Pretty easy!
apiUrl += '/' + profile + '/'; apiUrl += lng1 + ',' + lat1 + ';'; apiUrl += lng2 + ',' + lat2; let url = apiUrl + '?' + query.stringify(queryParams); request.message.directions = url; return request.ok(); };
All in all, it doesn’t take a lot of code to add turn-by-turn directions to our application. Next is the UI.
Diving into the Code – the User Interface
You’ll want to grab these 110 lines of HTML & JavaScript and save them to a file called pubnub_mapbox_directions_ui.html
.
The first thing you should do after saving the code is to replace two values in the JavaScript:
- YOUR_PUB_KEY: with the PubNub publish key mentioned above.
- YOUR_SUB_KEY: with the PubNub subscribe key mentioned above.
If you don’t, the UI will not be able to communicate with anything and probably clutter your console log with entirely too many errors. For your convenience, this code is also available as a Gist on GitHub, and a Codepen as well.
Dependencies
First up, we have the JavaScript code & CSS dependencies of our application.
<!DOCTYPE html> <html> <head> <title>Angular 2</title> <script src="https://unpkg.com/core-js@2.4.1/client/shim.min.js"></script> <script src="https://unpkg.com/zone.js@0.7.2/dist/zone.js"></script> <script src="https://unpkg.com/reflect-metadata@0.1.9/Reflect.js"></script> <script src="https://unpkg.com/rxjs@5.0.1/bundles/Rx.js"></script> <script src="https://unpkg.com/@angular/core/bundles/core.umd.js"></script> <script src="https://unpkg.com/@angular/common/bundles/common.umd.js"></script> <script src="https://unpkg.com/@angular/compiler/bundles/compiler.umd.js"></script> <script src="https://unpkg.com/@angular/platform-browser/bundles/platform-browser.umd.js"></script> <script src="https://unpkg.com/@angular/forms/bundles/forms.umd.js"></script> <script src="https://unpkg.com/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js"></script> <script src="https://unpkg.com/@angular/http/bundles/http.umd.js"></script> <script src="https://unpkg.com/pubnub@4.3.3/dist/web/pubnub.js"></script> <script src="https://unpkg.com/pubnub-angular2@1.0.0-beta.8/dist/pubnub-angular2.js"></script> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" /> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" /> </head>
For folks who have done front-end implementation with Angular2 before, these should be the usual suspects:
- CoreJS ES6 Shim, Zone.JS, Metadata Reflection, and RxJS : Dependencies of Angular2.
- Angular2 : core, common, compiler, platform-browser, forms, dynamic platform browser, and http modules.
- PubNub JavaScript client: to connect to our data stream integration channel.
- PubNub Angular2 JavaScript client: provides PubNub services in Angular2 quite nicely indeed.
In addition, we bring in the CSS features:
- Bootstrap: in this app, we use it just for vanilla UI presentation.
Overall, we were pretty pleased that we could build a nifty UI with so few dependencies.
The User Interface
Here’s what we intend the UI to look like:
The UI is pretty straightforward – everything is inside a main-component
tag that is managed by a single component that we’ll set up in the Angular2 code.
<body> <main-component> Loading... </main-component>
Let’s skip forward and show that Angular2 component template. We provide a simple button to perform the publish()
action which gets turn-by-turn directions via a structured data message that will be decorated by the BLOCK.
<div class="container"> <pre> NOTE: make sure to update the PubNub keys below with your keys, and ensure that the BLOCK settings are configured properly! </pre> <h3>MyApp Directions Integration</h3> <hr/> <p>Current location (requires location permission) : <b>lat1</b>={{lat1}}, <b>lng1</b>={{lng1}})</p> <p>Suggested Destination : <b>lat2</b>={{lat2}}, <b>lng2</b>={{lng2}})</p> <p>Profile : <b>{{profile}}</b></p> <button class="btn btn-primary" (click)="publish()">Get Directions!</button> <br/> <br/> <ol> <li *ngFor="let item of steps?.routes[0].legs[0].steps">{{item.maneuver.instruction}} for {{item.distance}} ft</li> </ol> <br /> <br /> </div>
The component UI consists of a simple list of steps (in our case, the turn-by-turn directions). We iterate over the steps in the controller scope using a trusty ngFor
. Each step includes a wealth of data from the Mapbox API. And that’s it – a functioning real-time UI in just a handful of code (thanks, Angular2)!
The Angular2 Code
Right on! Now we’re ready to dive into the Angular2 code. It’s not a ton of JavaScript, so this should hopefully be pretty straightforward.
The first lines we encounter set up our application (with a necessary dependency on the PubNub AngularJS service) and a single component (which we dub main-component
).
<script> var app = window.app = {}; app.main_component = ng.core.Component({ selector: 'main-component', template: `...see previous...`
The component has a constructor that takes care of initializing the PubNub and HTTP services, and configuring the channel name, client and destination lat/lng values, and directions profile name (walking, cycling or driving). NOTE: make sure the channel matches the channel specified by your BLOCK configuration and the BLOCK itself!
}).Class({ constructor: [PubNubAngular, ng.http.Http, function(pubnubService, http){ var self = this; self.pubnubService = pubnubService; self.http = http; self.steps = null; self.channelName = 'mapbox-directions-channel'; self.profile = "mapbox/walking"; self.lat1 = self.lng1 = "loading..."; self.lat2 = 37.7972705; self.lng2 = -122.4273027;
Early on, we initialize the pubnubService
with our credentials.
pubnubService.init({ publishKey: 'YOUR_PUB_KEY', subscribeKey: 'YOUR_SUB_KEY', uuid: PubNub.generateUUID(), ssl:true });
We subscribe to the relevant channel, create a dynamic attribute for the messages collection, and configure the event handler to set the self.steps
attribute based on the JSON returned from the directions API.
pubnubService.subscribe({channels: [self.channelName], triggerEvents: true}); self.messages = pubnubService.getMessage(this.channelName,function(msg){ self.http .get(msg.message.directions) .map((r)=>r.json()) .subscribe(data => self.steps = data); });
Also, since Angular2 doesn’t have an $interval
component, we use RxJS Observable
to set up an interval timer for updating the client browser’s location. Every 3 seconds, we update the Angular2 component with the client’s latitude/longitude.
Rx.Observable.interval(3000).subscribe(()=>(navigator.geolocation.getCurrentPosition((position)=>{ self.lat = position.coords.latitude; self.lng = position.coords.longitude; }))); }],
We also create a publish()
event handler that performs the action of publishing the new message to the PubNub channel including start/finish locations and directions profile identifier.
publish: function(){ this.pubnubService.publish({channel: this.channelName,message:{lat1:this.lat1,lng1:this.lng1,lat2:this.lat2,lng2:this.lng2,profile:this.profile}}); } });
Now that we have a new component, we can create a main module for the Angular2 app that uses it. This is pretty standard boilerplate that configures dependencies on the Browser, Forms, and HTTP modules and the PubNubAngular service.
app.main_module = ng.core.NgModule({ imports: [ng.platformBrowser.BrowserModule, ng.forms.FormsModule, ng.http.HttpModule], declarations: [app.main_component], providers: [PubNubAngular], bootstrap: [app.main_component] }).Class({ constructor: function(){} });
Finally, we bind the application bootstrap initialization to the browser DOM content loaded event.
document.addEventListener('DOMContentLoaded', function(){ ng.platformBrowserDynamic.platformBrowserDynamic().bootstrapModule(app.main_module); });
We mustn’t forget close out the HTML tags accordingly.
}); </script> </body> </html>
Not too shabby for about 110 lines of HTML & JavaScript! With that, we conclude this tutorial. You now have a functioning app that calculates turn-by-turn navigation, processed entire in the PubNub network and pushed in real time to your end user.
If you liked this and are looking for other dynamic mapping tutorials, we’ve got a ton of other Mapbox tutorials, including embedding static maps, as well as adding geocoding (turn text input into location).