|
|
|
@ -3,10 +3,12 @@ |
|
|
|
|
import path from 'path'; |
|
|
|
|
|
|
|
|
|
import L from 'leaflet'; |
|
|
|
|
import tf from '@tensorflow/tfjs'; |
|
|
|
|
import * as tf 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() { |
|
|
|
@ -37,21 +39,28 @@ class LayerAI { |
|
|
|
|
); |
|
|
|
|
console.log(index); |
|
|
|
|
|
|
|
|
|
for(let gateway of this.index) { |
|
|
|
|
let gateway_data = { |
|
|
|
|
// TODO: Swap this out for the real thing - probably a GeoJSON layer or something
|
|
|
|
|
// This is just a placeholder
|
|
|
|
|
layer: L.layerGroup([ |
|
|
|
|
|
|
|
|
|
]), |
|
|
|
|
|
|
|
|
|
ai: await tf.loadModel(`${window.location.href}/${path.dirname(Config.ai_index_file)}/${gateway.id}`) |
|
|
|
|
} |
|
|
|
|
this.gateways.set(gateway.id, gateway_data); |
|
|
|
|
for(let gateway of this.index.index) { |
|
|
|
|
this.gateways.set( |
|
|
|
|
gateway.id, |
|
|
|
|
await tf.LayersModel.loadModel(`${window.location.href}/${path.dirname(Config.ai_index_file)}/${gateway.id}`) |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
this.layer = this.generate_layer(); |
|
|
|
|
this.layer.addTo(this.map); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
generate_layer() { |
|
|
|
|
return L.geoJSON(this.render_map(), { |
|
|
|
|
style: (feature) => { return { |
|
|
|
|
fillColor: feature.properties.colour, |
|
|
|
|
fillOpacity: 0.4 |
|
|
|
|
} } |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async render_map() { |
|
|
|
|
render_map() { |
|
|
|
|
// FUTURE: Do this in a web worker?
|
|
|
|
|
let map_bounds = this.gateway_bounds; |
|
|
|
|
map_bounds.north += Config.border; |
|
|
|
|
map_bounds.south -= Config.border; |
|
|
|
@ -59,7 +68,55 @@ class LayerAI { |
|
|
|
|
map_bounds.east += Config.border; |
|
|
|
|
map_bounds.west -= Config.border; |
|
|
|
|
|
|
|
|
|
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.west; lat < map_bounds.east; lat += Config.step) { |
|
|
|
|
for(let lng = map_bounds.south; lng < map_bounds.north; lng += Config.step) { |
|
|
|
|
let max_predicted_rssi = -Infinity; |
|
|
|
|
|
|
|
|
|
for(let [, ai] of this.gateways) { |
|
|
|
|
max_predicted_rssi = Math.max( |
|
|
|
|
max_predicted_rssi, |
|
|
|
|
ai.predict( |
|
|
|
|
tf.tensor1d([ lat, lng ]) |
|
|
|
|
) |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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: [ |
|
|
|
|
[lat, lng], |
|
|
|
|
[lat + Config.step, lng], |
|
|
|
|
[lat + Config.step, lng + Config.step], |
|
|
|
|
[lat, lng + Config.step] |
|
|
|
|
] |
|
|
|
|
}, |
|
|
|
|
properties: { |
|
|
|
|
colour: colour_scale(max_predicted_rssi) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return coverage; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|