<!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: © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: © <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 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.
This course has not yet begun.
Course material will appear here week by week as the course progresses.