Monthly Archives: January 2014

Building a mobile mapping application

Leaflet mobile mapping application

Leaflet mobile mapping application

Our earlier articles on mobile application development demonstrated the process of getting an app up and running on an Android phone using Adobe PhoneGap and GitHub. For the purposes of this article we’re going to assume that you are now comfortable with that process and will demonstrate how easy it is begin building mobile mapping applications. This is something we are developing a lot of interest in here at Cranfield University.

As with previous examples, the app is going to be based on a package of HTML, CSS and JavaScript code and will therefore use one of the many JavaScript web mapping APIs available. Any of the following APIs would work well:

  • Google Maps API
  • ArcGIS API for Javascript
  • Leaflet
  • OpenLayers
  • OS OpenSpace API

The decision on which one to use will depend on what the functional requirements of your app are, how familiar you are with a particular API and a number of other factors. For this example we are going to build a simple mobile web map using the Leaflet mapping API. Leaflet is an extremely lightweight JavaScript library which makes it ideal for bundling with a mobile app, as we shall see shortly. It is also relatively simple to use and has great flexibility for puling in geospatial resources from a number of different sources, in a variety of formats.

The Leaflet website provides a page of useful examples to get you started with creating basic web maps. Below is a typical web page that would display a basic web map using Leaflet. You can save this code to a HTML file and view it in a browser to test it out for yourself.

<!DOCTYPE html>
<html>
<head>
<title>Basic Leaflet Map</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.1/leaflet.css" />

<style type="text/css">
body {
	padding: 0;
	margin: 0;
}
html, body, #map {
	height: 100%;
}
</style>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
</head>
<body>
<div id="map"></div>
<script src="http://cdn.leafletjs.com/leaflet-0.7.1/leaflet.js"></script> 
<script>
	var map = L.map('map').setView([52.04, -0.73], 12);
        L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
		maxZoom: 18			
	}).addTo(map);
</script>
</body>
</html>

The process of turning this into a mobile app is very straightforward, assuming you’ve already got your PhoneGap/Cordova framework in place. Rather than build a new index.htm for our project, we’ll take the default index.htm that has been created for us and drop in the relevant sections of the Leaflet map example above into the correct places. The index.htm file that sits within the www folder of you mobile app framework (taken from the default PhoneGap example on GitHub) should look something like this:

<html>
    <head>
        <meta charset="utf-8" />
        <meta name="format-detection" content="telephone=no" />
        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
        <link rel="stylesheet" type="text/css" href="css/index.css" />
        <title>Hello World</title>
    </head>
    <body>
        <div class="app">
            <h1>PhoneGap</h1>
            <div id="deviceready" class="blink">
                <p class="event listening">Connecting to Device</p>
                <p class="event received">Device is Ready</p>
            </div>
        </div>
        <script type="text/javascript" src="phonegap.js"></script>
        <script type="text/javascript" src="js/index.js"></script>
        <script type="text/javascript">
            app.initialize();
        </script>
    </body>
</html>

The key parts of the Leaflet map example are as follows:

<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.1/leaflet.css" />

This ensures the appropriate CSS files are loaded so that the map is is styled correctly.

body {
	padding: 0;
	margin: 0;
}
html, body, #map {
	height: 100%;
}

Here we are defining some layout parameters, including how much space the #map element of the page should use

<div id=”map”></div>

This is the container in the body of the page that will house our map

<script src="http://cdn.leafletjs.com/leaflet-0.7.1/leaflet.js"></script> 

Load in the Leaflet API

<script>
	var map = L.map('map').setView([52.04, -0.73], 12);
	L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
		maxZoom: 18			
	}).addTo(map);
</script>

This is the code that generates and renders the map on the page.

Dropping these elements into our existing index.htm whilst retaining the important components (such as app.initialise(); and the calls to phonegap.js and js/index.js) that are already there, gives a page that looks as follows:

<html>
    <head>
        <meta charset="utf-8" />
        <meta name="format-detection" content="telephone=no" />
        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
        <link rel="stylesheet" type="text/css" href="css/index.css" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.1/leaflet.css" />
        <title>Basic Leaflet Mobile Map</title>
    </head>
    <body>
        <div class="app">
            <div id="map"></div>

        </div>
	<script src="http://cdn.leafletjs.com/leaflet-0.7.1/leaflet.js"></script> 
	<script>
		var map = L.map('map').setView([52.04, -0.73], 12);		
	        L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
			maxZoom: 18					
		}).addTo(map);
	</script>
        <script type="text/javascript" src="phonegap.js"></script>
        <script type="text/javascript" src="js/index.js"></script>
        <script type="text/javascript">
            app.initialize();
        </script>
    </body>
</html>

This can now be compiled and deployed to your device using the process detailed earlier to give you a simple mobile mapping application.

Hosting libraries and APIs locally

Before we finish, we shall make one more minor alteration to our app to improve efficiency and performance. Currently the app loads up the Leaflet API from http://cdn.leafletjs.com/leaflet-0.7.1/leaflet.js. We are going to take a copy of the full API, copy it to our app’s folder tree and deploy it to the phone so that our app can reference it locally rather than having to load it over web each time the app is launched. This will reduce the reliance on network connectivity and speed up application launch times as well as eliminate any reliance on the Leaflet website; we want our app to continue to work regardless of whether any remote websites may be experiencing problems of their own.

The full leaflet API can be downloaded here:
http://leafletjs.com/download.html
Unpack the zipfile and place the contents into a subfolder of your www folder named leaflet within your mobile app framework. All that needs to be done now is to modify the calls in your code that load the Leaflet API and CSS so that they reference your local copies. They should be changed from

<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.1/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7.1/leaflet.js?2"></script>

to

<link rel="stylesheet" href="leaflet/leaflet.css" />
<script src="leaflet/leaflet.js"></script>

This change will make your app almost fully self sufficient, without any reliance on loading libraries from external websites. Note that the map tiles that make up your map are still being loaded from the following location:
http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png
Your app will therefore still require a working internet connection in order to function correctly. In our next post we’ll discuss bundling a selection of map tiles with your application so it can be used completely offline with no internet connection

Leaflet mobile mapping application

Leaflet mobile mapping application

Mobile App Development with Dojo

Our earlier blog article about Mobile App Development With PhoneGap showed how easy it is to develop a basic mobile app using the Apache Cordova / Adobe PhoneGap framework, compiled with Adobe PhoneGap Build. However, the resultant app is not styled in a way that resembles a ‘native app’.
RockPaperScissors_Game
In developing apps here at Cranfield University we are increasingly adopting the ‘Dojo’ JavaScript API (http://dojotoolkit.org/).
Dojo
Dojo allows a powerful open source API framework for developing industry-strength web applications, and includes a powerful suite of tools for developing mobile apps. To give an example of this in action, following the previous ‘Rock, Paper, Scissors’ app article, the following code can be substituted for the file ‘index.htm’.

<!DOCTYPE html>
<html>
	<head>
		<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
		<meta name="apple-mobile-web-app-capable" content="yes"/>
		<meta names="apple-mobile-web-app-status-bar-style" content="black-translucent" />
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
		<!--<link rel="stylesheet" type="text/css" href="css/index.css" />-->
		<title>Rock Paper Scissors Game</title>
		<!-- set Dojo configuration, load Dojo -->
		<script>
			dojoConfig= {
				async: true,
				mblAlwaysHideAddressBar:true
			};
		</script>
		<script src="dojox/mobile/deviceTheme.js"></script>
		<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/dojo/1.9.2/dojo/dojo.js"></script> 
		<script>
			require(["dojox/mobile",
			         "dojox/mobile/parser",
			         "dojox/mobile/compat",
			         "dojox/mobile/deviceTheme",
				 "dojox/mobile/Heading",
			         "dojox/mobile/ScrollableView",
			         "dojox/mobile/Button",
			         "dojo/dom-attr",
			         "dojo/_base/array",
			         "dojox/mobile/compat",
			         "dojo/domReady!"],
					function(dm, parser, compat, deviceTheme, heading, scrollableView,
					         button, domAttr, baseArray, compat) {
					parser.parse();
			});
		</script>
	<script type="text/javascript">
		var CHOICE = { // set up enumerations
			ROCK     : {value: 1, name: "Rock", code: "R"}, 
			PAPER    : {value: 2, name: "Paper", code: "P"}, 
			SCISSORS : {value: 3, name: "Scissors", code: "S"}
		};
		var plays = 0;
		var humanWins = 0;
		var compWins  = 0;

		function playGame(play) {
			if(typeof play === 'undefined'){return;}
			var humanChoice = null;
			var compChoice  = null;
			plays++;
			
			if (play==='R') {
				humanChoice = CHOICE.ROCK;
			} else if (play==='P') {
				humanChoice = CHOICE.PAPER;
			} else if (play==='S') {
				humanChoice = CHOICE.SCISSORS;
			}
	
			var computer = (Math.floor( Math.random() * 3 + 0.5 ));
				if ( computer === 1 ) {
					compChoice = CHOICE.ROCK }
				else { if ( computer === 2 ) {
					compChoice = CHOICE.PAPER }
				else { 
					compChoice = CHOICE.SCISSORS }
				}
			document.getElementById('result').innerHTML="<p>You chose '" + humanChoice.name + "' and I chose '" + compChoice.name + "'</p>";
			<!--document.getElementById('log').innerHTML+="H:" + humanChoice.code + " C:" + compChoice.code + "|";-->
	
			var win = humanChoice.value - compChoice.value;
			if ( win === 0 ) { 
				document.getElementById('result').innerHTML += "<p><b>So we've drawn</b></p>" } 
			else {
				if ( win === -2 || win === 1 ) { 
					document.getElementById('result').innerHTML += "<p><b>So you win</b></p>"; humanWins++; }
				else {
					if ( win === -1 || win === 2 ) { 
						document.getElementById('result').innerHTML += "<p><b>So I win</b></p>"; compWins++; }
					else {}
				}
			}
			document.getElementById('tally').innerHTML = "<p><i>Leaderboard</i>&nbsp;&nbsp;Plays:&nbsp;" + plays + "<br />Your wins:&nbsp;" + humanWins + "&nbsp;&nbsp;&nbsp;My wins:&nbsp;" + compWins + "&nbsp;&nbsp;&nbsp;Draws:&nbsp;" + (plays - (humanWins + compWins)) + "</p>";
			if ( compWins < humanWins ) { 
				document.getElementById('tally').innerHTML += "<p>You're in the lead</p>"; } 
			else if ( compWins > humanWins ) {
				document.getElementById('tally').innerHTML += "<p>I'm in the lead</p>"; } 
			else if ( compWins == humanWins ) {
				document.getElementById('tally').innerHTML += "<p>We're running head to head</p>"; }
		}
		
		function restart() {
			plays = 0;
			humanWins = 0;
			compWins  = 0;
			document.getElementById('result').innerHTML = "&nbsp;";
			document.getElementById('tally').innerHTML = "&nbsp;";
			document.getElementById('log').innerHTML = "&nbsp;";
		}
		</script>
		<style type="text/css">
			html, body{
				height: 100%;
				overflow: hidden;}
			p {font-size:25px;}
			button {font-size:45px; width:250px; margin:20px 50px;}
			.mblBlueButton {
				background-image: -webkit-gradient(linear, left top, left bottom, from(#7a9de9), to(#2362dd), color-stop(0.5, #366edf), color-stop(0.5, #215fdc));
				background-image: linear-gradient(to bottom, #7a9de9 0%, #366edf 50%, #215fdc 50%, #2362dd 100%);
				color: white;
			}
			.mblBlueButtonSelected {
				background-image: -webkit-gradient(linear, left top, left bottom, from(#8ea4c1), to(#4a6c9b), color-stop(0.5, #5877a2), color-stop(0.5, #476999));
				background-image: linear-gradient(to bottom, #8ea4c1 0%, #5877a2 50%, #476999 50%, #4a6c9b 100%);
			}
		</style>
	</head>
	<body style="visibility:hidden;">
	<div id="settings" dojoType="dojox/mobile/View">
		<div dojoType="dojox/mobile/Heading" data-dojo-props="fixed: 'top'">
			Rock Paper Scissors Game
		</div>
		<div data-dojo-type="dojox.mobile.RoundRect">
			<h2 dojoType="dojox.mobile.RoundRectCategory">Choose ...</h2>
			<button dojoType="dojox.mobile.Button" class="mblBlueButton" label="Rock" onClick="playGame('R')"></button>
			<button dojoType="dojox.mobile.Button" class="mblBlueButton" label="Paper" onClick="playGame('P')"></button>
			<button dojoType="dojox.mobile.Button" class="mblBlueButton" label="Scissors" onClick="playGame('S')"></button>
			<button dojoType="dojox.mobile.Button" class="mblButton" label="Restart" onClick="restart()"></button>
			<span id="result">&nbsp;</span><br />
			<span id="tally">&nbsp;</span><br />
			<span id="log">&nbsp;</span><br />
		</div>
	</div>
	</body>
</html>

The result of this is a styled app taking on the visual characteristics of a native Android app. Note the use of the Dojo ‘dojox/mobile/deviceTheme’ require. This permits automatic styling for all the different mobile platforms. For more information, see the excellent mobile tutorials on the Dojo website, as well as the mobile showcase. Note also that this code includes a live online call to load the Dojo API. Clearly this will not work if your device is offline. To resolve this, a further refinement would be to create a custom API library build stored locally containing the Dojo code needed.
RockPaperScissors_Game_Dojo