Subversion Repositories public iLand

Rev

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

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