Week 7 Solution

NB: You need to set the API Key for OpenRouteService to run this example.

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>GIS and the Web</title>
		
		<!-- Load Leaflet -->
		<link rel="stylesheet" href="https://unpkg.com/leaflet@1.4.0/dist/leaflet.css"/>
		<script src="https://unpkg.com/leaflet@1.4.0/dist/leaflet.js"></script>

		<style>
		
			/* Style the map */
			#map {
				/* This is the dimensions of the map on the screen, you can set it in 
				 px (pixels) or % of the total screen size */
				width:  800px;
				height: 500px;
			}
			
			/* Style the table */
			table {
				margin-top: 10px;
				font-family: sans-serif;
				min-width: 800px;
			}
			
			/* Style the table header */
			th {
			  background-color: #000000;
			  color: white;
			  padding: 3px;
			}
			
			/* Style the table cells */
			td {
			  padding: 3px;
			}
			
			/* Style the table rows (only colours the even numbered rows in the table) */
			tr:nth-child(even) {
				background-color: #f2f2f2;
			}			
		</style>
	</head>
	
	<body onload='initMap()'> 
	
		<!-- This is where the map will go -->
		<div id='map'></div>
		
		<!-- This is where the table of directions will go -->
		<div id='directions'></div>
		
		<script>		
			
			//setup global variables
			var map, start, end, startName, endName;
			
			/**
			 * Initialise the Map
			 */
			function initMap(){
			
				// this is a variable that holds the reference to the Leaflet map object
				// creates a world map (centre where the equator crosses the GPM)
				map = L.map('map').setView([0, 0], 1);

				// this adds the basemap tiles to the map
				L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
					attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors. Geocoding by <a href="https://nominatim.org">Nominatim</a>.Routing by <a href="https://openrouteservice.org/">OpenRouteService</a>'
				}).addTo(map);
				
				// set placenames for start and end point
				startName = "Nether Kellet";
				endName = "Oxford Road, Manchester";
							
				// geocode the place names and calculate route
				startGeocoding();
			}
			
			
			/**
			 * Step 1 - geocode the start location
			 */
			function startGeocoding(){
			
				// geocode start location...
				makeRequest(getGeocodeURL(startName), continueGeocoding);
			}
			
			
			/**
			 * Step 2 - store the start location and geocode the end location
			 */
			function continueGeocoding(data){
			
				// record the start locaion
				start = [data[0].lon, data[0].lat];
				
				// geocode end location...
				makeRequest(getGeocodeURL(endName), finishGeocoding);
			}
			
			
			/**
			 * Step 3 - store the end location and start routing
			 */
			function finishGeocoding(data){
				
				// record the end location
				end = [data[0].lon, data[0].lat];
				
				//calculate the route between the two locations
				doRouting();
			}
			
			
			/**
			 * Generate and return a Nominatim geocoding URL for a given placename
			 */
			function getGeocodeURL(placename) {
				return ["https://nominatim.openstreetmap.org/?format=json&limit=1&q=", placename].join("");
			}
			
			
			/**
			 * Calculate the route between start and end global variables
			 */
			function doRouting() {
				
				//construct a url out of the required options for OpenRouteService
				var url = [
	
					// these bits are fixed or imported from the start and variables above 
					'https://api.openrouteservice.org/directions?',
					'api_key=','API_KEY',	// TODO: SET YOUR API KEY HERE
					'&coordinates=',start[0].toString(),',',start[1].toString(),'%7C', end[0].toString(),',',end[1].toString(),
		
					// these are the options, a comprehensive list is available at: https://openrouteservice.org/dev/#/api-docs/directions/get
					'&profile=', 			'driving-car',	
					'&preference=', 		'fastest',
					'&format=', 			'geojson',
					'&units=', 				'km',
					'&geometry_format=', 	'geojson'
		
				].join("");	//join the array with no delimiter

				// log the url that was constructed
				//console.log(url);
	
				// send the request to OpenRouteService, set callback
				makeRequest(url, routeToMap);
			}
			
			
			/**
			 * Make a request for JSON over HTTP, pass resulting text to callback when ready
			 */
			function makeRequest(url, callback) {

				//initialise the XMLHttpRequest object
				var httpRequest = new XMLHttpRequest();

				//set an event listener for when the HTTP state changes
				httpRequest.onreadystatechange = function () {

					//if it works, parse the JSON and pass to the callback	
					//a successful HTTP request returns a state of DONE and a status of 200
					if (httpRequest.readyState === XMLHttpRequest.DONE && httpRequest.status === 200) {
						callback(JSON.parse(httpRequest.responseText));
					}
				};

				//prepare and send the request
				httpRequest.open('GET', url);
				httpRequest.send();
			}   
			
			
			/**
			 * Retrieve a GeoJSON route and add it to the map
			 */
			 function routeToMap(route) {
			 
			 	// load into L.GeoJson, style and add to the map
				geojson = L.geoJson(route, {
					style: {
						weight: 4,
						opacity: 1,
					}
				}).addTo(map);
				
				// zoom the map to fit
				map.fitBounds(geojson.getBounds());
				
				// add markers to the start and end (remember to flip longitude and latitude!)
				markerStart = L.marker([start[1], start[0]]).addTo(map);
				markerEnd = L.marker([end[1], end[0]]).addTo(map);
			 
			 	// get info about the route from the dataset		 
			 	var duration = route.features[0].properties.summary[0].duration;
			 	var distance = route.features[0].properties.summary[0].distance;

				// get the description of the route
			 	var segments = route.features[0].properties.segments;
			 	
			 	// build HTML directions as a table
			 	var directionsHTML = [
			 		"<table><th>Directions (", 
			 		getDistanceString(distance), 
			 		", <i>",
			 		getDurationString(duration),
			 		"</i>)</th>"
			 	].join("");
			 	
			 	// loop through the description for each segment of the journey
				for (var i = 0; i < segments.length; i++){
					
					// loop through each step of the current segment
					for (var j = 0; j < segments[i].steps.length; j++){
						
						// add a direction to the table
						directionsHTML += [ 
							"<tr><td><b>", 
							segments[i].steps[j].instruction,
							"</b> (",
							getDistanceString(segments[i].steps[j].distance),
							", <i>",
							getDurationString(segments[i].steps[j].duration),
							"</i>)</td></tr>"
						].join("");
					}
				}
				
				// close the table
				directionsHTML += "</table>";
				
				// load the directions into the div
				document.getElementById('directions').innerHTML = directionsHTML;
			 }


			/**
			 * Returns a sensible distance string for a given distance in km
			 */
			function getDistanceString(distance){
				
				//is it more than 1km?
				if (distance > 1) {
					
					//if so, use km
					return distance.toFixed(1) + "km";
				
				} else {
					
					// if not, use m
					return Math.ceil(distance * 1000).toFixed(0) + "m";
				}
			}
			
			
			/**
			 * Returns a sensible duration string for a given duration in seconds
			 */
			function getDurationString(duration){
				
				// hours
				if (duration > 3600) {
					
					//return hours and minutes
					return Math.floor(duration / 3600).toFixed(0) + " hrs " + 
						Math.ceil((duration % 3600) / 60).toFixed(0) + " mins";
				
				// minutes
				} else if (duration > 60) {
					
					// minutes only
					return Math.ceil(duration / 60).toFixed(0) + " mins";
				
				// seconds
				} else {
					
					// seconds only
					return Math.ceil(duration).toFixed(0) + " secs";
				}
			}

		</script>
	</body>
</html>