Subversion Repositories public iLand

Rev

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

Rev Author Line No. Line
1033 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
********************************************************************************************/
908 werner 19
#include "abe_global.h"
905 werner 20
#include "actsalvage.h"
21
 
22
#include "fmstp.h"
23
#include "fmstand.h"
24
#include "fomescript.h"
25
#include "scheduler.h"
912 werner 26
#include "forestmanagementengine.h"
27
#include "fmtreelist.h"
905 werner 28
 
29
#include "tree.h"
30
#include "expression.h"
31
#include "expressionwrapper.h"
914 werner 32
#include "mapgrid.h"
33
#include "helper.h"
905 werner 34
 
907 werner 35
namespace ABE {
905 werner 36
 
1095 werner 37
/** @class ActSalvage
38
    @ingroup abe
39
    The ActSalvage class handles salvage logging after disturbances.
40
 
41
  */
42
 
909 werner 43
ActSalvage::ActSalvage(FMSTP *parent): Activity(parent)
905 werner 44
{
45
    mCondition = 0;
46
    mMaxPreponeActivity = 0;
47
 
48
    mBaseActivity.setIsSalvage(true);
49
    mBaseActivity.setIsRepeating(true);
50
    mBaseActivity.setExecuteImmediate(true);
51
 
52
}
53
 
54
ActSalvage::~ActSalvage()
55
{
56
    if (mCondition)
57
        delete mCondition;
58
}
59
 
60
void ActSalvage::setup(QJSValue value)
61
{
62
    Activity::setup(value); // setup base events
1070 werner 63
    events().setup(value, QStringList() << "onBarkBeetleAttack");
905 werner 64
 
65
    QString condition = FMSTP::valueFromJs(value, "disturbanceCondition").toString();
66
    if (!condition.isEmpty() && condition!="undefined") {
67
        mCondition = new Expression(condition);
68
    }
69
    mMaxPreponeActivity = FMSTP::valueFromJs(value, "maxPrepone", "0").toInt();
1157 werner 70
    mThresholdSplit = FMSTP::valueFromJs(value, "thresholdSplitStand", "0.1").toNumber();
71
    mThresholdClear = FMSTP::valueFromJs(value, "thresholdClearStand", "0.9").toNumber();
72
    mThresholdMinimal = FMSTP::valueFromJs(value, "thresholdIgnoreDamage", "5").toNumber();
914 werner 73
    mDebugSplit = FMSTP::boolValueFromJs(value, "debugSplit", false);
905 werner 74
 
75
}
76
 
77
bool ActSalvage::execute(FMStand *stand)
78
{
914 werner 79
 
80
 
81
    if (stand->property("_run_salvage").toBool()) {
82
        // 2nd phase: do the after disturbance cleanup of a stand.
83
        bool simu = stand->currentFlags().isDoSimulate();
84
        stand->currentFlags().setDoSimulate(false);
1157 werner 85
        stand->currentFlags().setFinalHarvest(true); // this should be accounted as "final harvest"
914 werner 86
        // execute the "onExecute" event
87
        bool result =  Activity::execute(stand);
88
        stand->currentFlags().setDoSimulate(simu);
1157 werner 89
        stand->currentFlags().setFinalHarvest(false);
914 werner 90
        stand->setProperty("_run_salvage", false);
91
        return result;
92
    }
93
 
905 werner 94
    // the salvaged timber is already accounted for - so nothing needs to be done here.
95
    // however, we check if there is a planned activity for the stand which could be executed sooner
96
    // than planned.
909 werner 97
    bool preponed = const_cast<FMUnit*>(stand->unit())->scheduler()->forceHarvest(stand, mMaxPreponeActivity);
907 werner 98
    if (stand->trace())
909 werner 99
        qCDebug(abe) << "Salvage activity executed. Changed scheduled activites (preponed): " << preponed;
907 werner 100
 
909 werner 101
    const_cast<FMUnit*>(stand->unit())->scheduler()->addExtraHarvest(stand, stand->totalHarvest(), Scheduler::Salvage);
912 werner 102
    // check if we should re-assess the stand grid (after large disturbances)
1157 werner 103
    // as a preliminary check we only look closer, if we have more than  x m3/ha of damage.
1088 werner 104
    if (stand->disturbedTimber()/stand->area() > mThresholdMinimal)
914 werner 105
        checkStandAfterDisturbance(stand);
106
 
912 werner 107
    // the harvest happen(ed) anyways.
1157 werner 108
    //stand->resetHarvestCounter(); // set back to zero...
907 werner 109
    return true;
905 werner 110
}
111
 
112
QStringList ActSalvage::info()
113
{
114
    QStringList lines = Activity::info();
909 werner 115
    lines << QString("condition: %1").arg(mCondition?mCondition->expression():"-");
905 werner 116
    lines << QString("maxPrepone: %1").arg(mMaxPreponeActivity);
117
    return lines;
118
}
119
 
911 werner 120
bool ActSalvage::evaluateRemove(Tree *tree) const
905 werner 121
{
122
    if (!mCondition)
123
        return true; // default: remove all trees
124
    TreeWrapper tw(tree);
1157 werner 125
    bool result = static_cast<bool>( mCondition->execute(0, &tw) );
905 werner 126
    return result;
127
}
128
 
1070 werner 129
bool ActSalvage::barkbeetleAttack(FMStand *stand, double generations, int infested_px_ha)
130
{
131
 
132
    //QJSValue params;
133
    QJSValueList params=QJSValueList() << QJSValue(generations) << QJSValue(infested_px_ha);
134
 
135
    QJSValue result = events().run(QStringLiteral("onBarkBeetleAttack"), stand, &params);
136
    if (!result.isBool())
137
        qCDebug(abe) << "Salvage-Activity:onBarkBeetleAttack: expecting a boolean return";
138
    return result.toBool();
139
}
140
 
914 werner 141
void ActSalvage::checkStandAfterDisturbance(FMStand *stand)
912 werner 142
{
143
    //
144
    FMTreeList *trees = ForestManagementEngine::instance()->scriptBridge()->treesObj();
145
    //trees->runGrid();
913 werner 146
    trees->prepareStandGrid(QStringLiteral("height"), QString());
914 werner 147
 
1157 werner 148
    const int min_split_size = 50; // min size (100=1ha)
913 werner 149
    FloatGrid &grid = trees->standGrid();
914 werner 150
    static int no_split = 0;
151
    if (mDebugSplit)
152
        trees->exportStandGrid(QString("temp/height_%1.txt").arg(++no_split));
153
 
913 werner 154
    float h_max = grid.max();
155
 
156
    double r_low;
1157 werner 157
    int h_lower = 0, h_higher=0;
913 werner 158
    if (h_max==0.f) {
159
        // total disturbance...
160
        r_low = 1.;
161
    } else {
162
        // check coverage of disturbed area.
163
        for (float *p=grid.begin(); p!=grid.end(); ++p)
164
            if (*p>=0.f) {
165
                if (*p < h_max*0.33)
166
                    ++h_lower;
167
                else
168
                    ++h_higher;
169
            }
170
        if (h_lower==0 && h_higher==0)
171
            return;
172
        r_low = h_lower / double(h_lower+h_higher);
173
    }
174
 
1157 werner 175
    if (r_low < mThresholdSplit || (r_low<0.5 && h_lower<min_split_size)) {
913 werner 176
        // no big damage: return and do nothing
177
        return;
178
    }
1157 werner 179
 
180
    // restart if a large fraction is cleared, or if the remaining forest is <0.25ha
181
    if (r_low > mThresholdClear || (r_low>0.5 && h_higher<min_split_size)) {
913 werner 182
        // total disturbance: restart rotation...
1157 werner 183
        qCDebug(abe) << "ActSalvage: total damage for stand" << stand->id() << "Restarting rotation.";
914 werner 184
        stand->setProperty("_run_salvage", true);
185
        stand->reset(stand->stp());
913 werner 186
        return;
187
    }
188
    // medium disturbance: check if need to split the stand area:
914 werner 189
    Grid<int> my_map(grid.cellsize(), grid.sizeX(), grid.sizeY());
190
    GridRunner<float> runner(&grid);
191
    GridRunner<int> id_runner(&my_map);
913 werner 192
    float *neighbors[8];
914 werner 193
    int n_empty=0;
913 werner 194
    while (runner.next() && id_runner.next()) {
914 werner 195
        if (*runner.current()==-1.) {
196
            *id_runner.current() = -1;
197
            continue;
198
        }
913 werner 199
        runner.neighbors8(neighbors);
200
        double empty = 0.;
201
        int valid = 0;
202
        for (int i=0;i<8;++i) {
203
            if (neighbors[i] && *neighbors[i]<h_max*0.33)
204
                empty++;
205
            if (neighbors[i])
206
                valid++;
207
        }
208
        if (valid)
209
            empty /= double(valid);
914 werner 210
        // empty cells are marked with 0; areas covered by forest set to stand_id; -1: out-of-stand areas
211
        // if a cell is empty, some neighbors (i.e. >50%) need to be empty too;
212
        // if a cell is *not* empty, it has to be surrounded by a larger fraction of empty points (75%)
213
        if ( (*runner.current()<h_max*0.33 && empty>0.5)
214
             || (empty>=0.75) ) {
215
            *id_runner.current() = 0;
216
            n_empty++;
217
        } else {
218
            *id_runner.current() = stand->id();
219
        }
913 werner 220
    }
914 werner 221
    if (mDebugSplit)
1157 werner 222
        Helper::saveToTextFile(GlobalSettings::instance()->path(QString("temp/split_before_%1.txt").arg(no_split)), gridToESRIRaster(my_map) );
914 werner 223
 
224
 
225
    // now flood-fill 0ed areas....
226
    // if the "new" areas are too small (<0.25ha), then nothing happens.
1157 werner 227
    QVector<QPair<int,int> > cleared_small_areas; // areas of cleared "patches"
228
    QVector<QPair<int,int> > stand_areas; // areas of remaining forested "patches"
914 werner 229
    int fill_color = -1;
1157 werner 230
    int stand_fill_color = stand->id() + 1000;
914 werner 231
    id_runner.reset();
232
    while (id_runner.next()) {
1157 werner 233
        if (*id_runner.current()==0) {
234
            int s = floodFillHelper(my_map, id_runner.currentIndex(), 0, --fill_color);
235
            cleared_small_areas.push_back(QPair<int,int>(fill_color, s)); // patch size
236
        } else if (*id_runner.current()==stand->id()) {
237
            int s=floodFillHelper(my_map, id_runner.currentIndex(), stand->id(), stand_fill_color);
238
            stand_areas.push_back(QPair<int,int>(stand_fill_color,s));
239
            stand_fill_color++;
240
        }
241
    }
242
    if (mDebugSplit)
243
        Helper::saveToTextFile(GlobalSettings::instance()->path(QString("temp/split_stands_%1.txt").arg(no_split)), gridToESRIRaster(my_map) );
244
 
245
 
246
    // special case: remainnig forest are only small patches
247
    int max_size=0;
248
    for (int i=0;i<stand_areas.size();++i)
249
        max_size=std::max(max_size, stand_areas[i].second);
250
    if (max_size<min_split_size) {
251
        // total disturbance: restart rotation...
252
        qCDebug(abe) << "ActSalvage: total damage for stand" << stand->id() << "(remaining patches too small). Restarting rotation.";
253
        stand->setProperty("_run_salvage", true);
254
        stand->reset(stand->stp());
255
        return;
256
    }
257
 
258
    // clear small areas
259
    QVector<int> neighbor_ids;
260
    bool finished=false;
261
    int iter=100;
262
    while (!finished && cleared_small_areas.size()>0 && --iter>0) {
263
 
264
        // find smallest area....
265
        int i_min=-1;
266
        for (int i=0;i<cleared_small_areas.size();++i) {
267
            if (cleared_small_areas[i].second<min_split_size) {
268
                if (i_min==-1 || (i_min>-1 && cleared_small_areas[i].second<cleared_small_areas[i_min].second))
269
                    i_min = i;
914 werner 270
            }
1157 werner 271
        }
272
        if (i_min==-1) {
273
            finished = true;
274
            continue;
275
        }
276
 
277
        // loook for neighbors of the area
278
        // attach to largest "cleared" neighbor (if such a neighbor exists)
279
        neighborFinderHelper(my_map,neighbor_ids, cleared_small_areas[i_min].first);
280
        if (neighbor_ids.size()==0) {
281
            // patch fully surrounded by "out of project area". We'll add it to the *first* stand map entry
282
            neighbor_ids.append(stand_areas.first().first);
283
        }
284
        // look for "empty patches" first
285
        int i_empty=-1; int max_size=0;
286
        for (int i=0;i<cleared_small_areas.size();++i) {
287
            if (neighbor_ids.contains(cleared_small_areas[i].first))
288
                if (cleared_small_areas[i].second>max_size) {
289
                    i_empty=i;
290
                    max_size=cleared_small_areas[i].second;
291
                }
292
        }
293
        if (i_empty>-1) {
294
            // replace "i_min" with "i_empty"
295
            int r = replaceValueHelper(my_map, cleared_small_areas[i_min].first, cleared_small_areas[i_empty].first);
296
            cleared_small_areas[i_empty].second += r;
297
            cleared_small_areas.remove(i_min);
298
            continue;
299
        }
300
 
301
 
302
        if (stand_areas.size()>0) {
303
            // attach to largest stand part which is a neighbor
304
 
305
            int i_empty=-1; int max_size=0;
306
            for (int i=0;i<stand_areas.size();++i) {
307
                if (neighbor_ids.contains(stand_areas[i].first))
308
                    if (stand_areas[i].second>max_size) {
309
                        i_empty=i;
310
                        max_size=stand_areas[i].second;
311
                    }
312
            }
313
            if (i_empty>-1) {
314
                // replace "i_min" with "i_empty"
315
                int r = replaceValueHelper(my_map, cleared_small_areas[i_min].first, stand_areas[i_empty].first);
316
                stand_areas[i_empty].second += r;
317
                cleared_small_areas.remove(i_min);
318
            }
319
        }
320
        if (iter==3)
321
            qCDebug(abe) << "ActSalvage:Loop1: no solution.";
914 werner 322
    }
1157 werner 323
 
324
 
325
    // clear small forested stands
326
    finished = false;
327
    iter = 100;
328
    while (!finished && --iter>0) {
329
        finished=true;
330
        for (int i=0;i<stand_areas.size();++i) {
331
            if (stand_areas[i].second < min_split_size) {
332
                neighborFinderHelper(my_map, neighbor_ids, stand_areas[i].first);
333
 
334
                if (neighbor_ids.size()>0) {
335
                    int r = replaceValueHelper(my_map, stand_areas[i].first, neighbor_ids[0]);
336
                    if (neighbor_ids[0]>0) {
337
                        // another stand
338
                        for (int j=0;j<stand_areas.size();++j)
339
                            if (stand_areas[j].first == neighbor_ids[0]) {
340
                                stand_areas[j].second += r;
341
                            }
342
                    } else {
343
                        // clearing
344
                        for (int j=0;j<cleared_small_areas.size();++j)
345
                            if (cleared_small_areas[j].first == neighbor_ids[0]) {
346
                                cleared_small_areas[j].second += r;
347
                            }
348
                    }
349
 
350
                    stand_areas.remove(i);
351
                    finished=false;
352
                    break;
353
                }
354
            }
355
        }
356
    }
357
    if (iter==0)
358
        qCDebug(abe) << "ActSalvage:Loop2: no solution.";
914 werner 359
    if (mDebugSplit)
1157 werner 360
        Helper::saveToTextFile(GlobalSettings::instance()->path(QString("temp/split_final_%1.txt").arg(no_split)), gridToESRIRaster(my_map) );
914 werner 361
 
1157 werner 362
 
363
    // determine final new stands....
364
    QVector<int> new_stands; // internal ids that should become new stands
365
 
366
    for (int i=0;i<cleared_small_areas.size();++i)
367
        new_stands.push_back(cleared_small_areas[i].first);
368
 
369
    // only add new stands - keep the old stand as is
370
    //    if (new_stands.size()>0) {
371
//        // if there are no "cleared" parts, we keep the stand as is.
372
//        for (int i=0;i<stand_areas.size();++i)
373
//            if (stand_areas[i].first != stand->id()+1000)
374
//                new_stands.push_back(stand_areas[i].first);
375
//    }
376
 
377
 
914 werner 378
    for (int i=0;i<new_stands.size(); ++i) {
379
        // ok: we have new stands. Now do the actual splitting
380
        FMStand *new_stand = ForestManagementEngine::instance()->splitExistingStand(stand);
381
        // copy back to the stand grid
382
        GridRunner<int> sgrid(ForestManagementEngine::instance()->standGrid()->grid(), grid.metricRect());
383
        id_runner.reset();
1157 werner 384
        int n_px=0;
914 werner 385
        while (sgrid.next() && id_runner.next()) {
1157 werner 386
            if (*id_runner.current() == new_stands[i]) {
914 werner 387
                *sgrid.current() = new_stand->id();
1157 werner 388
                ++n_px;
389
            }
914 werner 390
        }
391
 
392
        // the new stand  is prepared.
393
        // at the end of this years execution, the stand will be re-evaluated.
1157 werner 394
        new_stand->setInitialId(stand->id());
395
        // year of splitting: all the area of the stand is still accounted for for the "old" stand
396
        // in the next year (after the update of the stand grid), the old stand shrinks and the new
397
        // stands get their correct size.
398
        // new_stand->setArea(n_px / (cHeightSize*cHeightSize));
399
        new_stand->setProperty("_run_salvage", true);
914 werner 400
        new_stand->reset(stand->stp());
1157 werner 401
        qCDebug(abe) << "ActSalvage: new stand" << new_stand->id() << "parent stand" << stand->id() << "#split:" << no_split;
914 werner 402
    }
912 werner 403
}
905 werner 404
 
914 werner 405
// quick and dirty implementation of the flood fill algroithm.
406
// based on: http://en.wikipedia.org/wiki/Flood_fill
407
// returns the number of pixels colored
1157 werner 408
int ActSalvage::floodFillHelper(Grid<int> &grid, QPoint start, int old_color, int color)
914 werner 409
{
410
    QQueue<QPoint> pqueue;
411
    pqueue.enqueue(start);
412
    int found = 0;
413
    while (!pqueue.isEmpty()) {
414
        QPoint p = pqueue.dequeue();
415
        if (!grid.isIndexValid(p))
416
            continue;
1157 werner 417
        if (grid.valueAtIndex(p)==old_color) {
914 werner 418
            grid.valueAtIndex(p) = color;
419
            pqueue.enqueue(p+QPoint(-1,0));
420
            pqueue.enqueue(p+QPoint(1,0));
421
            pqueue.enqueue(p+QPoint(0,-1));
422
            pqueue.enqueue(p+QPoint(0,1));
1157 werner 423
            pqueue.enqueue(p+QPoint(1,1));
424
            pqueue.enqueue(p+QPoint(1,-1));
425
            pqueue.enqueue(p+QPoint(-1,1));
426
            pqueue.enqueue(p+QPoint(-1,-1));
914 werner 427
            ++found;
428
        }
429
    }
430
    return found;
431
}
912 werner 432
 
1157 werner 433
// find all neigbors of color 'stand_id' and save in the 'neighbors' vector
434
int ActSalvage::neighborFinderHelper(Grid<int> &grid, QVector<int> &neighbors, int stand_id)
435
{
436
    GridRunner<int> id_runner(&grid);
437
    neighbors.clear();
438
    int *nb[8];
439
    while (id_runner.next()) {
440
        if (*id_runner.current()==stand_id) {
441
            id_runner.neighbors8(nb);
442
            for (int i=0;i<8;++i)
443
                if (nb[i] && *nb[i]!=-1 && *nb[i]!=stand_id) {
444
                    if (!neighbors.contains(*nb[i]))
445
                        neighbors.push_back(*nb[i]);
446
                }
447
        }
448
    }
449
    return neighbors.size();
450
}
914 werner 451
 
1157 werner 452
int ActSalvage::replaceValueHelper(Grid<int> &grid, int old_value, int new_value)
453
{
454
    int n=0;
455
    for (int *p=grid.begin(); p!=grid.end(); ++p)
456
        if (*p == old_value) {
457
            *p = new_value;
458
            ++n;
459
        }
460
    return n;
461
}
462
 
463
 
905 werner 464
} // namespace