From deb02b0edec05faf7f810875ce2a6aed39a7be0e Mon Sep 17 00:00:00 2001
From: Starbeamrainbowlabs <sbrl@starbeamrainbowlabs.com>
Date: Tue, 16 Apr 2019 18:08:15 +0100
Subject: [PATCH] [client] Refactor to support new HTTP API changes

---
 client_src/js/DeviceData.mjs         | 43 ++++++++++++++++++++++++++++
 client_src/js/LayerDeviceMarkers.mjs | 15 +++++-----
 client_src/js/LayerHeatmap.mjs       | 10 ++++++-
 client_src/js/MapManager.mjs         | 22 ++++++++------
 4 files changed, 72 insertions(+), 18 deletions(-)
 create mode 100644 client_src/js/DeviceData.mjs

diff --git a/client_src/js/DeviceData.mjs b/client_src/js/DeviceData.mjs
new file mode 100644
index 0000000..c0bb8e2
--- /dev/null
+++ b/client_src/js/DeviceData.mjs
@@ -0,0 +1,43 @@
+"use strict";
+
+import Config from './Config.mjs';
+
+import GetFromUrl from './Helpers/GetFromUrl.mjs';
+
+/**
+ * Handles and caches data about devices.
+ */
+class DeviceData {
+	/**
+	 * Creates a new DeviceData class instance.
+	 */
+	constructor() {
+		this.devices = [];
+	}
+	
+	/**
+	 * Fetches the device data from the server.
+	 * @return {Promise} A promise that resolves when the data has been fetched from the server.
+	 */
+	async setup() {
+		this.devices = JSON.parse(await GetFromUrl(
+			`${Config.api_root}?action=list-devices`
+		));
+		
+		// Create a map to help us lookup ids faster
+		this.device_map = new Map();
+		for(let device of this.devices)
+			this.device_map.set(device.id, device);
+	}
+	
+	/**
+	 * Looks up a device by its id.
+	 * @param	{Number}	device_id	The ID of the device to return 
+	 * @return	{object}	The info about the device with the specified id.
+	 */
+	get_by_id(device_id) {
+		return this.device_map.get(device_id);
+	}
+}
+
+export default DeviceData;
diff --git a/client_src/js/LayerDeviceMarkers.mjs b/client_src/js/LayerDeviceMarkers.mjs
index 6d8380c..9ea8400 100644
--- a/client_src/js/LayerDeviceMarkers.mjs
+++ b/client_src/js/LayerDeviceMarkers.mjs
@@ -12,8 +12,9 @@ import DeviceReadingDisplay from './DeviceReadingDisplay.mjs';
 import GetFromUrl from './Helpers/GetFromUrl.mjs';
 
 class LayerDeviceMarkers {
-	constructor(in_map) {
+	constructor(in_map, in_device_data) {
 		this.map = in_map;
+		this.device_data = in_device_data;
 		
 		// Create a new clustering layer
 		this.layer = L.markerClusterGroup({
@@ -22,14 +23,12 @@ class LayerDeviceMarkers {
 	}
 	
 	async setup() {
-		
-		// Fetch the device list
-		let device_list = JSON.parse(await GetFromUrl(
-			`${Config.api_root}?action=list-devices&only-with-location=yes`
-		));
-		
 		// Add a marker for each device
-		for (let device of device_list) {
+		for (let device of this.device_data.devices) {
+			// If the device doesn't have a location, we're not interested
+			// FUTURE: We might be able to displaymobile devices by adding additional logic here
+			if(typeof device.latitude != "number" || typeof device.longitude != "number")
+				continue;
 			this.add_device_marker(device);
 		}
 		
diff --git a/client_src/js/LayerHeatmap.mjs b/client_src/js/LayerHeatmap.mjs
index bb61f2a..9a93dd5 100644
--- a/client_src/js/LayerHeatmap.mjs
+++ b/client_src/js/LayerHeatmap.mjs
@@ -11,8 +11,9 @@ class LayerHeatmap {
 	 * Creates a new heatmap manager wrapper class fort he given map.
 	 * @param	{L.Map}	in_map	The leaflet map to attach to.
 	 */
-	constructor(in_map) {
+	constructor(in_map, in_device_data) {
 		this.map = in_map;
+		this.device_data = in_device_data;
 		
 		this.overlay_config = {
 			radius: Config.heatmap.blob_radius,
@@ -101,6 +102,13 @@ class LayerHeatmap {
 	 * @param {object[]} readings_list The array of data points to display.
 	 */
 	set_data(readings_list) {
+		// Substitute in the device locations
+		for(let reading of readings_list) {
+			let device_info = this.device_data.get_by_id(reading.device_id);
+			reading.latitude = device_info.latitude;
+			reading.longitude = device_info.longitude;
+		}
+		
 		let data_object = {
 			max: 0,
 			data: readings_list
diff --git a/client_src/js/MapManager.mjs b/client_src/js/MapManager.mjs
index 0efdf02..a958a91 100644
--- a/client_src/js/MapManager.mjs
+++ b/client_src/js/MapManager.mjs
@@ -9,6 +9,7 @@ import Config from './Config.mjs';
 import LayerDeviceMarkers from './LayerDeviceMarkers.mjs';
 import LayerHeatmap from './LayerHeatmap.mjs';
 import LayerHeatmapGlue from './LayerHeatmapGlue.mjs';
+import DeviceData from './DeviceData.mjs';
 import UI from './UI.mjs';
 
 class MapManager {
@@ -16,7 +17,7 @@ class MapManager {
 		console.log(Config);
 	}
 	
-	setup() {
+	async setup() {
 		// Create the map
 		this.map = L.map("map", {
 			fullscreenControl: true,
@@ -40,14 +41,16 @@ class MapManager {
 		this.map.attributionControl.addAttribution("<a href='https://github.com/ConnectedHumber/Air-Quality-Web/'>Air Quality Web</a> by <a href='https://starbeamrainbowlabs.com/'>Starbeamrainbowlabs</a>");
 		
 		
+		// Load the device information
+		this.device_data = new DeviceData();
+		await this.device_data.setup();
+		console.log("[map] Device data loaded");
+		
 		// Add the device markers
 		console.info("[map] Loading device markers....");
-		this.setup_device_markers().then(() => {
-			console.info("[map] Device markers loaded successfully.");
-			
-			// Display a layer controller
-			this.setup_layer_control();
-		});
+		this.setup_device_markers()
+			.then(() => console.info("[map] Device markers loaded successfully."))
+			.then(this.setup_layer_control.bind(this));
 		
 		// Add the heatmap
 		console.info("[map] Loading heatmap....");
@@ -59,6 +62,7 @@ class MapManager {
 		
 		this.ui = new UI(Config, this);
 		this.ui.setup().then(() => console.log("[map] Settings initialised."));
+		
 	}
 	
 	setup_time_dimension() {
@@ -97,12 +101,12 @@ class MapManager {
 	}
 	
 	async setup_device_markers() {
-		this.device_markers = new LayerDeviceMarkers(this.map);
+		this.device_markers = new LayerDeviceMarkers(this.map, this.device_data);
 		await this.device_markers.setup();
 	}
 	
 	async setup_heatmap() {
-		this.heatmap = new LayerHeatmap(this.map);
+		this.heatmap = new LayerHeatmap(this.map, this.device_data);
 		
 		// TODO: Use leaflet-timedimension here
 		// TODO: Allow configuration of the different reading types here
