Geocoding converts addresses and place names into latitude and longitude, and vice versa. So when a user inputs Empire State Building, they’re returned the exact geographic coordinates of that place, as well as the address.
The Esri Geocoding Service allows you to easily build in geocoding functionality, including geosearch (search for addresses and names), reverse geocoding (coordinates to name or address), and batch geocoding (multiple places at the same time).
In tandem with the meteoric rise of real-time mapping, an essential piece of a growing number of apps today, our new Esri Geocoding BLOCK lets you geocode on your data in motion, passing over the PubNub network. A couple example apps:
- A chat app that automatically geocodes location names and provides a marker on a map of its location.
- An interactive tourism app that lets users ask for the location of landmarks and receive its address in real time.
In this tutorial, we’ll dive into a simple example of how to embed geocoding lookups in a real-time Angular 2 web application. Our basic functionality looks like this:
Esri API Overview
Mapping services are enormously challenging to build and operate on your own; they require substantial effort and engineering resources to create, populate, and curate location data sets, manage tons of satellite and vector image data, implement walking/biking/driving directions, handle abusive changes, and more. Fortunately, the Esri APIs make it easy to enable your applications with location-aware features.
Looking closer at the APIs, geocoding is just the beginning. There are a lot of API methods available for things like static maps, 3D maps, custom maps, embedding in native mobile apps, and more. It really is a powerful tool for building in location awareness across a wide range of services. In this article though, we’ll keep it simple and implement a basic geocoding integration that takes a place query and provides candidate locations.
With that, let’s get started!
Obtaining your PubNub Developer Keys
You’ll first need your PubNub publish/subscribe keys. If you haven’t already, sign up for PubNub. Once you’ve done so, go to the PubNub Admin Portal to get your unique keys. The publish and subscribe keys look like UUIDs and start with “pub-c-” and “sub-c-” prefixes respectively. Keep those handy – you’ll need to plug them in when initializing the PubNub object in your HTML5 app below.
Getting Started with ESRI
For this application, you don’t need an API key or other token. If you decide to go further with mapping and geocoding APIs, you’ll want to follow these steps. The first thing you’ll need to get started with Esri geocoding is an Esri developer account to take advantage of the ArcGIS APIs.
- Go to the Esri signup form.
- Go to the ArcGIS dashboard page and configure your services.
- Create an “API token” credential to update the BLOCK below when accessing premium services.
Overall, it’s a pretty quick process. And free to get started, which is a bonus!
Setting up the Esri Geocoding BLOCK
Next is getting set up with PubNub BLOCKS.
- Go to the application instance on the PubNub Admin Portal.
- Create a new BLOCK.
- Paste in the BLOCK code from the next section and update the client token with the ESRI client token from the previous steps above (if applicable).
- 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
This Block code is 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 the URL variable for accessing the service (the API url).
let apiUrl = 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates';
Next, we set up the HTTP params for the geocoding API URL. We use a GET request for the URL. We use the JSON content of the message as the source of the query text parameter.
let queryParams = { singleLine: searchParam, outFields: "Addr_type", maxLocations: 10, f: 'json' };
We construct the actual API URL with the given parameters. Pretty easy!
let url = apiUrl + '?' + query.stringify(queryParams);
Finally, we call the API endpoint with the given parameters, decorate the message with a geocode
value containing the parsed JSON response data, and catch any errors and log to the BLOCKS console. Pretty easy!
return xhr.fetch(url) .then((response) => { request.message.geocode = JSON.parse(response.body); return request; }) .catch((err) => { console.log('error happened for XHR.fetch', err); return request; }); };
All in all, it doesn’t take a lot of code to add geocoding to our application. We like that!
Diving into the Code – the User Interface
You’ll want to grab these 97 lines of HTML & JavaScript and save them to a file called pubnub_esri_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. Enjoy!
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. 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 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 text input and button to perform the publish()
action which gets geocoding results 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 Geo Integration</h3> <hr/> <input type="text" [(ngModel)]="toSend" /> <button class="btn btn-primary" (click)="publish()">Lookup Locations!</button> <br/> <h4 *ngIf="orig_query">Query: {{orig_query}}</h4> <ol> <li *ngFor="let item of geo_response"> {{item.attributes.Loc_name}} ({{item.attributes.Type}}, score={{item.score}}) <br /> {{item.address}} <br /> lat={{item.attributes.X}}, lng={{item.attributes.Y}} </li> </ol> </div>
The component UI consists of a simple list of items (in our case, the geocoding candidates). We iterate over the items in the controller scope using a trusty ngFor
. Each item includes a wealth of data from the ESRI Geocoding API.
And that’s it – a functioning real-time UI in just a handful of code (thanks, Angular2)!
The Angular2 Code
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, initial query (empty), and geo response (also empty). 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.channelName = 'esri_geocode_input'; self.toSend = ""; self.geo_response = null; self.orig_query = null;
Early on, we initialize the pubnubService
with our credentials.
pubnubService.init({ publishKey: 'YOUR_PUB_KEY', subscribeKey: 'YOUR_SUB_KEY', ssl:true });
We subscribe to the relevant channel, create a dynamic function handler for the message events, configuring that handler to set the self.orig_query
and self.geo_response
attributes based on the values returned from the BLOCK we configured earlier.
pubnubService.subscribe({channels: [self.channelName], triggerEvents: true}); pubnubService.getMessage(this.channelName,function(msg){ self.orig_query = msg.message.text; self.geo_response = msg.message.geocode.candidates; });
We create a publish()
event handler that performs the action of publishing the new message to the PubNub channel and resetting the text box to empty.
}], publish: function(){ this.pubnubService.publish({channel: this.channelName,message:{text:this.toSend}}); this.toSend = ""; } });
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 97 lines of HTML & JavaScript!
Additional Features
There are a couple other endpoints worth mentioning in the Esri API. You can find detailed API documentation Clicking here.
- Map Services : ability to customize themes and other map attributes, such as markers.
- Spatial Analysis : analysis of geo information for things like watershed, drive time, and more!
- Geo Enrichment : annotating maps with detailed place information.
All in all, we found it pretty easy to get started with geocoding using the API, and we look forward to using more of the deeper integration features!
Conclusion
Thank you so much for joining us in the geocoding article of our BLOCKS and web services series! Hopefully it’s been a useful experience learning about location-aware technologies. In future articles, we’ll dive deeper into additional web service APIs and use cases for other nifty services in real time web applications.
Stay tuned, and please reach out anytime if you feel especially inspired or need any help!