Subversion Repositories public iLand

Rev

Rev 1221 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
671 werner 1
/********************************************************************************************
2
**    iLand - an individual based forest landscape and disturbance model
3
**    http://iland.boku.ac.at
4
**    Copyright (C) 2009-  Werner Rammer, Rupert Seidl
5
**
6
**    This program is free software: you can redistribute it and/or modify
7
**    it under the terms of the GNU General Public License as published by
8
**    the Free Software Foundation, either version 3 of the License, or
9
**    (at your option) any later version.
10
**
11
**    This program is distributed in the hope that it will be useful,
12
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
**    GNU General Public License for more details.
15
**
16
**    You should have received a copy of the GNU General Public License
17
**    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
********************************************************************************************/
19
 
646 werner 20
#include "firemodule.h"
21
 
650 werner 22
#include "grid.h"
646 werner 23
#include "model.h"
24
#include "modelcontroller.h"
25
#include "globalsettings.h"
26
#include "resourceunit.h"
27
#include "watercycle.h"
28
#include "climate.h"
662 werner 29
#include "soil.h"
654 werner 30
#include "dem.h"
1172 werner 31
#include "seeddispersal.h"
665 werner 32
#include "outputmanager.h"
690 werner 33
#include "species.h"
656 werner 34
#include "3rdparty/SimpleRNG.h"
646 werner 35
 
697 werner 36
/** @defgroup firemodule iLand firemodule
37
  The fire module is a disturbance module within the iLand framework.
38
 
39
  See http://iland.boku.ac.at/wildfire for the science behind the module,
40
  and http://iland.boku.ac.at/fire+module for the implementation/ using side.
41
 */
42
 
650 werner 43
//*********************************************************************************
44
//******************************************** FireData ***************************
45
//*********************************************************************************
46
 
47
void FireRUData::setup()
48
{
654 werner 49
    // data items loaded here are provided per resource unit
650 werner 50
    XmlHelper xml(GlobalSettings::instance()->settings().node("modules.fire"));
664 werner 51
    mKBDIref = xml.valueDouble(".KBDIref", 0.3);
650 werner 52
    mRefMgmt = xml.valueDouble(".rFireSuppression", 1.);
654 werner 53
    mRefLand = xml.valueDouble(".rLand", 1.);
650 werner 54
    mRefAnnualPrecipitation = xml.valueDouble(".meanAnnualPrecipitation", -1);
680 werner 55
    mAverageFireSize = xml.valueDouble(".averageFireSize", 10000.);
56
    mMinFireSize = xml.valueDouble(".minFireSize", 0.);
57
    mMaxFireSize = xml.valueDouble(".maxFireSize", 1000000.);
654 werner 58
    mFireReturnInterval =  xml.valueDouble(".fireReturnInterval", 100); // every x year
59
    if (mAverageFireSize * mFireReturnInterval == 0.)
656 werner 60
        throw IException("Fire-setup: invalid values for 'averageFireSize' or 'fireReturnInterval' (values must not be 0).");
654 werner 61
    double p_base = 1. / mFireReturnInterval;
62
    // calculate the base ignition probabiility for a cell (eg 20x20m)
63
    mBaseIgnitionProb = p_base * FireModule::cellsize()*FireModule::cellsize() / mAverageFireSize;
64
    mFireExtinctionProb = xml.valueDouble(".fireExtinctionProbability", 0.);
65
 
667 werner 66
 
650 werner 67
}
68
 
69
//*********************************************************************************
70
//****************************************** FireLayers ***************************
71
//*********************************************************************************
72
 
73
 
74
double FireLayers::value(const FireRUData &data, const int param_index) const
75
{
76
    switch(param_index){
757 werner 77
    case 0: return data.mBaseIgnitionProb; // base ignition value
755 werner 78
    case 1: return data.mKBDI; // KBDI values
79
    case 2: return data.mKBDIref; // reference KBDI value
80
    case 3: return data.fireRUStats.fire_id; // the ID of the last recorded fire
81
    case 4: return data.fireRUStats.crown_kill; // crown kill fraction (average on resource unit)
82
    case 5: return data.fireRUStats.died_basal_area; // basal area died in the last fire
83
    case 6: return data.fireRUStats.n_trees > 0 ? data.fireRUStats.n_trees_died / double(data.fireRUStats.n_trees) : 0.;
84
    case 7: return data.fireRUStats.fuel_dwd + data.fireRUStats.fuel_ff; // fuel load (forest floor + dwd) kg/ha
650 werner 85
    default: throw IException(QString("invalid variable index for FireData: %1").arg(param_index));
86
    }
87
}
88
 
1014 werner 89
const QVector<LayeredGridBase::LayerElement> &FireLayers::names()
650 werner 90
{
1014 werner 91
    if (mNames.isEmpty())
92
        mNames= QVector<LayeredGridBase::LayerElement>()
93
                << LayeredGridBase::LayerElement(QLatin1Literal("baseIgnition"), QLatin1Literal("base ignition rate"), GridViewRainbow)
94
                << LayeredGridBase::LayerElement(QLatin1Literal("KBDI"), QLatin1Literal("KBDI"), GridViewRainbow)
95
                << LayeredGridBase::LayerElement(QLatin1Literal("KBDIref"), QLatin1Literal("reference KBDI value"), GridViewRainbow)
96
                << LayeredGridBase::LayerElement(QLatin1Literal("fireID"), QLatin1Literal("Id of the fire"), GridViewRainbow)
97
                << LayeredGridBase::LayerElement(QLatin1Literal("crownKill"), QLatin1Literal("crown kill rate"), GridViewRainbow)
98
                << LayeredGridBase::LayerElement(QLatin1Literal("diedBasalArea"), QLatin1Literal("m2 of died basal area"), GridViewRainbow)
99
                << LayeredGridBase::LayerElement(QLatin1Literal("diedStemsFrac"), QLatin1Literal("fraction of died stems"), GridViewRainbow)
100
                << LayeredGridBase::LayerElement(QLatin1Literal("fuel"), QLatin1Literal("fuel"), GridViewRainbow);
101
    return mNames;
650 werner 102
 
103
}
104
 
105
//*********************************************************************************
106
//****************************************** FireModule ***************************
107
//*********************************************************************************
108
 
109
 
110
 
646 werner 111
FireModule::FireModule()
112
{
649 werner 113
    mFireLayers.setGrid(mRUGrid);
654 werner 114
    mWindSpeedMin=10.;mWindSpeedMax=10.;
115
    mWindDirection=45.;
662 werner 116
    mFireId = 0;
646 werner 117
}
118
 
119
// access data element
649 werner 120
FireRUData &FireModule::data(const ResourceUnit *ru)
646 werner 121
{
122
    QPointF p = ru->boundingBox().center();
649 werner 123
    return mRUGrid.valueAt(p.x(), p.y());
646 werner 124
}
125
void FireModule::setup()
126
{
127
    // setup the grid (using the size/resolution)
649 werner 128
    mRUGrid.setup(GlobalSettings::instance()->model()->RUgrid().metricRect(),
654 werner 129
                  GlobalSettings::instance()->model()->RUgrid().cellsize());
649 werner 130
    // setup the fire spread grid
131
    mGrid.setup(mRUGrid.metricRect(), cellsize());
132
    mGrid.initialize(0.f);
680 werner 133
    mFireId = 0;
646 werner 134
 
654 werner 135
    // set some global settings
646 werner 136
    XmlHelper xml(GlobalSettings::instance()->settings().node("modules.fire"));
654 werner 137
    mWindSpeedMin = xml.valueDouble(".wind.speedMin", 5.);
138
    mWindSpeedMax = xml.valueDouble(".wind.speedMax", 10.);
139
    mWindDirection = xml.valueDouble(".wind.direction", 270.); // defaults to "west"
656 werner 140
    mFireSizeSigma = xml.valueDouble(".fireSizeSigma", 0.25);
654 werner 141
 
680 werner 142
    mOnlyFireSimulation = xml.valueBool(".onlySimulation", false);
143
 
667 werner 144
    // fuel parameters
668 werner 145
    mFuelkFC1 = xml.valueDouble(".fuelKFC1", 0.8);
146
    mFuelkFC2 = xml.valueDouble(".fuelKFC2", 0.2);
147
    mFuelkFC3 = xml.valueDouble(".fuelKFC3", 0.4);
667 werner 148
 
149
    // parameters for crown kill
150
    mCrownKillkCK1 = xml.valueDouble(".crownKill1", 0.21111);
694 werner 151
    mCrownKillkCK2 = xml.valueDouble(".crownKill2", -0.00445);
667 werner 152
    mCrownKillDbh = xml.valueDouble(".crownKillDbh", 40.);
153
 
154
    QString formula = xml.value(".mortalityFormula", "1/(1 + exp(-1.466 + 1.91*bt - 0.1775*bt*bt - 5.41*ck*ck))");
155
    mFormula_bt = mMortalityFormula.addVar("bt");
156
    mFormula_ck = mMortalityFormula.addVar("ck");
157
    mMortalityFormula.setExpression(formula);
158
 
668 werner 159
    mBurnSoilBiomass = xml.valueDouble(".burnSOMFraction", 0.);
160
    mBurnStemFraction = xml.valueDouble(".burnStemFraction", 0.1);
161
    mBurnBranchFraction = xml.valueDouble(".burnBranchFraction", 0.5);
162
    mBurnFoliageFraction = xml.valueDouble(".burnFoliageFraction", 1.);
696 werner 163
 
164
    mAfterFireEvent = xml.value(".onAfterFire");
165
 
646 werner 166
    // setup of the visualization of the grid
647 werner 167
    GlobalSettings::instance()->controller()->addLayers(&mFireLayers, "fire");
651 werner 168
    GlobalSettings::instance()->controller()->addGrid(&mGrid, "fire spread", GridViewRainbow,0., 50.);
646 werner 169
 
654 werner 170
    // check if we have a DEM in the system
171
    if (!GlobalSettings::instance()->model()->dem())
172
        throw IException("FireModule:setup: a digital elevation model is required for the fire module!");
646 werner 173
}
174
 
175
void FireModule::setup(const ResourceUnit *ru)
176
{
649 werner 177
    if (mRUGrid.isEmpty())
646 werner 178
        throw IException("FireModule: grid not properly setup!");
649 werner 179
    FireRUData &fire_data = data(ru);
646 werner 180
    fire_data.setup();
181
}
182
 
649 werner 183
/** yearBegin is called at the beginnig of every year.
184
    do some cleanup here.
185
  */
186
void FireModule::yearBegin()
187
{
683 werner 188
// setting KBDI=0 is not really necessary; in addition: kbdi-grids are emtpy if grid export is called during management (between yearBegin() and run())
189
//for (FireRUData *fd = mRUGrid.begin(); fd!=mRUGrid.end(); ++fd)
190
//    fd->reset(); // reset drought index
649 werner 191
}
192
 
193
/** main function of the fire module.
194
 
195
*/
196
void FireModule::run()
197
{
701 werner 198
    if (GlobalSettings::instance()->settings().valueBool("modules.fire.enabled") == false)
199
        return;
200
 
649 werner 201
    // ignition() calculates ignition and calls 'spread()' if a new fire is created.
202
    ignition();
203
}
204
 
205
 
646 werner 206
/** perform the calculation of the KBDI drought index.
207
    see http://iland.boku.ac.at/wildfire#fire_ignition
208
  */
209
void FireModule::calculateDroughtIndex(const ResourceUnit *resource_unit, const WaterCycleData *water_data)
210
{
649 werner 211
    FireRUData &fire_data = data(resource_unit);
646 werner 212
    const ClimateDay *end = resource_unit->climate()->end();
213
    int iday = 0;
654 werner 214
    double kbdi = 100.; // starting value of the year
646 werner 215
    const double mean_ap = fire_data.mRefAnnualPrecipitation; // reference mean annual precipitation
216
    double dp, dq, tmax;
654 werner 217
 
681 werner 218
//  debug!!!
219
//    QFile dump("e:/kbdidump.txt");
220
//    dump.open(QFile::WriteOnly);
221
//    QTextStream ts(&dump);
222
 
654 werner 223
    double kbdi_sum = 0.;
646 werner 224
    for (const ClimateDay *day = resource_unit->climate()->begin(); day!=end; ++day, ++iday) {
225
        dp = water_data->water_to_ground[iday]; // water reaching the ground for this day
226
        double wetting = - dp/25.4 * 100.;
227
        kbdi += wetting;
228
        if (kbdi<0.) kbdi=0.;
229
 
654 werner 230
        tmax = day->max_temperature;
650 werner 231
        // drying is only simulated, if:
877 werner 232
        // * the temperature > 10�
646 werner 233
        // * there is no snow cover
234
        if (tmax > 10. && water_data->snow_cover[iday]==0.) {
235
            // calculate drying: (kbdi already includes current wetting!)
236
            dq = 0.001*(800.-kbdi)*( (0.9676*exp(0.0486*(tmax*9./5.+32.))-8.299) / (1 + 10.88*exp(-0.0441*mean_ap/25.4)) );
237
 
238
            kbdi += dq;
239
        }
654 werner 240
        kbdi_sum += kbdi;
681 werner 241
//        // debug
242
//        ts << iday << ";" << water_data->water_to_ground[iday] << ";" << water_data->snow_cover[iday] << ";" << tmax << ";" << kbdi << endl;
646 werner 243
    }
654 werner 244
    // the effective relative KBDI is calculated
245
    // as the year sum related to the maximum value (800*365)
246
    fire_data.mKBDI = kbdi_sum / (365. * 800.);
646 werner 247
}
248
 
249
 
649 werner 250
/** evaluates the probability that a fire starts for each cell (20x20m)
251
    see http://iland.boku.ac.at/wildfire#fire_ignition
252
 
253
*/
757 werner 254
double FireModule::ignition(bool only_ignite)
649 werner 255
{
654 werner 256
 
649 werner 257
    int cells_per_ru = (cRUSize / cellsize()) * (cRUSize / cellsize()); // number of fire cells per resource unit
258
 
259
    for (FireRUData *fd = mRUGrid.begin(); fd!=mRUGrid.end(); ++fd)
260
        if (fd->enabled() && fd->kbdi()>0.) {
261
            // calculate the probability that a fire ignites within this resource unit
262
            // the climate factor is the current drought index relative to the reference drought index
654 werner 263
            double odds_base = fd->mBaseIgnitionProb / (1. - fd->mBaseIgnitionProb);
649 werner 264
            double r_climate = fd->mKBDI / fd->mKBDIref;
265
            double odds = odds_base * r_climate / fd->mRefMgmt;
266
            // p_cell is the ignition probability for one 20x20m cell
267
            double p_cell = odds / (1. + odds);
758 werner 268
            // p_cell is the probability of ignition for a "fire"-pixel. We scale that to RU-level by multiplying with the number of pixels per RU.
269
            // for small probabilities this yields almost the same results as the more correct 1-(1-p)^cells_per_ru [for p=0.0000001 and cells=25 the difference is 0.000000000003]
270
            p_cell *= cells_per_ru;
649 werner 271
            if (!p_cell)
272
                continue;
273
 
758 werner 274
            double p = drandom();
649 werner 275
 
758 werner 276
            if (p < p_cell) {
277
                // We have a fire event on the particular resource unit
278
                // now randomly select a pixel within the resource unit as the starting point
1164 werner 279
                int pixel_index = irandom(0, cells_per_ru);
758 werner 280
                int ix = pixel_index % (int((cRUSize / cellsize())));
281
                int iy = pixel_index / (int((cRUSize / cellsize())));
282
                QPointF startcoord = mRUGrid.cellRect(mRUGrid.indexOf(fd)).bottomLeft();
283
                fireStats.startpoint = QPointF(startcoord.x() + (ix+0.5)*cellsize(), startcoord.y() + (iy+0.5)*cellsize() );
284
                QPoint startpoint = mGrid.indexAt(fireStats.startpoint);
757 werner 285
 
758 werner 286
                // check if we have enough fuel to start the fire: done in the spread routine
287
                // in this case "empty" fires (with area=0) are in the output
649 werner 288
 
758 werner 289
                // now start the fire!!!
290
                mFireId++; // this fire gets a new id
291
                if (only_ignite) {
292
                    int idx, gen, refill;
293
                    RandomGenerator::debugState(idx, gen, refill);
665 werner 294
 
802 werner 295
                    //qDebug() << "test: rand-sum" << test_rand_sum << "n" << test_rand_n << pixel_index << startpoint << p_cell<< "rng:" << idx << gen << refill;
758 werner 296
                    return p; // no real fire spread
297
                }
710 werner 298
 
758 werner 299
                spread(startpoint);
300
 
301
                // finalize statistics after fire event
302
                afterFire();
303
 
304
                // provide outputs: This calls the FireOut::exec() function
305
                GlobalSettings::instance()->outputManager()->execute("fire");
306
 
307
                // we allow only one fire event per year for the whole landscape
308
                return fireStats.fire_size_realized_m2;
649 werner 309
            }
758 werner 310
 
649 werner 311
        }
757 werner 312
    return -1.; // nothing burnt
649 werner 313
 
314
}
315
 
316
/** calculate the actual fire spread.
317
*/
674 werner 318
void FireModule::spread(const QPoint &start_point, const bool prescribed)
649 werner 319
{
320
    qDebug() << "fire event starting at position" << start_point;
650 werner 321
 
322
    mGrid.initialize(0.f);
649 werner 323
    mGrid.valueAtIndex(start_point) = 1.f;
694 werner 324
    for (FireRUData *fds = mRUGrid.begin(); fds!=mRUGrid.end(); ++fds)
325
        fds->fireRUStats.clear();
649 werner 326
 
674 werner 327
    if (!prescribed) {
328
        // randomly choose windspeed and wind direction
329
        mCurrentWindSpeed = nrandom(mWindSpeedMin, mWindSpeedMax);
330
        mCurrentWindDirection = fmod(mWindDirection + nrandom(-45., 45.)+360., 360.);
331
        mPrescribedFiresize = -1;
332
    }
654 werner 333
 
650 werner 334
    // choose spread algorithm
335
    probabilisticSpread(start_point);
649 werner 336
 
662 werner 337
 
650 werner 338
}
646 werner 339
 
650 werner 340
/// Estimate fire size (m2) from a fire size distribution.
680 werner 341
double FireModule::calculateFireSize(const FireRUData *data)
646 werner 342
{
680 werner 343
    // calculate fire size based on a negative exponential firesize distribution
344
    double size = -log(drandom()) * data->mAverageFireSize;
345
    size = qMin(size, data->mMaxFireSize);
346
    size = qMax(size, data->mMinFireSize);
656 werner 347
    return size;
680 werner 348
 
349
    // old code: uses a log normal distribution -- currently not used:
350
//    SimpleRNG rng;
351
//    rng.SetState(irandom(0, std::numeric_limits<unsigned int>::max()-1), irandom(0, std::numeric_limits<unsigned int>::max()-1));
352
//    double size = rng.GetLogNormal(log(average_fire_size), mFireSizeSigma);
353
//    return size;
646 werner 354
}
355
 
654 werner 356
/// calculate effect of slope on fire spread
357
/// for upslope following Keene and Albini 1976
358
///  It was designed by RKeane (2/2/99) (calc.c)
359
/// the downslope function is "not based on empirical data" (Keane in calc.c)
360
/// return is the metric distance to spread (and not number of pixels)
361
double FireModule::calcSlopeFactor(const double slope) const
362
{
363
    double slopespread;       /* Slope spread rate in pixels / timestep   */
364
    static double firebgc_cellsize = 30.; /* cellsize for which this functions were originally designed */
652 werner 365
 
654 werner 366
    if (slope < 0.) {
367
        // downslope effect
368
        slopespread = 1.0 - ( 20.0 * slope * slope );
369
 
370
    } else {
371
        // upslope effect
372
        static double alpha = 4.0; /* Maximum number of pixels to spread      */
373
        static double beta  = 3.5; /* Scaling coeff for inflection point      */
374
        static double gamma = 10.0;/* Scaling coeff for graph steepness       */
375
        static double zeta  = 0.0; /* Scaling coeff for y intercept           */
376
 
377
        slopespread = zeta + ( alpha / ( 1.0 + ( beta * exp( -gamma * slope ) ) ) );
378
    }
379
 
380
 
381
    return( slopespread ) * firebgc_cellsize;
382
 
383
}
384
 
385
/// calculate the effect of wind on the spread.
386
/// function designed by R. Keane, 2/2/99
387
/// @param direction direction (in degrees) of spread (0=north, 90=east, ...)
388
/// @return spread (in meters)
389
double FireModule::calcWindFactor(const double direction) const
646 werner 390
{
654 werner 391
    const double firebgc_cellsize = 30.; /* cellsize for which this functions were originally designed */
392
    double windspread;         /* Wind spread rate in pixels / timestep   */
393
    double coeff;              /* Coefficient that reflects wind direction*/
394
    double lwr;                /* Length to width ratio                   */
395
    const double alpha = 0.6; /* Wind spread power coeffieicnt           */
396
    const double MPStoMPH = 1. / 0.44704;
397
 
398
    /* .... If zero wind speed return 1.0 for the factor .... */
399
    if ( mCurrentWindSpeed <= 0.5 )
400
        return ( 1.0 ) * firebgc_cellsize; // not 0????
401
 
402
    /* .... Change degrees to radians .... */
403
    coeff = fabs( direction - mCurrentWindDirection ) * M_PI/180.;
404
 
405
    /* .... If spread direction equal zero, then spread direction = wind direct */
406
    if ( direction <= 0.01 )
407
        coeff = 0.0;
408
 
409
    /* .... Compute the length:width ratio from Andrews (1986) .....  */
410
 
411
    lwr = 1.0 + ( 0.125 * mCurrentWindSpeed * MPStoMPH );
412
 
413
    /* .... Scale the difference between direction between 0 and 1.0 .....  */
414
    coeff = ( cos( coeff ) + 1.0 ) / 2.0;
415
 
416
    /* .... Scale the function based on windspeed between 1 and 10...  */
417
    windspread = pow( coeff, pow( (mCurrentWindSpeed * MPStoMPH ), alpha ) ) * lwr;
418
 
419
    return( windspread ) * firebgc_cellsize;
420
 
421
}
422
 
423
 
656 werner 424
/** calculates probability of spread from one pixel to one neighbor.
425
    In this functions the effect of the terrain, the wind and others are used to estimate a probability.
426
    @param fire_data reference to the variables valid for the current resource unit
427
    @param height elevation (m) of the origin point
428
    @param pixel_from pointer to the origin point in the fire grid
429
    @param pixel_to pointer to the target pixel
1170 werner 430
    @param direction codes the direction from the origin point (1..8, N, E, S, W, NE, SE, SW, NW)
656 werner 431
  */
654 werner 432
void FireModule::calculateSpreadProbability(const FireRUData &fire_data, const double height, const float *pixel_from, float *pixel_to, const int direction)
433
{
1170 werner 434
    const double directions[8]= {0., 90., 180., 270., 45., 135., 225., 315. };
765 werner 435
    (void) pixel_from; // remove 'unused parameter' warning
654 werner 436
    double spread_metric; // distance that fire supposedly spreads
437
 
438
    // calculate the slope from the curent point (pixel_from) to the spreading cell (pixel_to)
439
    double h_to = GlobalSettings::instance()->model()->dem()->elevation(mGrid.cellCenterPoint(mGrid.indexOf(pixel_to)));
1170 werner 440
    if (h_to==-1) {
764 werner 441
        // qDebug() << "invalid elevation for pixel during fire spread: " << mGrid.cellCenterPoint(mGrid.indexOf(pixel_to));
442
        // the pixel is "outside" the project area. No spread is possible.
654 werner 443
        return;
444
    }
656 werner 445
    double pixel_size = cellsize();
654 werner 446
    // if we spread diagonal, the distance is longer:
447
    if (direction>4)
656 werner 448
        pixel_size *= 1.41421356;
654 werner 449
 
656 werner 450
    double slope = (h_to - height) / pixel_size;
451
 
654 werner 452
    double r_wind, r_slope; // metric distance for spread
453
    r_slope = calcSlopeFactor( slope ); // slope factor (upslope / downslope)
454
 
656 werner 455
    r_wind = calcWindFactor(directions[direction-1]); // metric distance from wind
654 werner 456
 
457
    spread_metric = r_slope + r_wind;
458
 
656 werner 459
    double spread_pixels = spread_metric / pixel_size;
460
    if (spread_pixels<=0.)
654 werner 461
        return;
462
 
463
    double p_spread = pow(0.5, 1. / spread_pixels);
464
    // apply the r_land factor that accounts for different land types
465
    p_spread *= fire_data.mRefLand;
652 werner 466
    // add probabilites
1170 werner 467
    *pixel_to = 1. - (1. - *pixel_to)*(1. - p_spread);
652 werner 468
 
646 werner 469
}
470
 
650 werner 471
/** a cellular automaton spread algorithm.
656 werner 472
    @param start_point the starting point of the fire spread as index of the fire grid
650 werner 473
*/
474
void FireModule::probabilisticSpread(const QPoint &start_point)
646 werner 475
{
1170 werner 476
    QRect max_spread = QRect(start_point, start_point+QPoint(1,1));
650 werner 477
    // grow the rectangle by one row/column but ensure validity
651 werner 478
    max_spread.setCoords(qMax(start_point.x()-1,0),qMax(start_point.y()-1,0),
1170 werner 479
                         qMin(max_spread.right()+1,mGrid.sizeX()),qMin(max_spread.bottom()+1,mGrid.sizeY()) );
656 werner 480
 
662 werner 481
    FireRUData *rudata = &mRUGrid.valueAt( mGrid.cellCenterPoint(start_point) );
680 werner 482
    double fire_size_m2 = calculateFireSize(rudata);
674 werner 483
 
484
    // for test cases, the size of the fire is predefined.
485
    if (mPrescribedFiresize>=0)
486
        fire_size_m2 = mPrescribedFiresize;
487
 
1170 werner 488
    fireStats.fire_size_plan_m2 = fire_size_m2;
665 werner 489
    fireStats.iterations = 0;
490
    fireStats.fire_size_realized_m2 = 0;
690 werner 491
    fireStats.fire_psme_died = 0.;
492
    fireStats.fire_psme_total = 0.;
656 werner 493
    double sum_fire_size = fire_size_m2; // cumulative fire size
494
    // calculate a factor describing how much larger/smaller the selected fire is compared to the average
495
    // fire size of the ignition cell
496
    double fire_scale_factor = fire_size_m2 / rudata->mAverageFireSize;
497
 
1170 werner 498
    int cells_to_burn = fire_size_m2 / (cellsize() * cellsize());
651 werner 499
    int cells_burned = 1;
656 werner 500
    int last_round_burned = cells_burned;
651 werner 501
    int iterations = 1;
650 werner 502
    // main loop
654 werner 503
    float *neighbor[8];
650 werner 504
    float *p;
662 werner 505
 
506
    rudata->fireRUStats.enter(mFireId);
507
    if (!burnPixel(start_point, *rudata)) {
508
        // no fuel / no trees on the starting pixel
509
        return;
510
    }
650 werner 511
    while (cells_burned < cells_to_burn) {
512
        // scan the current spread area
513
        // and calcuate for each pixel the probability of spread from a burning
514
        // pixel to a non-burning pixel
651 werner 515
        GridRunner<float> runner(mGrid, max_spread);
516
        while ((p = runner.next())) {
650 werner 517
            if (*p == 1.f) {
802 werner 518
                // p==1: pixel is burning in this iteration and might spread fire to neighbors
654 werner 519
                const QPointF pt = mGrid.cellCenterPoint(mGrid.indexOf(p));
662 werner 520
                FireRUData &fire_data = mRUGrid.valueAt(pt);
521
                fire_data.fireRUStats.enter(mFireId); // setup/clear statistics if this is the first pixel in the resource unit
654 werner 522
                double h = GlobalSettings::instance()->model()->dem()->elevation(pt);
1170 werner 523
                if (h==-1) {
760 werner 524
                    qDebug() << "Fire-Spread: invalid elevation at " << pt.x() << "/" << pt.y();
525
                    qDebug() << "value is: " << GlobalSettings::instance()->model()->dem()->elevation(pt);
526
                    return;
527
                }
654 werner 528
 
650 werner 529
                // current cell is burning.
656 werner 530
                // check the neighbors: get an array with neighbors
531
                // 1-4: north, east, west, south
532
                // 5-8: NE/NW/SE/SW
654 werner 533
                runner.neighbors8(neighbor);
650 werner 534
                if (neighbor[0] && *(neighbor[0])<1.f)
654 werner 535
                    calculateSpreadProbability(fire_data, h, p, neighbor[0], 1);
651 werner 536
                if (neighbor[1] && *(neighbor[1])<1.f)
654 werner 537
                    calculateSpreadProbability(fire_data, h, p, neighbor[1], 2);
651 werner 538
                if (neighbor[2] && *(neighbor[2])<1.f)
654 werner 539
                    calculateSpreadProbability(fire_data, h, p, neighbor[2], 3);
651 werner 540
                if (neighbor[3] && *(neighbor[3])<1.f)
654 werner 541
                    calculateSpreadProbability(fire_data, h, p, neighbor[3], 4);
542
                if (neighbor[4] && *(neighbor[4])<1.f)
543
                    calculateSpreadProbability(fire_data, h, p, neighbor[4], 5);
544
                if (neighbor[5] && *(neighbor[5])<1.f)
545
                    calculateSpreadProbability(fire_data, h, p, neighbor[5], 6);
656 werner 546
                if (neighbor[6] && *(neighbor[6])<1.f)
654 werner 547
                    calculateSpreadProbability(fire_data, h, p, neighbor[6], 7);
656 werner 548
                if (neighbor[7] && *(neighbor[7])<1.f)
654 werner 549
                    calculateSpreadProbability(fire_data, h, p, neighbor[7], 8);
651 werner 550
                *p = iterations + 1;
650 werner 551
            }
552
        }
553
        // now draw random numbers and calculate the real spread
554
        runner.reset();
651 werner 555
        while ((p = runner.next())) {
650 werner 556
            if (*p<1.f && *p>0.f) {
651 werner 557
                if (drandom() < *p) {
650 werner 558
                    // the fire spreads:
651 werner 559
                    *p = 1.f;
662 werner 560
                    FireRUData &fire_data = mRUGrid.valueAt(mGrid.cellCenterPoint(mGrid.indexOf(p)));
694 werner 561
                    fire_data.fireRUStats.enter(mFireId);
650 werner 562
                    cells_burned++;
662 werner 563
                    // do the severity calculations:
564
                    // the function returns false if no trees are on the pixel
565
                    bool really_burnt = burnPixel(mGrid.indexOf(p), fire_data);
656 werner 566
                    // update the fire size
567
                    sum_fire_size += fire_data.mAverageFireSize * fire_scale_factor;
764 werner 568
                    // the fire stops
569
                    //    (*) if no trees were on the pixel, or
570
                    //    (*) if the fire extinguishes
699 werner 571
                    bool spread = really_burnt;
572
                    if (spread && fire_data.mFireExtinctionProb>0.) {
755 werner 573
                        // exinguishing of fire is only effective, when at least the minimum fire size is already reached
699 werner 574
                        if (cells_burned*cellsize()*cellsize() > fire_data.mMinFireSize) {
575
                            if (drandom() < fire_data.mFireExtinctionProb)
576
                                spread = false;
654 werner 577
                        }
699 werner 578
 
654 werner 579
                    }
699 werner 580
                    if (!spread)
581
                        *p = iterations + 1;
582
 
662 werner 583
                } else {
584
                    *p = 0.f; // if the fire does note spread to the cell, the value is cleared again.
650 werner 585
                }
586
            }
587
        }
656 werner 588
 
1170 werner 589
        cells_to_burn = (sum_fire_size/(double)cells_burned) / (cellsize() * cellsize());
656 werner 590
        if (cells_to_burn <= cells_burned)
591
            break;
592
 
651 werner 593
        // now determine the maximum extent with burning pixels...
594
        runner.reset();
595
        int left = mGrid.sizeX(), right = 0, top = mGrid.sizeY(), bottom = 0;
596
        while ((p = runner.next())) {
597
            if (*p == 1.f) {
598
                QPoint pt = mGrid.indexOf(p);
599
                left = qMin(left, pt.x()-1);
1170 werner 600
                right = qMax(right, pt.x()+2); // coord of right is never reached
651 werner 601
                top = qMin(top, pt.y()-1);
1170 werner 602
                bottom = qMax(bottom, pt.y()+2); // coord bottom never reacher
651 werner 603
            }
604
        }
1170 werner 605
        max_spread.setCoords(qMax(left,0),
606
                             qMax(top,0),
607
                             qMin(right, mGrid.sizeX()),
608
                             qMin(bottom, mGrid.sizeY()) );
651 werner 609
 
1170 werner 610
        qDebug() << "Iter: " << iterations << "totalburned:" << cells_burned << "spread-rect:" << max_spread << "cells to burn" << cells_to_burn;
650 werner 611
        iterations++;
656 werner 612
        if (last_round_burned == cells_burned) {
613
            qDebug() << "Firespread: a round without new burning cells - exiting!";
651 werner 614
            break;
615
        }
656 werner 616
        last_round_burned = cells_burned;
617
        if (iterations > 10000) {
618
            qDebug() << "Firespread: maximum number of iterations (10000) reached!";
619
            break;
620
        }
650 werner 621
    }
656 werner 622
    qDebug() << "Fire:probabilstic spread: used " << iterations
623
             << "iterations. Planned (m2/cells):" << fire_size_m2 << "/" << cells_to_burn
624
             << "burned (m2/cells):" << cells_burned*cellsize()*cellsize() << "/" << cells_burned;
665 werner 625
 
668 werner 626
    fireStats.iterations = iterations;
665 werner 627
    fireStats.fire_size_realized_m2 = cells_burned*cellsize()*cellsize();
628
 
646 werner 629
}
630
 
654 werner 631
void FireModule::testSpread()
632
{
656 werner 633
//    QPoint pt = mGrid.indexAt(QPointF(1000., 600.));
634
//    spread( pt );
635
    SimpleRNG rng;
1164 werner 636
    rng.SetState(irandom(0, std::numeric_limits<unsigned int>::max()), irandom(0, std::numeric_limits<unsigned int>::max()));
656 werner 637
    int bins[20];
638
    for(int i=0;i<20;i++) bins[i]=0;
639
    for (int i=0;i<10000;i++) {
640
        double value = rng.GetLogNormal(log(2000.),0.25);
641
        if (value>=0 && value<10000.)
642
            bins[(int)(value/500.)]++;
643
    }
644
    for(int i=0;i<20;i++)
645
        qDebug() << bins[i];
646 werner 646
 
656 werner 647
    for (int r=0;r<360;r+=90) {
648
        mWindDirection = r;
649
        for (int i=0;i<5;i++) {
663 werner 650
            QPoint pt = mGrid.indexAt(QPointF(730., 610.)); // was: 1100/750
662 werner 651
            mFireId++; // this fire gets a new id
652
 
656 werner 653
            spread( pt );
662 werner 654
            // stats
655
            for (FireRUData *fds = mRUGrid.begin(); fds!=mRUGrid.end(); ++fds)
656
                fds->fireRUStats.calculate(mFireId);
657
 
656 werner 658
            GlobalSettings::instance()->controller()->repaint();
659
            GlobalSettings::instance()->controller()->saveScreenshot(GlobalSettings::instance()->path(QString("%1_%2.png").arg(r).arg(i), "temp"));
660
        }
661
    }
654 werner 662
}
649 werner 663
 
664
 
757 werner 665
double FireModule::prescribedIgnition(const double x_m, const double y_m, const double firesize, const double windspeed, const double winddirection)
674 werner 666
{
667
    QPoint pt = mGrid.indexAt(QPointF(x_m, y_m));
668
    if (!mGrid.isIndexValid(pt)) {
669
        qDebug() << "Fire starting point is not valid!";
757 werner 670
        return -1.;
674 werner 671
    }
672
    mFireId++; // this fire gets a new id
673
 
755 werner 674
    mPrescribedFiresize = firesize; // if -1, then a fire size is estimated
674 werner 675
 
676
    if (windspeed>=0) {
677
        mCurrentWindSpeed = windspeed;
678
        mCurrentWindDirection = winddirection;
679
    }
680
 
681
    spread( pt, true );
682
 
680 werner 683
    afterFire();
674 werner 684
 
681 werner 685
    // provide outputs: This calls the FireOut::exec() function
686
    GlobalSettings::instance()->outputManager()->execute("fire");
690 werner 687
    GlobalSettings::instance()->outputManager()->save();
757 werner 688
 
689
    return fireStats.fire_size_realized_m2;
674 werner 690
}
691
 
662 werner 692
/** burning of a single 20x20m pixel. see http://iland.boku.ac.at/wildfire.
693
   The function is called from the fire spread function.
694
   @return boolean true, if any trees were burned on the pixel
649 werner 695
 
662 werner 696
  */
697
bool FireModule::burnPixel(const QPoint &pos, FireRUData &ru_data)
698
{
699
    // extract a list of trees that are within the pixel boundaries
700
    QRectF pixel_rect = mGrid.cellRect(pos);
701
    ResourceUnit *ru = GlobalSettings::instance()->model()->ru(pixel_rect.center());
702
    if (!ru)
703
        return false;
649 werner 704
 
662 werner 705
    // retrieve a list of trees within the active pixel
665 werner 706
    // NOTE: the check with isDead() is necessary because dead trees could be already in the trees list
662 werner 707
    QVector<Tree*> trees;
708
    QVector<Tree>::iterator tend = ru->trees().end();
709
    for (QVector<Tree>::iterator t = ru->trees().begin(); t!=tend; ++t) {
1172 werner 710
        if ( pixel_rect.contains( (*t).position() ) && !(*t).isDead()) {
662 werner 711
            trees.push_back(&(*t));
1172 werner 712
        }
662 werner 713
    }
649 werner 714
 
662 werner 715
    // calculate mean values for dbh
716
    double sum_dbh = 0.;
680 werner 717
    double sum_ba = 0.;
760 werner 718
    double avg_dbh = 0.;
680 werner 719
    foreach (const Tree* t, trees) {
662 werner 720
        sum_dbh += t->dbh();
680 werner 721
        sum_ba += t->basalArea();
722
    }
654 werner 723
 
760 werner 724
    if(trees.size()>0)
1165 werner 725
        avg_dbh = sum_dbh / static_cast<double>( trees.size() );
760 werner 726
 
662 werner 727
    // (1) calculate fuel
667 werner 728
    const double kfc1 = mFuelkFC1;
729
    const double kfc2 = mFuelkFC2;
730
    const double kfc3 = mFuelkFC3;
662 werner 731
    // retrieve values for fuel.
732
    // forest_floor: sum of leaves and twigs (t/ha) = yR pool
733
    // DWD: downed woody debris (t/ha) = yL pool
654 werner 734
 
680 werner 735
    // fuel per ha (kg biomass): derive available fuel using the KBDI as estimate for humidity.
1172 werner 736
    double fuel_ff = (kfc1 + kfc2*ru_data.kbdi()) * (ru->soil()? ru->soil()->youngLabile().biomass() * 1000. : 1000.);
737
    double fuel_dwd = kfc3*ru_data.kbdi() * (ru->soil() ? ru->soil()->youngRefractory().biomass() * 1000. : 1000. );
680 werner 738
    // calculate fuel (kg biomass / ha)
739
    double fuel = (fuel_ff + fuel_dwd);
654 werner 740
 
680 werner 741
    ru_data.fireRUStats.n_cells++; // number of cells burned in the resource unit
742
    ru_data.fireRUStats.fuel_ff += fuel_ff; // fuel in kg/ha Biomass
743
    ru_data.fireRUStats.fuel_dwd += fuel_dwd; // fuel in kg/ha Biomass
663 werner 744
    ru_data.fireRUStats.n_trees += trees.size();
680 werner 745
    ru_data.fireRUStats.basal_area += sum_ba;
760 werner 746
 
747
    // if fuel level is below 0.05kg BM/m2 (=500kg/ha), then no burning happens!
748
    // note, that it is not necessary that trees are on the pixel, as long as there is enough fuel on the ground.
662 werner 749
    if (fuel < 500.)
750
        return false;
751
 
752
    // (2) calculate the "crownkill" fraction
667 werner 753
    const double dbh_trehshold = mCrownKillDbh; // dbh
754
    const double kck1 = mCrownKillkCK1;
755
    const double kck2 = mCrownKillkCK2;
662 werner 756
    if (avg_dbh > dbh_trehshold)
757
        avg_dbh = dbh_trehshold;
758
 
759
    double crown_kill_fraction = (kck1+kck2*avg_dbh)*fuel/1000.; // fuel: to t/ha
691 werner 760
    crown_kill_fraction = limit(crown_kill_fraction, 0., 1.); // limit to 0..1
662 werner 761
 
762
 
763
    // (3) derive mortality of single trees
764
    double p_mort;
765
    int died = 0;
766
    double died_basal_area=0.;
690 werner 767
    bool tree_is_psme;
662 werner 768
    foreach (Tree* t, trees) {
769
        // the mortality probability depends on the thickness of the bark:
667 werner 770
        *mFormula_bt = t->barkThickness(); // cm
771
        *mFormula_ck = crown_kill_fraction; // fraction of crown that is killed (0..1)
772
        p_mort = mMortalityFormula.execute();
662 werner 773
        // note: 5.41 = 0.000541*10000, (fraction*fraction) = 10000 * pct*pct
667 werner 774
        //p_mort = 1. / (1. + exp(-1.466 + 1.91*bt - 0.1775*bt*bt - 5.41*crown_kill_fraction*crown_kill_fraction));
690 werner 775
        tree_is_psme = t->species()->id()=="Psme";
776
        if (tree_is_psme)
777
            fireStats.fire_psme_total += t->basalArea();
663 werner 778
        if (drandom() < p_mort) {
662 werner 779
            // the tree actually dies.
780
            died_basal_area += t->basalArea();
690 werner 781
            if (tree_is_psme)
782
                fireStats.fire_psme_died += t->basalArea();
1172 werner 783
 
784
            if (t->species()->seedDispersal() && t->species()->isTreeSerotinous(t->age()) ) {
785
                //SeedDispersal *sd = t->species()->seedDispersal();
786
                t->species()->seedDispersal()->seedProductionSerotiny(t->positionIndex());
787
            }
788
 
680 werner 789
            if (!mOnlyFireSimulation) {
790
                // before tree biomass is transferred to the snag-state, a part of the biomass is combusted:
1165 werner 791
                t->setDeathReasonFire();
903 werner 792
                t->removeBiomassOfTree(mBurnFoliageFraction, mBurnBranchFraction, mBurnStemFraction);
1165 werner 793
                // kill the tree and calculate flows to soil/snags
794
                t->removeDisturbance(0., 1., // 100% of the remaining stem goes to snags
795
                                     0., 1., // 100% of the remaining branches go to snags
796
                                     1.); // the remaining foliage goes to soil
797
 
680 werner 798
            }
662 werner 799
            ++died;
800
            // some statistics???
801
        }
802
    }
803
 
804
    // update statistics
805
    ru_data.fireRUStats.n_trees_died += died;
806
    ru_data.fireRUStats.died_basal_area += died_basal_area;
807
    ru_data.fireRUStats.crown_kill += crown_kill_fraction;
693 werner 808
    ru_data.fireRUStats.avg_dbh += avg_dbh;
662 werner 809
 
680 werner 810
    if (!mOnlyFireSimulation) {
811
        // (4) effect of forest fire on saplings: all saplings are killed.
812
        //     As regeneration happens before the fire routine, any newly regenarated saplings are killed as well.
1162 werner 813
        if (GlobalSettings::instance()->model()->saplings())
814
            GlobalSettings::instance()->model()->saplings()->clearSaplings(pixel_rect, true);
815
        //ru->clearSaplings(pixel_rect, true); [old version]
680 werner 816
    }
817
    return true;
818
}
662 werner 819
 
680 werner 820
/// do some cleanup / statistics after the fire.
821
/// apply the effect of fire on dead wood pools and soil pools of the resource units
822
/// biomass of living trees is consumed in the burnPixel() routine.
823
void FireModule::afterFire()
824
{
1157 werner 825
    const double pixel_fraction = cellsize()*cellsize() / cRUArea; // fraction of one pixel, default: 0.04 (20x20 / 100x100)
662 werner 826
 
680 werner 827
    int ru_idx=0;
828
    for (FireRUData *fds = mRUGrid.begin(); fds!=mRUGrid.end(); ++fds, ++ru_idx) {
829
        fds->fireRUStats.calculate(mFireId);
830
        if (fds->fireRUStats.n_cells>0) {
831
            // on this resource unit really a fire happened.
832
            // so we need to update snags/soil pools
833
            if (!mOnlyFireSimulation) {
834
                ResourceUnit *ru = GlobalSettings::instance()->model()->ru(ru_idx);
835
                double ru_fraction = fds->fireRUStats.n_cells * pixel_fraction; // fraction of RU burned (0..1)
836
 
1172 werner 837
                if (ru && ru->soil()) {
838
                    // (1) effect of forest fire on the dead wood pools. fuel_dwd and fuel_ff is the amount of fuel
839
                    //     available on the pixels that are burnt. The assumption is: all of it was burnt.
1165 werner 840
                    ru->soil()->disturbanceBiomass(fds->fireRUStats.fuel_dwd, fds->fireRUStats.fuel_ff, 0.);
680 werner 841
 
1172 werner 842
                    // (2) remove also a fixed fraction of the biomass that is in the soil
843
                    if (mBurnSoilBiomass>0.)
844
                        ru->soil()->disturbance(0,0, mBurnSoilBiomass*ru_fraction);
845
                    // (3) effect on the snsgs
1165 werner 846
                    ru->snag()->removeCarbon(mBurnStemFraction*ru_fraction);
1172 werner 847
                }
680 werner 848
            }
849
        }
850
    }
696 werner 851
 
852
    // execute the after fire event
853
    if (!mAfterFireEvent.isEmpty()) {
854
        // evaluate the javascript function...
767 werner 855
        GlobalSettings::instance()->executeJavascript(mAfterFireEvent);
696 werner 856
    }
662 werner 857
}
858
 
859
 
860
 
861
 
862
 
863
 
864
 
865
 
866
 
674 werner 867