diff --git a/client_src/js/Helpers/Rectangle.mjs b/client_src/js/Helpers/Rectangle.mjs
new file mode 100644
index 0000000..22d664a
--- /dev/null
+++ b/client_src/js/Helpers/Rectangle.mjs
@@ -0,0 +1,247 @@
+"use strict";
+
+import Vector from './Vector2.mjs';
+
+/**
+ * Represents a rectangle in 2D space.
+ * @source	https://gist.github.com/d7ff700d3ca57ab5d52ec12a1ee8b8a0
+ * Changelog:
+ 	* 29th May 2019
+ 		* Created this changelog
+ 		* Added setters to TopLeft, Top, Size, etc.
+ */
+class Rectangle
+{
+	/**
+	 * The top-left corner of the rectangle.
+	 * @returns {Vector}
+	 */
+	get TopLeft() {
+		return new Vector(this.x, this.y);
+	}
+	/**
+	 * Sets the top-left corner of the rectangle.
+	 * @param {Vector} point	The point to set it to.
+	 */
+	set TopLeft(point) {
+		this.x = point.x;
+		this.y = point.y;
+	}
+	
+	/**
+	 * The top-right corner of the rectangle.
+	 * @returns {Vector}
+	 */
+	get TopRight() {
+		return new Vector(this.x + this.width, this.y);
+	}
+	/**
+	 * Sets the top-right corner of the rectangle.
+	 * @param {Vector} point	The point to set it to.
+	 */
+	set TopRight(point) {
+		this.width = point.x - this.x;
+		this.y = point.y;
+	}
+	
+	/**
+	 * The bottom-left corner of the rectangle.
+	 * @returns {Vector}
+	 */
+	get BottomLeft() {
+		return new Vector(this.x, this.y + this.height);
+	}
+	/**
+	 * Sets the bottom-left corner of the rectangle.
+	 * @param {Vector} point	The point to set it to.
+	 */
+	set BottomLeft(point) {
+		this.width = point.x;
+		this.y = point.y - this.y;
+	}
+	
+	/**
+	 * The bottom-right corner of the rectangle.
+	 * @returns {Vector}
+	 */
+	get BottomRight() {
+		return new Vector(this.x + this.width, this.y + this.height);
+	}
+	/**
+	 * Sets the bottom-right corner of the rectangle.
+	 * @param {Vector} point	The point to set it to.
+	 */
+	set BottomRight(point) {
+		this.width = point.x - this.x;
+		this.y = point.y - this.y;
+	}
+	
+	/**
+	 * The centre of the rectangle.
+	 * @returns {Vector}
+	 */
+	get Centre() {
+		return new Vector(
+			this.x + (this.width/2),
+			this.y + (this.height/2)
+		);
+	}
+	
+	/**
+	 * The Y coordinate of the top of the rectangle.
+	 * @returns {Number}
+	 */
+	get Top() {
+		return this.y;
+	}
+	/**
+	 * Sets the Y coordinate of the top of the rectangle.
+	 * @param {Number}	y
+	 */
+	set Top(y) {
+		this.y = y;
+	}
+	
+	/**
+	 * The Y coordinate of the bottom of the rectangle.
+	 * @returns {Number}
+	 */
+	get Bottom() {
+		return this.y + this.height;
+	}
+	/**
+	 * Sets the Y coordinate of the bottom of the rectangle.
+	 * @param {Number}	y
+	 */
+	set Bottom(y) {
+		this.height = y - this.y;
+	}
+	
+	/**
+	 * The X coordinate of the left side of the rectangle.
+	 * @returns {Number}
+	 */
+	get Left() {
+		return this.x;
+	}
+	/**
+	 * Sets the X coordinate of the left of the rectangle.
+	 * @param {Number}	x
+	 */
+	set Left(x) {
+		this.x = x;
+	}
+	
+	/**
+	 * The X coordinate of the right side of the rectangle.
+	 * @returns {Number}
+	 */
+	get Right() {
+		return this.x + this.width;
+	}
+	/**
+	 * Sets the X coordinate of the right of the rectangle.
+	 * @param {Number}	x
+	 */
+	set Right(x) {
+		this.x = x - this.x;
+	}
+	
+	/**
+	 * The size of this rectangle.
+	 * @returns {Vector}
+	 */
+	get Size() {
+		return new Vector(this.width, this.height);
+	}
+	/**
+	 * Sets the size of the rectangle.
+	 * @param {Vector} newSize The new size of the rectangle.
+	 */
+	set Size(newSize) {
+		this.width = newSize.x;
+		this.height = newSize.y;
+	}
+
+	constructor(x, y, width, height) {
+		this.x = x;
+		this.y = y;
+		this.width = width;
+		this.height = height;
+	}
+	
+	/**
+	 * Moves this Rectangle by the specified vector.
+	 * @param	{Vector}	v	The amount to move this Rectangle by.
+	 * @return	{Rectangle}	The current rectangle. Useful for daisy-chaining.
+	 */
+	move_by(v) {
+		this.x += v.x;
+		this.y += v.y;
+		
+		return this;
+	}
+	
+	/**
+	 * Figures out whether this rectangle overlaps another rectangle.
+	 * @param	{Rectangle}	otherRectangle	The other rectangle to check the overlap of.
+	 * @return	{bool}		Whether this rectangle overlaps another rectangle.
+	 */
+	overlaps(otherRectangle)
+	{
+		if(this.Top > otherRectangle.Bottom ||
+			this.Bottom < otherRectangle.Top ||
+			this.Left > otherRectangle.Right ||
+			this.Right < otherRectangle.Left)
+			return false;
+
+		return true;
+	}
+	
+	/**
+	 * Works out whether the specified vector falls within this Rectangle.
+	 * @param	{Vector}	v	The vector to test.
+	 * @return	{Boolean}	Whether the specified vector falls within this Rectangle or not.
+	 */
+	is_inside_vector(v) {
+		return v.x >= this.x && v.y >= this.y &&
+			v.x <= this.Right && v.y <= this.Bottom;
+	}
+	
+	/**
+	 * Works out if this Rectangle falls completely within the specified
+	 * rectangle.
+	 * @param	{Rectangle}	otherRectangle	The other rectangle to compare against.
+	 * @return	{Boolean}	Whether this Rectangle faalls within the specified Rectangle.
+	 */
+	is_inside(otherRectangle) {
+		if(this.Top >= otherRectangle.Top &&
+			this.Bottom <= otherRectangle.Bottom &&
+			this.Left >= otherRectangle.Left &&
+			this.Right <= otherRectangle.Right)
+			return true;
+		return false;
+	}
+	
+	/**
+	 * Returns a copy of this rectangle that can be safely edited without affecting the original.
+	 * @returns {Rectangle}
+	 */
+	clone()
+	{
+		return new Rectangle(this.x, this.y, this.width, this.height);
+	}
+	
+	toString() {
+		return `[Rectangle @ (${this.x}, ${this.y}) ${this.width} x ${this.height}]`;
+	}
+}
+
+/**
+ * A rectangle with all it's values initalised to zero.
+ * Don't forget to clone it before editing!
+ * @type {Rectangle}
+ */
+Rectangle.Zero = new Rectangle(0, 0, 0, 0);
+
+export default Rectangle;
diff --git a/client_src/js/Helpers/SVGWriter.mjs b/client_src/js/Helpers/SVGWriter.mjs
index 83fba5b..68f608f 100644
--- a/client_src/js/Helpers/SVGWriter.mjs
+++ b/client_src/js/Helpers/SVGWriter.mjs
@@ -2,6 +2,7 @@
 
 import XMLWriter from 'xml-writer';
 
+import Rectangle from './Rectangle.mjs';
 import Vector2 from './Vector2.mjs';
 
 /*
@@ -11,8 +12,12 @@ import Vector2 from './Vector2.mjs';
  * @license MPL-2.0
  */
 class SvgWriter {
-	constructor(viewBox) {
-		// string outputFilename, string widthspec = "100%", string heightspec = "100%", Rectangle? rawViewBox = null
+	/**
+	 * @param	{String}	[widthspec="100%"]
+	 * @param	{String}	[heightspec="100%"]
+	 * @param	{Retangle}	[viewBox=null]
+	 */
+	constructor(widthspec = "100%", heightspec = "100%", viewBox = null) {
 		this.unitSuffix = "";
 		
 		// ----------------------------------
diff --git a/client_src/js/Overlay/VoronoiOverlay.mjs b/client_src/js/Overlay/VoronoiOverlay.mjs
index 466bf46..eb794d3 100644
--- a/client_src/js/Overlay/VoronoiOverlay.mjs
+++ b/client_src/js/Overlay/VoronoiOverlay.mjs
@@ -1,9 +1,69 @@
 "use strict";
 
+import Voronoi from 'voronoi';
+
+import Vector2 from '../Helpers/Vector2.mjs';
+import Rectangle from '../Helpers/Rectangle.mjs';
+
+import SvgWriter from '../Helpers/SVGWriter.mjs';
+
+
+/**
+ * Generates and manages a single voronoi SVGOverlay layer.
+ */
 class VoronoiOverlay {
 	constructor() {
+		this.border = new Vector2(0.1, 0.1); // lat / long
 		
+		this.cells = [];
+	}
+	
+	addCells(...cells) {
+		this.cells.push(...cells);
+	}
+	
+	/**
+	 * Computes the bounding box of all the currently registered points.
+	 * Includes a border, which is defined by this.border.
+	 * @return {Rectangle} The bounding box of the currently registered points.
+	 */
+	computeBoundingBox() {
+		let result = new Rectangle(Infinity, Infinity, -Infinity, -Infinity);
+		
+		for(let cell of this.cells) {
+			if(cell.point.x < result.x) result.x = cell.point.x;
+			if(cell.point.y < result.y) result.y = cell.point.y;
+			if(cell.point.x > result.Right) result.Right = cell.point.x;
+			if(cell.point.y > result.Bottom) result.Bottom = cell.point.y;
+		}
+		
+		result.Left -= this.border.x;
+		result.Right += this.border.x;
+		result.Top -= this.border.y;
+		result.Bottom += this.border.y;
+		
+		return result;
+	}
+	
+	render() {
+		let bounding_box = this.computeBoundingBox();
+		
+		// Recycle the diagram object if possible
+		if(typeof VoronoiOverlay.diagram !== "undefined")
+			VoronoiOverlay.voronoi.recycle(VoronoiOverlay.diagram);
+		
+		VoronoiOverlay.diagram = VoronoiOverlay.voronoi.compute(
+			this.cells.map((cell) => cell.point),
+			bounding_box
+		);
+		
+		console.log(VoronoiOverlay.diagram)
+		
+		let svg = new SvgWriter();
 	}
 }
 
+VoronoiOverlay.voronoi = new Voronoi();
+
+
 export default VoronoiOverlay;
diff --git a/package-lock.json b/package-lock.json
index 4b23cae..31bb7a6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4362,6 +4362,11 @@
       "integrity": "sha512-w/hry/368nO21AN9QljsaIhb9ZiZtZARoVH5f3CsFbawdLdayCgKRPup7CggujvySMxx0I91NOyxdVENohprLQ==",
       "dev": true
     },
+    "voronoi": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/voronoi/-/voronoi-1.0.0.tgz",
+      "integrity": "sha1-wIPK+pjENZpRgBi/6IYkyyJ6wpE="
+    },
     "xml-writer": {
       "version": "1.7.0",
       "resolved": "https://registry.npmjs.org/xml-writer/-/xml-writer-1.7.0.tgz",
diff --git a/package.json b/package.json
index d4348d8..85d57ae 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,7 @@
 		"nanomodal": "^5.1.1",
 		"smartsettings": "^1.2.3",
 		"tabs": "^0.2.0",
+		"voronoi": "^1.0.0",
 		"xml-writer": "^1.7.0"
 	},
 	"devDependencies": {
