IN THIS ARTICLE
In recent years, the line between “application” and “mobile application” has become increasingly blurry. Right now, developers are creating responsive HTML5 applications that function on a huge range of devices, from laptops to TVs to smart phones, tablets and IoT devices. As the level of mobile accessibility has increased, so has the importance of mapping and location capabilities.
Enter Mapbox, a phenomenal platform for building location-aware applications.
In this article, we present a moderately sophisticated example of how to enable realtime geocoding and mapping in an AngularJS web application with a modest 44-line PubNub JavaScript BLOCK and 73 lines of HTML and JavaScript. Geocoding refers to the ability to turn text descriptions of places into map coordinates. It can be useful in a variety situations, such as finding nearby restaurants, landmarks, or even reverse geocoding for finding places nearby. It typically faces challenges due to level of granularity, accuracy of crowdsourced data, and power constraints of maintaining precise GPS location.
Mapping refers to display of vector-based or raster graphics to represent a user’s location. Accordingly, there are a number of issues to consider, including zoom levels, accuracy of maps over time, control of map interfaces using limited screen real estate, and political versus physical features of interest. Taken together though, these services can give powerful location-aware context to mobile applications.
As we prepare to explore our sample AngularJS web application with geocoding and mapping features, let’s take a couple seconds to check out the underlying Mapbox APIs.
Mapbox Geocoding and Mapping APIs
There are a ton of APIs available in the Mapbox platform – in this article, we just focus on 2 APIs that should be useful to get you started. Geocoding is the first one – it allows you to translate place names and addresses to map coordinates. Maps is the second one – it allows a huge variety of options to create your own custom maps. With both of these APIs, we’re not even scratching the surface – check out the API documentation to learn more.
Getting Started with Mapbox
The next thing you’ll need to get started with Mapbox services is a Mapbox developer account to take advantage of the mapping APIs.
- Step 1: go to the Mapbox signup form.
- Step 2: go to the Mapbox studio page and configure your services.
- Step 3: 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 a PubNub BLOCK
With PubNub BLOCKS, it’s really easy to create code to run in the network. Create a new block and event handler in the PubNub admin dashboard, then paste in the BLOCK code from the next section. Make sure you update the credentials with the Mapbox credentials from the previous steps above.
Now 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!
Coding the BLOCK
You’ll want to grab the 44 lines of BLOCK JavaScript and save them to a file, say, pubnub_mapbox.js
. It’s available as a Gist on GitHub for your convenience.
First up, we declare our dependencies: xhr (for HTTP requests), and query (for query string encoding).
let xhr = require('xhr'); let query = require('codec/query_string');
Next, we create a method to handle incoming messages, declare the credential for accessing the Mapbox services, and set up the URLs for talking to the remote web services.
export default request => { let mapbox_clientToken = '000000000000000000000000000000000000000000000000000000000000000000000000'; let mapbox_geo_apiUrl = 'https://api.mapbox.com/geocoding/v5/mapbox.places/'; let mapbox_maps_apiUrl = 'https://api.mapbox.com/styles/v1/mapbox/streets-v8/static';
We retrieve the query from the message map and return if it’s not present.
let searchParam = request.message.query; if (!query) { return request.ok(); }
We declare static settings maps for the query parameters and URL values as follows.
let mapbox_queryParams = { access_token: mapbox_clientToken, }; let geo_url = mapbox_geo_apiUrl + escape(searchParam) + '.json' + '?' + query.stringify(mapbox_queryParams); let map_zoom = 9; let map_resolution = '200x150';
Since this is JavaScript and we are making a series of asynchronous operations, please brace yourself for some nesting and callbacks. The first part of the operation is making an asynchronous call to the geocoding API to get the coordinates.
return xhr.fetch(geo_url) .then((response) => { return response.json() .then((parsedResponse) => {
Then, we examine the parsed response. If we found a place via the geocoding search, we use the first one and decorate the message with its description and a map of the area.
if (parsedResponse.features && parsedResponse.features[0]) { let geo = parsedResponse.features[0]; request.message.geocode = geo; request.message.map = mapbox_maps_apiUrl += '/' + geo.center[0] + ',' + geo.center[1] + ',' + map_zoom + '/' + map_resolution + '?' + query.stringify(mapbox_queryParams); } return request;
The last bit is all error handling…
.catch((err) => { console.log('error happened on JSON parse', err); return request; }); }) .catch((err) => { console.log('error happened for XHR.fetch', err); return request; }); };
OK, let’s move on to the UI!
The User Interface
You’ll want to grab the 73 lines of HTML & JavaScript and save them to a file, say, pubnub_mapbox.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. Enjoy!
Dependencies
First up, we have the JavaScript code & CSS dependencies of our application.
<!doctype html> <html> <head> <script src="https://cdn.pubnub.com/pubnub-3.15.1.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script> <script src="https://cdn.pubnub.com/sdk/pubnub-angular/pubnub-angular-3.2.1.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css" /> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" /> </head> <body>
For folks who have done front-end implementation with AngularJS before, these should be the usual suspects:
- PubNub JavaScript client: to connect to our data stream integration channel.
- AngularJS: were you expecting a niftier front-end framework? Impossible!
- PubNub Angular JavaScript client: provides PubNub services in AngularJS quite nicely indeed.
- Underscore.js: we could avoid using Underscore.JS, but then our code would be less awesome.
In addition, we bring in 2 CSS features:
- Bootstrap: in this app, we use it just for vanilla UI presentation.
- Font-Awesome: we love Font Awesome because it lets us use truetype font characters instead of image-based icons. Pretty sweet!
Overall, we were pretty pleased that we could build a nifty UI with so few dependencies. And with that… on to the UI!
The User Interface
Here’s what we intend the UI to look like:
The UI is pretty straightforward – everything is inside a div
tag that is managed by a single controller that we’ll set up in the AngularJS code. That h3
heading should be pretty self-explanatory.
<div class="container" ng-app="PubNubAngularApp" ng-controller="MyGeoCtrl"> <pre> NOTE: make sure to update the PubNub keys below with your keys, and ensure that the geo/mapping BLOCK is configured properly! </pre> <h3>MyGeo</h3>
We provide a simple text input for a location query to send to the PubNub channel as well as a button to perform the publish()
action.
<input ng-model="toSend" /> <input type="button" ng-click="publish()" value="Send!" />
Our UI consists of a simple list of messages. We iterate over the messages in the controller scope using a trusty ng-repeat
. Each message includes the image of its corresponding map.
<ul> <li ng-repeat="message in messages track by $index"> {{message.query}} <br /> <img ng-src="{{message.map}}"></img> </li> </ul>
And that’s it – a functioning realtime UI in just a handful of code (thanks, AngularJS)!
The AngularJS Code
Right on! Now we’re ready to dive into the AngularJS 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 controller (which we dub MyGeoCtrl
). Both of these values correspond to the ng-app
and ng-controller
attributes from the preceding UI code.
<script> angular.module('PubNubAngularApp', ["pubnub.angular.service"]) .controller('MyGeoCtrl', function($rootScope, $scope, Pubnub) {
Next up, we initialize a bunch of values. First is an array of message objects which starts out empty. After that, we set up the msgChannel as the channel name where we will send and receive realtime structured data messages. NOTE: make sure this matches the channel specified by your BLOCK configuration!
$scope.messages = []; $scope.msgChannel = 'mapbox-directions-channel';
We initialize the Pubnub
object with our PubNub publish and subscribe keys mentioned above, and set a scope variable to make sure the initialization only occurs once. NOTE: this uses the v3 API syntax.
if (!$rootScope.initialized) { Pubnub.init({ publish_key: 'YOUR_PUB_KEY', subscribe_key: 'YOUR_SUB_KEY', ssl:true }); $rootScope.initialized = true; }
The next thing we’ll need is a realtime message callback called msgCallback
; it takes care of all the realtime messages we need to handle from PubNub. In our case, we have only one scenario – an incoming message containing the geo data and map URL. We push the message object onto the scope array; that push()
operation should be in a $scope.$apply()
call so that AngularJS gets the idea that a change came in asynchronously.
var msgCallback = function(payload) { $scope.$apply(function() { $scope.messages.push(payload); }); };
The publish()
function takes the contents of the text input, publishes it as a structured data object to the PubNub channel, and resets the text box to empty.
$scope.publish = function() { Pubnub.publish({ channel: $scope.msgChannel, message: {query:$scope.toSend} }); $scope.toSend = ""; };
Lastly, in the main body of the controller, we subscribe()
to the message channel (using the JavaScript v3 API syntax) and bind the events to the callback function we just created.
Pubnub.subscribe({ channel: [$scope.msgChannel], message: msgCallback });
We mustn’t forget close out the HTML tags accordingly.
}); </script> </body> </html>
Not too shabby for about seventy lines of HTML & JavaScript!
Conclusion
Thank you so much for joining us in exploring the capabilities of Mapbox geocoding and mapping combined in a realtime data stream application using PubNub! Hopefully it’s been a useful experience learning about these geo-enabled technologies that will guide the way towards adoption of additional location-aware services to give your own custom applications a leg up in the physical world. We’ve been pleasantly surprised by how easy it is to integrate these features into our applications, and we can’t wait to see what interesting applications the wider community comes up with next!