diff --git a/client_src/js/LayerAI.mjs b/client_src/js/LayerAI.mjs
index 8c68b5b..3f5d883 100644
--- a/client_src/js/LayerAI.mjs
+++ b/client_src/js/LayerAI.mjs
@@ -3,6 +3,7 @@
 import L from 'leaflet';
 import chroma from 'chroma-js';
 
+import WorkerWrapper from './WorkerWrapper.mjs';
 import GetFromUrl from './Helpers/GetFromUrl.mjs';
 import Config from './ClientConfig.mjs';
 
@@ -37,67 +38,12 @@ class LayerAI {
 	 */
 	constructor(map) {
 		this.map = map;
-		this.worker = new Worker("worker.js"); // We could pass { type: "module" } as the 2nd argument here to allow Rollup code splitting to function as normal, but it's not supported by browsers yet
+		this.worker = new WorkerWrapper();
 		
 		this.map_bounds = null;
 	}
 	
-	/**
-	 * Sets up the Web Worker that does the TensorFlow prediction.
-	 * Using a web worker avoids hanging the main thread.
-	 * @return	{Promise}	A Promise that resolves when setup is complete.
-	 */
-	worker_setup() {
-		// Arrow functions inherit the parent scope, including the "this" 
-		// special variable.
-		return new Promise((resolve, reject) => {
-			// Attach the listener first
-			this.worker.addEventListener("message", (event) => {
-				if(event.data.event !== "setup-complete") {
-					reject(`Error: AIWorker responded with event ${event.data.event}, but 'setup-complete' was expected.`, event.data);
-					return;
-				}
-				
-				resolve();
-			}, { once: true });
-			
-			// Ask the web worker to set itself up
-			this.worker.postMessage({
-				event: "setup",
-				setup_info: {
-					bounds: this.map_bounds,
-					index: this.index,
-					Config
-				}
-			});
-		})
-	}
 	
-	/**
-	 * Uses the Web Worker to predict a row of signal strength values.
-	 * @param	{number}	latitude	The latitude for which predictions should be made.
-	 * @return	{Promise<number[]>}		A Promise returning the array of predictions calculated by the web worker.
-	 */
-	worker_predict_row(latitude) {
-		return new Promise((resolve, reject) => {
-			// Attach the event listener....
-			this.worker.addEventListener("message", (event) => {
-				if(event.data.event !== "result") {
-					reject(`Error: AIWorker responded with event ${event.data.event}, but 'result' was expected.`, event.data);
-					return;
-				}
-				
-				resolve(event.data);
-				
-			}, { once: true });
-			
-			// ....and send the request
-			this.worker.postMessage({
-				event: "predict-row",
-				latitude
-			});
-		});
-	}
 	
 	/**
 	 * Sets up the Leaflet AI visualisation layer.
@@ -120,7 +66,7 @@ class LayerAI {
 		this.map_bounds.west -= Config.border.lng;
 		
 		// Setup the web worker
-		await this.worker_setup();
+		await this.worker.setup(this.map_bounds, this.index);
 		
 		// Generate the Leaflet layer
 		this.layer = await this.generate_layer();
@@ -170,7 +116,7 @@ class LayerAI {
 		};
 		
 		for(let lat = this.map_bounds.south; lat < this.map_bounds.north; lat += Config.step.lat) {
-			let next_result = await this.worker_predict_row(lat);
+			let next_result = await this.worker.predict_row(lat);
 			let lng = this.map_bounds.west;
 			
 			// Keep up with the statistics
@@ -205,6 +151,8 @@ class LayerAI {
 			}
 		}
 		
+		this.worker.end(this.worker);
+		
 		console.log("Stats:", stats);
 		
 		return coverage;
diff --git a/client_src/js/WorkerWrapper.mjs b/client_src/js/WorkerWrapper.mjs
new file mode 100644
index 0000000..b58e874
--- /dev/null
+++ b/client_src/js/WorkerWrapper.mjs
@@ -0,0 +1,74 @@
+"use strict";
+
+import Config from './ClientConfig.mjs';
+
+class WorkerWrapper {
+	constructor() {
+		this.worker = new Worker("worker.js"); // We could pass { type: "module" } as the 2nd argument here to allow Rollup code splitting to function as normal, but it's not supported by browsers yet
+	}
+	
+	/**
+	 * Sets up the Web Worker that does the TensorFlow prediction.
+	 * Using a web worker avoids hanging the main thread.
+	 * @return	{Promise}	A Promise that resolves when setup is complete.
+	 */
+	setup(map_bounds, index) {
+		// Arrow functions inherit the parent scope, including the "this" 
+		// special variable.
+		return new Promise((resolve, reject) => {
+			// Attach the listener first
+			this.worker.addEventListener("message", (event) => {
+				if(event.data.event !== "setup-complete") {
+					reject(`Error: AIWorker responded with event ${event.data.event}, but 'setup-complete' was expected.`, event.data);
+					return;
+				}
+				
+				resolve();
+			}, { once: true });
+			
+			// Ask the web worker to set itself up
+			this.worker.postMessage({
+				event: "setup",
+				setup_info: {
+					bounds: map_bounds,
+					index,
+					Config
+				}
+			});
+		})
+	}
+	
+	/**
+	 * Uses the Web Worker to predict a row of signal strength values.
+	 * @param	{number}	latitude	The latitude for which predictions should be made.
+	 * @return	{Promise<number[]>}		A Promise returning the array of predictions calculated by the web worker.
+	 */
+	predict_row(latitude) {
+		return new Promise((resolve, reject) => {
+			// Attach the event listener....
+			this.worker.addEventListener("message", (event) => {
+				if(event.data.event !== "result") {
+					reject(`Error: AIWorker responded with event ${event.data.event}, but 'result' was expected.`, event.data);
+					return;
+				}
+				
+				resolve(event.data);
+				
+			}, { once: true });
+			
+			// ....and send the request
+			this.worker.postMessage({
+				event: "predict-row",
+				latitude
+			});
+		});
+	}
+	
+	end() {
+		this.worker.postMessage({
+			event: "end"
+		})
+	}
+}
+
+export default WorkerWrapper;
