"use strict"; import path from 'path'; import L from 'leaflet'; import { loadLayersModel as tf_loadLayersModel, tensor as tf_tensor } from '@tensorflow/tfjs'; import chroma from 'chroma-js'; import GetFromUrl from './Helpers/GetFromUrl.mjs'; import Config from './ClientConfig.mjs'; import { normalise } from '../../common/Math.mjs'; class LayerAI { 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; } constructor(map) { this.map = map; this.gateways = new Map(); } async setup() { this.index = JSON.parse( await GetFromUrl(Config.ai_index_file) ); console.log(this.index); for(let gateway of this.index.index) { this.gateways.set( gateway.id, await tf_loadLayersModel(`${window.location.href}/${path.dirname(Config.ai_index_file)}/${gateway.id}/model.json`) ); } this.layer = this.generate_layer(); this.layer.addTo(this.map); console.log("[Layer/AI] Complete"); } generate_layer() { console.log("[Layer/AI] Rendering map"); let map = 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 } } }); } render_map() { // FUTURE: Do this in a web worker? let map_bounds = this.gateway_bounds; map_bounds.north += Config.border.lat; map_bounds.south -= Config.border.lat; map_bounds.east += Config.border.lng; map_bounds.west -= Config.border.lng; 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 ); for(let lat = map_bounds.south; lat < map_bounds.north; lat += Config.step.lat) { for(let lng = map_bounds.west; lng < map_bounds.east; lng += Config.step.lng) { let max_predicted_rssi = -Infinity; for(let [, ai] of this.gateways) { let next_prediction = ai.predict( tf_tensor([ lat, lng ], [1, 2]) ); max_predicted_rssi = Math.max( max_predicted_rssi, next_prediction.arraySync()[0][0] ); } max_predicted_rssi = normalise(max_predicted_rssi, { min: 0, max: 1 }, { min: this.index.properties.rssi_min, max: this.index.properties.rssi_max } ); 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(max_predicted_rssi).toString() } }) } } return coverage; } } export default LayerAI;