Subversion Repositories public iLand

Rev

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

Rev Author Line No. Line
1
 
671 werner 2
/********************************************************************************************
3
**    iLand - an individual based forest landscape and disturbance model
4
**    http://iland.boku.ac.at
5
**    Copyright (C) 2009-  Werner Rammer, Rupert Seidl
6
**
7
**    This program is free software: you can redistribute it and/or modify
8
**    it under the terms of the GNU General Public License as published by
9
**    the Free Software Foundation, either version 3 of the License, or
10
**    (at your option) any later version.
11
**
12
**    This program is distributed in the hope that it will be useful,
13
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
14
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
**    GNU General Public License for more details.
16
**
17
**    You should have received a copy of the GNU General Public License
18
**    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
********************************************************************************************/
117 Werner 20
#include "global.h"
21
#include "tree.h"
3 Werner 22
 
83 Werner 23
#include "grid.h"
3 Werner 24
 
83 Werner 25
#include "stamp.h"
90 Werner 26
#include "species.h"
189 iland 27
#include "resourceunit.h"
151 iland 28
#include "model.h"
468 werner 29
#include "snag.h"
38 Werner 30
 
904 werner 31
#include "forestmanagementengine.h"
32
 
110 Werner 33
// static varaibles
106 Werner 34
FloatGrid *Tree::mGrid = 0;
151 iland 35
HeightGrid *Tree::mHeightGrid = 0;
40 Werner 36
int Tree::m_statPrint=0;
48 Werner 37
int Tree::m_statAboveZ=0;
105 Werner 38
int Tree::m_statCreated=0;
40 Werner 39
int Tree::m_nextId=0;
40
 
697 werner 41
/** @class Tree
42
    @ingroup core
43
    A tree is the basic simulation entity of iLand and represents a single tree.
44
    Trees in iLand are designed to be lightweight, thus the list of stored properties is limited. Basic properties
45
    are dimensions (dbh, height), biomass pools (stem, leaves, roots), the reserve NPP pool. Additionally, the location and species are stored.
46
    A Tree has a height of at least 4m; trees below this threshold are covered by the regeneration layer (see Sapling).
47
    Trees are stored in lists managed at the resource unit level.
158 werner 48
 
697 werner 49
  */
257 werner 50
 
158 werner 51
/** get distance and direction between two points.
52
  returns the distance (m), and the angle between PStart and PEnd (radians) in referenced param rAngle. */
53
float dist_and_direction(const QPointF &PStart, const QPointF &PEnd, float &rAngle)
151 iland 54
{
158 werner 55
    float dx = PEnd.x() - PStart.x();
56
    float dy = PEnd.y() - PStart.y();
57
    float d = sqrt(dx*dx + dy*dy);
58
    // direction:
59
    rAngle = atan2(dx, dy);
60
    return d;
151 iland 61
}
62
 
158 werner 63
 
110 Werner 64
// lifecycle
3 Werner 65
Tree::Tree()
66
{
149 werner 67
    mDbh = mHeight = 0;
68
    mRU = 0; mSpecies = 0;
169 werner 69
    mFlags = mAge = 0;
276 werner 70
    mOpacity=mFoliageMass=mWoodyMass=mCoarseRootMass=mFineRootMass=mLeafArea=0.;
159 werner 71
    mDbhDelta=mNPPReserve=mLRI=mStressIndex=0.;
264 werner 72
    mLightResponse = 0.;
975 werner 73
    mStamp=0;
106 Werner 74
    mId = m_nextId++;
105 Werner 75
    m_statCreated++;
3 Werner 76
}
38 Werner 77
 
407 werner 78
float Tree::crownRadius() const
79
{
80
    Q_ASSERT(mStamp!=0);
81
    return mStamp->crownRadius();
82
}
83
 
476 werner 84
float Tree::biomassBranch() const
85
{
86
    return mSpecies->biomassBranch(mDbh);
87
}
88
 
158 werner 89
void Tree::setGrid(FloatGrid* gridToStamp, Grid<HeightGridValue> *dominanceGrid)
3 Werner 90
{
158 werner 91
    mGrid = gridToStamp; mHeightGrid = dominanceGrid;
3 Werner 92
}
93
 
667 werner 94
// calculate the thickness of the bark of the tree
95
double Tree::barkThickness() const
96
{
97
    return mSpecies->barkThickness(mDbh);
98
}
99
 
125 Werner 100
/// dumps some core variables of a tree to a string.
101
QString Tree::dump()
102
{
103
    QString result = QString("id %1 species %2 dbh %3 h %4 x/y %5/%6 ru# %7 LRI %8")
159 werner 104
                            .arg(mId).arg(species()->id()).arg(mDbh).arg(mHeight)
156 werner 105
                            .arg(position().x()).arg(position().y())
125 Werner 106
                            .arg(mRU->index()).arg(mLRI);
107
    return result;
108
}
3 Werner 109
 
129 Werner 110
void Tree::dumpList(DebugList &rTargetList)
111
{
159 werner 112
    rTargetList << mId << species()->id() << mDbh << mHeight  << position().x() << position().y()   << mRU->index() << mLRI
276 werner 113
                << mWoodyMass << mCoarseRootMass << mFoliageMass << mLeafArea;
129 Werner 114
}
115
 
38 Werner 116
void Tree::setup()
117
{
975 werner 118
    if (mDbh<=0 || mHeight<=0) {
119
        throw IException(QString("Error: trying to set up a tree with invalid dimensions: dbh: %1 height: %2 id: %3 RU-index: %4").arg(mDbh).arg(mHeight).arg(mId).arg(mRU->index()));
120
    }
38 Werner 121
    // check stamp
159 werner 122
    Q_ASSERT_X(species()!=0, "Tree::setup()", "species is NULL");
123
    mStamp = species()->stamp(mDbh, mHeight);
505 werner 124
    if (!mStamp) {
125
        throw IException("Tree::setup() with invalid stamp!");
126
    }
110 Werner 127
 
159 werner 128
    mFoliageMass = species()->biomassFoliage(mDbh);
276 werner 129
    mCoarseRootMass = species()->biomassRoot(mDbh); // coarse root (allometry)
130
    mFineRootMass = mFoliageMass * species()->finerootFoliageRatio(); //  fine root (size defined  by finerootFoliageRatio)
159 werner 131
    mWoodyMass = species()->biomassWoody(mDbh);
110 Werner 132
 
137 Werner 133
    // LeafArea[m2] = LeafMass[kg] * specificLeafArea[m2/kg]
159 werner 134
    mLeafArea = mFoliageMass * species()->specificLeafArea();
276 werner 135
    mOpacity = 1. - exp(- Model::settings().lightExtinctionCoefficientOpacity * mLeafArea / mStamp->crownArea());
136
    mNPPReserve = (1+species()->finerootFoliageRatio())*mFoliageMass; // initial value
780 werner 137
    mDbhDelta = 0.1f; // initial value: used in growth() to estimate diameter increment
376 werner 138
 
38 Werner 139
}
39 Werner 140
 
388 werner 141
void Tree::setAge(const int age, const float treeheight)
142
{
143
    mAge = age;
144
    if (age==0) {
145
        // estimate age using the tree height
146
        mAge = mSpecies->estimateAge(treeheight);
147
    }
148
}
149
 
110 Werner 150
//////////////////////////////////////////////////
151
////  Light functions (Pattern-stuff)
152
//////////////////////////////////////////////////
153
 
70 Werner 154
#define NOFULLDBG
77 Werner 155
//#define NOFULLOPT
39 Werner 156
 
40 Werner 157
 
158 werner 158
void Tree::applyLIP()
77 Werner 159
{
144 Werner 160
    if (!mStamp)
161
        return;
106 Werner 162
    Q_ASSERT(mGrid!=0 && mStamp!=0 && mRU!=0);
156 werner 163
    QPoint pos = mPositionIndex;
106 Werner 164
    int offset = mStamp->offset();
77 Werner 165
    pos-=QPoint(offset, offset);
166
 
167
    float local_dom; // height of Z* on the current position
168
    int x,y;
401 werner 169
    float value, z, z_zstar;
106 Werner 170
    int gr_stamp = mStamp->size();
705 werner 171
 
106 Werner 172
    if (!mGrid->isIndexValid(pos) || !mGrid->isIndexValid(pos+QPoint(gr_stamp, gr_stamp))) {
407 werner 173
        // this should not happen because of the buffer
77 Werner 174
        return;
175
    }
705 werner 176
    int grid_y = pos.y();
77 Werner 177
    for (y=0;y<gr_stamp; ++y) {
403 werner 178
 
705 werner 179
        float *grid_value_ptr = mGrid->ptr(pos.x(), grid_y);
180
        int grid_x = pos.x();
181
        for (x=0;x<gr_stamp;++x, ++grid_x, ++grid_value_ptr) {
77 Werner 182
            // suppose there is no stamping outside
106 Werner 183
            value = (*mStamp)(x,y); // stampvalue
705 werner 184
            //if (value>0.f) {
185
                local_dom = (*mHeightGrid)(grid_x/cPxPerHeight, grid_y/cPxPerHeight).height;
884 werner 186
                z = std::max(mHeight - (*mStamp).distanceToCenter(x,y), 0.f); // distance to center = height (45 degree line)
705 werner 187
                z_zstar = (z>=local_dom)?1.f:z/local_dom;
188
                value = 1. - value*mOpacity * z_zstar; // calculated value
189
                value = std::max(value, 0.02f); // limit value
77 Werner 190
 
705 werner 191
                *grid_value_ptr *= value;
192
            //}
403 werner 193
 
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
 
884 werner 248
            z = std::max(mHeight - (*mStamp).distanceToCenter(x,y), 0.f); // distance to center = height (45 degree line)
407 werner 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
 
718 werner 429
/** reads the light influence field value for a tree.
430
    The LIF field is scanned within the crown area of the focal tree, and the influence of
431
    the focal tree is "subtracted" from the LIF values.
432
    Finally, the "LRI correction" is applied.
433
    see http://iland.boku.ac.at/competition+for+light for details.
434
  */
158 werner 435
void Tree::readLIF()
40 Werner 436
{
106 Werner 437
    if (!mStamp)
155 werner 438
        return;
439
    const Stamp *reader = mStamp->reader();
440
    if (!reader)
441
        return;
156 werner 442
    QPoint pos_reader = mPositionIndex;
780 werner 443
    const float outside_area_factor = 0.1f; //
155 werner 444
 
445
    int offset_reader = reader->offset();
446
    int offset_writer = mStamp->offset();
447
    int d_offset = offset_writer - offset_reader; // offset on the *stamp* to the crown-cells
448
 
449
    pos_reader-=QPoint(offset_reader, offset_reader);
40 Werner 450
 
155 werner 451
    float local_dom;
452
 
40 Werner 453
    int x,y;
454
    double sum=0.;
155 werner 455
    double value, own_value;
456
    float *grid_value;
403 werner 457
    float z, z_zstar;
155 werner 458
    int reader_size = reader->size();
459
    int rx = pos_reader.x();
460
    int ry = pos_reader.y();
461
    for (y=0;y<reader_size; ++y, ++ry) {
462
        grid_value = mGrid->ptr(rx, ry);
463
        for (x=0;x<reader_size;++x) {
464
 
718 werner 465
            const HeightGridValue &hgv = mHeightGrid->constValueAtIndex((rx+x)/cPxPerHeight, ry/cPxPerHeight); // the height grid value, ry: gets ++ed in outer loop, rx not
466
            local_dom = hgv.height;
884 werner 467
            z = std::max(mHeight - reader->distanceToCenter(x,y), 0.f); // distance to center = height (45 degree line)
403 werner 468
            z_zstar = (z>=local_dom)?1.f:z/local_dom;
155 werner 469
 
403 werner 470
            own_value = 1. - mStamp->offsetValue(x,y,d_offset)*mOpacity * z_zstar;
155 werner 471
            own_value = qMax(own_value, 0.02);
472
            value =  *grid_value++ / own_value; // remove impact of focal tree
720 werner 473
            // additional punishment if pixel is outside:
718 werner 474
            if (hgv.isForestOutside())
720 werner 475
               value *= outside_area_factor;
403 werner 476
 
477
            //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 478
            //if (value>0.)
479
            sum += value * (*reader)(x,y);
480
 
40 Werner 481
        }
482
    }
155 werner 483
    mLRI = sum;
426 werner 484
    // LRI correction...
485
    double hrel = mHeight / mHeightGrid->valueAtIndex(mPositionIndex.x()/cPxPerHeight, mPositionIndex.y()/cPxPerHeight).height;
486
    if (hrel<1.)
487
        mLRI = species()->speciesSet()->LRIcorrection(mLRI, hrel);
488
 
403 werner 489
 
155 werner 490
    if (mLRI > 1.)
491
        mLRI = 1.;
206 werner 492
 
493
    // Finally, add LRI of this Tree to the ResourceUnit!
251 werner 494
    mRU->addWLA(mLeafArea, mLRI);
206 werner 495
 
155 werner 496
    //qDebug() << "Tree #"<< id() << "value" << sum << "Impact" << mImpact;
40 Werner 497
}
498
 
155 werner 499
/// Torus version of read stamp (glued edges)
158 werner 500
void Tree::readLIF_torus()
78 Werner 501
{
106 Werner 502
    if (!mStamp)
107 Werner 503
        return;
106 Werner 504
    const Stamp *reader = mStamp->reader();
78 Werner 505
    if (!reader)
107 Werner 506
        return;
295 werner 507
    int bufferOffset = mGrid->indexAt(QPointF(0.,0.)).x(); // offset of buffer
78 Werner 508
 
387 werner 509
    QPoint pos_reader = QPoint((mPositionIndex.x()-bufferOffset)%cPxPerRU + bufferOffset,
510
                               (mPositionIndex.y()-bufferOffset)%cPxPerRU + bufferOffset); // offset within the ha
295 werner 511
    QPoint ru_offset = QPoint(mPositionIndex.x() - pos_reader.x(), mPositionIndex.y() - pos_reader.y()); // offset of the corner of the resource index
512
 
78 Werner 513
    int offset_reader = reader->offset();
106 Werner 514
    int offset_writer = mStamp->offset();
78 Werner 515
    int d_offset = offset_writer - offset_reader; // offset on the *stamp* to the crown-cells
516
 
517
    pos_reader-=QPoint(offset_reader, offset_reader);
518
 
519
    float local_dom;
520
 
521
    int x,y;
522
    double sum=0.;
523
    double value, own_value;
524
    float *grid_value;
407 werner 525
    float z, z_zstar;
78 Werner 526
    int reader_size = reader->size();
527
    int rx = pos_reader.x();
528
    int ry = pos_reader.y();
155 werner 529
    int xt, yt; // wrapped coords
530
 
397 werner 531
    for (y=0;y<reader_size; ++y) {
532
        yt = torusIndex(ry+y,cPxPerRU, bufferOffset, ru_offset.y());
78 Werner 533
        for (x=0;x<reader_size;++x) {
387 werner 534
            xt = torusIndex(rx+x,cPxPerRU, bufferOffset, ru_offset.x());
155 werner 535
            grid_value = mGrid->ptr(xt,yt);
407 werner 536
 
387 werner 537
            local_dom = mHeightGrid->valueAtIndex(xt/cPxPerHeight, yt/cPxPerHeight).height; // ry: gets ++ed in outer loop, rx not
884 werner 538
            z = std::max(mHeight - reader->distanceToCenter(x,y), 0.f); // distance to center = height (45 degree line)
407 werner 539
            z_zstar = (z>=local_dom)?1.f:z/local_dom;
125 Werner 540
 
407 werner 541
            own_value = 1. - mStamp->offsetValue(x,y,d_offset)*mOpacity * z_zstar;
542
            // old: own_value = 1. - mStamp->offsetValue(x,y,d_offset)*mOpacity / local_dom; // old: dom_height;
78 Werner 543
            own_value = qMax(own_value, 0.02);
407 werner 544
            value =  *grid_value++ / own_value; // remove impact of focal tree
545
 
397 werner 546
            // debug for one tree in HJA
547
            //if (id()==178020)
548
            //    qDebug() << x << y << xt << yt << *grid_value << local_dom << own_value << value << (*reader)(x,y);
321 werner 549
            //if (_isnan(value))
550
            //    qDebug() << "isnan" << id();
397 werner 551
            if (value * (*reader)(x,y)>1.)
552
                qDebug() << "LIFTorus: value>1.";
78 Werner 553
            sum += value * (*reader)(x,y);
554
 
555
            //} // isIndexValid
556
        }
557
    }
106 Werner 558
    mLRI = sum;
426 werner 559
 
560
    // LRI correction...
561
    double hrel = mHeight / mHeightGrid->valueAtIndex(mPositionIndex.x()/cPxPerHeight, mPositionIndex.y()/cPxPerHeight).height;
562
    if (hrel<1.)
563
        mLRI = species()->speciesSet()->LRIcorrection(mLRI, hrel);
564
 
565
 
615 werner 566
    if (isnan(mLRI)) {
321 werner 567
        qDebug() << "LRI invalid (nan)!" << id();
568
        mLRI=0.;
569
        //qDebug() << reader->dump();
570
    }
148 iland 571
    if (mLRI > 1.)
572
        mLRI = 1.;
78 Werner 573
    //qDebug() << "Tree #"<< id() << "value" << sum << "Impact" << mImpact;
205 werner 574
 
575
    // Finally, add LRI of this Tree to the ResourceUnit!
251 werner 576
    mRU->addWLA(mLeafArea, mLRI);
58 Werner 577
}
578
 
155 werner 579
 
40 Werner 580
void Tree::resetStatistics()
581
{
582
    m_statPrint=0;
105 Werner 583
    m_statCreated=0;
48 Werner 584
    m_statAboveZ=0;
40 Werner 585
    m_nextId=1;
586
}
107 Werner 587
 
251 werner 588
void Tree::calcLightResponse()
589
{
590
    // calculate a light response from lri:
298 werner 591
    // http://iland.boku.ac.at/individual+tree+light+availability
470 werner 592
    double lri = limit(mLRI * mRU->LRImodifier(), 0., 1.); // Eq. (3)
593
    mLightResponse = mSpecies->lightResponse(lri); // Eq. (4)
251 werner 594
    mRU->addLR(mLeafArea, mLightResponse);
595
 
596
}
597
 
110 Werner 598
//////////////////////////////////////////////////
599
////  Growth Functions
600
//////////////////////////////////////////////////
107 Werner 601
 
227 werner 602
/** grow() is the main function of the yearly tree growth.
603
  The main steps are:
298 werner 604
  - Production of GPP/NPP   @sa http://iland.boku.ac.at/primary+production http://iland.boku.ac.at/individual+tree+light+availability
605
  - Partitioning of NPP to biomass compartments of the tree @sa http://iland.boku.ac.at/allocation
227 werner 606
  - Growth of the stem http://iland.boku.ac.at/stem+growth (???)
387 werner 607
  Further activties: * the age of the tree is increased
608
                     * the mortality sub routine is executed
609
                     * seeds are produced */
107 Werner 610
void Tree::grow()
611
{
159 werner 612
    TreeGrowthData d;
169 werner 613
    mAge++; // increase age
230 werner 614
    // step 1: get "interception area" of the tree individual [m2]
615
    // the sum of all area of all trees of a unit equal the total stocked area * interception_factor(Beer-Lambert)
616
    double effective_area = mRU->interceptedArea(mLeafArea, mLightResponse);
107 Werner 617
 
230 werner 618
    // step 2: calculate GPP of the tree based
619
    // (1) get the amount of GPP for a "unit area" of the tree species
620
    double raw_gpp_per_area = mRU->resourceUnitSpecies(species()).prod3PG().GPPperArea();
621
    // (2) GPP (without aging-effect) in kg Biomass / year
622
    double raw_gpp = raw_gpp_per_area * effective_area;
161 werner 623
 
227 werner 624
    // apply aging according to the state of the individuum
388 werner 625
    const double aging_factor = mSpecies->aging(mHeight, mAge);
376 werner 626
    mRU->addTreeAging(mLeafArea, aging_factor);
227 werner 627
    double gpp = raw_gpp * aging_factor; //
608 werner 628
    d.NPP = gpp * cAutotrophicRespiration; // respiration loss (0.47), cf. Waring et al 1998.
113 Werner 629
 
279 werner 630
    //DBGMODE(
137 Werner 631
        if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreeNPP) && isDebugging()) {
133 Werner 632
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreeNPP);
633
            dumpList(out); // add tree headers
299 werner 634
            out << mLRI * mRU->LRImodifier() << mLightResponse << effective_area << raw_gpp << gpp << d.NPP << aging_factor;
133 Werner 635
        }
279 werner 636
    //); // DBGMODE()
217 werner 637
    if (d.NPP>0.)
638
        partitioning(d); // split npp to compartments and grow (diameter, height)
133 Werner 639
 
387 werner 640
    // mortality
200 werner 641
    if (Model::settings().mortalityEnabled)
642
        mortality(d);
110 Werner 643
 
159 werner 644
    mStressIndex = d.stress_index;
180 werner 645
 
646
    if (!isDead())
257 werner 647
        mRU->resourceUnitSpecies(species()).statistics().add(this, &d);
277 werner 648
 
387 werner 649
    // regeneration
460 werner 650
    mSpecies->seedProduction(mAge, mHeight, mPositionIndex);
387 werner 651
 
107 Werner 652
}
653
 
227 werner 654
/** partitioning of this years assimilates (NPP) to biomass compartments.
298 werner 655
  Conceptionally, the algorithm is based on Duursma, 2007.
656
  @sa http://iland.boku.ac.at/allocation */
159 werner 657
inline void Tree::partitioning(TreeGrowthData &d)
115 Werner 658
{
159 werner 659
    double npp = d.NPP;
115 Werner 660
    // add content of reserve pool
116 Werner 661
    npp += mNPPReserve;
159 werner 662
    const double foliage_mass_allo = species()->biomassFoliage(mDbh);
276 werner 663
    const double reserve_size = foliage_mass_allo * (1. + mSpecies->finerootFoliageRatio());
297 werner 664
    double refill_reserve = qMin(reserve_size, (1. + mSpecies->finerootFoliageRatio())*mFoliageMass); // not always try to refill reserve 100%
119 Werner 665
 
136 Werner 666
    double apct_wood, apct_root, apct_foliage; // allocation percentages (sum=1) (eta)
468 werner 667
    ResourceUnitSpecies &rus = mRU->resourceUnitSpecies(species());
117 Werner 668
    // turnover rates
159 werner 669
    const double &to_fol = species()->turnoverLeaf();
670
    const double &to_root = species()->turnoverRoot();
136 Werner 671
    // the turnover rate of wood depends on the size of the reserve pool:
116 Werner 672
 
136 Werner 673
 
163 werner 674
    double to_wood = refill_reserve / (mWoodyMass + refill_reserve);
675
 
468 werner 676
    apct_root = rus.prod3PG().rootFraction();
261 werner 677
    d.NPP_above = d.NPP * (1. - apct_root); // aboveground: total NPP - fraction to roots
298 werner 678
    double b_wf = species()->allometricRatio_wf(); // ratio of allometric exponents (b_woody / b_foliage)
117 Werner 679
 
680
    // Duursma 2007, Eq. (20)
167 werner 681
    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 682
 
683
    apct_wood = limit(apct_wood, 0., 1.-apct_root);
684
 
117 Werner 685
    apct_foliage = 1. - apct_root - apct_wood;
686
 
163 werner 687
 
902 werner 688
    DBGMODE(
163 werner 689
            if (apct_foliage<0 || apct_wood<0)
690
                qDebug() << "transfer to foliage or wood < 0";
691
             if (npp<0)
692
                 qDebug() << "NPP < 0";
902 werner 693
            );
163 werner 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());
949 werner 886
    DBG_IF_X(hd_low < 10 || hd_high>250, "Tree::relative_height_growth", "hd out of range ", dump() + QString(" hd-low: %1 hd-high: %2").arg(hd_low).arg(hd_high));
125 Werner 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
904 werner 903
    recordRemovedVolume(TreeDeath);
521 werner 904
    if (ru()->snag())
905
        ru()->snag()->addMortality(this);
277 werner 906
}
907
 
903 werner 908
/// remove a tree (most likely due to harvest) from the system.
564 werner 909
void Tree::remove(double removeFoliage, double removeBranch, double removeStem )
278 werner 910
{
911
    setFlag(Tree::TreeDead, true); // set flag that tree is dead
664 werner 912
    mRU->treeDied();
468 werner 913
    ResourceUnitSpecies &rus = mRU->resourceUnitSpecies(species());
914
    rus.statisticsMgmt().add(this, 0);
904 werner 915
    recordRemovedVolume(TreeHarvest);
903 werner 916
 
521 werner 917
    if (ru()->snag())
564 werner 918
        ru()->snag()->addHarvest(this, removeStem, removeBranch, removeFoliage);
278 werner 919
}
920
 
713 werner 921
/// remove the tree due to an special event (disturbance)
903 werner 922
/// this is +- the same as die().
713 werner 923
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)
924
{
925
    setFlag(Tree::TreeDead, true); // set flag that tree is dead
926
    mRU->treeDied();
927
    ResourceUnitSpecies &rus = mRU->resourceUnitSpecies(species());
714 werner 928
    rus.statisticsDead().add(this, 0);
904 werner 929
    recordRemovedVolume(TreeDisturbance);
903 werner 930
 
713 werner 931
    if (ru()->snag())
932
        ru()->snag()->addDisturbance(this, stem_to_snag_fraction, stem_to_soil_fraction, branch_to_snag_fraction, branch_to_soil_fraction, foliage_to_soil_fraction);
933
}
934
 
903 werner 935
/// remove a part of the biomass of the tree, e.g. due to fire.
936
void Tree::removeBiomassOfTree(const double removeFoliageFraction, const double removeBranchFraction, const double removeStemFraction)
668 werner 937
{
938
    mFoliageMass *= 1. - removeFoliageFraction;
939
    mWoodyMass *= (1. - removeStemFraction);
940
    // we have a problem with the branches: this currently cannot be done properly!
766 werner 941
    (void) removeBranchFraction; // silence warning
668 werner 942
}
943
 
975 werner 944
void Tree::setHeight(const float height)
945
{
946
    if (height<=0. || height>150.)
947
        qWarning() << "trying to set tree height to invalid value:" << height << " for tree on RU:" << (mRU?mRU->boundingBox():QRect());
948
    mHeight=height;
949
}
950
 
159 werner 951
void Tree::mortality(TreeGrowthData &d)
952
{
163 werner 953
    // death if leaf area is 0
954
    if (mFoliageMass<0.00001)
955
        die();
956
 
308 werner 957
    double p_death,  p_stress, p_intrinsic;
958
    p_intrinsic = species()->deathProb_intrinsic();
959
    p_stress = species()->deathProb_stress(d.stress_index);
960
    p_death =  p_intrinsic + p_stress;
707 werner 961
    double p = drandom(); //0..1
159 werner 962
    if (p<p_death) {
963
        // die...
964
        die();
965
    }
966
}
141 Werner 967
 
904 werner 968
void Tree::recordRemovedVolume(TreeRemovalType reason)
903 werner 969
{
970
    // add the volume of the current tree to the height grid
904 werner 971
    // this information is used to track the removed volume for stands based on grids.
909 werner 972
    ABE::ForestManagementEngine *abe = GlobalSettings::instance()->model()->ABEngine();
973
    if (abe)
974
        abe->addTreeRemoval(this, (int)reason);
903 werner 975
}
976
 
141 Werner 977
//////////////////////////////////////////////////
978
////  value functions
979
//////////////////////////////////////////////////
980
 
145 Werner 981
double Tree::volume() const
141 Werner 982
{
983
    /// @see Species::volumeFactor() for details
159 werner 984
    const double volume_factor = species()->volumeFactor();
157 werner 985
    const double volume =  volume_factor * mDbh*mDbh*mHeight * 0.0001; // dbh in cm: cm/100 * cm/100 = cm*cm * 0.0001 = m2
141 Werner 986
    return volume;
987
}
180 werner 988
 
579 werner 989
/// return the basal area in m2
180 werner 990
double Tree::basalArea() const
991
{
992
    // A = r^2 * pi = d/2*pi; from cm->m: d/200
993
    const double b = (mDbh/200.)*(mDbh/200.)*M_PI;
994
    return b;
995
}
668 werner 996