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, ¶ms); |
||
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 |