Welcome to the information age! We are part of a lucky generation to be living in a time of great technological change. All around us, data and information about the world is being collected and collated, just waiting for someone to take advantage of it. Based on a recent demonstration we provided to a group of enthusiastic startups, this blog post looks at how to pull in some publicly available data via web services into a basic ArcGIS web app. We are going to use the Public transport data for emerging cities from wheresmytransport through a simple Restful web service.

We will build a basic web (but mobile-ready) app that locates the user and identifies the 10 nearest public transport stops and shows them on a map.

So, where do we begin? With the map of course!

1. Create a web app stub

Open a new project/page in your favourite web development IDE (I still just use notepad++) and create a basic framework for an app. We will pull in the ArcGIS JSAPI and related CSS, a simple HTML structure with some associated CSS classes for controlling how the app will look.

<title>Find My Nearest Transport Stops</title>
<!-- GET ESRI JAVASCRIPT API AND RELEVANT STYLE SHEET -->
<link rel="stylesheet" href="https://js.arcgis.com/4.4/esri/css/main.css">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22https%3A%2F%2Fjs.arcgis.com%2F4.4%2F%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&lt;script&gt;" title="&lt;script&gt;" />
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cstyle%3E%0A%23wrapper%20%7B%3Cbr%20%2F%3E%0A%09position%3A%20absolute%3B%3Cbr%20%2F%3E%0A%09top%3A%200%3B%20left%3A%200%3B%20right%3A%200%3B%20bottom%3A%200%3B%3Cbr%20%2F%3E%0A%09background-color%3A%20rgb(237%2C242%2C245)%3B%3Cbr%20%2F%3E%0A%09overflow%3A%20hidden%3B%3Cbr%20%2F%3E%0A%7D%3Cbr%20%2F%3E%0A%23map%20%7B%3Cbr%20%2F%3E%0A%09position%3A%20relative%3B%3Cbr%20%2F%3E%0A%09width%3A%20100%25%3B%3Cbr%20%2F%3E%0A%09height%3A%20100%25%3B%3Cbr%20%2F%3E%0A%7D%3Cbr%20%2F%3E%0A%3C%2Fstyle%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&lt;style&gt;" title="&lt;style&gt;" />


<div id="wrapper">
<div id="map"></div>
</div>


You wont see much when opening this, but here is a tip for how I view and debug my “mobile-ready” apps – open Chrome Developer tools (F12) and toggle the device toolbar. Dock the dev tools window to the right and choose an appropriate mobile device from the drop-down. You can now easily view your app and follow the console alongside.

2. Add a map control

Using the JSAPI that has been loaded, follow the basic steps for creating a WebMap from an ArcGIS Online web map ID, add it to a MapView and assign it to the dom node you just created called “map”. Place the following code inside a script tag in the document header.

require(["esri/views/MapView","esri/WebMap","dojo/dom","dojo/domReady!"],
	function(MapView, WebMap, dom) {
		var webMapID = "d46eaef12c0a4864aec85c8708446f00"; // replace with your web map's ID

        // create web map from existing web map ID on ArcGIS Online
		var map = new WebMap({
			portalItem: {
				id: webMapID
			}
		});
		console.log("Loading map: " + webMapID);

		// add the web map view and hitch to a dom node
		var view = new MapView({
			container: "map",
			map: map
		});

		// wait for map to be loaded
		view.then(function(){
			console.log("...Map resources have been loaded successfully.");
		}, function(error){
			// Use the errback function to handle when the view doesn't load properly
			console.log("The view's resources failed to load: ", error);
		});
	}
);

If you are using the Chrome Developer tip I mentioned, you should see something like this.
Web App - Step 2

3. Add “find me” button
HTML5 provides a handy geolocation API for getting the user’s location based on the best available method on the device. Esri has packaged this method into a simple widget that makes it easy to add to a map. Note that in v4.x of the JSAPI you can now add items to the “ui” of the map using the “view.ui” properties. To add the widget, insert the following code into the main function that will load the widget, add it to the UI and set up a handler that zooms and centres the map to your coordinates.

function addMyLocationButton() {
	console.log("Adding my location button.");
	var gl = new GraphicsLayer();
	map.add(gl);

	var locateWidget = new Locate({
	  view: view,   // Attaches the Locate button to the view
	  graphicsLayer: gl,  // The layer the locate graphic is assigned to
	  goToLocationEnabled: false	// override this to control the zoom level on goto
	});
	view.ui.add(locateWidget, "top-right");
	locateWidget.on("locate", function(event){
		// event is the event handle returned after the event fires.
		console.log("Located your position at: " + event.position.coords.latitude + ", " + event.position.coords.longitude);
		console.log("Taking you there now.");
		view.center = new Point({x: event.position.coords.longitude, y: event.position.coords.latitude, spatialReference: 4326});
		view.zoom = 13;
	});
}

TIP: don’t forget to add the new requires: “esri/widgets/Locate”,”esri/layers/GraphicsLayer”

And, then call the new function once the map has loaded (i.e. within the “view.then(function(){“ function).

addMyLocationButton();

Your output should look a little like this after clicking the new button:
Web App - Step 3

4. Get stops from wheresmytransport
First, I recommend going to check out wheresmytransport.com and read up on their cool data service that provides real-time access to consolidated public transport information in many major global cities (including Joburg and Cape Town). Use of their API is free if you sign up as a developer here: https://developer.whereismytransport.com/ – you will need to do this to get a client id and secret to run the app.
Now, we will add a new button that will call the service by adding the following code into your main function:

view.ui.add({
	component: "stopsButton",
	position: "bottom-right",
	index: 0
});
console.log("Get stops button added to the UI");

And add a dom node to host the new button to the HTML body:

<div id="stopsButton" class="esri-widget-button esri-widget esri-interactive" role="button" tabindex="4" title="Get stops">
		<span class="esri-icon esri-icon-map-pin" role="presentation" aria-hidden="true"></span><span class="esri-icon-font-fallback-text">Get stops</span>
</div>

Now that we have a button, let’s listen for it’s click event and then call the wheresmytransport service – by adding this code to the main function:

setTimeout(function () { // short delay to wait for the ui element to be loaded into the DOM, then attach an event listener for the click event

	document.getElementById("stopsButton").addEventListener("click", function(){
		console.log("Get Stops button clicked.");
	});
}, 100);

Now, before you can use the wheresmytransport service, you need to request a security token. Add this code into the function you just created.

// get API token
	console.log("Getting WMT secure token...");
	var CLIENT_ID = 'enter your client ID here from the developer portal';
	var CLIENT_SECRET = 'enter your client secret here from the developer portal';
	var payload = {
		"client_id": CLIENT_ID,
		"client_secret": CLIENT_SECRET,
		"grant_type": "client_credentials",
		"scope": "transportapi:all"
	};

	var request = new XMLHttpRequest();
	request.open('POST', 'https://identity.whereismytransport.com/connect/token', true);
	request.setRequestHeader('Accept', 'application/json');
	request.onreadystatechange = function () {
		if (request.readyState == 4 &amp;&amp; request.status == 200) {
			var response = JSON.parse(request.responseText);
			//console.log('WMT token: ', response.access_token);
			console.log('...WMT token created successfully.');
			var token = response.access_token;
			localStorage.setItem('token', token);

			// now, with the token, go get the nearest stops
			getStops(token);	

			// and log the request
			//logRequest(view.center);
		}
	};

	var formData = new FormData();
	for (var key in payload) {
		formData.append(key, payload[key]);
	}
	request.send(formData);

You will notice that the successful callback runs a new function called “getStops(token)” to which it passes that new token. The getStops function looks like this.

function getStops(theToken) {
	console.log("Loading stops from wheresmytransport...");
	var payload = {
		"point": view.center.latitude +","+ view.center.longitude,
		"radius": 1000
	};

	// convert the payload into url parameters
	var payloadParams = Object.keys(payload).map(function(key) { return key + '=' + payload[key]; }).join('&amp;');
	var getLocalStops = getWMTdata('stops', payloadParams, theToken);
}

This code only sends the request, now we need to handle the response by adding “then” and “catch” callbacks for the promise variable we created called “getLocalStops”. This code will process the results by creating a new Esri graphic and adding to the graphics layer with the specified symbology.

getLocalStops.then((successMessage) =&gt; {
	var theStops = JSON.parse(successMessage);
	console.log("..." + theStops.length + " stops successfully loaded.");
	console.log("Now creating map features.");

	graphicsLayer = new GraphicsLayer();
	var stopSymbol = new SimpleMarkerSymbol({
		style: "circle",
		color: "yellow",
		size: "8px",  // pixels
		outline: {  // autocasts as esri/symbols/SimpleLineSymbol
		color: [ 255, 255, 0 ],
			width: 1  // points
		}
	});

	theStops.forEach(function(stop, i){
		// create a new graphic object for each result (stop)
		graphicsLayer.graphics.add(new Graphic({
			geometry: new Point({x: stop.geometry.coordinates[0], y: stop.geometry.coordinates[1], spatialReference: 4326}),
			symbol: stopSymbol,
			attributes: {
				name: stop.name,
				modes: stop.modes.toString(),
				agency: stop.agency.name
			}
		}));
	});
	// now add the graphics layer to the map
	map.add(graphicsLayer);

});
getLocalStops.catch((errorMessage) =&gt; {
	console.log(errorMessage);
});

TIP: Remember to add the new requires “esri/symbols/SimpleMarkerSymbol”, “esri/Graphic” and “esri/geometry/Point”

Finally, for the eagle-eyed among you, there is a utility function called from within the code called “getWMTdata” that takes the url, token and payload which you can use generically for calls to those services.

function getWMTdata (url, payload, token) {
	return new Promise((resolve, reject) =&gt; {
		var request = new XMLHttpRequest();
		request.open('GET', 'https://platform.whereismytransport.com/api/' + url + "?" + payload, true);
		request.setRequestHeader('Accept', 'application/json');
		request.setRequestHeader('Authorization', 'Bearer ' + token);

		request.onload = () =&gt; resolve(request.responseText);
		request.onerror = () =&gt; reject(request.statusText);
		request.send(null);
	});
}

That’s it. Run the app and after clicking both the “Locate Me” and the “Get Stops” button, your app should look a little like this:
Web App - Step 4

5. Take it further…
This is a basic example, but think about how you can take this app further, by adding capabilities such as:

  • Finding the nearest stop based on travel distance/time using the FindNearest method from ArcGIS
  • Display results back to the user in a table or list for easy viewing
  • Log requests made by users to be able to assess where the demand for the app is…
  • Many many more!!

If you would like to try out the sample code, you can download this example here. Let me know in the comments below if you want some more info/help!

Happy app-making

– Richard