"use strict"; 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'; class LayerAI { /** * Computes a bounding box that exactly encompasses all the gateways in * the index. * @return {{north:number,south:number,east:number,west:number}} The computed bounding box */ get gateway_bounds() { let result = { east: Infinity, west: -Infinity, north: Infinity, south: -Infinity }; for(let gateway of this.index.index) { result.east = Math.min(gateway.longitude, result.east); result.west = Math.max(gateway.longitude, result.west); result.north = Math.min(gateway.latitude, result.north); result.south = Math.max(gateway.latitude, result.south); } return result; } /** * Initialises a new Leaflet AI layer instance. * @param {[type]} map [description] */ constructor(map) { this.map = map; this.worker = new WorkerWrapper(); this.map_bounds = null; } /** * Sets up the Leaflet AI visualisation layer. * @return {Promise} A promise that resolves when setup is complete. */ async setup() { // Download the index file that tells us where the gateways and their // trained models are located this.index = JSON.parse( await GetFromUrl(Config.ai_index_file) ); console.log(this.index); // Figure out the bounds of the map we're going to generate this.map_bounds = this.gateway_bounds; this.map_bounds.north += Config.border.lat; this.map_bounds.south -= Config.border.lat; this.map_bounds.east += Config.border.lng; this.map_bounds.west -= Config.border.lng; // Setup the web worker await this.worker.setup(this.map_bounds, this.index); // Generate the Leaflet layer this.layer = await this.generate_layer(); this.layer.addTo(this.map); console.log("[Layer/AI] Complete"); } /** * Generates and returns the Leaflet layer containing the AI-predicted * values. * @return {Promise} A Promise that resolves to the generated Leaflet layer. */ async generate_layer() { console.log("[Layer/AI] Rendering map"); let map = await this.render_map(); console.log("[Layer/AI] Passing to Leaflet"); return L.geoJSON(map, { style: (feature) => { return { stroke: true, color: feature.properties.colour, weight: 1, fillColor: feature.properties.colour, fillOpacity: 0.4 } } }); } /** * Uses a Web Worker and pre-trained AIs to generate a GeoJSON map of * signal strength for a bounding box around the known gateways. * @return {Promise} A Promise that resolves to a GeoJSON array representing the map. */ async render_map() { let coverage = [], colour_scale = chroma.scale([ Config.colour_scale.min, Config.colour_scale.max ]).domain( this.index.properties.rssi_min, this.index.properties.rssi_max ); let stats = { rssi_min: Infinity, rssi_max: -Infinity }; 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 lng = this.map_bounds.west; // Keep up with the statistics if(next_result.stats.rssi_max > stats.rssi_max) stats.rssi_max = next_result.stats.rssi_max; if(next_result.stats.rssi_min < stats.rssi_min) stats.rssi_min = next_result.stats.rssi_min; for(let value of next_result.result) { // Generate the GeoJSON feature for this cell of the map coverage.push({ type: "Feature", geometry: { type: "Polygon", coordinates: [ [ // Outer shape [lng, lat], [lng, lat + Config.step.lat], [lng + Config.step.lng, lat + Config.step.lat], [lng + Config.step.lng, lat] ] // If there were any holes in the shape, we'd put them here ] }, properties: { colour: colour_scale(value).toString() } }); lng += Config.step.lng; } } this.worker.end(this.worker); console.log("Stats:", stats); return coverage; } } export default LayerAI;