mirror of
https://github.com/wassname/spaceapps2017_matches.git
synced 2026-06-27 16:17:00 +08:00
tart using leaflet js
This commit is contained in:
+338
-129
@@ -5,46 +5,64 @@
|
|||||||
<title>Montecarlo simulation of bushfire risk</title>
|
<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/bootstrap.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="/css/style.css" />
|
<link rel="stylesheet" type="text/css" href="/css/style.css" />
|
||||||
|
|
||||||
<!-- JS -->
|
<!-- JS -->
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-10 col-md-offset-1">
|
<div class="col-md-10 col-md-offset-1">
|
||||||
|
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h1>Bushfire Simulator</h1>
|
<h1>Bushfire Simulator</h1>
|
||||||
<h3>A simulation on landsat 8 data from NASA</h3>
|
<h3>A simulation on landsat 8 data from NASA</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons-group">
|
<div class="buttons-group">
|
||||||
<button id="start" type="button">Start</button>
|
<button id="start" type="button">Start</button>
|
||||||
<button id="stop" type="button">Stop</button>
|
<button id="stop" type="button">Stop</button>
|
||||||
<button id="next" type="button">next</button>
|
<button id="next" type="button">next</button>
|
||||||
<button id="reset" type="button">reset</button>
|
<button id="reset" type="button">reset</button>
|
||||||
</div>
|
<div><label>Hours: </label><span id="tick">0</span></div>
|
||||||
<canvas id="myCanvas"></canvas>
|
</div>
|
||||||
<div class="description">
|
|
||||||
This simulation
|
<div id="canvases">
|
||||||
|
<!-- Hiden canvases used to generate rasters for leaflet, can be used for debugging -->
|
||||||
|
<style media="screen">
|
||||||
|
.layer1 {z-index: 1;}
|
||||||
|
.layer2 {z-index: 2; opacity: 0.5}
|
||||||
|
.layer3 {z-index: 3}
|
||||||
|
.layer4 {z-index: 4}
|
||||||
|
.layer5 {z-index: 5}
|
||||||
|
.overlay {position:absolute}
|
||||||
|
.overlay { width: 50%; opacity:0 }
|
||||||
|
</style>
|
||||||
|
<canvas id="map" class="layer1 overlay"></canvas>
|
||||||
|
<canvas id="fuel" class="layer2 overlay"></canvas>
|
||||||
|
<canvas id="ash" class="layer3 overlay"></canvas>
|
||||||
|
<canvas id="fire" class="layer4 overlay"></canvas>
|
||||||
|
<canvas id="elevation" class="layer5 overlay"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="width:100%; height:500px" id="leaflet-map">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</body>
|
||||||
</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/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/jquery/3.2.1/jquery.js"></script> -->
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.8.0/d3.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.8.0/d3.js"></script>
|
||||||
|
|
||||||
<!-- jstat -->
|
<!-- mapping -->
|
||||||
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/random-js/1.0.8/random.min.js"></script> -->
|
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css" />
|
||||||
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/chance/1.0.6/chance.min.js"></script> -->
|
<script src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js"></script>
|
||||||
<script src="https://d3js.org/d3-random.v1.js"></script>
|
<link rel="stylesheet" href="https://api.mapbox.com/mapbox.js/v2.2.1/mapbox.css">
|
||||||
|
<script src="https://api.mapbox.com/mapbox.js/v2.2.1/mapbox.js"></script>
|
||||||
|
|
||||||
<!-- We need matricies with convolution and reshape -->
|
<!-- We need matrices with convolution and reshape methods -->
|
||||||
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/3.12.1/math.min.js"></script> -->
|
<script src="https://rawgit.com/waylonflinn/weblas/master/dist/weblas.js"></script>
|
||||||
|
|
||||||
<!-- nj.image.read doesn't work -->
|
|
||||||
<script src="https://rawgit.com/nicolaspanel/numjs/893016ec40e62eaaa126e1024dbe250aafb3014b/dist/numjs.js"></script>
|
<script src="https://rawgit.com/nicolaspanel/numjs/893016ec40e62eaaa126e1024dbe250aafb3014b/dist/numjs.js"></script>
|
||||||
|
|
||||||
|
|
||||||
@@ -52,22 +70,36 @@
|
|||||||
class Simulation {
|
class Simulation {
|
||||||
constructor(canvas,data) {
|
constructor(canvas,data) {
|
||||||
this.canvas=canvas
|
this.canvas=canvas
|
||||||
this.W = canvas.width = data.fuel.shape[0]
|
this.H = canvas.height = data.fuel.shape[0]
|
||||||
this.H = canvas.height = data.fuel.shape[1]
|
this.W = canvas.width = data.fuel.shape[1]
|
||||||
this.clock = 0;
|
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.fuel = data.fuel.reshape([data.fuel.shape[0],data.fuel.shape[1],1])
|
||||||
data.fire = nj.zeros(data.fuel.shape)
|
data.fire = nj.zeros(data.fuel.shape)
|
||||||
data.ash = nj.zeros(data.fuel.shape)
|
data.ash = nj.zeros(data.fuel.shape)
|
||||||
data.alpha = nj.ones(data.fuel.shape)
|
data.elev = data.elev.reshape([data.elev.shape[0],data.elev.shape[1],1])
|
||||||
this.data = nj.concatenate([data.fire,data.fuel,data.ash,data.alpha])
|
this.data = nj.concatenate([data.fire,data.fuel,data.ash,data.elev])
|
||||||
|
|
||||||
|
var coords = this.ignite()
|
||||||
|
this.display();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set an initial fire */
|
||||||
|
ignite(e){
|
||||||
|
// TODO make click to ignore work with zooming
|
||||||
|
var canvas = this.canvas
|
||||||
|
|
||||||
// initial point away from walls
|
// initial point away from walls
|
||||||
// we will make a nice bright cross
|
// we will make a nice bright cross so it's visible
|
||||||
var x = _.round(d3.randomUniform(20, this.W-20)())
|
var x,y
|
||||||
var y = _.round(d3.randomUniform(20, this.H-20)())
|
if (e){
|
||||||
|
// get click location FIXME might not work on all browsers
|
||||||
|
y=_.round(e.layerX/this.canvas.offsetWidth*this.canvas.width)
|
||||||
|
x=_.round(e.layerY/this.canvas.offsetHeight*this.canvas.height)
|
||||||
|
} else {
|
||||||
|
x = _.round(d3.randomUniform(20, this.W-20)())
|
||||||
|
y = _.round(d3.randomUniform(20, this.H-20)())
|
||||||
|
}
|
||||||
console.log('fire',x,y)
|
console.log('fire',x,y)
|
||||||
this.data.set(x,y,0,1)
|
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)
|
||||||
@@ -75,130 +107,199 @@ class Simulation {
|
|||||||
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.data.set(x,y,1,0.3)
|
||||||
|
this.display()
|
||||||
this.display();
|
return [x,y]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* advance model by one tick */
|
||||||
tick(){
|
tick(){
|
||||||
// PARAMS TODO move them
|
// PARAMS TODO move them
|
||||||
var fuelMultipler = 5 // how many turns it burns for
|
var fuelMultipler = 5 // how many turns it burns for
|
||||||
var fireMultiplier = 1
|
var fireMultiplier = 1
|
||||||
|
var fireGrowth = 1.5
|
||||||
|
var transmissionChance = 0.3
|
||||||
|
|
||||||
|
// distance to diagonal tiles as a ratio to tile size np.sqrt(1**2+1**2)
|
||||||
|
var diagDist = 1.42
|
||||||
|
|
||||||
// tick the environment
|
// tick the environment
|
||||||
this.clock++;
|
this.clock++;
|
||||||
if (this.clock>100) return 0;
|
var t0 = new Date().getTime()
|
||||||
|
document.getElementById('tick').innerText=this.clock
|
||||||
|
if (this.clock>1000) return 0;
|
||||||
console.log('tick',this.clock)
|
console.log('tick',this.clock)
|
||||||
|
//
|
||||||
|
// we are modifying the data in place, so freeze a copy of the old data
|
||||||
|
var oldData = this.data.clone()
|
||||||
|
|
||||||
// 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()
|
* Equations: transmission_probability from a nearby tile:
|
||||||
// var filter = nj.array([0.5,1,0.5,1,0,1,0.5,1,0.5]).reshape(3,3,1)
|
* $ t = I * Rt$ where I is fire intensity and Rt is the slope term
|
||||||
// var nearby_fires = nj.convolve(all_fires,filter)
|
* $ Rt = exp(0.069 theta) $ theta is the slope angle from -90 to 90 degrees
|
||||||
// console.log('max',nearby_fires.max())
|
* this reflects that its hard for fire to spread downhill
|
||||||
|
* $ theta = atan(dh/w) $ where dh is the difference in height and w is width
|
||||||
var new_fires = []
|
* $ theta ~= dh/w $ using the small tan approximation
|
||||||
|
*
|
||||||
|
* giving the total equation
|
||||||
|
* $ t = I * exp(0.069 *dh/w) $
|
||||||
|
*
|
||||||
|
* (from Noble et al 1980, DOI: 10.1111/j.1442-9993.1980.tb01243.x)
|
||||||
|
*/
|
||||||
var fires=0
|
var fires=0
|
||||||
for (var x = 1; x < this.W-1; x++) {
|
for (var x = 1; x < this.W-1; x++) {
|
||||||
for (var y = 1; y < this.H-1; y++) {
|
for (var y = 1; y < this.H-1; y++) {
|
||||||
// can we do this as a convolution?
|
// can we do this as a convolution?
|
||||||
var fire = this.data.get(x,y,0)*fireMultiplier
|
var fire = oldData.get(x,y,0)*fireMultiplier
|
||||||
var fuel = this.data.get(x,y,1)*fuelMultipler
|
var fuel = oldData.get(x,y,1)*fuelMultipler
|
||||||
var ash = this.data.get(x,y,2)
|
var ash = oldData.get(x,y,2)
|
||||||
|
|
||||||
|
var transmissionProbability = 0
|
||||||
if (fuel==0) continue
|
if (fuel==0) continue
|
||||||
|
|
||||||
ash += fire // fire from last turn causes ash to build up
|
ash += fire // fire from last turn causes ash to build up
|
||||||
fuel = _.clamp(fuel-fire,0,fuelMultipler) // and fuel to decrease
|
fuel = _.clamp(fuel-fire,0,fuelMultipler) // and fuel to decrease
|
||||||
if (fire>0){
|
if (fire>0){
|
||||||
fire*=2 // exponentially grow within pixel
|
fire*=fireGrowth // exponentially grow within pixel
|
||||||
} else {
|
} else {
|
||||||
// each neighbouring tile might light it
|
// 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
|
// intensity of fires in nearby cells
|
||||||
var fires_adajacent = [
|
var fires_nearby = oldData.slice([x-1,x+2],[y-1,y+2],[0,1]).reshape(3,3)
|
||||||
this.data.get(x-1,y,0),
|
// account for diagonal and zero the middle
|
||||||
this.data.get(x,y+1,0),
|
if (fires_nearby.sum()==0) continue
|
||||||
this.data.get(x+1,y,0),
|
var width_inv = nj.array([
|
||||||
this.data.get(x,y-1,0),
|
[1/diagDist,1, 1/diagDist ],
|
||||||
]
|
[1, 1e-7, 1 ],
|
||||||
var fires_diagonal = [
|
[diagDist, 1, 1/diagDist ]
|
||||||
this.data.get(x-1,y-1,0),
|
])
|
||||||
this.data.get(x+1,y+1,0),
|
var intensity = nj.multiply(fires_nearby,width_inv)
|
||||||
this.data.get(x+1,y-1,0),
|
|
||||||
this.data.get(x-1,y+1,0),
|
|
||||||
]
|
// Slope spreading term //
|
||||||
var nearby_fires = (_.sum(fires_adajacent)+_.sum(fires_diagonal)/2)/8
|
// get difference in height ( height is in pixel width units)
|
||||||
if (nearby_fires==0) continue
|
var height = oldData.slice([x-1,x+2],[y-1,y+2],[3,4]).reshape(3,3)
|
||||||
|
var h0 = oldData.get(x,y,3)
|
||||||
|
var dHeight = nj.subtract(height,h0)
|
||||||
|
var Rt = nj.exp(nj.multiply(dHeight,width_inv).multiply(0.069))
|
||||||
|
|
||||||
|
transmissionProbability = nj.multiply(intensity,Rt)
|
||||||
|
transmissionProbability = transmissionProbability.mean()
|
||||||
|
|
||||||
// neighbouring tiles have a chance of lighting our tile
|
// neighbouring tiles have a chance of lighting our tile
|
||||||
if (nearby_fires<Math.random()) nearby_fires=0
|
console.assert(transmissionProbability<=1)
|
||||||
|
if ((transmissionProbability*(fuel**2)*transmissionChance)<Math.random()) {
|
||||||
|
// nearby_fires=0
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// clamp between 0 and fuel remaining.
|
// clamp between 0 and fuel remaining.
|
||||||
fire = _.clamp((fire+nearby_fires),0,_.min([1,fuel]))
|
fire = _.clamp((fire+transmissionProbability),0,_.min([1,fuel/fuelMultipler]))
|
||||||
// round it so we don't deal with tiny fractions
|
// round it so we don't deal with tiny fractions
|
||||||
fire = _.round(fire,2)
|
fire = _.round(fire,2)
|
||||||
|
|
||||||
console.assert(fire!=undefined)
|
console.assert(fire!=undefined)
|
||||||
|
console.assert(fire!=null)
|
||||||
console.assert(fuel!=undefined)
|
console.assert(fuel!=undefined)
|
||||||
console.assert(ash!=undefined)
|
|
||||||
|
|
||||||
all_fires.set(x,y,0,fire/fireMultiplier)
|
this.data.set(x,y,0,fire/fireMultiplier)
|
||||||
this.data.set(x,y,1,fuel/fuelMultipler)
|
this.data.set(x,y,1,fuel/fuelMultipler)
|
||||||
this.data.set(x,y,2,ash)
|
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
|
var t = new Date().getTime()-t0
|
||||||
for (var x = 1; x < this.W-1; x++) {
|
console.log('tick',this.clock,'time',t)
|
||||||
for (var y = 1; y < this.H-1; y++) {
|
return t
|
||||||
this.data.set(x,y,0,all_fires.get(x,y,0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('tick',this.clock,'change',fires)
|
|
||||||
return fires
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** push data onto canvases */
|
||||||
display(){
|
display(){
|
||||||
var H = this.canvas.height;
|
var H = this.W;
|
||||||
var W = this.canvas.width;
|
var W = this.H;
|
||||||
var ctx = this.canvas.getContext('2d');
|
|
||||||
|
// first show fire
|
||||||
|
|
||||||
|
// Fill in the fire layer
|
||||||
|
var canvas = document.getElementById('fire')
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
var imageData = ctx.getImageData(0,0,H,W)
|
var imageData = ctx.getImageData(0,0,H,W)
|
||||||
console.assert(this.data.selection.data.length==imageData.data.length)
|
var color_scale = d3.interpolateLab('yellow','red')
|
||||||
|
var fire = this.data.slice(null,null,[0,1])
|
||||||
// for (var x = 1; x < this.W-1; x++) {
|
for (var x = 0; x < imageData.width; x++) {
|
||||||
// for (var y = 1; y < this.H-1; y++) {
|
for (var y = 0; y < imageData.height; y++) {
|
||||||
// // mix the data into colors
|
var d = fire.get(x,y,0)*2
|
||||||
// var cell = this.data.slice([0,1],[0,1],null).reshape(1,4)
|
var c = new d3.color(color_scale(d))
|
||||||
// var filter = nj.array([
|
var i = (x*H+y)*4
|
||||||
// [1,0,0,0],
|
imageData.data[i+0]=c.r
|
||||||
// [0,1,0,0],
|
imageData.data[i+1]=c.g
|
||||||
// [0,0,1,0],
|
imageData.data[i+2]=c.b
|
||||||
// [0,0,0,1] // r,g,b,a
|
imageData.data[i+3]=_.clamp(d*255*2,0,255)
|
||||||
// ]).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);
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
console.assert(this.data.selection.data.length==imageData.data.length)
|
||||||
|
|
||||||
|
// Fill in the fire layer
|
||||||
|
var canvas = document.getElementById('fuel')
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
var imageData = ctx.getImageData(0,0,H,W)
|
||||||
|
var color_scale = d3.interpolateLab('white','green')
|
||||||
|
var fuel = this.data.slice(null,null,[1,2])
|
||||||
|
for (var x = 0; x < imageData.width; x++) {
|
||||||
|
for (var y = 0; y < imageData.height; y++) {
|
||||||
|
var d = fuel.get(x,y,0)*2
|
||||||
|
var c = new d3.color(color_scale(d))
|
||||||
|
var i = (x*H+y)*4
|
||||||
|
imageData.data[i+0]=c.r
|
||||||
|
imageData.data[i+1]=c.g
|
||||||
|
imageData.data[i+2]=c.b
|
||||||
|
imageData.data[i+3]=_.clamp(d*255*2,0,255)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
console.assert(this.data.selection.data.length==imageData.data.length)
|
||||||
|
|
||||||
|
|
||||||
|
// Fill in the ash layer
|
||||||
|
var canvas = document.getElementById('ash')
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
var imageData = ctx.getImageData(0,0,H,W)
|
||||||
|
var color_scale = d3.interpolateLab('white','grey')
|
||||||
|
var ash = this.data.slice(null,null,[2,3])
|
||||||
|
for (var x = 0; x < imageData.width; x++) {
|
||||||
|
for (var y = 0; y < imageData.height; y++) {
|
||||||
|
var d = ash.get(x,y,0)*2
|
||||||
|
var c = new d3.color(color_scale(d))
|
||||||
|
var i = (x*H+y)*4
|
||||||
|
imageData.data[i+0]=c.r
|
||||||
|
imageData.data[i+1]=c.g
|
||||||
|
imageData.data[i+2]=c.b
|
||||||
|
imageData.data[i+3]=_.clamp(d*255*2,0,255)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
console.assert(this.data.selection.data.length==imageData.data.length)
|
||||||
|
|
||||||
}
|
}
|
||||||
start(n=10000){
|
/** this returns stats **/
|
||||||
|
stats(){
|
||||||
|
var fire = this.data.slice(null,null,[0,1])
|
||||||
|
var burning_cells = 0
|
||||||
|
for (var x = 1; x < this.W-1; x++) {
|
||||||
|
for (var y = 1; y < this.H-1; y++) {
|
||||||
|
if (fire.get(x,y,0)!=0) burning_cells++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
fire: _.round(fire.sum(),2),
|
||||||
|
fuel: _.round(this.data.slice(null,null,[1,2]).sum(),2),
|
||||||
|
ash: _.round(this.data.slice(null,null,[2,3]).sum(),2),
|
||||||
|
burning_cells:burning_cells
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start(n=5000){
|
||||||
this.stop()
|
this.stop()
|
||||||
this.loop = setInterval(()=>{
|
this.loop = setInterval(()=>{
|
||||||
this.tick()
|
this.tick()
|
||||||
@@ -212,11 +313,18 @@ class Simulation {
|
|||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
<style media="screen">
|
||||||
|
.leaflet-image-layer {
|
||||||
|
image-rendering: pixelated
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="images/X145.34083_Y-37.533_DT20170428-042328_3857_30/metadata.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
// parameters
|
// parameters
|
||||||
var simulations = 2
|
var simulations = 2
|
||||||
var fuel_src = 'images/X145.34083_Y-37.533_DT20170427-093030_3857_30/2017.EVI.png'
|
var fuel_src = 'images/X145.34083_Y-37.533_DT20170428-042328_3857_30/2017.EVI.png'
|
||||||
var elevation_src = 'images/X145.34083_Y-37.533_DT20170427-093030_3857_30/NSW.elevation.png'
|
var elevation_src = 'images/X145.34083_Y-37.533_DT20170428-042328_3857_30/SRTMGL1_003.elevation_pixel_units.png'
|
||||||
|
var visual = 'images/X145.34083_Y-37.533_DT20170428-042328_3857_30/visual.png'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load png images as an array pf [r,g,b,a,r1,g1,b1,a1,...]
|
* Load png images as an array pf [r,g,b,a,r1,g1,b1,a1,...]
|
||||||
@@ -232,12 +340,6 @@ class Simulation {
|
|||||||
// load using numpyjs
|
// load using numpyjs
|
||||||
var data = nj.images.read(img)
|
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)
|
resolve(data)
|
||||||
}
|
}
|
||||||
img.onerror=reject
|
img.onerror=reject
|
||||||
@@ -249,21 +351,114 @@ class Simulation {
|
|||||||
|
|
||||||
// load image into canvases
|
// load image into canvases
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
var canvas=document.getElementById('myCanvas');
|
|
||||||
|
/* leaflet setup */
|
||||||
|
var southWest = L.latLng(metadata.bounds[0][0],metadata.bounds[0][1]),
|
||||||
|
northEast = L.latLng(metadata.bounds[1][0],metadata.bounds[1][1]),
|
||||||
|
bounds = L.latLngBounds(southWest, northEast);
|
||||||
|
|
||||||
|
var canvas=document.getElementById('map');
|
||||||
|
// image_layer1.addTo(leafletMap);
|
||||||
|
var elevation_layer = new L.ImageOverlay(
|
||||||
|
canvas.toDataURL(),
|
||||||
|
bounds,
|
||||||
|
{opacity:0.5}
|
||||||
|
)
|
||||||
|
var fuel_layer = new L.ImageOverlay(
|
||||||
|
canvas.toDataURL(),
|
||||||
|
bounds,
|
||||||
|
{opacity:0.5,attribution:'based on NASA Landsat-8 satellite data'}
|
||||||
|
)
|
||||||
|
var ash_layer = new L.ImageOverlay(
|
||||||
|
canvas.toDataURL(),
|
||||||
|
bounds,
|
||||||
|
{opacity:0.5}
|
||||||
|
)
|
||||||
|
var fire_layer = new L.ImageOverlay(
|
||||||
|
canvas.toDataURL(),
|
||||||
|
bounds,
|
||||||
|
{opacity:0.5}
|
||||||
|
)
|
||||||
|
// image_layer1.addTo(leafletMap);
|
||||||
|
L.mapbox.accessToken = 'pk.eyJ1IjoiZGlnaXRhbGdsb2JlIiwiYSI6ImNpc2t0ZG16NzA2Y2QydW53M2s0c2liNncifQ.YzEK6kmdCrtvssUfrncwKQ';
|
||||||
|
var base = L.mapbox.tileLayer('digitalglobe.nal0g75k')
|
||||||
|
|
||||||
|
|
||||||
|
var leafletMap = L.map('leaflet-map',{
|
||||||
|
center:metadata.point,
|
||||||
|
zoom:15,
|
||||||
|
maxBounds:bounds,
|
||||||
|
// zoomControl: true,
|
||||||
|
maxZoom:16,
|
||||||
|
minZoom:4,
|
||||||
|
layers: [base,ash_layer,fire_layer]
|
||||||
|
})
|
||||||
|
baseLayers = {
|
||||||
|
'DigitalGlobe Maps API: Recent Imagery':base
|
||||||
|
}
|
||||||
|
var overlays = {
|
||||||
|
'fire_layer':fire_layer,
|
||||||
|
'ash_layer':ash_layer,
|
||||||
|
'fuel_layer':fuel_layer,
|
||||||
|
'elevation_layer':elevation_layer
|
||||||
|
}
|
||||||
|
L.control.layers(baseLayers, overlays, {collapsed: true}).addTo(leafletMap);
|
||||||
|
L.control.scale().addTo(leafletMap);
|
||||||
|
/* there is probobly a better way but this works for now **/
|
||||||
|
function updateLayers(){
|
||||||
|
// convert each canvas to a png and load as a map overlay
|
||||||
|
if (elevation_layer._image) elevation_layer.setUrl(document.getElementById('elevation').toDataURL())
|
||||||
|
if (fuel_layer._image) fuel_layer.setUrl(document.getElementById('fuel').toDataURL())
|
||||||
|
if (ash_layer._image) ash_layer.setUrl(document.getElementById('ash').toDataURL())
|
||||||
|
if (fire_layer._image) fire_layer.setUrl(document.getElementById('fire').toDataURL())
|
||||||
|
// fire_layer.bringToFront()
|
||||||
|
}
|
||||||
|
leafletMap.on('overlayadd',updateLayers)
|
||||||
|
|
||||||
|
/* start simulation */
|
||||||
|
|
||||||
|
// load elevation onto a canvas
|
||||||
|
var canvas=document.getElementById('elevation');
|
||||||
|
var im = new Image()
|
||||||
|
im.src=visual
|
||||||
|
im.onload=function(){
|
||||||
|
// set size of all canvases
|
||||||
|
document.getElementById('canvases').querySelectorAll('canvas').forEach((canvas)=>{
|
||||||
|
canvas.width=this.width
|
||||||
|
canvas.height=this.height
|
||||||
|
})
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
ctx.drawImage(this, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var canvas=document.getElementById('fire');
|
||||||
var prom = Promise.all([
|
var prom = Promise.all([
|
||||||
loadImage(fuel_src),
|
loadImage(fuel_src),
|
||||||
loadImage(elevation_src),
|
loadImage(elevation_src),
|
||||||
|
loadImage(elevation_src),
|
||||||
]);
|
]);
|
||||||
// prom.then(fuel=>console.log(fuel))
|
|
||||||
prom.then(([fuel,elev]) => {
|
prom.then(([fuel,elev]) => {
|
||||||
|
|
||||||
|
// convert to float 32?
|
||||||
|
fuel.dtype="float32"
|
||||||
|
fuel=fuel.multiply(1/255)
|
||||||
|
|
||||||
|
elev.dtype="float32"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// crop to smallest
|
||||||
|
var canvas=document.getElementById('fire');
|
||||||
|
|
||||||
var simulation = new Simulation(
|
var simulation = new Simulation(
|
||||||
canvas,
|
canvas,
|
||||||
{
|
{
|
||||||
fuel:nj.images.rgb2gray(fuel),
|
fuel:fuel,//nj.images.rgb2gray(fuel),
|
||||||
elev:nj.images.rgb2gray(elev),
|
elev:elev,//nj.images.rgb2gray(elev),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
// simulation.start(20000)
|
|
||||||
|
|
||||||
// start simulation
|
// start simulation
|
||||||
|
|
||||||
@@ -285,7 +480,21 @@ class Simulation {
|
|||||||
document.getElementById('next').onclick=function(){
|
document.getElementById('next').onclick=function(){
|
||||||
simulation.tick()
|
simulation.tick()
|
||||||
simulation.display()
|
simulation.display()
|
||||||
|
console.log(simulation.stats())
|
||||||
|
updateLayers()
|
||||||
}
|
}
|
||||||
|
document.getElementById('reset').onclick=function(){
|
||||||
|
simulation = new Simulation(
|
||||||
|
canvas,
|
||||||
|
{
|
||||||
|
fuel:nj.images.rgb2gray(fuel),
|
||||||
|
elev:nj.images.rgb2gray(elev),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
document.getElementById('tick').innterText="0"
|
||||||
|
updateLayers()
|
||||||
|
}
|
||||||
|
updateLayers()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+620
-452
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,38 +1,5 @@
|
|||||||
|
This Matches project for the Spaceapps2017 perth hackathon.
|
||||||
|
|
||||||
Running a bushfire risk simulation using satellite images.
|
Running a bushfire risk simulation using satellite images.
|
||||||
|
|
||||||
Risk of starting->monte carlo risk of spreading
|
Try it at [the-winter.github.io/spaceapps2017-matches](the-winter.github.io/spaceapps2017-matches)
|
||||||
|
|
||||||
TODO:
|
|
||||||
- uncertainty
|
|
||||||
- elevation and slope
|
|
||||||
- cumulative risk
|
|
||||||
|
|
||||||
data sources:
|
|
||||||
- auscover http://www.auscover.org.au/browse-all-data/
|
|
||||||
- satellite:
|
|
||||||
- sentinel 2
|
|
||||||
- landsat 8
|
|
||||||
- GLCF: Landsat Tree Cover Continuous Fields https://explorer.earthengine.google.com/#detail/GLCF%2FGLS_TCC
|
|
||||||
- Normalised Difference Vegetation Index providing a measure of vegetation density and condition. It is influenced by the fractional cover of the ground by vegetation, the vegetation density and the vegetation greenness. It indicates the photosynthetic capacity of the land surface cover.
|
|
||||||
- EVI Enhanced vegetation index
|
|
||||||
- slope
|
|
||||||
|
|
||||||
model:
|
|
||||||
start at random pixel
|
|
||||||
spread based on vegetation in nearby cells
|
|
||||||
also die out
|
|
||||||
in each cell remember risk of fire from all simulations
|
|
||||||
|
|
||||||
# steps
|
|
||||||
- download tifs of
|
|
||||||
- l8
|
|
||||||
- EVI or other veg index
|
|
||||||
- elevation or slope
|
|
||||||
- load into js
|
|
||||||
-
|
|
||||||
|
|
||||||
# search
|
|
||||||
|
|
||||||
- A stochastic model for assessing bush fire attack on the buildings in bush fire prone areas http://mssanz.org.au/modsim09/A4/tan_z.pdf
|
|
||||||
- no listed predictive ability
|
|
||||||
- estimate of loss http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.542.453&rep=rep1&type=pdf
|
|
||||||
|
|||||||
Reference in New Issue
Block a user