This version of the course is several years out of date and parts of it will not work - it is here just for the benefit of my 2022 dissertation students who want some grounding in Leaflet and associated technologies.

Week 9 Solution

<!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>
		 
		<!-- Load Proj4js -->
		<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.5.0/proj4.js"></script>
		
		<!-- Load Turf -->
		<script src="https://cdn.jsdelivr.net/npm/@turf/turf@5/turf.min.js"></script>

		<!-- Load Firebase -->
		<script src="https://www.gstatic.com/firebasejs/4.6.1/firebase.js"></script>

		<!-- Load the astar library -->
		<script src='astar.js'></script>

		<!-- Load the elevation data -->
		<script src='data/barra.js'></script>

		<!-- Style map -->
		<style>
		
			html, body {
				padding: 0;
				margin: 0;
				width: 100%;
				height: 100%;
			}
			
			#map {
				width:  100%;
				height: 100%;
			}
			
			.info { 
				font-family: Arial, sans-serif;
				padding: 6px 8px; 
				background: white; 
				background: rgba(255,255,255,0.8); 
				box-shadow: 0 0 15px rgba(0,0,0,0.2); 
				border-radius: 5px; 
			} 
		</style>
	</head>
	
	<body onload='initMap();'> 
	
		<div id='map'></div>
		
		<script>		
			
			// Load global variables
			var map, myDb, points, markers, transformer, graph, geojson, routeCollection;

			/**
			 * Initialise the Map
			 */
			function initMap(){
						
				// initialise the database
				initDb();

				// init global arrays
				points = [];
				markers = [];
				
				// set transform between wgs84 and the projection of the dataset
				var wgs84 = "+proj=longlat +datum=WGS84 +no_defs";
				transformer = proj4(wgs84, data.proj);
				
				//test that the transformations are working
// 				TEST_transforms([data.getWidth()/2, data.getHeight()/2]);
				
				// build the graph for the astar library
				graph = new Graph(data.data);

				// this is a variable that holds the reference to the Leaflet map object
				map = L.map('map').setView([56.98094722327509,-7.493614270972837], 12);

				// this adds the basemap tiles to the map
				L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
					maxZoom: 17,
					attribution: 'Map data: &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: &copy; <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
				}).addTo(map);
				
				// add the info box (containing buttons) to the map
				addInfoBox();
	 
				// add listener for click event on the map
				map.on('click', function(e) {
				
					// get the array location of the click (remember to reverse the coordinates!)
					var a = wgs842image([e.latlng.lng, e.latlng.lat]);
				
					// validate the click location
					if (a[0] > 0 &&	a[1] > 0 && 	//reject any points outside the dataset
						a[0] < data.getWidth() && 	
						a[1] < data.getHeight() && 
						data.data[a[0]][a[1]] ) {	// reject and points with value 0 (barriers)
				
						// snap the location to the dataset
						var snap = image2wgs84(wgs842image([e.latlng.lng, e.latlng.lat]));
					
						//store the point
						points.push(snap);
					
						//add marker (remember to reverse the coordinates!)
						markers.push(L.marker([ snap[1], snap[0] ]).addTo(map));
// 						markers.push(L.marker(snap.slice().reverse()).addTo(map));		// alternative syntax
					
						// calculate the route between all of the points
						getRoute();
					
					} else {
						alert("Sorry, I can't go there");
					}
				});
			}
			
			
			/**
			 * Test all of the transformation functions
			 */
			function TEST_transforms(arrayPos) {
  
			  // log the array location
			  console.log(arrayPos);
  
			  // calculate and log the corresponding osgb location
			  var projectedPos = image2osgb(arrayPos);
			  console.log(projectedPos);
  
			  //calculate and log the corresponding wgs84 location
			  var sphericalPos = osgb2wgs84(projectedPos);
			  console.log(sphericalPos);
  
			  //back to osgb location
			  var projectedPos2 = wgs842osgb(sphericalPos);
			  console.log(projectedPos2);
  
			  //back to array location
			  console.log(osgb2image(projectedPos2));
			}
			
			
			/**
			 * Initialise the database into the global myDb variable
			 */
			function initDb() {
			
				// initialize Firebase (this is copied and pasted from your firebase console)
				var config = {
					apiKey: "AIzaSyCX3L-_REs-xhMLCrbqovtCCHX7Uc2jEMg",
					authDomain: "barra-61fa4.firebaseapp.com",
					databaseURL: "https://barra-61fa4.firebaseio.com",
					projectId: "barra-61fa4",
					storageBucket: "barra-61fa4.appspot.com",
					messagingSenderId: "1045926249626"
				};
				firebase.initializeApp(config);

				// sign in anonymously - this helps stop your database being abused
				firebase.auth().signInAnonymously().catch(function(error) {
					console.log(error.code);
					console.log(error.message);
				});
		
				// create a global reference to your 'clicks' database
				myDb = firebase.database().ref().child('paths');
			}
			
			
			/**
			 * Create the info box in the top right corner of the map
			 */
			function addInfoBox(){
			
				// create a Leaflet control (generic term for anything you add to the map)
				info = L.control();

				// create the info box to update with population figures on hover
				info.onAdd = function (map) {
					
					// add a div to the control with the class info
					this._div = L.DomUtil.create('div', 'info');
					
					// add content to the div
					this._div.innerHTML = "<button type='button' onclick='saveToDb();'>Save Path</button><button type='button' onclick='undo();'>Undo</button><button type='button' onclick='clearMap();'>Clear Map</button>";
					
					// prevent clicks on the div from being propagated to the map
					L.DomEvent.disableClickPropagation(this._div);
					
					//return the div
					return this._div;
				};

				// add the info window to the map
				info.addTo(map);
			}


			/**
			 * Get the route between all of the points in the points array
			 */
			function getRoute() {
			
				// remove the route from the map if already there
				if (geojson) map.removeLayer(geojson);
			
				//need at least two points
				if (points.length > 1) {
				
					// get new route between all points
					for (var i = 0, route = []; i < points.length-1; i++) {
						route.push(getPath(points[i], points[i+1]));
					}
					
					// convert the resulting route array into a feature collection 
					routeCollection = turf.featureCollection(route);
					
					// convert to leaflet GeoJSON object and add to the map
					geojson = L.geoJson(routeCollection, {
						style: {
							color: 'red', 
							weight: 12, 
							opacity: .7
						}
					}).addTo(map);	
				}
			}


			/**
			 * Get the path between two points as turf linestring
			 */
			function getPath(p1, p2) {
				
				// define a route between p1 and p2
				var startPoint = wgs842image(p1);
				var endPoint = wgs842image(p2);

				// get grid location					
				var start = graph.grid[startPoint[0]][startPoint[1]];
				var end = graph.grid[endPoint[0]][endPoint[1]];

				// get the result
				var result = astar.search(graph, start, end);
	
				// check that it worked...
				if (result.length > 1) {
				
					// convert from image space to wgs84 coordinates and return 
					// init the path array with start location
					for (var i = 0, path = [p1]; i < result.length; i++) {
						path.push(image2wgs84([result[i].x, result[i].y]));
					}
		
					// return the route as a linestring
					return turf.lineString(path);
	
				// ...otherwise alert and undo
				} else {
					alert("Sorry, I can't get there from here...");
					undo();
				}
			}
			
			
			/**
			 * convert osgb to image coordinates
			 */
			function osgb2image(coord) {
				return [ 
					parseInt((coord[0] - data.bl[0]) / data.resolution), 	
					(data.getHeight() - parseInt((coord[1] - data.bl[1]) / data.resolution)) - 1 
				];
			}


			/**
			 * convert image coordinates to osgb
			 */
			function image2osgb(px) {
				return [
					data.bl[0] + (px[0] * data.resolution), 
					data.bl[1] + ((data.getHeight() - px[1]) * data.resolution)
				];
			}


			/**
			 * transform osgb coords to wgs84
			 */
			function osgb2wgs84(osgb) {
				return transformer.inverse(osgb);
			}


			/**
			 * transform wgs84 coords to osgb
			 */
			function wgs842osgb(lngLat) {
				return transformer.forward(lngLat);
			}
 

			/**
			 * convenience function to convert from image coordinates to wgs84
			 */
			function image2wgs84(px) {
				return osgb2wgs84(image2osgb(px));
			}


			/**
			 * convenience function to convert from wgs84 to image coordinates
			 */
			function wgs842image(lngLat) {
				return osgb2image(wgs842osgb(lngLat));
			}
			
			
			/**
			 * Add GeoJSON to database
			 */
			function saveToDb() {
				
				// don't save if route is empty
				if (geojson) {
				
// 					clearMap();	//this would be removed if the below were reactivated
			
					// push new data into database object
					myDb.push(routeCollection, function(error) {

						// if no error is returned to the callback, then it was loaded successfully
						if (!error) { 
						
							// clear all data from the map and globals
							clearMap();
							console.log("successfully added to firebase!");
			
						// otherwise pass the error to the console and alert user
						} else {
							alert("Data Upload Failed: " + error.message);
							console.error(error.message);
						}
					});
				}
			}
			
			/**
			 * Clear everything from the map
			 */
			function clearMap() {
				
				// reset map and globals
				if (geojson) map.removeLayer(geojson);
				for (var m  = 0; m < markers.length; m++) {
					map.removeLayer(markers[m]);
				}
					markers = [];
					points = [];
					route = null;
					geojson = null;
			}
			
			
			/**
			 * Undo the last click
			 */
			function undo() {

				// is there anything to undo?
				if (markers.length > 1){
	
					// remove the last point from the points array
					points.pop();
	
					//remove marker
					map.removeLayer(markers.pop());
	
					// recalculate the route
					getRoute();
		
				} else if (markers.length === 1) {
	
					// remove the last point from the points array
					points.pop();
	
					// just remove the one remaining marker
					map.removeLayer(markers.pop());
	
				} else {
		
					//in form user nothing to undo
					alert("Nothing to undo!")
				}
			}
 		</script>
	</body>
</html>

This course has not yet begun.
Course material will appear here week by week as the course progresses.