Subversion Repositories public iLand

Rev

Rev 903 | Rev 905 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

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