Subversion Repositories public iLand

Rev

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

Rev Author Line No. Line
671 werner 1
/********************************************************************************************
2
**    iLand - an individual based forest landscape and disturbance model
3
**    http://iland.boku.ac.at
4
**    Copyright (C) 2009-  Werner Rammer, Rupert Seidl
5
**
6
**    This program is free software: you can redistribute it and/or modify
7
**    it under the terms of the GNU General Public License as published by
8
**    the Free Software Foundation, either version 3 of the License, or
9
**    (at your option) any later version.
10
**
11
**    This program is distributed in the hope that it will be useful,
12
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
**    GNU General Public License for more details.
15
**
16
**    You should have received a copy of the GNU General Public License
17
**    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
********************************************************************************************/
117 Werner 19
#include "global.h"
20
#include "tree.h"
3 Werner 21
 
83 Werner 22
#include "grid.h"
3 Werner 23
 
83 Werner 24
#include "stamp.h"
90 Werner 25
#include "species.h"
189 iland 26
#include "resourceunit.h"
151 iland 27
#include "model.h"
468 werner 28
#include "snag.h"
38 Werner 29
 
904 werner 30
#include "forestmanagementengine.h"
1044 werner 31
#include "modules.h"
904 werner 32
 
1102 werner 33
#include "treeout.h"
1114 werner 34
#include "landscapeout.h"
1071 werner 35
 
110 Werner 36
// static varaibles
106 Werner 37
FloatGrid *Tree::mGrid = 0;
151 iland 38
HeightGrid *Tree::mHeightGrid = 0;
1102 werner 39
TreeRemovedOut *Tree::mRemovalOutput = 0;
1114 werner 40
LandscapeRemovedOut *Tree::mLSRemovalOutput = 0;
40 Werner 41
int Tree::m_statPrint=0;
48 Werner 42
int Tree::m_statAboveZ=0;
105 Werner 43
int Tree::m_statCreated=0;
40 Werner 44
int Tree::m_nextId=0;
45
 
1071 werner 46
#ifdef ALT_TREE_MORTALITY
1102 werner 47
static double _stress_threshold = 0.05;
48
static int _stress_years = 5;
49
static double _stress_death_prob = 0.33;
1071 werner 50
#endif
51
 
697 werner 52
/** @class Tree
53
    @ingroup core
54
    A tree is the basic simulation entity of iLand and represents a single tree.
55
    Trees in iLand are designed to be lightweight, thus the list of stored properties is limited. Basic properties
56
    are dimensions (dbh, height), biomass pools (stem, leaves, roots), the reserve NPP pool. Additionally, the location and species are stored.
57
    A Tree has a height of at least 4m; trees below this threshold are covered by the regeneration layer (see Sapling).
58
    Trees are stored in lists managed at the resource unit level.
158 werner 59
 
697 werner 60
  */
257 werner 61
 
158 werner 62
/** get distance and direction between two points.
63
  returns the distance (m), and the angle between PStart and PEnd (radians) in referenced param rAngle. */
1102 werner 64
double dist_and_direction(const QPointF &PStart, const QPointF &PEnd, double &rAngle)
151 iland 65
{
1102 werner 66
    double dx = PEnd.x() - PStart.x();
67
    double dy = PEnd.y() - PStart.y();
68
    double d = sqrt(dx*dx + dy*dy);
158 werner 69
    // direction:
70
    rAngle = atan2(dx, dy);
71
    return d;
151 iland 72
}
73
 
158 werner 74
 
110 Werner 75
// lifecycle
3 Werner 76
Tree::Tree()
77
{
149 werner 78
    mDbh = mHeight = 0;
79
    mRU = 0; mSpecies = 0;
169 werner 80
    mFlags = mAge = 0;
276 werner 81
    mOpacity=mFoliageMass=mWoodyMass=mCoarseRootMass=mFineRootMass=mLeafArea=0.;
159 werner 82
    mDbhDelta=mNPPReserve=mLRI=mStressIndex=0.;
264 werner 83
    mLightResponse = 0.;
975 werner 84
    mStamp=0;
106 Werner 85
    mId = m_nextId++;
105 Werner 86
    m_statCreated++;
3 Werner 87
}
38 Werner 88
 
407 werner 89
float Tree::crownRadius() const
90
{
91
    Q_ASSERT(mStamp!=0);
92
    return mStamp->crownRadius();
93
}
94
 
476 werner 95
float Tree::biomassBranch() const
96
{
1102 werner 97
    return static_cast<float>( mSpecies->biomassBranch(mDbh) );
476 werner 98
}
99
 
158 werner 100
void Tree::setGrid(FloatGrid* gridToStamp, Grid<HeightGridValue> *dominanceGrid)
3 Werner 101
{
158 werner 102
    mGrid = gridToStamp; mHeightGrid = dominanceGrid;
3 Werner 103
}
104
 
667 werner 105
// calculate the thickness of the bark of the tree
106
double Tree::barkThickness() const
107
{
108
    return mSpecies->barkThickness(mDbh);
109
}
110
 
125 Werner 111
/// dumps some core variables of a tree to a string.
112
QString Tree::dump()
113
{
114
    QString result = QString("id %1 species %2 dbh %3 h %4 x/y %5/%6 ru# %7 LRI %8")
159 werner 115
                            .arg(mId).arg(species()->id()).arg(mDbh).arg(mHeight)
156 werner 116
                            .arg(position().x()).arg(position().y())
125 Werner 117
                            .arg(mRU->index()).arg(mLRI);
118
    return result;
119
}
3 Werner 120
 
129 Werner 121
void Tree::dumpList(DebugList &rTargetList)
122
{
159 werner 123
    rTargetList << mId << species()->id() << mDbh << mHeight  << position().x() << position().y()   << mRU->index() << mLRI
276 werner 124
                << mWoodyMass << mCoarseRootMass << mFoliageMass << mLeafArea;
129 Werner 125
}
126
 
38 Werner 127
void Tree::setup()
128
{
975 werner 129
    if (mDbh<=0 || mHeight<=0) {
130
        throw IException(QString("Error: trying to set up a tree with invalid dimensions: dbh: %1 height: %2 id: %3 RU-index: %4").arg(mDbh).arg(mHeight).arg(mId).arg(mRU->index()));
131
    }
38 Werner 132
    // check stamp
159 werner 133
    Q_ASSERT_X(species()!=0, "Tree::setup()", "species is NULL");
134
    mStamp = species()->stamp(mDbh, mHeight);
505 werner 135
    if (!mStamp) {
136
        throw IException("Tree::setup() with invalid stamp!");
137
    }
110 Werner 138
 
1102 werner 139
    mFoliageMass = static_cast<float>( species()->biomassFoliage(mDbh) );
140
    mCoarseRootMass = static_cast<float>( species()->biomassRoot(mDbh) ); // coarse root (allometry)
141
    mFineRootMass = static_cast<float>( mFoliageMass * species()->finerootFoliageRatio() ); //  fine root (size defined  by finerootFoliageRatio)
142
    mWoodyMass = static_cast<float>( species()->biomassWoody(mDbh) );
110 Werner 143
 
137 Werner 144
    // LeafArea[m2] = LeafMass[kg] * specificLeafArea[m2/kg]
1102 werner 145
    mLeafArea = static_cast<float>( mFoliageMass * species()->specificLeafArea() );
146
    mOpacity = static_cast<float>( 1. - exp(- Model::settings().lightExtinctionCoefficientOpacity * mLeafArea / mStamp->crownArea()) );
147
    mNPPReserve = static_cast<float>( (1+species()->finerootFoliageRatio())*mFoliageMass ); // initial value
780 werner 148
    mDbhDelta = 0.1f; // initial value: used in growth() to estimate diameter increment
376 werner 149
 
38 Werner 150
}
39 Werner 151
 
388 werner 152
void Tree::setAge(const int age, const float treeheight)
153
{
154
    mAge = age;
155
    if (age==0) {
156
        // estimate age using the tree height
157
        mAge = mSpecies->estimateAge(treeheight);
158
    }
159
}
160
 
110 Werner 161
//////////////////////////////////////////////////
162
////  Light functions (Pattern-stuff)
163
//////////////////////////////////////////////////
164
 
70 Werner 165
#define NOFULLDBG
77 Werner 166
//#define NOFULLOPT
39 Werner 167
 
40 Werner 168
 
158 werner 169
void Tree::applyLIP()
77 Werner 170
{
144 Werner 171
    if (!mStamp)
172
        return;
106 Werner 173
    Q_ASSERT(mGrid!=0 && mStamp!=0 && mRU!=0);
156 werner 174
    QPoint pos = mPositionIndex;
106 Werner 175
    int offset = mStamp->offset();
77 Werner 176
    pos-=QPoint(offset, offset);
177
 
178
    float local_dom; // height of Z* on the current position
179
    int x,y;
401 werner 180
    float value, z, z_zstar;
106 Werner 181
    int gr_stamp = mStamp->size();
705 werner 182
 
106 Werner 183
    if (!mGrid->isIndexValid(pos) || !mGrid->isIndexValid(pos+QPoint(gr_stamp, gr_stamp))) {
407 werner 184
        // this should not happen because of the buffer
77 Werner 185
        return;
186
    }
705 werner 187
    int grid_y = pos.y();
77 Werner 188
    for (y=0;y<gr_stamp; ++y) {
403 werner 189
 
705 werner 190
        float *grid_value_ptr = mGrid->ptr(pos.x(), grid_y);
191
        int grid_x = pos.x();
192
        for (x=0;x<gr_stamp;++x, ++grid_x, ++grid_value_ptr) {
77 Werner 193
            // suppose there is no stamping outside
106 Werner 194
            value = (*mStamp)(x,y); // stampvalue
705 werner 195
            //if (value>0.f) {
196
                local_dom = (*mHeightGrid)(grid_x/cPxPerHeight, grid_y/cPxPerHeight).height;
884 werner 197
                z = std::max(mHeight - (*mStamp).distanceToCenter(x,y), 0.f); // distance to center = height (45 degree line)
705 werner 198
                z_zstar = (z>=local_dom)?1.f:z/local_dom;
1102 werner 199
                value = 1.f - value*mOpacity * z_zstar; // calculated value
705 werner 200
                value = std::max(value, 0.02f); // limit value
77 Werner 201
 
705 werner 202
                *grid_value_ptr *= value;
203
            //}
403 werner 204
 
77 Werner 205
        }
403 werner 206
        grid_y++;
77 Werner 207
    }
208
 
209
    m_statPrint++; // count # of stamp applications...
210
}
211
 
155 werner 212
/// helper function for gluing the edges together
213
/// index: index at grid
214
/// count: number of pixels that are the simulation area (e.g. 100m and 2m pixel -> 50)
215
/// buffer: size of buffer around simulation area (in pixels)
295 werner 216
inline int torusIndex(int index, int count, int buffer, int ru_index)
155 werner 217
{
295 werner 218
    return buffer + ru_index + (index-buffer+count)%count;
155 werner 219
}
62 Werner 220
 
155 werner 221
 
222
/** Apply LIPs. This "Torus" functions wraps the influence at the edges of a 1ha simulation area.
223
  */
158 werner 224
void Tree::applyLIP_torus()
155 werner 225
{
226
    if (!mStamp)
227
        return;
228
    Q_ASSERT(mGrid!=0 && mStamp!=0 && mRU!=0);
295 werner 229
    int bufferOffset = mGrid->indexAt(QPointF(0.,0.)).x(); // offset of buffer
387 werner 230
    QPoint pos = QPoint((mPositionIndex.x()-bufferOffset)%cPxPerRU  + bufferOffset,
231
                        (mPositionIndex.y()-bufferOffset)%cPxPerRU + bufferOffset); // offset within the ha
295 werner 232
    QPoint ru_offset = QPoint(mPositionIndex.x() - pos.x(), mPositionIndex.y() - pos.y()); // offset of the corner of the resource index
155 werner 233
 
234
    int offset = mStamp->offset();
235
    pos-=QPoint(offset, offset);
236
 
237
    float local_dom; // height of Z* on the current position
238
    int x,y;
239
    float value;
240
    int gr_stamp = mStamp->size();
241
    int grid_x, grid_y;
242
    float *grid_value;
243
    if (!mGrid->isIndexValid(pos) || !mGrid->isIndexValid(pos+QPoint(gr_stamp, gr_stamp))) {
244
        // todo: in this case we should use another algorithm!!! necessary????
245
        return;
246
    }
407 werner 247
    float z, z_zstar;
155 werner 248
    int xt, yt; // wraparound coordinates
249
    for (y=0;y<gr_stamp; ++y) {
250
        grid_y = pos.y() + y;
387 werner 251
        yt = torusIndex(grid_y, cPxPerRU,bufferOffset, ru_offset.y()); // 50 cells per 100m
155 werner 252
        for (x=0;x<gr_stamp;++x) {
253
            // suppose there is no stamping outside
254
            grid_x = pos.x() + x;
387 werner 255
            xt = torusIndex(grid_x,cPxPerRU,bufferOffset, ru_offset.x());
155 werner 256
 
387 werner 257
            local_dom = mHeightGrid->valueAtIndex(xt/cPxPerHeight,yt/cPxPerHeight).height;
407 werner 258
 
884 werner 259
            z = std::max(mHeight - (*mStamp).distanceToCenter(x,y), 0.f); // distance to center = height (45 degree line)
407 werner 260
            z_zstar = (z>=local_dom)?1.f:z/local_dom;
155 werner 261
            value = (*mStamp)(x,y); // stampvalue
1102 werner 262
            value = 1.f - value*mOpacity * z_zstar; // calculated value
407 werner 263
            // old: value = 1. - value*mOpacity / local_dom; // calculated value
155 werner 264
            value = qMax(value, 0.02f); // limit value
265
 
266
            grid_value = mGrid->ptr(xt, yt); // use wraparound coordinates
267
            *grid_value *= value;
268
        }
269
    }
270
 
271
    m_statPrint++; // count # of stamp applications...
272
}
273
 
74 Werner 274
/** heightGrid()
275
  This function calculates the "dominant height field". This grid is coarser as the fine-scaled light-grid.
276
*/
277
void Tree::heightGrid()
278
{
279
 
387 werner 280
    QPoint p = QPoint(mPositionIndex.x()/cPxPerHeight, mPositionIndex.y()/cPxPerHeight); // pos of tree on height grid
74 Werner 281
 
151 iland 282
    // count trees that are on height-grid cells (used for stockable area)
285 werner 283
    mHeightGrid->valueAtIndex(p).increaseCount();
401 werner 284
    if (mHeight > mHeightGrid->valueAtIndex(p).height)
285
        mHeightGrid->valueAtIndex(p).height=mHeight;
406 werner 286
 
287
    int r = mStamp->reader()->offset(); // distance between edge and the center pixel. e.g.: if r = 2 -> stamp=5x5
288
    int index_eastwest = mPositionIndex.x() % cPxPerHeight; // 4: very west, 0 east edge
289
    int index_northsouth = mPositionIndex.y() % cPxPerHeight; // 4: northern edge, 0: southern edge
290
    if (index_eastwest - r < 0) { // east
410 werner 291
        mHeightGrid->valueAtIndex(p.x()-1, p.y()).height=qMax(mHeightGrid->valueAtIndex(p.x()-1, p.y()).height,mHeight);
406 werner 292
    }
293
    if (index_eastwest + r >= cPxPerHeight) {  // west
410 werner 294
        mHeightGrid->valueAtIndex(p.x()+1, p.y()).height=qMax(mHeightGrid->valueAtIndex(p.x()+1, p.y()).height,mHeight);
406 werner 295
    }
296
    if (index_northsouth - r < 0) {  // south
410 werner 297
        mHeightGrid->valueAtIndex(p.x(), p.y()-1).height=qMax(mHeightGrid->valueAtIndex(p.x(), p.y()-1).height,mHeight);
406 werner 298
    }
299
    if (index_northsouth + r >= cPxPerHeight) {  // north
410 werner 300
        mHeightGrid->valueAtIndex(p.x(), p.y()+1).height=qMax(mHeightGrid->valueAtIndex(p.x(), p.y()+1).height,mHeight);
406 werner 301
    }
302
 
303
 
401 werner 304
    // without spread of the height grid
151 iland 305
 
401 werner 306
//    // height of Z*
307
//    const float cellsize = mHeightGrid->cellsize();
308
//
309
//    int index_eastwest = mPositionIndex.x() % cPxPerHeight; // 4: very west, 0 east edge
310
//    int index_northsouth = mPositionIndex.y() % cPxPerHeight; // 4: northern edge, 0: southern edge
311
//    int dist[9];
312
//    dist[3] = index_northsouth * 2 + 1; // south
313
//    dist[1] = index_eastwest * 2 + 1; // west
314
//    dist[5] = 10 - dist[3]; // north
315
//    dist[7] = 10 - dist[1]; // east
316
//    dist[8] = qMax(dist[5], dist[7]); // north-east
317
//    dist[6] = qMax(dist[3], dist[7]); // south-east
318
//    dist[0] = qMax(dist[3], dist[1]); // south-west
319
//    dist[2] = qMax(dist[5], dist[1]); // north-west
320
//    dist[4] = 0; // center cell
321
//    /* the scheme of indices is as follows:  if sign(ix)= -1, if ix<0, 0 for ix=0, 1 for ix>0 (detto iy), then:
322
//       index = 4 + 3*sign(ix) + sign(iy) transforms combinations of directions to unique ids (0..8), which are used above.
323
//        e.g.: sign(ix) = -1, sign(iy) = 1 (=north-west) -> index = 4 + -3 + 1 = 2
324
//    */
325
//
326
//
327
//    int ringcount = int(floor(mHeight / cellsize)) + 1;
328
//    int ix, iy;
329
//    int ring;
330
//    float hdom;
331
//
332
//    for (ix=-ringcount;ix<=ringcount;ix++)
333
//        for (iy=-ringcount; iy<=+ringcount; iy++) {
334
//        ring = qMax(abs(ix), abs(iy));
335
//        QPoint pos(ix+p.x(), iy+p.y());
336
//        if (mHeightGrid->isIndexValid(pos)) {
337
//            float &rHGrid = mHeightGrid->valueAtIndex(pos).height;
338
//            if (rHGrid > mHeight) // skip calculation if grid is higher than tree
339
//                continue;
340
//            int direction = 4 + (ix?(ix<0?-3:3):0) + (iy?(iy<0?-1:1):0); // 4 + 3*sgn(x) + sgn(y)
341
//            hdom = mHeight - dist[direction];
342
//            if (ring>1)
343
//                hdom -= (ring-1)*10;
344
//
345
//            rHGrid = qMax(rHGrid, hdom); // write value
346
//        } // is valid
347
//    } // for (y)
39 Werner 348
}
40 Werner 349
 
407 werner 350
void Tree::heightGrid_torus()
351
{
352
    // height of Z*
155 werner 353
 
407 werner 354
    QPoint p = QPoint(mPositionIndex.x()/cPxPerHeight, mPositionIndex.y()/cPxPerHeight); // pos of tree on height grid
355
    int bufferOffset = mHeightGrid->indexAt(QPointF(0.,0.)).x(); // offset of buffer (i.e.: size of buffer in height-pixels)
356
    p.setX((p.x()-bufferOffset)%10 + bufferOffset); // 10: 10 x 10m pixeln in 100m
357
    p.setY((p.y()-bufferOffset)%10 + bufferOffset);
155 werner 358
 
407 werner 359
 
360
    // torus coordinates: ru_offset = coords of lower left corner of 1ha patch
361
    QPoint ru_offset =QPoint(mPositionIndex.x()/cPxPerHeight - p.x(), mPositionIndex.y()/cPxPerHeight - p.y());
362
 
363
    // count trees that are on height-grid cells (used for stockable area)
364
    HeightGridValue &v = mHeightGrid->valueAtIndex(torusIndex(p.x(),10,bufferOffset,ru_offset.x()),
365
                                                   torusIndex(p.y(),10,bufferOffset,ru_offset.y()));
366
    v.increaseCount();
367
    v.height = qMax(v.height, mHeight);
368
 
369
 
370
    int r = mStamp->reader()->offset(); // distance between edge and the center pixel. e.g.: if r = 2 -> stamp=5x5
371
    int index_eastwest = mPositionIndex.x() % cPxPerHeight; // 4: very west, 0 east edge
372
    int index_northsouth = mPositionIndex.y() % cPxPerHeight; // 4: northern edge, 0: southern edge
373
    if (index_eastwest - r < 0) { // east
374
        HeightGridValue &v = mHeightGrid->valueAtIndex(torusIndex(p.x()-1,10,bufferOffset,ru_offset.x()),
375
                                                       torusIndex(p.y(),10,bufferOffset,ru_offset.y()));
410 werner 376
        v.height = qMax(v.height, mHeight);
407 werner 377
    }
378
    if (index_eastwest + r >= cPxPerHeight) {  // west
379
        HeightGridValue &v = mHeightGrid->valueAtIndex(torusIndex(p.x()+1,10,bufferOffset,ru_offset.x()),
380
                                                       torusIndex(p.y(),10,bufferOffset,ru_offset.y()));
410 werner 381
        v.height = qMax(v.height, mHeight);
407 werner 382
    }
383
    if (index_northsouth - r < 0) {  // south
384
        HeightGridValue &v = mHeightGrid->valueAtIndex(torusIndex(p.x(),10,bufferOffset,ru_offset.x()),
385
                                                       torusIndex(p.y()-1,10,bufferOffset,ru_offset.y()));
410 werner 386
        v.height = qMax(v.height, mHeight);
407 werner 387
    }
388
    if (index_northsouth + r >= cPxPerHeight) {  // north
389
        HeightGridValue &v = mHeightGrid->valueAtIndex(torusIndex(p.x(),10,bufferOffset,ru_offset.x()),
390
                                                       torusIndex(p.y()+1,10,bufferOffset,ru_offset.y()));
410 werner 391
        v.height = qMax(v.height, mHeight);
407 werner 392
    }
393
 
394
 
395
 
396
 
397
//    int index_eastwest = mPositionIndex.x() % cPxPerHeight; // 4: very west, 0 east edge
398
//    int index_northsouth = mPositionIndex.y() % cPxPerHeight; // 4: northern edge, 0: southern edge
399
//    int dist[9];
400
//    dist[3] = index_northsouth * 2 + 1; // south
401
//    dist[1] = index_eastwest * 2 + 1; // west
402
//    dist[5] = 10 - dist[3]; // north
403
//    dist[7] = 10 - dist[1]; // east
404
//    dist[8] = qMax(dist[5], dist[7]); // north-east
405
//    dist[6] = qMax(dist[3], dist[7]); // south-east
406
//    dist[0] = qMax(dist[3], dist[1]); // south-west
407
//    dist[2] = qMax(dist[5], dist[1]); // north-west
408
//    dist[4] = 0; // center cell
409
//    /* the scheme of indices is as follows:  if sign(ix)= -1, if ix<0, 0 for ix=0, 1 for ix>0 (detto iy), then:
410
//       index = 4 + 3*sign(ix) + sign(iy) transforms combinations of directions to unique ids (0..8), which are used above.
411
//        e.g.: sign(ix) = -1, sign(iy) = 1 (=north-west) -> index = 4 + -3 + 1 = 2
412
//    */
413
//
414
//
415
//    int ringcount = int(floor(mHeight / cellsize)) + 1;
416
//    int ix, iy;
417
//    int ring;
418
//    float hdom;
419
//    for (ix=-ringcount;ix<=ringcount;ix++)
420
//        for (iy=-ringcount; iy<=+ringcount; iy++) {
421
//        ring = qMax(abs(ix), abs(iy));
422
//        QPoint pos(ix+p.x(), iy+p.y());
423
//        QPoint p_torus(torusIndex(pos.x(),10,bufferOffset,ru_offset.x()),
424
//                       torusIndex(pos.y(),10,bufferOffset,ru_offset.y()));
425
//        if (mHeightGrid->isIndexValid(p_torus)) {
426
//            float &rHGrid = mHeightGrid->valueAtIndex(p_torus.x(),p_torus.y()).height;
427
//            if (rHGrid > mHeight) // skip calculation if grid is higher than tree
428
//                continue;
429
//            int direction = 4 + (ix?(ix<0?-3:3):0) + (iy?(iy<0?-1:1):0); // 4 + 3*sgn(x) + sgn(y)
430
//            hdom = mHeight - dist[direction];
431
//            if (ring>1)
432
//                hdom -= (ring-1)*10;
433
//
434
//            rHGrid = qMax(rHGrid, hdom); // write value
435
//        } // is valid
436
//    } // for (y)
437
}
438
 
439
 
718 werner 440
/** reads the light influence field value for a tree.
441
    The LIF field is scanned within the crown area of the focal tree, and the influence of
442
    the focal tree is "subtracted" from the LIF values.
443
    Finally, the "LRI correction" is applied.
444
    see http://iland.boku.ac.at/competition+for+light for details.
445
  */
158 werner 446
void Tree::readLIF()
40 Werner 447
{
106 Werner 448
    if (!mStamp)
155 werner 449
        return;
450
    const Stamp *reader = mStamp->reader();
451
    if (!reader)
452
        return;
156 werner 453
    QPoint pos_reader = mPositionIndex;
780 werner 454
    const float outside_area_factor = 0.1f; //
155 werner 455
 
456
    int offset_reader = reader->offset();
457
    int offset_writer = mStamp->offset();
458
    int d_offset = offset_writer - offset_reader; // offset on the *stamp* to the crown-cells
459
 
460
    pos_reader-=QPoint(offset_reader, offset_reader);
40 Werner 461
 
155 werner 462
    float local_dom;
463
 
40 Werner 464
    int x,y;
465
    double sum=0.;
155 werner 466
    double value, own_value;
467
    float *grid_value;
403 werner 468
    float z, z_zstar;
155 werner 469
    int reader_size = reader->size();
470
    int rx = pos_reader.x();
471
    int ry = pos_reader.y();
472
    for (y=0;y<reader_size; ++y, ++ry) {
473
        grid_value = mGrid->ptr(rx, ry);
474
        for (x=0;x<reader_size;++x) {
475
 
718 werner 476
            const HeightGridValue &hgv = mHeightGrid->constValueAtIndex((rx+x)/cPxPerHeight, ry/cPxPerHeight); // the height grid value, ry: gets ++ed in outer loop, rx not
477
            local_dom = hgv.height;
884 werner 478
            z = std::max(mHeight - reader->distanceToCenter(x,y), 0.f); // distance to center = height (45 degree line)
403 werner 479
            z_zstar = (z>=local_dom)?1.f:z/local_dom;
155 werner 480
 
403 werner 481
            own_value = 1. - mStamp->offsetValue(x,y,d_offset)*mOpacity * z_zstar;
155 werner 482
            own_value = qMax(own_value, 0.02);
483
            value =  *grid_value++ / own_value; // remove impact of focal tree
720 werner 484
            // additional punishment if pixel is outside:
718 werner 485
            if (hgv.isForestOutside())
720 werner 486
               value *= outside_area_factor;
403 werner 487
 
488
            //qDebug() << x << y << local_dom << z << z_zstar << own_value << value << *(grid_value-1) << (*reader)(x,y) << mStamp->offsetValue(x,y,d_offset);
155 werner 489
            //if (value>0.)
490
            sum += value * (*reader)(x,y);
491
 
40 Werner 492
        }
493
    }
1102 werner 494
    mLRI = static_cast<float>( sum );
426 werner 495
    // LRI correction...
496
    double hrel = mHeight / mHeightGrid->valueAtIndex(mPositionIndex.x()/cPxPerHeight, mPositionIndex.y()/cPxPerHeight).height;
497
    if (hrel<1.)
1102 werner 498
        mLRI = static_cast<float>( species()->speciesSet()->LRIcorrection(mLRI, hrel) );
426 werner 499
 
403 werner 500
 
155 werner 501
    if (mLRI > 1.)
502
        mLRI = 1.;
206 werner 503
 
504
    // Finally, add LRI of this Tree to the ResourceUnit!
251 werner 505
    mRU->addWLA(mLeafArea, mLRI);
206 werner 506
 
155 werner 507
    //qDebug() << "Tree #"<< id() << "value" << sum << "Impact" << mImpact;
40 Werner 508
}
509
 
155 werner 510
/// Torus version of read stamp (glued edges)
158 werner 511
void Tree::readLIF_torus()
78 Werner 512
{
106 Werner 513
    if (!mStamp)
107 Werner 514
        return;
106 Werner 515
    const Stamp *reader = mStamp->reader();
78 Werner 516
    if (!reader)
107 Werner 517
        return;
295 werner 518
    int bufferOffset = mGrid->indexAt(QPointF(0.,0.)).x(); // offset of buffer
78 Werner 519
 
387 werner 520
    QPoint pos_reader = QPoint((mPositionIndex.x()-bufferOffset)%cPxPerRU + bufferOffset,
521
                               (mPositionIndex.y()-bufferOffset)%cPxPerRU + bufferOffset); // offset within the ha
295 werner 522
    QPoint ru_offset = QPoint(mPositionIndex.x() - pos_reader.x(), mPositionIndex.y() - pos_reader.y()); // offset of the corner of the resource index
523
 
78 Werner 524
    int offset_reader = reader->offset();
106 Werner 525
    int offset_writer = mStamp->offset();
78 Werner 526
    int d_offset = offset_writer - offset_reader; // offset on the *stamp* to the crown-cells
527
 
528
    pos_reader-=QPoint(offset_reader, offset_reader);
529
 
530
    float local_dom;
531
 
532
    int x,y;
533
    double sum=0.;
534
    double value, own_value;
535
    float *grid_value;
407 werner 536
    float z, z_zstar;
78 Werner 537
    int reader_size = reader->size();
538
    int rx = pos_reader.x();
539
    int ry = pos_reader.y();
155 werner 540
    int xt, yt; // wrapped coords
541
 
397 werner 542
    for (y=0;y<reader_size; ++y) {
543
        yt = torusIndex(ry+y,cPxPerRU, bufferOffset, ru_offset.y());
78 Werner 544
        for (x=0;x<reader_size;++x) {
387 werner 545
            xt = torusIndex(rx+x,cPxPerRU, bufferOffset, ru_offset.x());
155 werner 546
            grid_value = mGrid->ptr(xt,yt);
407 werner 547
 
387 werner 548
            local_dom = mHeightGrid->valueAtIndex(xt/cPxPerHeight, yt/cPxPerHeight).height; // ry: gets ++ed in outer loop, rx not
884 werner 549
            z = std::max(mHeight - reader->distanceToCenter(x,y), 0.f); // distance to center = height (45 degree line)
407 werner 550
            z_zstar = (z>=local_dom)?1.f:z/local_dom;
125 Werner 551
 
407 werner 552
            own_value = 1. - mStamp->offsetValue(x,y,d_offset)*mOpacity * z_zstar;
553
            // old: own_value = 1. - mStamp->offsetValue(x,y,d_offset)*mOpacity / local_dom; // old: dom_height;
78 Werner 554
            own_value = qMax(own_value, 0.02);
407 werner 555
            value =  *grid_value++ / own_value; // remove impact of focal tree
556
 
397 werner 557
            // debug for one tree in HJA
558
            //if (id()==178020)
559
            //    qDebug() << x << y << xt << yt << *grid_value << local_dom << own_value << value << (*reader)(x,y);
321 werner 560
            //if (_isnan(value))
561
            //    qDebug() << "isnan" << id();
397 werner 562
            if (value * (*reader)(x,y)>1.)
563
                qDebug() << "LIFTorus: value>1.";
78 Werner 564
            sum += value * (*reader)(x,y);
565
 
566
            //} // isIndexValid
567
        }
568
    }
1102 werner 569
    mLRI = static_cast<float>( sum );
426 werner 570
 
571
    // LRI correction...
572
    double hrel = mHeight / mHeightGrid->valueAtIndex(mPositionIndex.x()/cPxPerHeight, mPositionIndex.y()/cPxPerHeight).height;
573
    if (hrel<1.)
1102 werner 574
        mLRI = static_cast<float>( species()->speciesSet()->LRIcorrection(mLRI, hrel) );
426 werner 575
 
615 werner 576
    if (isnan(mLRI)) {
321 werner 577
        qDebug() << "LRI invalid (nan)!" << id();
578
        mLRI=0.;
579
        //qDebug() << reader->dump();
580
    }
148 iland 581
    if (mLRI > 1.)
582
        mLRI = 1.;
78 Werner 583
    //qDebug() << "Tree #"<< id() << "value" << sum << "Impact" << mImpact;
205 werner 584
 
585
    // Finally, add LRI of this Tree to the ResourceUnit!
251 werner 586
    mRU->addWLA(mLeafArea, mLRI);
58 Werner 587
}
588
 
155 werner 589
 
40 Werner 590
void Tree::resetStatistics()
591
{
592
    m_statPrint=0;
105 Werner 593
    m_statCreated=0;
48 Werner 594
    m_statAboveZ=0;
40 Werner 595
    m_nextId=1;
596
}
107 Werner 597
 
1072 werner 598
#ifdef ALT_TREE_MORTALITY
1071 werner 599
void Tree::mortalityParams(double dbh_inc_threshold, int stress_years, double stress_mort_prob)
600
{
601
    _stress_threshold = dbh_inc_threshold;
602
    _stress_years = stress_years;
603
    _stress_death_prob = stress_mort_prob;
604
    qDebug() << "Alternative Mortality enabled: threshold" << dbh_inc_threshold << ", years:" << _stress_years << ", level:" << _stress_death_prob;
605
}
1072 werner 606
#endif
1071 werner 607
 
251 werner 608
void Tree::calcLightResponse()
609
{
610
    // calculate a light response from lri:
298 werner 611
    // http://iland.boku.ac.at/individual+tree+light+availability
470 werner 612
    double lri = limit(mLRI * mRU->LRImodifier(), 0., 1.); // Eq. (3)
1102 werner 613
    mLightResponse = static_cast<float>( mSpecies->lightResponse(lri) ); // Eq. (4)
251 werner 614
    mRU->addLR(mLeafArea, mLightResponse);
615
 
616
}
617
 
110 Werner 618
//////////////////////////////////////////////////
619
////  Growth Functions
620
//////////////////////////////////////////////////
107 Werner 621
 
227 werner 622
/** grow() is the main function of the yearly tree growth.
623
  The main steps are:
298 werner 624
  - Production of GPP/NPP   @sa http://iland.boku.ac.at/primary+production http://iland.boku.ac.at/individual+tree+light+availability
625
  - Partitioning of NPP to biomass compartments of the tree @sa http://iland.boku.ac.at/allocation
227 werner 626
  - Growth of the stem http://iland.boku.ac.at/stem+growth (???)
387 werner 627
  Further activties: * the age of the tree is increased
628
                     * the mortality sub routine is executed
629
                     * seeds are produced */
107 Werner 630
void Tree::grow()
631
{
159 werner 632
    TreeGrowthData d;
169 werner 633
    mAge++; // increase age
230 werner 634
    // step 1: get "interception area" of the tree individual [m2]
635
    // the sum of all area of all trees of a unit equal the total stocked area * interception_factor(Beer-Lambert)
636
    double effective_area = mRU->interceptedArea(mLeafArea, mLightResponse);
107 Werner 637
 
230 werner 638
    // step 2: calculate GPP of the tree based
639
    // (1) get the amount of GPP for a "unit area" of the tree species
640
    double raw_gpp_per_area = mRU->resourceUnitSpecies(species()).prod3PG().GPPperArea();
641
    // (2) GPP (without aging-effect) in kg Biomass / year
642
    double raw_gpp = raw_gpp_per_area * effective_area;
161 werner 643
 
227 werner 644
    // apply aging according to the state of the individuum
388 werner 645
    const double aging_factor = mSpecies->aging(mHeight, mAge);
376 werner 646
    mRU->addTreeAging(mLeafArea, aging_factor);
227 werner 647
    double gpp = raw_gpp * aging_factor; //
608 werner 648
    d.NPP = gpp * cAutotrophicRespiration; // respiration loss (0.47), cf. Waring et al 1998.
113 Werner 649
 
279 werner 650
    //DBGMODE(
137 Werner 651
        if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreeNPP) && isDebugging()) {
133 Werner 652
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreeNPP);
653
            dumpList(out); // add tree headers
299 werner 654
            out << mLRI * mRU->LRImodifier() << mLightResponse << effective_area << raw_gpp << gpp << d.NPP << aging_factor;
133 Werner 655
        }
279 werner 656
    //); // DBGMODE()
1196 werner 657
    if (Globals->model()->settings().growthEnabled) {
658
        if (d.NPP>0.)
659
            partitioning(d); // split npp to compartments and grow (diameter, height)
660
    }
133 Werner 661
 
387 werner 662
    // mortality
1071 werner 663
#ifdef ALT_TREE_MORTALITY
664
    // alternative variant of tree mortality (note: mStrssIndex used otherwise)
665
    altMortality(d);
666
 
667
#else
200 werner 668
    if (Model::settings().mortalityEnabled)
669
        mortality(d);
110 Werner 670
 
159 werner 671
    mStressIndex = d.stress_index;
1071 werner 672
#endif
180 werner 673
 
674
    if (!isDead())
257 werner 675
        mRU->resourceUnitSpecies(species()).statistics().add(this, &d);
277 werner 676
 
387 werner 677
    // regeneration
1180 werner 678
    mSpecies->seedProduction(this);
387 werner 679
 
107 Werner 680
}
681
 
227 werner 682
/** partitioning of this years assimilates (NPP) to biomass compartments.
298 werner 683
  Conceptionally, the algorithm is based on Duursma, 2007.
684
  @sa http://iland.boku.ac.at/allocation */
159 werner 685
inline void Tree::partitioning(TreeGrowthData &d)
115 Werner 686
{
159 werner 687
    double npp = d.NPP;
115 Werner 688
    // add content of reserve pool
116 Werner 689
    npp += mNPPReserve;
159 werner 690
    const double foliage_mass_allo = species()->biomassFoliage(mDbh);
276 werner 691
    const double reserve_size = foliage_mass_allo * (1. + mSpecies->finerootFoliageRatio());
297 werner 692
    double refill_reserve = qMin(reserve_size, (1. + mSpecies->finerootFoliageRatio())*mFoliageMass); // not always try to refill reserve 100%
119 Werner 693
 
136 Werner 694
    double apct_wood, apct_root, apct_foliage; // allocation percentages (sum=1) (eta)
468 werner 695
    ResourceUnitSpecies &rus = mRU->resourceUnitSpecies(species());
117 Werner 696
    // turnover rates
159 werner 697
    const double &to_fol = species()->turnoverLeaf();
698
    const double &to_root = species()->turnoverRoot();
136 Werner 699
    // the turnover rate of wood depends on the size of the reserve pool:
116 Werner 700
 
136 Werner 701
 
163 werner 702
    double to_wood = refill_reserve / (mWoodyMass + refill_reserve);
703
 
468 werner 704
    apct_root = rus.prod3PG().rootFraction();
261 werner 705
    d.NPP_above = d.NPP * (1. - apct_root); // aboveground: total NPP - fraction to roots
298 werner 706
    double b_wf = species()->allometricRatio_wf(); // ratio of allometric exponents (b_woody / b_foliage)
117 Werner 707
 
708
    // Duursma 2007, Eq. (20)
167 werner 709
    apct_wood = (foliage_mass_allo*to_wood/npp + b_wf*(1.-apct_root) - b_wf*foliage_mass_allo*to_fol/npp) / ( foliage_mass_allo/mWoodyMass + b_wf );
902 werner 710
 
711
    apct_wood = limit(apct_wood, 0., 1.-apct_root);
712
 
117 Werner 713
    apct_foliage = 1. - apct_root - apct_wood;
714
 
163 werner 715
 
902 werner 716
    DBGMODE(
163 werner 717
            if (apct_foliage<0 || apct_wood<0)
718
                qDebug() << "transfer to foliage or wood < 0";
719
             if (npp<0)
720
                 qDebug() << "NPP < 0";
902 werner 721
            );
163 werner 722
 
136 Werner 723
    // Change of biomass compartments
276 werner 724
    double sen_root = mFineRootMass * to_root;
725
    double sen_foliage = mFoliageMass * to_fol;
521 werner 726
    if (ru()->snag())
588 werner 727
        ru()->snag()->addTurnoverLitter(this->species(), sen_foliage, sen_root);
298 werner 728
 
136 Werner 729
    // Roots
298 werner 730
    // http://iland.boku.ac.at/allocation#belowground_NPP
276 werner 731
    mFineRootMass -= sen_root; // reduce only fine root pool
732
    double delta_root = apct_root * npp;
733
    // 1st, refill the fine root pool
734
    double fineroot_miss = mFoliageMass * mSpecies->finerootFoliageRatio() - mFineRootMass;
735
    if (fineroot_miss>0.){
736
        double delta_fineroot = qMin(fineroot_miss, delta_root);
737
        mFineRootMass += delta_fineroot;
738
        delta_root -= delta_fineroot;
739
    }
740
    // 2nd, the rest of NPP allocated to roots go to coarse roots
595 werner 741
    double max_coarse_root = species()->biomassRoot(mDbh);
276 werner 742
    mCoarseRootMass += delta_root;
595 werner 743
    if (mCoarseRootMass > max_coarse_root) {
744
        // if the coarse root pool exceeds the value given by the allometry, then the
745
        // surplus is accounted as turnover
746
        if (ru()->snag())
747
            ru()->snag()->addTurnoverWood(species(), mCoarseRootMass-max_coarse_root);
119 Werner 748
 
1102 werner 749
        mCoarseRootMass = static_cast<float>( max_coarse_root );
595 werner 750
    }
751
 
136 Werner 752
    // Foliage
159 werner 753
    double delta_foliage = apct_foliage * npp - sen_foliage;
137 Werner 754
    mFoliageMass += delta_foliage;
615 werner 755
    if (isnan(mFoliageMass))
217 werner 756
        qDebug() << "foliage mass invalid!";
163 werner 757
    if (mFoliageMass<0.) mFoliageMass=0.; // limit to zero
758
 
1102 werner 759
    mLeafArea = static_cast<float>( mFoliageMass * species()->specificLeafArea() ); // update leaf area
119 Werner 760
 
271 werner 761
    // stress index: different varaints at denominator: to_fol*foliage_mass = leafmass to rebuild,
198 werner 762
    // foliage_mass_allo: simply higher chance for stress
271 werner 763
    // note: npp = NPP + reserve (see above)
276 werner 764
    d.stress_index =qMax(1. - (npp) / ( to_fol*foliage_mass_allo + to_root*foliage_mass_allo*species()->finerootFoliageRatio() + reserve_size), 0.);
198 werner 765
 
136 Werner 766
    // Woody compartments
298 werner 767
    // see also: http://iland.boku.ac.at/allocation#reserve_and_allocation_to_stem_growth
136 Werner 768
    // (1) transfer to reserve pool
769
    double gross_woody = apct_wood * npp;
770
    double to_reserve = qMin(reserve_size, gross_woody);
1102 werner 771
    mNPPReserve = static_cast<float>( to_reserve );
136 Werner 772
    double net_woody = gross_woody - to_reserve;
137 Werner 773
    double net_stem = 0.;
164 werner 774
    mDbhDelta = 0.;
165 werner 775
 
776
 
136 Werner 777
    if (net_woody > 0.) {
778
        // (2) calculate part of increment that is dedicated to the stem (which is a function of diameter)
159 werner 779
        net_stem = net_woody * species()->allometricFractionStem(mDbh);
780
        d.NPP_stem = net_stem;
137 Werner 781
        mWoodyMass += net_woody;
136 Werner 782
        //  (3) growth of diameter and height baseed on net stem increment
159 werner 783
        grow_diameter(d);
136 Werner 784
    }
119 Werner 785
 
279 werner 786
    //DBGMODE(
137 Werner 787
     if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreePartition)
788
         && isDebugging() ) {
129 Werner 789
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreePartition);
790
            dumpList(out); // add tree headers
136 Werner 791
            out << npp << apct_foliage << apct_wood << apct_root
276 werner 792
                    << delta_foliage << net_woody << delta_root << mNPPReserve << net_stem << d.stress_index;
137 Werner 793
     }
144 Werner 794
 
279 werner 795
    //); // DBGMODE()
497 werner 796
    DBGMODE(
428 werner 797
      if (mWoodyMass<0. || mWoodyMass>50000 || mFoliageMass<0. || mFoliageMass>2000. || mCoarseRootMass<0. || mCoarseRootMass>30000
393 werner 798
         || mNPPReserve>4000.) {
389 werner 799
         qDebug() << "Tree:partitioning: invalid or unlikely pools.";
144 Werner 800
         qDebug() << GlobalSettings::instance()->debugListCaptions(GlobalSettings::DebugOutputs(0));
801
         DebugList dbg; dumpList(dbg);
802
         qDebug() << dbg;
497 werner 803
     } );
144 Werner 804
 
136 Werner 805
    /*DBG_IF_X(mId == 1 , "Tree::partitioning", "dump", dump()
806
             + QString("npp %1 npp_reserve %9 sen_fol %2 sen_stem %3 sen_root %4 net_fol %5 net_stem %6 net_root %7 to_reserve %8")
807
               .arg(npp).arg(senescence_foliage).arg(senescence_stem).arg(senescence_root)
808
               .arg(net_foliage).arg(net_stem).arg(net_root).arg(to_reserve).arg(mNPPReserve) );*/
129 Werner 809
 
115 Werner 810
}
811
 
125 Werner 812
 
134 Werner 813
/** Determination of diamter and height growth based on increment of the stem mass (@p net_stem_npp).
125 Werner 814
    Refer to XXX for equations and variables.
815
    This function updates the dbh and height of the tree.
227 werner 816
    The equations are based on dbh in meters! */
159 werner 817
inline void Tree::grow_diameter(TreeGrowthData &d)
119 Werner 818
{
819
    // determine dh-ratio of increment
820
    // height increment is a function of light competition:
125 Werner 821
    double hd_growth = relative_height_growth(); // hd of height growth
153 werner 822
    double d_m = mDbh / 100.; // current diameter in [m]
159 werner 823
    double net_stem_npp = d.NPP_stem;
824
 
153 werner 825
    const double d_delta_m = mDbhDelta / 100.; // increment of last year in [m]
115 Werner 826
 
159 werner 827
    const double mass_factor = species()->volumeFactor() * species()->density();
153 werner 828
    double stem_mass = mass_factor * d_m*d_m * mHeight; // result: kg, dbh[cm], h[meter]
123 Werner 829
 
153 werner 830
    // factor is in diameter increment per NPP [m/kg]
831
    double factor_diameter = 1. / (  mass_factor * (d_m + d_delta_m)*(d_m + d_delta_m) * ( 2. * mHeight/d_m + hd_growth) );
125 Werner 832
    double delta_d_estimate = factor_diameter * net_stem_npp; // estimated dbh-inc using last years increment
833
 
834
    // using that dbh-increment we estimate a stem-mass-increment and the residual (Eq. 9)
153 werner 835
    double stem_estimate = mass_factor * (d_m + delta_d_estimate)*(d_m + delta_d_estimate)*(mHeight + delta_d_estimate*hd_growth);
137 Werner 836
    double stem_residual = stem_estimate - (stem_mass + net_stem_npp);
125 Werner 837
 
838
    // the final increment is then:
839
    double d_increment = factor_diameter * (net_stem_npp - stem_residual); // Eq. (11)
463 werner 840
    double res_final  = 0.;
1118 werner 841
    if (fabs(stem_residual) > std::min(1.,stem_mass)) {
465 werner 842
 
463 werner 843
        // calculate final residual in stem
844
        res_final = mass_factor * (d_m + d_increment)*(d_m + d_increment)*(mHeight + d_increment*hd_growth)-((stem_mass + net_stem_npp));
1118 werner 845
        if (fabs(res_final)>std::min(1.,stem_mass)) {
846
            // for large errors in stem biomass due to errors in diameter increment (> 1kg or >stem mass), we solve the increment iteratively.
463 werner 847
            // first, increase increment with constant step until we overestimate the first time
848
            // then,
849
            d_increment = 0.02; // start with 2cm increment
850
            bool reached_error = false;
851
            double step=0.01; // step-width 1cm
852
            double est_stem;
853
            do {
854
                est_stem = mass_factor * (d_m + d_increment)*(d_m + d_increment)*(mHeight + d_increment*hd_growth); // estimate with current increment
855
                stem_residual = est_stem - (stem_mass + net_stem_npp);
856
 
857
                if (fabs(stem_residual) <1.) // finished, if stem residual below 1kg
858
                    break;
859
                if (stem_residual > 0.) {
860
                    d_increment -= step;
861
                    reached_error=true;
862
                } else {
863
                    d_increment += step;
864
                }
865
                if (reached_error)
866
                    step /= 2.;
867
            } while (step>0.00001); // continue until diameter "accuracy" falls below 1/100mm
868
        }
869
    }
870
 
871
    if (d_increment<0.f)
872
        qDebug() << "Tree::grow_diameter: d_inc < 0.";
144 Werner 873
    DBG_IF_X(d_increment<0. || d_increment>0.1, "Tree::grow_dimater", "increment out of range.", dump()
125 Werner 874
             + QString("\nhdz %1 factor_diameter %2 stem_residual %3 delta_d_estimate %4 d_increment %5 final residual(kg) %6")
875
               .arg(hd_growth).arg(factor_diameter).arg(stem_residual).arg(delta_d_estimate).arg(d_increment)
142 Werner 876
               .arg( mass_factor * (mDbh + d_increment)*(mDbh + d_increment)*(mHeight + d_increment*hd_growth)-((stem_mass + net_stem_npp)) ));
125 Werner 877
 
303 werner 878
    //DBGMODE(
463 werner 879
        // do not calculate res_final twice if already done
880
        DBG_IF_X( (res_final==0.?fabs(mass_factor * (d_m + d_increment)*(d_m + d_increment)*(mHeight + d_increment*hd_growth)-((stem_mass + net_stem_npp))):res_final) > 1, "Tree::grow_diameter", "final residual stem estimate > 1kg", dump());
153 werner 881
        DBG_IF_X(d_increment > 10. || d_increment*hd_growth >10., "Tree::grow_diameter", "growth out of bound:",QString("d-increment %1 h-increment %2 ").arg(d_increment).arg(d_increment*hd_growth/100.) + dump());
158 werner 882
 
137 Werner 883
        if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreeGrowth) && isDebugging() ) {
126 Werner 884
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreeGrowth);
129 Werner 885
            dumpList(out); // add tree headers
143 Werner 886
            out << net_stem_npp << stem_mass << hd_growth << factor_diameter << delta_d_estimate*100 << d_increment*100;
126 Werner 887
        }
153 werner 888
 
303 werner 889
    //); // DBGMODE()
125 Werner 890
 
891
    d_increment = qMax(d_increment, 0.);
892
 
893
    // update state variables
1102 werner 894
    mDbh += d_increment*100.f; // convert from [m] to [cm]
895
    mDbhDelta = static_cast<float>( d_increment*100. ); // save for next year's growth
153 werner 896
    mHeight += d_increment * hd_growth;
158 werner 897
 
898
    // update state of LIP stamp and opacity
159 werner 899
    mStamp = species()->stamp(mDbh, mHeight); // get new stamp for updated dimensions
158 werner 900
    // calculate the CrownFactor which reflects the opacity of the crown
200 werner 901
    const double k=Model::settings().lightExtinctionCoefficientOpacity;
1102 werner 902
    mOpacity = static_cast<float>( 1. - exp(-k * mLeafArea / mStamp->crownArea()) );
158 werner 903
 
119 Werner 904
}
905
 
125 Werner 906
 
907
/// return the HD ratio of this year's increment based on the light status.
119 Werner 908
inline double Tree::relative_height_growth()
909
{
910
    double hd_low, hd_high;
911
    mSpecies->hdRange(mDbh, hd_low, hd_high);
912
 
125 Werner 913
    DBG_IF_X(hd_low>hd_high, "Tree::relative_height_growth", "hd low higher dann hd_high for ", dump());
949 werner 914
    DBG_IF_X(hd_low < 10 || hd_high>250, "Tree::relative_height_growth", "hd out of range ", dump() + QString(" hd-low: %1 hd-high: %2").arg(hd_low).arg(hd_high));
125 Werner 915
 
916
    // scale according to LRI: if receiving much light (LRI=1), the result is hd_low (for open grown trees)
326 werner 917
    // use the corrected LRI (see tracker#11)
918
    double lri = limit(mLRI * mRU->LRImodifier(),0.,1.);
919
    double hd_ratio = hd_high - (hd_high-hd_low)*lri;
125 Werner 920
    return hd_ratio;
119 Werner 921
}
141 Werner 922
 
278 werner 923
/** This function is called if a tree dies.
924
  @sa ResourceUnit::cleanTreeList(), remove() */
277 werner 925
void Tree::die(TreeGrowthData *d)
926
{
927
    setFlag(Tree::TreeDead, true); // set flag that tree is dead
664 werner 928
    mRU->treeDied();
468 werner 929
    ResourceUnitSpecies &rus = mRU->resourceUnitSpecies(species());
930
    rus.statisticsDead().add(this, d); // add tree to statistics
1064 werner 931
    notifyTreeRemoved(TreeDeath);
521 werner 932
    if (ru()->snag())
933
        ru()->snag()->addMortality(this);
277 werner 934
}
935
 
903 werner 936
/// remove a tree (most likely due to harvest) from the system.
564 werner 937
void Tree::remove(double removeFoliage, double removeBranch, double removeStem )
278 werner 938
{
939
    setFlag(Tree::TreeDead, true); // set flag that tree is dead
1050 werner 940
    setIsHarvested();
664 werner 941
    mRU->treeDied();
468 werner 942
    ResourceUnitSpecies &rus = mRU->resourceUnitSpecies(species());
943
    rus.statisticsMgmt().add(this, 0);
1157 werner 944
    if (isCutdown())
945
        notifyTreeRemoved(TreeCutDown);
946
    else
947
        notifyTreeRemoved(TreeHarvest);
903 werner 948
 
1165 werner 949
    if (GlobalSettings::instance()->model()->saplings())
950
        GlobalSettings::instance()->model()->saplings()->addSprout(this);
951
 
521 werner 952
    if (ru()->snag())
564 werner 953
        ru()->snag()->addHarvest(this, removeStem, removeBranch, removeFoliage);
278 werner 954
}
955
 
713 werner 956
/// remove the tree due to an special event (disturbance)
903 werner 957
/// this is +- the same as die().
713 werner 958
void Tree::removeDisturbance(const double stem_to_soil_fraction, const double stem_to_snag_fraction, const double branch_to_soil_fraction, const double branch_to_snag_fraction, const double foliage_to_soil_fraction)
959
{
960
    setFlag(Tree::TreeDead, true); // set flag that tree is dead
961
    mRU->treeDied();
962
    ResourceUnitSpecies &rus = mRU->resourceUnitSpecies(species());
714 werner 963
    rus.statisticsDead().add(this, 0);
1064 werner 964
    notifyTreeRemoved(TreeDisturbance);
903 werner 965
 
1165 werner 966
    if (GlobalSettings::instance()->model()->saplings())
967
        GlobalSettings::instance()->model()->saplings()->addSprout(this);
1088 werner 968
 
1100 werner 969
    if (ru()->snag()) {
1102 werner 970
        if (isHarvested()) { // if the tree is harvested, do the same as in normal tree harvest (but with default values)
1088 werner 971
            ru()->snag()->addHarvest(this, 1., 0., 0.);
1102 werner 972
        } else {
1088 werner 973
            ru()->snag()->addDisturbance(this, stem_to_snag_fraction, stem_to_soil_fraction, branch_to_snag_fraction, branch_to_soil_fraction, foliage_to_soil_fraction);
1102 werner 974
        }
1100 werner 975
    }
713 werner 976
}
977
 
903 werner 978
/// remove a part of the biomass of the tree, e.g. due to fire.
979
void Tree::removeBiomassOfTree(const double removeFoliageFraction, const double removeBranchFraction, const double removeStemFraction)
668 werner 980
{
981
    mFoliageMass *= 1. - removeFoliageFraction;
982
    mWoodyMass *= (1. - removeStemFraction);
983
    // we have a problem with the branches: this currently cannot be done properly!
766 werner 984
    (void) removeBranchFraction; // silence warning
668 werner 985
}
986
 
975 werner 987
void Tree::setHeight(const float height)
988
{
989
    if (height<=0. || height>150.)
990
        qWarning() << "trying to set tree height to invalid value:" << height << " for tree on RU:" << (mRU?mRU->boundingBox():QRect());
991
    mHeight=height;
992
}
993
 
159 werner 994
void Tree::mortality(TreeGrowthData &d)
995
{
163 werner 996
    // death if leaf area is 0
997
    if (mFoliageMass<0.00001)
998
        die();
999
 
308 werner 1000
    double p_death,  p_stress, p_intrinsic;
1001
    p_intrinsic = species()->deathProb_intrinsic();
1002
    p_stress = species()->deathProb_stress(d.stress_index);
1003
    p_death =  p_intrinsic + p_stress;
707 werner 1004
    double p = drandom(); //0..1
159 werner 1005
    if (p<p_death) {
1006
        // die...
1007
        die();
1008
    }
1009
}
141 Werner 1010
 
1072 werner 1011
#ifdef ALT_TREE_MORTALITY
1071 werner 1012
void Tree::altMortality(TreeGrowthData &d)
1013
{
1014
    // death if leaf area is 0
1015
    if (mFoliageMass<0.00001)
1016
        die();
1017
 
1018
    double  p_intrinsic, p_stress=0.;
1019
    p_intrinsic = species()->deathProb_intrinsic();
1020
 
1021
    if (mDbhDelta < _stress_threshold) {
1022
        mStressIndex++;
1023
        if (mStressIndex> _stress_years)
1024
            p_stress = _stress_death_prob;
1025
    } else
1026
        mStressIndex = 0;
1027
 
1028
    double p = drandom(); //0..1
1029
    if (p<p_intrinsic + p_stress) {
1030
        // die...
1031
        die();
1032
    }
1033
}
1072 werner 1034
#endif
1071 werner 1035
 
1064 werner 1036
void Tree::notifyTreeRemoved(TreeRemovalType reason)
903 werner 1037
{
1077 werner 1038
    // this information is used to track the removed volume for stands based on grids (and for salvaging operations)
909 werner 1039
    ABE::ForestManagementEngine *abe = GlobalSettings::instance()->model()->ABEngine();
1040
    if (abe)
1102 werner 1041
        abe->notifyTreeRemoval(this, static_cast<int>(reason));
1044 werner 1042
 
1043
    // tell disturbance modules that a tree died
1102 werner 1044
    GlobalSettings::instance()->model()->modules()->treeDeath(this, static_cast<int>(reason) );
1045
 
1157 werner 1046
    // update reason, if ABE handled the tree
1047
    if (reason==TreeDisturbance && isHarvested())
1048
        reason = TreeSalavaged;
1049
    if (isCutdown())
1050
        reason = TreeCutDown;
1102 werner 1051
    // create output for tree removals
1052
    if (mRemovalOutput && mRemovalOutput->isEnabled())
1053
        mRemovalOutput->execRemovedTree(this, static_cast<int>(reason));
1114 werner 1054
    if (mLSRemovalOutput && mLSRemovalOutput->isEnabled())
1055
        mLSRemovalOutput->execRemovedTree(this, static_cast<int>(reason));
903 werner 1056
}
1057
 
141 Werner 1058
//////////////////////////////////////////////////
1059
////  value functions
1060
//////////////////////////////////////////////////
1061
 
145 Werner 1062
double Tree::volume() const
141 Werner 1063
{
1064
    /// @see Species::volumeFactor() for details
159 werner 1065
    const double volume_factor = species()->volumeFactor();
157 werner 1066
    const double volume =  volume_factor * mDbh*mDbh*mHeight * 0.0001; // dbh in cm: cm/100 * cm/100 = cm*cm * 0.0001 = m2
141 Werner 1067
    return volume;
1068
}
180 werner 1069
 
579 werner 1070
/// return the basal area in m2
180 werner 1071
double Tree::basalArea() const
1072
{
1073
    // A = r^2 * pi = d/2*pi; from cm->m: d/200
1074
    const double b = (mDbh/200.)*(mDbh/200.)*M_PI;
1075
    return b;
1076
}
668 werner 1077