mirror of
https://github.com/ConnectedHumber/Air-Quality-Web
synced 2024-11-25 06:53:00 +00:00
126 lines
3 KiB
JavaScript
126 lines
3 KiB
JavaScript
"use strict";
|
|
|
|
import L from 'leaflet';
|
|
import { Delaunay } from 'd3-delaunay';
|
|
import chroma from 'chroma-js';
|
|
|
|
import Vector2 from '../Helpers/Vector2.mjs';
|
|
import Rectangle from '../Helpers/Rectangle.mjs';
|
|
|
|
/**
|
|
* Generates and manages a single voronoi SVGOverlay layer.
|
|
*/
|
|
class VoronoiOverlay {
|
|
constructor() {
|
|
this.border = new Vector2(0.1, 0.1); // lat / long
|
|
|
|
this.cells = [];
|
|
}
|
|
|
|
/**
|
|
* Sets the list of cells in the voronoi overlay.
|
|
* @param {VoronoiCell[]} cells The cells to add, as an array.
|
|
*/
|
|
set_cells(cells) {
|
|
this.cells.length = 0;
|
|
this.add_cells(...cells);
|
|
}
|
|
/**
|
|
* Adds a cell to the voronoi overlay.
|
|
* @param {VoronoiCell} cells The cell to add. May be specified as many times as requires to add cells in bulk.
|
|
*/
|
|
add_cells(...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 = {
|
|
x_min: Infinity,
|
|
x_max: -Infinity,
|
|
y_min: Infinity,
|
|
y_max: -Infinity
|
|
}
|
|
|
|
for(let cell of this.cells) {
|
|
if(cell.point.x < result.x_min) result.x_min = cell.point.x;
|
|
if(cell.point.x > result.x_max) result.x_max = cell.point.x;
|
|
if(cell.point.y < result.y_min) result.y_min = cell.point.y;
|
|
if(cell.point.y > result.y_max) result.y_max = cell.point.y;
|
|
}
|
|
|
|
result.x_min -= this.border.x;
|
|
result.y_min -= this.border.y;
|
|
result.x_max += this.border.x;
|
|
result.y_max += this.border.y;
|
|
|
|
return new Rectangle(
|
|
result.x_min,
|
|
result.y_min,
|
|
result.x_max - result.x_min,
|
|
result.y_max - result.y_min
|
|
);
|
|
}
|
|
|
|
render() {
|
|
let bounding_box = this.computeBoundingBox();
|
|
|
|
let voronoi = Delaunay.from(this.cells.map(
|
|
(cell) => [cell.point.x, cell.point.y])
|
|
).voronoi([
|
|
bounding_box.x, bounding_box.y,
|
|
bounding_box.Right, bounding_box.Bottom
|
|
]);
|
|
|
|
let i = 0;
|
|
for(let polygon of voronoi.cellPolygons()) {
|
|
let our_cell = this.cells[i];
|
|
our_cell.polygon = polygon.map((point) => new Vector2(...point));
|
|
|
|
i++;
|
|
}
|
|
|
|
let geojson = [];
|
|
|
|
// TODO: Render the SVG here
|
|
for(let cell of this.cells) {
|
|
if(cell.polygon == null) {
|
|
console.warn("Warning: Null cell polygon encountered.", cell);
|
|
continue;
|
|
}
|
|
|
|
geojson.push({
|
|
"type": "Feature",
|
|
"geometry": {
|
|
"type": "Polygon",
|
|
"coordinates": [cell.polygon.map((point) => [point.x, point.y])],
|
|
},
|
|
"properties": {
|
|
"colour": cell.colour == null ? "hsl(0, 100%, 100%)" : cell.colour
|
|
}
|
|
});
|
|
}
|
|
return geojson;
|
|
}
|
|
|
|
generate_layer() {
|
|
return L.geoJSON(this.render(), {
|
|
// FUTURE: If we want to be even moar fanceh, we can check out https://leafletjs.com/reference-1.5.0.html#path
|
|
style: (feature) => { return {
|
|
// Stroke
|
|
color: chroma(feature.properties.colour).darken(0.75).toString(),
|
|
|
|
// Fill
|
|
fillColor: feature.properties.colour,
|
|
fillOpacity: 0.4
|
|
} }
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
export default VoronoiOverlay;
|