Subversion Repositories public iLand

Rev

Rev 697 | Rev 706 | 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
********************************************************************************************/
20
 
117 Werner 21
#include "global.h"
488 werner 22
#include "math.h"
117 Werner 23
#include "tree.h"
3 Werner 24
 
83 Werner 25
#include "grid.h"
3 Werner 26
 
83 Werner 27
#include "stamp.h"
90 Werner 28
#include "species.h"
189 iland 29
#include "resourceunit.h"
151 iland 30
#include "model.h"
468 werner 31
#include "snag.h"
38 Werner 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
137 Werner 135
    mDbhDelta = 0.1; // 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;
184
                z = std::max(mHeight - (*mStamp).distanceToCenter(x,y), 0.f); // distance to center = height (45° line)
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
 
246
            z = std::max(mHeight - (*mStamp).distanceToCenter(x,y), 0.f); // distance to center = height (45° line)
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
 
427
 
158 werner 428
void Tree::readLIF()
40 Werner 429
{
106 Werner 430
    if (!mStamp)
155 werner 431
        return;
432
    const Stamp *reader = mStamp->reader();
433
    if (!reader)
434
        return;
156 werner 435
    QPoint pos_reader = mPositionIndex;
155 werner 436
 
437
    int offset_reader = reader->offset();
438
    int offset_writer = mStamp->offset();
439
    int d_offset = offset_writer - offset_reader; // offset on the *stamp* to the crown-cells
440
 
441
    pos_reader-=QPoint(offset_reader, offset_reader);
40 Werner 442
 
155 werner 443
    float local_dom;
444
 
40 Werner 445
    int x,y;
446
    double sum=0.;
155 werner 447
    double value, own_value;
448
    float *grid_value;
403 werner 449
    float z, z_zstar;
155 werner 450
    int reader_size = reader->size();
451
    int rx = pos_reader.x();
452
    int ry = pos_reader.y();
453
    for (y=0;y<reader_size; ++y, ++ry) {
454
        grid_value = mGrid->ptr(rx, ry);
455
        for (x=0;x<reader_size;++x) {
456
 
387 werner 457
            local_dom = mHeightGrid->valueAtIndex((rx+x)/cPxPerHeight, ry/cPxPerHeight).height; // ry: gets ++ed in outer loop, rx not
403 werner 458
            z = std::max(mHeight - reader->distanceToCenter(x,y), 0.f); // distance to center = height (45° line)
459
            z_zstar = (z>=local_dom)?1.f:z/local_dom;
155 werner 460
 
403 werner 461
            own_value = 1. - mStamp->offsetValue(x,y,d_offset)*mOpacity * z_zstar;
462
            // old: own_value = 1. - mStamp->offsetValue(x,y,d_offset)*mOpacity / local_dom; // old: dom_height;
155 werner 463
            own_value = qMax(own_value, 0.02);
464
            value =  *grid_value++ / own_value; // remove impact of focal tree
403 werner 465
 
466
            //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 467
            //if (value>0.)
468
            sum += value * (*reader)(x,y);
469
 
40 Werner 470
        }
471
    }
155 werner 472
    mLRI = sum;
426 werner 473
    // LRI correction...
474
    double hrel = mHeight / mHeightGrid->valueAtIndex(mPositionIndex.x()/cPxPerHeight, mPositionIndex.y()/cPxPerHeight).height;
475
    if (hrel<1.)
476
        mLRI = species()->speciesSet()->LRIcorrection(mLRI, hrel);
477
 
48 Werner 478
    // read dominance field...
155 werner 479
    // this applies only if some trees are potentially *higher* than the dominant height grid
480
    //if (dom_height < m_Height) {
48 Werner 481
        // if tree is higher than Z*, the dominance height
482
        // a part of the crown is in "full light".
155 werner 483
    //    m_statAboveZ++;
484
    //    mImpact = 1. - (1. - mImpact)*dom_height/m_Height;
485
    //}
403 werner 486
 
155 werner 487
    if (mLRI > 1.)
488
        mLRI = 1.;
206 werner 489
 
490
    // Finally, add LRI of this Tree to the ResourceUnit!
251 werner 491
    mRU->addWLA(mLeafArea, mLRI);
206 werner 492
 
212 werner 493
 
155 werner 494
    //qDebug() << "Tree #"<< id() << "value" << sum << "Impact" << mImpact;
206 werner 495
    //mRU->addWLA(mLRI*mLeafArea, mLeafArea);
40 Werner 496
}
497
 
155 werner 498
/// Torus version of read stamp (glued edges)
158 werner 499
void Tree::readLIF_torus()
78 Werner 500
{
106 Werner 501
    if (!mStamp)
107 Werner 502
        return;
106 Werner 503
    const Stamp *reader = mStamp->reader();
78 Werner 504
    if (!reader)
107 Werner 505
        return;
295 werner 506
    int bufferOffset = mGrid->indexAt(QPointF(0.,0.)).x(); // offset of buffer
78 Werner 507
 
387 werner 508
    QPoint pos_reader = QPoint((mPositionIndex.x()-bufferOffset)%cPxPerRU + bufferOffset,
509
                               (mPositionIndex.y()-bufferOffset)%cPxPerRU + bufferOffset); // offset within the ha
295 werner 510
    QPoint ru_offset = QPoint(mPositionIndex.x() - pos_reader.x(), mPositionIndex.y() - pos_reader.y()); // offset of the corner of the resource index
511
 
78 Werner 512
    int offset_reader = reader->offset();
106 Werner 513
    int offset_writer = mStamp->offset();
78 Werner 514
    int d_offset = offset_writer - offset_reader; // offset on the *stamp* to the crown-cells
515
 
516
    pos_reader-=QPoint(offset_reader, offset_reader);
517
 
518
    float local_dom;
519
 
520
    int x,y;
521
    double sum=0.;
522
    double value, own_value;
523
    float *grid_value;
407 werner 524
    float z, z_zstar;
78 Werner 525
    int reader_size = reader->size();
526
    int rx = pos_reader.x();
527
    int ry = pos_reader.y();
155 werner 528
    int xt, yt; // wrapped coords
529
 
397 werner 530
    for (y=0;y<reader_size; ++y) {
531
        yt = torusIndex(ry+y,cPxPerRU, bufferOffset, ru_offset.y());
78 Werner 532
        for (x=0;x<reader_size;++x) {
387 werner 533
            xt = torusIndex(rx+x,cPxPerRU, bufferOffset, ru_offset.x());
155 werner 534
            grid_value = mGrid->ptr(xt,yt);
407 werner 535
 
387 werner 536
            local_dom = mHeightGrid->valueAtIndex(xt/cPxPerHeight, yt/cPxPerHeight).height; // ry: gets ++ed in outer loop, rx not
407 werner 537
            z = std::max(mHeight - reader->distanceToCenter(x,y), 0.f); // distance to center = height (45° line)
538
            z_zstar = (z>=local_dom)?1.f:z/local_dom;
125 Werner 539
 
407 werner 540
            own_value = 1. - mStamp->offsetValue(x,y,d_offset)*mOpacity * z_zstar;
541
            // old: own_value = 1. - mStamp->offsetValue(x,y,d_offset)*mOpacity / local_dom; // old: dom_height;
78 Werner 542
            own_value = qMax(own_value, 0.02);
407 werner 543
            value =  *grid_value++ / own_value; // remove impact of focal tree
544
 
397 werner 545
            // debug for one tree in HJA
546
            //if (id()==178020)
547
            //    qDebug() << x << y << xt << yt << *grid_value << local_dom << own_value << value << (*reader)(x,y);
321 werner 548
            //if (_isnan(value))
549
            //    qDebug() << "isnan" << id();
397 werner 550
            if (value * (*reader)(x,y)>1.)
551
                qDebug() << "LIFTorus: value>1.";
78 Werner 552
            sum += value * (*reader)(x,y);
553
 
554
            //} // isIndexValid
555
        }
556
    }
106 Werner 557
    mLRI = sum;
426 werner 558
 
559
    // LRI correction...
560
    double hrel = mHeight / mHeightGrid->valueAtIndex(mPositionIndex.x()/cPxPerHeight, mPositionIndex.y()/cPxPerHeight).height;
561
    if (hrel<1.)
562
        mLRI = species()->speciesSet()->LRIcorrection(mLRI, hrel);
563
 
564
 
615 werner 565
    if (isnan(mLRI)) {
321 werner 566
        qDebug() << "LRI invalid (nan)!" << id();
567
        mLRI=0.;
568
        //qDebug() << reader->dump();
569
    }
148 iland 570
    if (mLRI > 1.)
571
        mLRI = 1.;
78 Werner 572
    //qDebug() << "Tree #"<< id() << "value" << sum << "Impact" << mImpact;
205 werner 573
 
574
    // Finally, add LRI of this Tree to the ResourceUnit!
251 werner 575
    mRU->addWLA(mLeafArea, mLRI);
58 Werner 576
}
577
 
155 werner 578
 
40 Werner 579
void Tree::resetStatistics()
580
{
581
    m_statPrint=0;
105 Werner 582
    m_statCreated=0;
48 Werner 583
    m_statAboveZ=0;
40 Werner 584
    m_nextId=1;
585
}
107 Werner 586
 
251 werner 587
void Tree::calcLightResponse()
588
{
589
    // calculate a light response from lri:
298 werner 590
    // http://iland.boku.ac.at/individual+tree+light+availability
470 werner 591
    double lri = limit(mLRI * mRU->LRImodifier(), 0., 1.); // Eq. (3)
592
    mLightResponse = mSpecies->lightResponse(lri); // Eq. (4)
251 werner 593
    mRU->addLR(mLeafArea, mLightResponse);
594
 
595
}
596
 
110 Werner 597
//////////////////////////////////////////////////
598
////  Growth Functions
599
//////////////////////////////////////////////////
107 Werner 600
 
227 werner 601
/** grow() is the main function of the yearly tree growth.
602
  The main steps are:
298 werner 603
  - Production of GPP/NPP   @sa http://iland.boku.ac.at/primary+production http://iland.boku.ac.at/individual+tree+light+availability
604
  - Partitioning of NPP to biomass compartments of the tree @sa http://iland.boku.ac.at/allocation
227 werner 605
  - Growth of the stem http://iland.boku.ac.at/stem+growth (???)
387 werner 606
  Further activties: * the age of the tree is increased
607
                     * the mortality sub routine is executed
608
                     * seeds are produced */
107 Werner 609
void Tree::grow()
610
{
159 werner 611
    TreeGrowthData d;
169 werner 612
    mAge++; // increase age
230 werner 613
    // step 1: get "interception area" of the tree individual [m2]
614
    // the sum of all area of all trees of a unit equal the total stocked area * interception_factor(Beer-Lambert)
615
    double effective_area = mRU->interceptedArea(mLeafArea, mLightResponse);
107 Werner 616
 
230 werner 617
    // step 2: calculate GPP of the tree based
618
    // (1) get the amount of GPP for a "unit area" of the tree species
619
    double raw_gpp_per_area = mRU->resourceUnitSpecies(species()).prod3PG().GPPperArea();
620
    // (2) GPP (without aging-effect) in kg Biomass / year
621
    double raw_gpp = raw_gpp_per_area * effective_area;
161 werner 622
 
227 werner 623
    // apply aging according to the state of the individuum
388 werner 624
    const double aging_factor = mSpecies->aging(mHeight, mAge);
376 werner 625
    mRU->addTreeAging(mLeafArea, aging_factor);
227 werner 626
    double gpp = raw_gpp * aging_factor; //
608 werner 627
    d.NPP = gpp * cAutotrophicRespiration; // respiration loss (0.47), cf. Waring et al 1998.
113 Werner 628
 
279 werner 629
    //DBGMODE(
137 Werner 630
        if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreeNPP) && isDebugging()) {
133 Werner 631
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreeNPP);
632
            dumpList(out); // add tree headers
299 werner 633
            out << mLRI * mRU->LRImodifier() << mLightResponse << effective_area << raw_gpp << gpp << d.NPP << aging_factor;
133 Werner 634
        }
279 werner 635
    //); // DBGMODE()
217 werner 636
    if (d.NPP>0.)
637
        partitioning(d); // split npp to compartments and grow (diameter, height)
133 Werner 638
 
387 werner 639
    // mortality
200 werner 640
    if (Model::settings().mortalityEnabled)
641
        mortality(d);
110 Werner 642
 
159 werner 643
    mStressIndex = d.stress_index;
180 werner 644
 
645
    if (!isDead())
257 werner 646
        mRU->resourceUnitSpecies(species()).statistics().add(this, &d);
277 werner 647
 
387 werner 648
    // regeneration
460 werner 649
    mSpecies->seedProduction(mAge, mHeight, mPositionIndex);
387 werner 650
 
107 Werner 651
}
652
 
227 werner 653
/** partitioning of this years assimilates (NPP) to biomass compartments.
298 werner 654
  Conceptionally, the algorithm is based on Duursma, 2007.
655
  @sa http://iland.boku.ac.at/allocation */
159 werner 656
inline void Tree::partitioning(TreeGrowthData &d)
115 Werner 657
{
159 werner 658
    double npp = d.NPP;
115 Werner 659
    // add content of reserve pool
116 Werner 660
    npp += mNPPReserve;
159 werner 661
    const double foliage_mass_allo = species()->biomassFoliage(mDbh);
276 werner 662
    const double reserve_size = foliage_mass_allo * (1. + mSpecies->finerootFoliageRatio());
297 werner 663
    double refill_reserve = qMin(reserve_size, (1. + mSpecies->finerootFoliageRatio())*mFoliageMass); // not always try to refill reserve 100%
119 Werner 664
 
136 Werner 665
    double apct_wood, apct_root, apct_foliage; // allocation percentages (sum=1) (eta)
468 werner 666
    ResourceUnitSpecies &rus = mRU->resourceUnitSpecies(species());
117 Werner 667
    // turnover rates
159 werner 668
    const double &to_fol = species()->turnoverLeaf();
669
    const double &to_root = species()->turnoverRoot();
136 Werner 670
    // the turnover rate of wood depends on the size of the reserve pool:
116 Werner 671
 
136 Werner 672
 
163 werner 673
    double to_wood = refill_reserve / (mWoodyMass + refill_reserve);
674
 
468 werner 675
    apct_root = rus.prod3PG().rootFraction();
261 werner 676
    d.NPP_above = d.NPP * (1. - apct_root); // aboveground: total NPP - fraction to roots
298 werner 677
    double b_wf = species()->allometricRatio_wf(); // ratio of allometric exponents (b_woody / b_foliage)
117 Werner 678
 
679
    // Duursma 2007, Eq. (20)
167 werner 680
    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 );
163 werner 681
    if (apct_wood<0)
682
        apct_wood = 0.;
117 Werner 683
    apct_foliage = 1. - apct_root - apct_wood;
684
 
163 werner 685
 
686
    //DBGMODE(
687
            if (apct_foliage<0 || apct_wood<0)
688
                qDebug() << "transfer to foliage or wood < 0";
689
             if (npp<0)
690
                 qDebug() << "NPP < 0";
691
         //   );
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
521 werner 901
    if (ru()->snag())
902
        ru()->snag()->addMortality(this);
277 werner 903
}
904
 
564 werner 905
void Tree::remove(double removeFoliage, double removeBranch, double removeStem )
278 werner 906
{
907
    setFlag(Tree::TreeDead, true); // set flag that tree is dead
664 werner 908
    mRU->treeDied();
468 werner 909
    ResourceUnitSpecies &rus = mRU->resourceUnitSpecies(species());
910
    rus.statisticsMgmt().add(this, 0);
521 werner 911
    if (ru()->snag())
564 werner 912
        ru()->snag()->addHarvest(this, removeStem, removeBranch, removeFoliage);
278 werner 913
}
914
 
668 werner 915
void Tree::removeBiomass(const double removeFoliageFraction, const double removeBranchFraction, const double removeStemFraction)
916
{
917
    mFoliageMass *= 1. - removeFoliageFraction;
918
    mWoodyMass *= (1. - removeStemFraction);
919
    // we have a problem with the branches: this currently cannot be done properly!
920
}
921
 
159 werner 922
void Tree::mortality(TreeGrowthData &d)
923
{
163 werner 924
    // death if leaf area is 0
925
    if (mFoliageMass<0.00001)
926
        die();
927
 
308 werner 928
    double p_death,  p_stress, p_intrinsic;
929
    p_intrinsic = species()->deathProb_intrinsic();
930
    p_stress = species()->deathProb_stress(d.stress_index);
931
    p_death =  p_intrinsic + p_stress;
444 werner 932
    double p = drandom(ru()->randomGenerator()); //0..1
159 werner 933
    if (p<p_death) {
934
        // die...
935
        die();
936
    }
937
}
141 Werner 938
 
939
//////////////////////////////////////////////////
940
////  value functions
941
//////////////////////////////////////////////////
942
 
145 Werner 943
double Tree::volume() const
141 Werner 944
{
945
    /// @see Species::volumeFactor() for details
159 werner 946
    const double volume_factor = species()->volumeFactor();
157 werner 947
    const double volume =  volume_factor * mDbh*mDbh*mHeight * 0.0001; // dbh in cm: cm/100 * cm/100 = cm*cm * 0.0001 = m2
141 Werner 948
    return volume;
949
}
180 werner 950
 
579 werner 951
/// return the basal area in m2
180 werner 952
double Tree::basalArea() const
953
{
954
    // A = r^2 * pi = d/2*pi; from cm->m: d/200
955
    const double b = (mDbh/200.)*(mDbh/200.)*M_PI;
956
    return b;
957
}
668 werner 958