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
********************************************************************************************/
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, &currentFlags(), 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