Subversion Repositories public iLand

Rev

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