Subversion Repositories public iLand

Rev

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