tart using leaflet js

This commit is contained in:
2017-04-29 12:01:29 +08:00
parent 53ba6f9a16
commit 8700b9636e
4 changed files with 1212 additions and 617 deletions
+338 -129
View File
@@ -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
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3 -36
View File
@@ -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