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 | ********************************************************************************************/ |
||
19 | |||
908 | werner | 20 | #include "abe_global.h" |
811 | werner | 21 | #include "fmstand.h" |
22 | |||
23 | #include "fmunit.h" |
||
873 | werner | 24 | #include "management.h" |
890 | werner | 25 | #include "fmtreelist.h" |
873 | werner | 26 | #include "forestmanagementengine.h" |
27 | #include "mapgrid.h" |
||
875 | werner | 28 | #include "fmstp.h" |
889 | werner | 29 | #include "scheduler.h" |
892 | werner | 30 | #include "fomescript.h" |
896 | werner | 31 | #include "agent.h" |
32 | #include "agenttype.h" |
||
811 | werner | 33 | |
873 | werner | 34 | #include "tree.h" |
874 | werner | 35 | #include "species.h" |
873 | werner | 36 | |
1059 | werner | 37 | #include "statdata.h" |
890 | werner | 38 | #include "debugtimer.h" |
39 | |||
907 | werner | 40 | namespace ABE { |
870 | werner | 41 | |
1095 | werner | 42 | /** @class FMStand |
43 | @ingroup abe |
||
44 | The FMStand class encapsulates forest stands which are defined as polygons. FMStand tracks properties of the stands (e.g. mean volume), and |
||
45 | is a central player in the ABE system. |
||
46 | |||
47 | |||
48 | */ |
||
49 | |||
50 | |||
816 | werner | 51 | FMStand::FMStand(FMUnit *unit, const int id) |
811 | werner | 52 | { |
53 | mUnit = unit; |
||
816 | werner | 54 | mId = id; |
1157 | werner | 55 | mInitialId = id; |
870 | werner | 56 | mPhase = Activity::Invalid; |
903 | werner | 57 | |
811 | werner | 58 | // testing: |
870 | werner | 59 | mPhase = Activity::Tending; |
815 | werner | 60 | mStandType = 1; // just testing... |
903 | werner | 61 | |
940 | werner | 62 | mU = 0, mSpeciesCompositionIndex = -1, mThinningIntensityClass = -1; |
63 | |||
903 | werner | 64 | newRotatation(); |
873 | werner | 65 | mSTP = 0; |
813 | werner | 66 | mVolume = 0.; |
67 | mAge = 0.; |
||
873 | werner | 68 | mTotalBasalArea = 0.; |
885 | werner | 69 | mStems = 0.; |
929 | werner | 70 | mDbh = 0.; |
71 | mHeight = 0.; |
||
889 | werner | 72 | mScheduledHarvest = 0.; |
921 | werner | 73 | mFinalHarvested = 0.; |
74 | mThinningHarvest = 0.; |
||
905 | werner | 75 | mDisturbed = 0.; |
892 | werner | 76 | mRotationStartYear = 0; |
904 | werner | 77 | mLastUpdate = -1.; |
78 | mLastExecution = -1.; |
||
871 | werner | 79 | |
878 | werner | 80 | mCurrentIndex=-1; |
922 | werner | 81 | mLastExecutedIndex=-1; |
953 | werner | 82 | mLastRotationAge = -1; |
930 | werner | 83 | |
1157 | werner | 84 | mArea = ForestManagementEngine::standGrid()->area(mId)/cRUArea; |
930 | werner | 85 | |
811 | werner | 86 | } |
875 | werner | 87 | |
934 | werner | 88 | void FMStand::initialize() |
875 | werner | 89 | { |
934 | werner | 90 | if (!mSTP) |
91 | throw IException(QString("FMStand::initialize, no valid STP for stand %1").arg(id())); |
||
875 | werner | 92 | // copy activity flags |
934 | werner | 93 | mStandFlags = mSTP->defaultFlags(); |
875 | werner | 94 | mCurrentIndex=-1; |
922 | werner | 95 | mLastExecutedIndex=-1; |
875 | werner | 96 | mYearsToWait=0; |
887 | werner | 97 | mContextStr = QString("S%2Y%1:").arg(ForestManagementEngine::instance()->currentYear()).arg(id()); // initialize... |
875 | werner | 98 | |
892 | werner | 99 | // load data and aggregate averages |
100 | reload(); |
||
934 | werner | 101 | if (mRotationStartYear==0.) // only set if not explicitely set previously. |
102 | mRotationStartYear = ForestManagementEngine::instance()->currentYear() - age(); |
||
903 | werner | 103 | // when a stand is initialized, we assume that 20% of the standing volume |
104 | // have been removed already. |
||
105 | mRemovedVolumeTotal = volume() * 0.2; |
||
1090 | werner | 106 | if (absoluteAge()>0) |
107 | mMAItotal = volume() * 1.2 / absoluteAge(); |
||
108 | else |
||
109 | mMAItotal = 0.; |
||
110 | |||
903 | werner | 111 | mMAIdecade = mMAItotal; |
112 | mLastMAIVolume = volume(); |
||
892 | werner | 113 | |
875 | werner | 114 | // find out the first activity... |
876 | werner | 115 | int min_years_to_wait = 100000; |
875 | werner | 116 | for (int i=0;i<mStandFlags.count(); ++i) { |
901 | werner | 117 | // run the onSetup event |
118 | // specifically set 'i' as the activity to be evaluated. |
||
119 | FomeScript::setExecutionContext(this); |
||
120 | FomeScript::bridge()->activityObj()->setActivityIndex(i); |
||
121 | mStandFlags[i].activity()->events().run(QStringLiteral("onSetup"), 0); |
||
122 | |||
878 | werner | 123 | if (!mStandFlags[i].enabled() || !mStandFlags[i].active()) |
124 | continue; |
||
875 | werner | 125 | // set active to false which have already passed |
901 | werner | 126 | if (!mStandFlags[i].activity()->isRepeatingActivity()) { |
942 | werner | 127 | if (!mStandFlags[i].activity()->schedule().absolute && mStandFlags[i].activity()->latestSchedule(U()) < age()) { |
901 | werner | 128 | mStandFlags[i].setActive(false); |
129 | } else { |
||
942 | werner | 130 | int delta = mStandFlags[i].activity()->earlistSchedule(U()) - age(); |
901 | werner | 131 | if (mStandFlags[i].activity()->schedule().absolute) |
132 | delta += age(); // absolute timing: starting from 0 |
||
888 | werner | 133 | |
901 | werner | 134 | if (delta<min_years_to_wait) { |
135 | min_years_to_wait = qMax(delta,0); // limit to 0 years |
||
136 | mCurrentIndex = i; // first activity to execute |
||
137 | } |
||
878 | werner | 138 | } |
876 | werner | 139 | } |
875 | werner | 140 | } |
902 | werner | 141 | if (mCurrentIndex==-1) { |
142 | // the stand is "outside" the time frames provided by the activities. |
||
143 | // set the last activity with "force" = true as the active |
||
144 | for (int i=mStandFlags.count()-1;i>=0; --i) |
||
145 | if (mStandFlags[i].enabled() && mStandFlags[i].activity()->schedule().force_execution==true) { |
||
146 | mCurrentIndex = i; |
||
147 | break; |
||
148 | } |
||
149 | |||
150 | } |
||
151 | |||
876 | werner | 152 | if (min_years_to_wait<100000) |
153 | sleep(min_years_to_wait); |
||
875 | werner | 154 | |
155 | // call onInit handler on the level of the STP |
||
934 | werner | 156 | mSTP->events().run(QStringLiteral("onInit"), this); |
915 | werner | 157 | if (mCurrentIndex>-1) { |
890 | werner | 158 | mStandFlags[mCurrentIndex].activity()->events().run(QStringLiteral("onEnter"), this); |
875 | werner | 159 | |
915 | werner | 160 | // if it is a scheduled activity, then execute (to get initial estimates for harvests) |
161 | if (currentFlags().isScheduled()) |
||
162 | executeActivity(currentActivity()); |
||
163 | } |
||
164 | |||
875 | werner | 165 | } |
166 | |||
914 | werner | 167 | void FMStand::reset(FMSTP *stp) |
168 | { |
||
169 | mSTP = stp; |
||
170 | newRotatation(); |
||
171 | mCurrentIndex = -1; |
||
172 | } |
||
173 | |||
1157 | werner | 174 | void FMStand::checkArea() |
175 | { |
||
176 | mArea = ForestManagementEngine::standGrid()->area(mId)/cRUArea; |
||
177 | } |
||
178 | |||
873 | werner | 179 | bool relBasalAreaIsHigher(const SSpeciesStand &a, const SSpeciesStand &b) |
180 | { |
||
181 | return a.relBasalArea > b.relBasalArea; |
||
182 | } |
||
811 | werner | 183 | |
904 | werner | 184 | void FMStand::reload(bool force) |
869 | werner | 185 | { |
904 | werner | 186 | if (!force && mLastUpdate == ForestManagementEngine::instance()->currentYear()) |
187 | return; |
||
188 | |||
909 | werner | 189 | DebugTimer t("ABE:FMStand::reload"); |
873 | werner | 190 | // load all trees that are located on this stand |
191 | mTotalBasalArea = 0.; |
||
885 | werner | 192 | mVolume = 0.; |
193 | mAge = 0.; |
||
194 | mStems = 0.; |
||
929 | werner | 195 | mDbh = 0.; |
196 | mHeight = 0.; |
||
1059 | werner | 197 | mTopHeight = 0.; |
897 | werner | 198 | mLastUpdate = ForestManagementEngine::instance()->currentYear(); |
873 | werner | 199 | mSpeciesData.clear(); |
890 | werner | 200 | |
892 | werner | 201 | // load all trees of the forest stand (use the treelist of the current execution context) |
202 | FMTreeList *trees = ForestManagementEngine::instance()->scriptBridge()->treesObj(); |
||
203 | trees->setStand(this); |
||
204 | trees->loadAll(); |
||
890 | werner | 205 | |
206 | //qDebug() << "fmstand-reload: load trees from map:" << t.elapsed(); |
||
873 | werner | 207 | // use: value_per_ha = value_stand * area_factor |
904 | werner | 208 | double area_factor = 1. / area(); |
892 | werner | 209 | const QVector<QPair<Tree*, double> > &treelist = trees->trees(); |
1059 | werner | 210 | |
211 | // calculate top-height: diameter of the 100 thickest trees per ha |
||
212 | QVector<double> dbhvalues; |
||
213 | dbhvalues.reserve(trees->trees().size()); |
||
214 | |||
215 | for ( QVector<QPair<Tree*, double> >::const_iterator it=treelist.constBegin(); it!=treelist.constEnd(); ++it) |
||
216 | dbhvalues.push_back(it->first->dbh()); |
||
217 | |||
1099 | werner | 218 | double topheight_threshhold=0.; |
1059 | werner | 219 | double topheight_height = 0.; |
220 | int topheight_trees = 0; |
||
221 | if (treelist.size()>0) { |
||
222 | StatData s(dbhvalues); |
||
223 | topheight_threshhold= s.percentile( 100*(1- area()*100/treelist.size()) ); // sorted ascending -> thick trees at the end of the list |
||
224 | } |
||
890 | werner | 225 | for ( QVector<QPair<Tree*, double> >::const_iterator it=treelist.constBegin(); it!=treelist.constEnd(); ++it) { |
873 | werner | 226 | double ba = it->first->basalArea() * area_factor; |
227 | mTotalBasalArea+=ba; |
||
228 | mVolume += it->first->volume() * area_factor; |
||
229 | mAge += it->first->age()*ba; |
||
929 | werner | 230 | mDbh += it->first->dbh()*ba; |
1059 | werner | 231 | mHeight += it->first->height()*ba; |
885 | werner | 232 | mStems++; |
873 | werner | 233 | SSpeciesStand &sd = speciesData(it->first->species()); |
234 | sd.basalArea += ba; |
||
1059 | werner | 235 | if (it->first->dbh() >= topheight_threshhold) { |
236 | topheight_height += it->first->height(); |
||
237 | ++topheight_trees; |
||
238 | } |
||
873 | werner | 239 | } |
240 | if (mTotalBasalArea>0.) { |
||
241 | mAge /= mTotalBasalArea; |
||
929 | werner | 242 | mDbh /= mTotalBasalArea; |
243 | mHeight /= mTotalBasalArea; |
||
873 | werner | 244 | for (int i=0;i<mSpeciesData.count();++i) { |
245 | mSpeciesData[i].relBasalArea = mSpeciesData[i].basalArea / mTotalBasalArea; |
||
246 | } |
||
247 | } |
||
1059 | werner | 248 | if (topheight_trees>0) { |
249 | mTopHeight = topheight_height / double(topheight_trees); |
||
250 | } |
||
885 | werner | 251 | mStems *= area_factor; // convert to stems/ha |
873 | werner | 252 | // sort species data by relative share.... |
253 | std::sort(mSpeciesData.begin(), mSpeciesData.end(), relBasalAreaIsHigher); |
||
869 | werner | 254 | } |
255 | |||
912 | werner | 256 | // return stand area in ha |
875 | werner | 257 | |
930 | werner | 258 | |
892 | werner | 259 | double FMStand::absoluteAge() const |
260 | { |
||
261 | return ForestManagementEngine::instance()->currentYear() - mRotationStartYear; |
||
262 | } |
||
885 | werner | 263 | |
892 | werner | 264 | |
875 | werner | 265 | bool FMStand::execute() |
266 | { |
||
891 | werner | 267 | // the age of the stand increases by one |
268 | mAge++; |
||
269 | |||
875 | werner | 270 | // do nothing if we are still waiting (sleep) |
271 | if (mYearsToWait>0) { |
||
887 | werner | 272 | if (--mYearsToWait > 0) { |
875 | werner | 273 | return false; |
887 | werner | 274 | } |
875 | werner | 275 | } |
887 | werner | 276 | |
1065 | werner | 277 | mContextStr = QString("S%2Y%1:").arg(ForestManagementEngine::instance()->currentYear()).arg(id()); |
942 | werner | 278 | |
1065 | werner | 279 | |
875 | werner | 280 | // what to do if there is no active activity?? |
887 | werner | 281 | if (mCurrentIndex==-1) { |
897 | werner | 282 | if (trace()) |
909 | werner | 283 | qCDebug(abe) << context() << "*** No action - no currently active activity ***"; |
875 | werner | 284 | return false; |
887 | werner | 285 | } |
897 | werner | 286 | if (trace()) |
909 | werner | 287 | qCDebug(abe) << context() << "*** start evaulate activity:" << currentActivity()->name(); |
875 | werner | 288 | |
897 | werner | 289 | // do nothing if there is already an activity in the scheduler |
887 | werner | 290 | if (currentFlags().isPending()) { |
897 | werner | 291 | if (trace()) |
909 | werner | 292 | qCDebug(abe) << context() << "*** No action - stand in the scheduler. ***"; |
875 | werner | 293 | return false; |
887 | werner | 294 | } |
875 | werner | 295 | |
897 | werner | 296 | // do nothing if the the current year is not within the window of opportunity of the activity |
875 | werner | 297 | double p_schedule = currentActivity()->scheduleProbability(this); |
897 | werner | 298 | if (p_schedule == -1.) { |
299 | if (trace()) |
||
909 | werner | 300 | qCDebug(abe)<< context() << "*** Activity expired. ***"; |
929 | werner | 301 | // cancel the activity |
302 | currentFlags().setActive(false); |
||
303 | afterExecution(true); |
||
304 | return false; |
||
897 | werner | 305 | } |
306 | if (p_schedule>=0. && p_schedule < 0.00001) { |
||
307 | if (trace()) |
||
909 | werner | 308 | qCDebug(abe)<< context() << "*** No action - Schedule probability 0. ***"; |
875 | werner | 309 | return false; |
887 | werner | 310 | } |
875 | werner | 311 | |
897 | werner | 312 | |
892 | werner | 313 | // we need to renew the stand data |
314 | reload(); |
||
315 | |||
316 | |||
875 | werner | 317 | // check if there are some constraints that prevent execution.... |
889 | werner | 318 | double p_execute = currentActivity()->execeuteProbability(this); |
319 | if (p_execute == 0.) { |
||
897 | werner | 320 | if (trace()) |
909 | werner | 321 | qCDebug(abe)<< context() << "*** No action - Constraints preventing execution. ***"; |
875 | werner | 322 | return false; |
887 | werner | 323 | } |
875 | werner | 324 | |
891 | werner | 325 | // ok, we should execute the current activity. |
326 | // if it is not scheduled, it is executed immediately, otherwise a ticket is created. |
||
327 | if (currentFlags().isScheduled()) { |
||
328 | // ok, we schedule the current activity |
||
897 | werner | 329 | if (trace()) |
909 | werner | 330 | qCDebug(abe)<< context() << "adding ticket for execution."; |
904 | werner | 331 | |
891 | werner | 332 | mScheduledHarvest = 0.; |
333 | bool should_schedule = currentActivity()->evaluate(this); |
||
334 | if (trace()) |
||
909 | werner | 335 | qCDebug(abe) << context() << "evaluated stand. add a ticket:" << should_schedule; |
891 | werner | 336 | if (should_schedule) { |
337 | mUnit->scheduler()->addTicket(this, ¤tFlags(), p_schedule, p_execute ); |
||
904 | werner | 338 | } else { |
339 | // cancel the activity |
||
340 | currentFlags().setActive(false); |
||
341 | afterExecution(true); |
||
891 | werner | 342 | } |
343 | return should_schedule; |
||
344 | } else { |
||
345 | // execute immediately |
||
897 | werner | 346 | if (trace()) |
909 | werner | 347 | qCDebug(abe) << context() << "executing activty" << currentActivity()->name(); |
891 | werner | 348 | mScheduledHarvest = 0.; |
349 | bool executed = currentActivity()->execute(this); |
||
914 | werner | 350 | if (!currentActivity()) // special case: the activity invalidated the active activtity |
351 | return executed; |
||
352 | |||
901 | werner | 353 | if (!currentActivity()->isRepeatingActivity()) { |
354 | currentFlags().setActive(false); |
||
355 | afterExecution(!executed); // check what comes next for the stand |
||
356 | } |
||
891 | werner | 357 | return executed; |
358 | } |
||
875 | werner | 359 | } |
360 | |||
897 | werner | 361 | bool FMStand::executeActivity(Activity *act) |
362 | { |
||
363 | int old_activity_index = mCurrentIndex; |
||
875 | werner | 364 | |
897 | werner | 365 | int new_index = stp()->activityIndex(act); |
1032 | werner | 366 | bool result=false; |
897 | werner | 367 | if (new_index>-1) { |
901 | werner | 368 | mCurrentIndex = new_index; |
369 | int old_years = mYearsToWait; |
||
370 | mYearsToWait = 0; |
||
897 | werner | 371 | result = execute(); |
372 | mAge--; // undo modification of age |
||
901 | werner | 373 | mYearsToWait = old_years; // undo... |
897 | werner | 374 | } |
375 | mCurrentIndex = old_activity_index; |
||
376 | return result; |
||
377 | } |
||
378 | |||
379 | |||
889 | werner | 380 | bool FMStand::afterExecution(bool cancel) |
875 | werner | 381 | { |
942 | werner | 382 | // check if an agent update is necessary |
383 | unit()->agent()->type()->agentUpdateForStand(this, currentFlags().activity()->name(), -1); |
||
384 | |||
875 | werner | 385 | // is called after an activity has run |
386 | int tmin = 10000000; |
||
387 | int indexmin = -1; |
||
388 | for (int i=0;i<mStandFlags.count(); ++i) { |
||
389 | if (mStandFlags[i].isForcedNext()) { |
||
390 | mStandFlags[i].setForceNext(false); // reset flag |
||
391 | indexmin = i; |
||
392 | break; // we "jump" to this activity |
||
393 | } |
||
394 | } |
||
892 | werner | 395 | |
396 | if (indexmin == -1) { |
||
397 | // check if a restart is needed |
||
398 | // TODO: find a better way!! |
||
893 | werner | 399 | if (currentFlags().isFinalHarvest()) { |
892 | werner | 400 | // we have reached the last activity |
401 | for (int i=0;i<mStandFlags.count(); ++i) |
||
402 | mStandFlags[i].setActive(true); |
||
903 | werner | 403 | newRotatation(); |
902 | werner | 404 | reload(); |
892 | werner | 405 | } |
406 | |||
407 | // look for the next (enabled) activity. |
||
408 | for (int i=0;i<mStandFlags.count(); ++i) { |
||
1089 | werner | 409 | if ( mStandFlags[i].enabled() && mStandFlags[i].active() && !mStandFlags[i].isRepeating()) |
892 | werner | 410 | if (mStandFlags[i].activity()->earlistSchedule() < tmin) { |
411 | tmin = mStandFlags[i].activity()->earlistSchedule(); |
||
412 | indexmin = i; |
||
413 | } |
||
414 | } |
||
415 | } |
||
416 | |||
889 | werner | 417 | if (!cancel) |
892 | werner | 418 | currentActivity()->events().run(QStringLiteral("onExecuted"),this); |
889 | werner | 419 | else |
420 | currentActivity()->events().run(QStringLiteral("onCancel"),this); |
||
421 | |||
888 | werner | 422 | if (indexmin != mCurrentIndex) { |
423 | // call events: |
||
424 | currentActivity()->events().run(QStringLiteral("onExit"), this); |
||
893 | werner | 425 | if (indexmin>-1 && indexmin<mStandFlags.count()) |
888 | werner | 426 | mStandFlags[indexmin].activity()->events().run(QStringLiteral("onEnter"), this); |
427 | |||
428 | } |
||
922 | werner | 429 | mLastExecutedIndex = mCurrentIndex; |
430 | |||
875 | werner | 431 | mCurrentIndex = indexmin; |
432 | if (mCurrentIndex>-1) { |
||
904 | werner | 433 | int to_sleep = tmin - absoluteAge(); |
875 | werner | 434 | if (to_sleep>0) |
435 | sleep(to_sleep); |
||
436 | } |
||
889 | werner | 437 | mScheduledHarvest = 0.; // reset |
438 | |||
902 | werner | 439 | mLastExecution = ForestManagementEngine::instance()->currentYear(); |
440 | |||
875 | werner | 441 | return mCurrentIndex > -1; |
442 | } |
||
443 | |||
1064 | werner | 444 | void FMStand::notifyTreeRemoval(Tree *tree, int reason) |
904 | werner | 445 | { |
905 | werner | 446 | double removed_volume = tree->volume(); |
929 | werner | 447 | mVolume -= removed_volume/area(); |
905 | werner | 448 | |
449 | // for MAI calculations: store removal regardless of the reason |
||
450 | mRemovedVolumeDecade+=removed_volume / area(); |
||
451 | mRemovedVolumeTotal+=removed_volume / area(); |
||
452 | |||
904 | werner | 453 | Tree::TreeRemovalType r = Tree::TreeRemovalType (reason); |
454 | if (r == Tree::TreeDeath) |
||
455 | return; // do nothing atm |
||
456 | if (r==Tree::TreeHarvest) { |
||
457 | // regular harvest |
||
1099 | werner | 458 | if (currentActivity()) { |
1070 | werner | 459 | if (currentFlags().isFinalHarvest()) |
460 | mFinalHarvested += removed_volume; |
||
461 | else |
||
462 | mThinningHarvest += removed_volume; |
||
1099 | werner | 463 | } |
904 | werner | 464 | } |
905 | werner | 465 | if (r==Tree::TreeDisturbance) { |
466 | // if we have an active salvage activity, then store |
||
912 | werner | 467 | mDisturbed += removed_volume; |
1089 | werner | 468 | // check if we have an (active) salvage activity; both the activity flags and the stand flags need to be "enabled" |
469 | if (mSTP->salvageActivity() && mSTP->salvageActivity()->standFlags().enabled() && mSTP->salvageActivity()->standFlags(this).enabled() ) { |
||
911 | werner | 470 | if (mSTP->salvageActivity()->evaluateRemove(tree)) { |
921 | werner | 471 | mFinalHarvested += removed_volume; |
1050 | werner | 472 | tree->setIsHarvested(); // set the flag that the tree is removed from the forest |
905 | werner | 473 | } |
474 | } |
||
475 | |||
476 | } |
||
904 | werner | 477 | } |
889 | werner | 478 | |
1070 | werner | 479 | bool FMStand::notifyBarkBeetleAttack(double generations, int infested_px_per_ha) |
480 | { |
||
1089 | werner | 481 | // check if we have an (active) salvage activity; both the activity flags and the stand flags need to be "enabled" |
482 | if (mSTP->salvageActivity() && mSTP->salvageActivity()->standFlags().enabled() && mSTP->salvageActivity()->standFlags(this).enabled()) { |
||
1070 | werner | 483 | return mSTP->salvageActivity()->barkbeetleAttack(this, generations, infested_px_per_ha); |
484 | } |
||
485 | return false; |
||
486 | } |
||
904 | werner | 487 | |
1070 | werner | 488 | |
875 | werner | 489 | void FMStand::sleep(int years_to_sleep) |
490 | { |
||
878 | werner | 491 | mYearsToWait = qMax(mYearsToWait, qMax(years_to_sleep,0)); |
875 | werner | 492 | } |
493 | |||
903 | werner | 494 | |
495 | double FMStand::calculateMAI() |
||
496 | { |
||
497 | // MAI: delta standing volume + removed volume, per year |
||
498 | // removed volume: mortality, management, disturbances |
||
910 | werner | 499 | mMAIdecade = ((mVolume - mLastMAIVolume) + mRemovedVolumeDecade) / 10.; |
1090 | werner | 500 | if (absoluteAge()>0) |
501 | mMAItotal = (mVolume + mRemovedVolumeTotal) / absoluteAge(); |
||
502 | |||
903 | werner | 503 | mLastMAIVolume = mVolume; |
504 | // reset counters |
||
505 | mRemovedVolumeDecade = 0.; |
||
930 | werner | 506 | return meanAnnualIncrementTotal(); |
903 | werner | 507 | } |
508 | |||
815 | werner | 509 | double FMStand::basalArea(const QString &species_id) const |
813 | werner | 510 | { |
874 | werner | 511 | foreach (const SSpeciesStand &sd, mSpeciesData) |
512 | if (sd.species->id()==species_id) |
||
513 | return sd.basalArea; |
||
514 | return 0.; |
||
813 | werner | 515 | } |
516 | |||
1061 | werner | 517 | double FMStand::relBasalArea(const QString &species_id) const |
518 | { |
||
519 | foreach (const SSpeciesStand &sd, mSpeciesData) |
||
520 | if (sd.species->id()==species_id) |
||
521 | return sd.relBasalArea; |
||
522 | return 0.; |
||
523 | } |
||
524 | |||
934 | werner | 525 | void FMStand::setAbsoluteAge(const double age) |
526 | { |
||
527 | mRotationStartYear = ForestManagementEngine::instance()->currentYear() - age; |
||
935 | werner | 528 | mAge = age; |
934 | werner | 529 | } |
530 | |||
811 | werner | 531 | // storage for properties (static) |
887 | werner | 532 | QHash<const FMStand*, QHash<QString, QJSValue> > FMStand::mStandPropertyStorage; |
811 | werner | 533 | |
534 | |||
535 | void FMStand::setProperty(const QString &name, QJSValue value) |
||
536 | { |
||
537 | // save a property value for the current stand |
||
538 | mStandPropertyStorage[this][name] = value; |
||
539 | } |
||
540 | |||
887 | werner | 541 | QJSValue FMStand::property(const QString &name) const |
811 | werner | 542 | { |
543 | // check if values are already stored for the current stand |
||
544 | if (!mStandPropertyStorage.contains(this)) |
||
545 | return QJSValue(); |
||
546 | // check if something is stored for the property name (return a undefined value if not) |
||
547 | if (!mStandPropertyStorage[this].contains(name)) |
||
548 | return QJSValue(); |
||
549 | return mStandPropertyStorage[this][name]; |
||
550 | } |
||
870 | werner | 551 | |
896 | werner | 552 | QStringList FMStand::info() |
553 | { |
||
554 | QStringList lines; |
||
555 | lines << QString("id: %1").arg(id()) |
||
556 | << QString("unit: %1").arg(unit()->id()); |
||
557 | lines << "-" << unit()->info() << "/-"; // sub sections |
||
558 | if (currentActivity()) { |
||
559 | lines << QString("activity: %1").arg(currentActivity()->name()) << "-" << currentActivity()->info(); |
||
560 | // activity properties |
||
561 | lines << QString("active: %1").arg(currentFlags().active()); |
||
562 | lines << QString("enabled: %1").arg(currentFlags().enabled()); |
||
563 | lines << QString("simulate: %1").arg(currentFlags().isDoSimulate()); |
||
564 | lines << QString("execute immediate: %1").arg(currentFlags().isExecuteImmediate()); |
||
565 | lines << QString("final harvest: %1").arg(currentFlags().isFinalHarvest()); |
||
566 | lines << QString("use scheduler: %1").arg(currentFlags().isScheduled()); |
||
897 | werner | 567 | lines << QString("in scheduler: %1").arg(currentFlags().isPending()); |
896 | werner | 568 | lines << "/-"; |
569 | } |
||
570 | lines << QString("agent: %1").arg(unit()->agent()->type()->name()); |
||
1058 | werner | 571 | lines << QString("STP: %1").arg(stp()?stp()->name():QStringLiteral("-")); |
953 | werner | 572 | lines << QString("U (yrs): %1").arg(U()); |
573 | lines << QString("thinning int.: %1").arg(thinningIntensity()); |
||
897 | werner | 574 | lines << QString("last update: %1").arg(lastUpdate()); |
896 | werner | 575 | lines << QString("sleep (years): %1").arg(sleepYears()); |
576 | lines << QString("scheduled harvest: %1").arg(scheduledHarvest()); |
||
577 | lines << QString("basal area: %1").arg(basalArea()); |
||
897 | werner | 578 | lines << QString("volume: %1").arg(volume()); |
896 | werner | 579 | lines << QString("age: %1").arg(age()); |
580 | lines << QString("absolute age: %1").arg(absoluteAge()); |
||
581 | lines << QString("N/ha: %1").arg(stems()); |
||
904 | werner | 582 | lines << QString("MAI (decadal) m3/ha*yr: %1").arg(meanAnnualIncrement()); |
896 | werner | 583 | lines << "Basal area per species"; |
584 | for (int i=0;i<nspecies();++i) { |
||
585 | lines << QString("%1: %2").arg(speciesData(i).species->id()).arg(speciesData(i).basalArea); |
||
586 | } |
||
587 | |||
897 | werner | 588 | lines << "All activities" << "-"; |
589 | for (QVector<ActivityFlags>::const_iterator it = mStandFlags.constBegin(); it!=mStandFlags.constEnd(); ++it) { |
||
590 | const ActivityFlags &a = *it; |
||
591 | lines << QString("%1 (active): %2").arg(a.activity()->name()).arg(a.active()) |
||
592 | << QString("%1 (enabled): %2").arg(a.activity()->name()).arg(a.enabled()); |
||
593 | } |
||
594 | lines << "/-"; |
||
896 | werner | 595 | |
897 | werner | 596 | |
896 | werner | 597 | // stand properties |
598 | if (mStandPropertyStorage.contains(this)) { |
||
599 | QHash<QString, QJSValue> &props = mStandPropertyStorage[this]; |
||
600 | lines << QString("properties: %1").arg(props.size()) << "-"; |
||
601 | QHash<QString, QJSValue>::const_iterator i = props.constBegin(); |
||
602 | while (i != props.constEnd()) { |
||
603 | lines << QString("%1: %2").arg(i.key()).arg(i.value().toString()); |
||
604 | ++i; |
||
605 | } |
||
606 | lines << "/-"; |
||
607 | } |
||
903 | werner | 608 | |
609 | // scheduler info |
||
610 | lines << unit()->constScheduler()->info(id()); |
||
611 | |||
896 | werner | 612 | return lines; |
613 | } |
||
614 | |||
903 | werner | 615 | void FMStand::newRotatation() |
616 | { |
||
953 | werner | 617 | mLastRotationAge = absoluteAge(); |
903 | werner | 618 | mRotationStartYear = ForestManagementEngine::instance()->currentYear(); // reset stand age to 0. |
619 | mRemovedVolumeTotal = 0.; |
||
620 | mRemovedVolumeDecade = 0.; |
||
621 | mLastMAIVolume = 0.; |
||
622 | mMAIdecade = 0.; |
||
623 | mMAItotal = 0.; |
||
944 | werner | 624 | // use default values |
625 | setU( unit()->U() ); |
||
626 | setThinningIntensity( unit()->thinningIntensity() ); |
||
627 | unit()->agent()->type()->agentUpdateForStand(this, QString(), 0); // update at age 0? maybe switch to new STP? |
||
942 | werner | 628 | |
903 | werner | 629 | } |
630 | |||
873 | werner | 631 | SSpeciesStand &FMStand::speciesData(const Species *species) |
632 | { |
||
633 | for (int i=0;i<mSpeciesData.count(); ++i) |
||
634 | if (mSpeciesData[i].species == species) |
||
635 | return mSpeciesData[i]; |
||
870 | werner | 636 | |
873 | werner | 637 | mSpeciesData.append(SSpeciesStand()); |
638 | mSpeciesData.last().species = species; |
||
639 | return mSpeciesData.last(); |
||
640 | } |
||
641 | |||
642 | |||
870 | werner | 643 | } // namespace |