Week 4 Solution

<!DOCTYPE html>
<html>
	<head>
		<!-- This is the HEAD of the HTML file, things in here do not appear on the page. It is
		 used for settings, such as the language and page title, as well as loading CSS styles and
		 scripts that you want to load before the content of the page. -->
		<meta charset="UTF-8">
		<title>GIS and the Web</title>

		<!-- Load the CSS and JavaScript for Leaflet -->
		<link rel="stylesheet" href="https://unpkg.com/leaflet@1.5.1/dist/leaflet.css" />
		<script src="https://unpkg.com/leaflet@1.5.1/dist/leaflet.js"></script>

		<!-- Load Turf.js -->
		<script src="https://npmcdn.com/@turf/turf/turf.min.js"></script>
		<!-- http://tannerjt.github.io/geotanner/javascript/color-theory/2014/10/29/classybrew-jenks-heart-colorbrewer.html -->
		<!-- https://www.npmjs.com/package/classybrew -->

		<!-- This is where we set the style for the different elements in the page's content -->
		<style>

			/* Avoid having a newline between the two divs */
			div {
				display:inline-block;
			}

			/* 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 info box so that it looks like a Leaflet control */
			.info {
				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 the legend */
			.legend {
				font-size: small;
				padding-top: 10px;
				text-align: left;
				line-height: 18px;
			}

			/* Style the i tags within the legend - we use these to make the coloured squares */
			.legend i {
				width: 18px;
				height: 18px;
				float: left;
				margin-right: 8px;
				opacity: 0.6;
			}

		</style>
	</head>

	<body onload='initMap()'>

		<!-- This is where the map will go -->
		<div id='map'></div>

		<!-- Add the legend for the map -->
		<div class="legend">
			<i style="background:#FFEDA0"></i> 0 - 10<br>
			<i style="background:#FED976"></i> 10 - 20<br>
			<i style="background:#FEB24C"></i> 20 - 40<br>
			<i style="background:#FD8D3C"></i> 40 - 60<br>
			<i style="background:#FC4E2A"></i> 60 - 80<br>
			<i style="background:#E31A1C"></i> 80 - 100<br>
			<i style="background:#BD0026"></i> 100 - 120<br>
			<i style="background:#800026"></i> 120 +
		</div>

		<script>

			//setup global variables
			let map, info, crimesLayer;

			/**
			 * Initialise the Map
			 */
			function initMap(){

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

				// 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'
				}).addTo(map);

				// add the info box to the map
				addInfoBox();

				//call to get all crimes (set to June 2019 as data availability is poor latter half of this year)
				makeRequest("https://data.police.uk/api/crimes-street/all-crime?date=2019-06&lat=53.4807593&lng=-2.2426305", crimesToMap);
			}


			/**
			 * 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 crime figures
				info.onAdd = function (map) {
					this._div = L.DomUtil.create('div', 'info');
					this.update("<b>Loading...</b>");
					return this._div;
				};

				// create a function called update() that updates the contents of the info box
				info.update = function (value) {
						this._div.innerHTML = value;
				};

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


			/**
			 * 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();

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

					// a successful HTTP request returns a state of DONE and a status of 200
					if (httpRequest.readyState === XMLHttpRequest.DONE) {
						if (httpRequest.status === 200) {

							// parse the JSON and pass to the callback
							callback(JSON.parse(httpRequest.responseText));
						}
					}
				};

				//prepare and send the request
				httpRequest.open('GET', url);
				httpRequest.send();
			}


      /**
       * Parse JSON crime data and load onto the map
       */
      function crimesToMap(data) {

				// loop through data and build an array of turf point features
				let points = [];
				for (let i = 0; i < data.length; i++) {
						points.push(turf.point([parseFloat(data[i].location.longitude),
							parseFloat(data[i].location.latitude)], {}));
				}

				// convert the resulting points array into a feature collection
				const pointsCollection = turf.featureCollection(points);

				// get the bounds of the points
				const bbox = turf.bbox(pointsCollection);

				// build a 100m hex grid to cover the area of interest
				const hexgrid = turf.hexGrid(bbox, 100, {units: 'meters'});

				// loop through each polygon feature in the hex grid
				let pointsWithin;
				let polygons = []
				for (let i = 0; i < hexgrid.features.length; i++) {

					// calculate the number of crimes within that polygon
					pointsWithin = turf.pointsWithinPolygon(pointsCollection, hexgrid.features[i]);

					// create a new polygon, combining the old geometry with the new 'crimes' property
					polygons.push( turf.polygon(hexgrid.features[i].geometry.coordinates,
							{ crimes: pointsWithin.features.length }));
				}

				// convert the resulting points array into a feature collection
				const polygonCollection = turf.featureCollection(polygons);

				// load into L.GeoJson, style and add to the map
				crimesLayer = L.geoJson(polygonCollection, {
					style: styleGenerator,
					onEachFeature: setEvents
				}).addTo(map);

				//update the info box with the default message
				info.update("Hover over a hex bin");
      }


			/**
			 * This function styles the data (a single country)
			 */
			function styleGenerator(feature) {

				//return a style
				return {
					weight: 1,
					color: 'white',
					fillOpacity: 0.6,
					fillColor: getColour(feature.properties.crimes)	//the colour is set using a function
				};
			}

			/**
			 * Return a colour based upon the given population value
			 */
			function getColour(crimes) {

				// directly return a colour value based upon the value of crimes
				if (crimes > 120){
					return '#800026';
				} else if (crimes > 100) {
					return '#BD0026';
				} else if (crimes > 80) {
					return '#E31A1C';
				} else if (crimes > 60) {
					return '#FC4E2A';
				} else if (crimes > 40) {
					return '#FD8D3C';
				} else if (crimes > 20) {
					return '#FEB24C';
				} else if (crimes > 10) {
					return '#FED976';
				} else if (crimes > 0) {
					return '#FFEDA0';
				} else {
					return "#DDDDDD";
				}
			}


			/**
			 * Create a function to tie the mouseover and mouseout events to re-styling the layer
			 */
			function setEvents(feature, layer) {

				//add event listeners for mouseover and mouseout
				layer.on({
					mouseover: highlightFeature,
					mouseout: resetFeature,
				});
			}


			/**
			 * This function re-styles the data to yellow (for when you hover over it)
			 */
			function highlightFeature(e) {	// e refers to the event object

				// e.target is the hexbin that was hovered over
				const feature = e.target;

				// set the style to yellow
				feature.setStyle({
					fillColor: 'yellow',
				});

				//update the info box
				info.update("<b>" + feature.feature.properties.crimes + "</b> crimes");
			}


			/**
			 * Reset the style after the hover is over
			 */
			function resetFeature(e) {	// e refers to the event object

				// e.target is the hexbin that was hovered over
				const feature = e.target;

				//reset the style of the country that was turned yellow
				crimesLayer.resetStyle(feature);	// e.target is the hexbin that was hovered over

				//update the info box
				info.update("Hover over a hex bin");
			}
		</script>
	</body>
</html>