mirror of
https://github.com/wassname/spaceapps2017_matches.git
synced 2026-06-27 16:17:00 +08:00
298 lines
10 KiB
HTML
298 lines
10 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Montecarlo simulation of bushfire risk</title>
|
|
<link rel="stylesheet" type="text/css" href="/css/bootstrap.css" />
|
|
<link rel="stylesheet" type="text/css" href="/css/style.css" />
|
|
|
|
<!-- JS -->
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="row">
|
|
<div class="col-md-10 col-md-offset-1">
|
|
|
|
<div class="page-header">
|
|
<h1>Bushfire Simulator</h1>
|
|
<h3>A simulation on landsat 8 data from NASA</h3>
|
|
</div>
|
|
<div class="buttons-group">
|
|
<button id="start" type="button">Start</button>
|
|
<button id="stop" type="button">Stop</button>
|
|
<button id="next" type="button">next</button>
|
|
<button id="reset" type="button">reset</button>
|
|
</div>
|
|
<canvas id="myCanvas"></canvas>
|
|
<div class="description">
|
|
This simulation
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
|
|
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script> -->
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.8.0/d3.js"></script>
|
|
|
|
<!-- jstat -->
|
|
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/random-js/1.0.8/random.min.js"></script> -->
|
|
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/chance/1.0.6/chance.min.js"></script> -->
|
|
<script src="https://d3js.org/d3-random.v1.js"></script>
|
|
|
|
<!-- We need matricies with convolution and reshape -->
|
|
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/3.12.1/math.min.js"></script> -->
|
|
|
|
<!-- nj.image.read doesn't work -->
|
|
<script src="https://rawgit.com/nicolaspanel/numjs/893016ec40e62eaaa126e1024dbe250aafb3014b/dist/numjs.js"></script>
|
|
|
|
|
|
<script type="text/javascript">
|
|
class Simulation {
|
|
constructor(canvas,data) {
|
|
this.canvas=canvas
|
|
this.W = canvas.width = data.fuel.shape[0]
|
|
this.H = canvas.height = data.fuel.shape[1]
|
|
this.clock = 0;
|
|
|
|
// this.data = nj.zeros([this.W,this.H,4]) // fire,fuel, ash, alpha
|
|
|
|
data.fuel = data.fuel.reshape([data.fuel.shape[0],data.fuel.shape[1],1])
|
|
data.fire = nj.zeros(data.fuel.shape)
|
|
data.ash = nj.zeros(data.fuel.shape)
|
|
data.alpha = nj.ones(data.fuel.shape)
|
|
this.data = nj.concatenate([data.fire,data.fuel,data.ash,data.alpha])
|
|
|
|
// initial point away from walls
|
|
// we will make a nice bright cross
|
|
var x = _.round(d3.randomUniform(20, this.W-20)())
|
|
var y = _.round(d3.randomUniform(20, this.H-20)())
|
|
console.log('fire',x,y)
|
|
this.data.set(x,y,0,1)
|
|
this.data.set(x-1,y-1,0,0.3)
|
|
this.data.set(x+1,y-1,0,0.3)
|
|
this.data.set(x-1,y+1,0,0.3)
|
|
this.data.set(x+1,y+1,0,0.3)
|
|
this.data.set(x,y,1,0.3)
|
|
|
|
this.display();
|
|
}
|
|
tick(){
|
|
// PARAMS TODO move them
|
|
var fuelMultipler = 5 // how many turns it burns for
|
|
var fireMultiplier = 1
|
|
|
|
// tick the environment
|
|
this.clock++;
|
|
if (this.clock>100) return 0;
|
|
console.log('tick',this.clock)
|
|
|
|
// I could do nearby fires as a convoluton, but lets wait to include elevation diff
|
|
var all_fires = this.data.slice(null,null,[0,1]).clone()
|
|
// var filter = nj.array([0.5,1,0.5,1,0,1,0.5,1,0.5]).reshape(3,3,1)
|
|
// var nearby_fires = nj.convolve(all_fires,filter)
|
|
// console.log('max',nearby_fires.max())
|
|
|
|
var new_fires = []
|
|
|
|
var fires=0
|
|
for (var x = 1; x < this.W-1; x++) {
|
|
for (var y = 1; y < this.H-1; y++) {
|
|
// can we do this as a convolution?
|
|
var fire = this.data.get(x,y,0)*fireMultiplier
|
|
var fuel = this.data.get(x,y,1)*fuelMultipler
|
|
var ash = this.data.get(x,y,2)
|
|
|
|
if (fuel==0) continue
|
|
|
|
ash += fire // fire from last turn causes ash to build up
|
|
fuel = _.clamp(fuel-fire,0,fuelMultipler) // and fuel to decrease
|
|
if (fire>0){
|
|
fire*=2 // exponentially grow within pixel
|
|
} else {
|
|
// each neighbouring tile might light it
|
|
//
|
|
// TODO it spreads right because new updates effects the next cell, fix this
|
|
|
|
// TODO include slope or elevation differences
|
|
var fires_adajacent = [
|
|
this.data.get(x-1,y,0),
|
|
this.data.get(x,y+1,0),
|
|
this.data.get(x+1,y,0),
|
|
this.data.get(x,y-1,0),
|
|
]
|
|
var fires_diagonal = [
|
|
this.data.get(x-1,y-1,0),
|
|
this.data.get(x+1,y+1,0),
|
|
this.data.get(x+1,y-1,0),
|
|
this.data.get(x-1,y+1,0),
|
|
]
|
|
var nearby_fires = (_.sum(fires_adajacent)+_.sum(fires_diagonal)/2)/8
|
|
if (nearby_fires==0) continue
|
|
|
|
// neighbouring tiles have a chance of lighting our tile
|
|
if (nearby_fires<Math.random()) nearby_fires=0
|
|
}
|
|
// clamp between 0 and fuel remaining.
|
|
fire = _.clamp((fire+nearby_fires),0,_.min([1,fuel]))
|
|
// round it so we don't deal with tiny fractions
|
|
fire = _.round(fire,2)
|
|
|
|
console.assert(fire!=undefined)
|
|
console.assert(fuel!=undefined)
|
|
console.assert(ash!=undefined)
|
|
|
|
all_fires.set(x,y,0,fire/fireMultiplier)
|
|
this.data.set(x,y,1,fuel/fuelMultipler)
|
|
this.data.set(x,y,2,ash)
|
|
if (fire>0){
|
|
fires+=fire
|
|
// console.debug('fire',x,y,fire,fuel,ash)
|
|
}
|
|
}
|
|
}
|
|
|
|
// set all the new fires
|
|
for (var x = 1; x < this.W-1; x++) {
|
|
for (var y = 1; y < this.H-1; y++) {
|
|
this.data.set(x,y,0,all_fires.get(x,y,0))
|
|
}
|
|
}
|
|
|
|
console.log('tick',this.clock,'change',fires)
|
|
return fires
|
|
|
|
|
|
}
|
|
display(){
|
|
var H = this.canvas.height;
|
|
var W = this.canvas.width;
|
|
var ctx = this.canvas.getContext('2d');
|
|
var imageData = ctx.getImageData(0,0,H,W)
|
|
console.assert(this.data.selection.data.length==imageData.data.length)
|
|
|
|
// for (var x = 1; x < this.W-1; x++) {
|
|
// for (var y = 1; y < this.H-1; y++) {
|
|
// // mix the data into colors
|
|
// var cell = this.data.slice([0,1],[0,1],null).reshape(1,4)
|
|
// var filter = nj.array([
|
|
// [1,0,0,0],
|
|
// [0,1,0,0],
|
|
// [0,0,1,0],
|
|
// [0,0,0,1] // r,g,b,a
|
|
// ]).reshape(4,4)
|
|
// var colors = nj.dot(cell,filter)
|
|
//
|
|
// // now set imageData
|
|
// for (var i = 0; i < 4; i++) {
|
|
// imageData.data[i + this.W *(x + 4*z)]=colors.selection.data[i]*255.0
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// set the data with fire,fuel,ash as red,green,blue
|
|
for (var i = 0; i < imageData.data.length; i++) {
|
|
imageData.data[i]=this.data.selection.data[i]*255.0
|
|
}
|
|
ctx.putImageData(imageData, 0, 0);
|
|
|
|
}
|
|
start(n=10000){
|
|
this.stop()
|
|
this.loop = setInterval(()=>{
|
|
this.tick()
|
|
this.display()
|
|
},n)
|
|
}
|
|
stop(){
|
|
if (this.loop) clearInterval(this.loop)
|
|
}
|
|
}
|
|
|
|
|
|
</script>
|
|
<script type="text/javascript">
|
|
// parameters
|
|
var simulations = 2
|
|
var fuel_src = 'images/X145.34083_Y-37.533_DT20170427-093030_3857_30/2017.EVI.png'
|
|
var elevation_src = 'images/X145.34083_Y-37.533_DT20170427-093030_3857_30/NSW.elevation.png'
|
|
|
|
/**
|
|
* Load png images as an array pf [r,g,b,a,r1,g1,b1,a1,...]
|
|
* @param {String} image - image url
|
|
* @return {Array}
|
|
*/
|
|
function loadImage(image){
|
|
return new Promise(function(resolve, reject) {
|
|
// var canvas = document.createElement('canvas');
|
|
var img=new Image();
|
|
// https://jsfiddle.net/nicolaspanel/047gwg0q/
|
|
img.onload = function() {
|
|
// load using numpyjs
|
|
var data = nj.images.read(img)
|
|
|
|
// convert to float 32?
|
|
data.dtype="float32"
|
|
for (var i = 0; i < data.selection.data.length; i++) {
|
|
data.selection.data[i]/=255
|
|
}
|
|
|
|
resolve(data)
|
|
}
|
|
img.onerror=reject
|
|
img.src=image;
|
|
});
|
|
|
|
|
|
}
|
|
|
|
// load image into canvases
|
|
window.onload = function() {
|
|
var canvas=document.getElementById('myCanvas');
|
|
var prom = Promise.all([
|
|
loadImage(fuel_src),
|
|
loadImage(elevation_src),
|
|
]);
|
|
// prom.then(fuel=>console.log(fuel))
|
|
prom.then(([fuel,elev]) => {
|
|
var simulation = new Simulation(
|
|
canvas,
|
|
{
|
|
fuel:nj.images.rgb2gray(fuel),
|
|
elev:nj.images.rgb2gray(elev),
|
|
}
|
|
);
|
|
// simulation.start(20000)
|
|
|
|
// start simulation
|
|
|
|
|
|
|
|
// choose a starting point
|
|
|
|
// also load a cumulative risk canvas
|
|
// for each
|
|
// choose starting pixels and display
|
|
// now propogate
|
|
// repeat
|
|
document.getElementById('start').onclick=function(){
|
|
simulation.start()
|
|
}
|
|
document.getElementById('stop').onclick=function(){
|
|
simulation.stop()
|
|
}
|
|
document.getElementById('next').onclick=function(){
|
|
simulation.tick()
|
|
simulation.display()
|
|
}
|
|
})
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
</script>
|
|
</html>
|