Subversion Repositories public iLand

Rev

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

Rev Author Line No. Line
1
 
117 Werner 2
#include "global.h"
3
#include "tree.h"
3 Werner 4
 
83 Werner 5
#include "grid.h"
3 Werner 6
 
83 Werner 7
#include "stamp.h"
90 Werner 8
#include "species.h"
189 iland 9
#include "resourceunit.h"
151 iland 10
#include "model.h"
38 Werner 11
 
110 Werner 12
// static varaibles
106 Werner 13
FloatGrid *Tree::mGrid = 0;
151 iland 14
HeightGrid *Tree::mHeightGrid = 0;
40 Werner 15
int Tree::m_statPrint=0;
48 Werner 16
int Tree::m_statAboveZ=0;
105 Werner 17
int Tree::m_statCreated=0;
40 Werner 18
int Tree::m_nextId=0;
19
 
158 werner 20
 
257 werner 21
 
158 werner 22
/** get distance and direction between two points.
23
  returns the distance (m), and the angle between PStart and PEnd (radians) in referenced param rAngle. */
24
float dist_and_direction(const QPointF &PStart, const QPointF &PEnd, float &rAngle)
151 iland 25
{
158 werner 26
    float dx = PEnd.x() - PStart.x();
27
    float dy = PEnd.y() - PStart.y();
28
    float d = sqrt(dx*dx + dy*dy);
29
    // direction:
30
    rAngle = atan2(dx, dy);
31
    return d;
151 iland 32
}
33
 
158 werner 34
 
110 Werner 35
// lifecycle
3 Werner 36
Tree::Tree()
37
{
149 werner 38
    mDbh = mHeight = 0;
39
    mRU = 0; mSpecies = 0;
169 werner 40
    mFlags = mAge = 0;
276 werner 41
    mOpacity=mFoliageMass=mWoodyMass=mCoarseRootMass=mFineRootMass=mLeafArea=0.;
159 werner 42
    mDbhDelta=mNPPReserve=mLRI=mStressIndex=0.;
264 werner 43
    mLightResponse = 0.;
106 Werner 44
    mId = m_nextId++;
105 Werner 45
    m_statCreated++;
3 Werner 46
}
38 Werner 47
 
158 werner 48
void Tree::setGrid(FloatGrid* gridToStamp, Grid<HeightGridValue> *dominanceGrid)
3 Werner 49
{
158 werner 50
    mGrid = gridToStamp; mHeightGrid = dominanceGrid;
3 Werner 51
}
52
 
125 Werner 53
/// dumps some core variables of a tree to a string.
54
QString Tree::dump()
55
{
56
    QString result = QString("id %1 species %2 dbh %3 h %4 x/y %5/%6 ru# %7 LRI %8")
159 werner 57
                            .arg(mId).arg(species()->id()).arg(mDbh).arg(mHeight)
156 werner 58
                            .arg(position().x()).arg(position().y())
125 Werner 59
                            .arg(mRU->index()).arg(mLRI);
60
    return result;
61
}
3 Werner 62
 
129 Werner 63
void Tree::dumpList(DebugList &rTargetList)
64
{
159 werner 65
    rTargetList << mId << species()->id() << mDbh << mHeight  << position().x() << position().y()   << mRU->index() << mLRI
276 werner 66
                << mWoodyMass << mCoarseRootMass << mFoliageMass << mLeafArea;
129 Werner 67
}
68
 
38 Werner 69
void Tree::setup()
70
{
106 Werner 71
    if (mDbh<=0 || mHeight<=0)
38 Werner 72
        return;
73
    // check stamp
159 werner 74
    Q_ASSERT_X(species()!=0, "Tree::setup()", "species is NULL");
75
    mStamp = species()->stamp(mDbh, mHeight);
110 Werner 76
 
159 werner 77
    mFoliageMass = species()->biomassFoliage(mDbh);
276 werner 78
    mCoarseRootMass = species()->biomassRoot(mDbh); // coarse root (allometry)
79
    mFineRootMass = mFoliageMass * species()->finerootFoliageRatio(); //  fine root (size defined  by finerootFoliageRatio)
159 werner 80
    mWoodyMass = species()->biomassWoody(mDbh);
110 Werner 81
 
137 Werner 82
    // LeafArea[m2] = LeafMass[kg] * specificLeafArea[m2/kg]
159 werner 83
    mLeafArea = mFoliageMass * species()->specificLeafArea();
276 werner 84
    mOpacity = 1. - exp(- Model::settings().lightExtinctionCoefficientOpacity * mLeafArea / mStamp->crownArea());
85
    mNPPReserve = (1+species()->finerootFoliageRatio())*mFoliageMass; // initial value
137 Werner 86
    mDbhDelta = 0.1; // initial value: used in growth() to estimate diameter increment
376 werner 87
 
88
    // initial value for tree aging...
388 werner 89
    mRU->addTreeAging(mLeafArea,mSpecies->aging(mHeight, mAge));
38 Werner 90
}
39 Werner 91
 
388 werner 92
void Tree::setAge(const int age, const float treeheight)
93
{
94
    mAge = age;
95
    if (age==0) {
96
        // estimate age using the tree height
97
        mAge = mSpecies->estimateAge(treeheight);
98
    }
99
}
100
 
110 Werner 101
//////////////////////////////////////////////////
102
////  Light functions (Pattern-stuff)
103
//////////////////////////////////////////////////
104
 
70 Werner 105
#define NOFULLDBG
77 Werner 106
//#define NOFULLOPT
39 Werner 107
 
40 Werner 108
 
158 werner 109
void Tree::applyLIP()
77 Werner 110
{
144 Werner 111
    if (!mStamp)
112
        return;
106 Werner 113
    Q_ASSERT(mGrid!=0 && mStamp!=0 && mRU!=0);
156 werner 114
    QPoint pos = mPositionIndex;
106 Werner 115
    int offset = mStamp->offset();
77 Werner 116
    pos-=QPoint(offset, offset);
117
 
118
    float local_dom; // height of Z* on the current position
119
    int x,y;
401 werner 120
    float value, z, z_zstar;
106 Werner 121
    int gr_stamp = mStamp->size();
77 Werner 122
    int grid_x, grid_y;
399 werner 123
    float *grid_value_ptr;
106 Werner 124
    if (!mGrid->isIndexValid(pos) || !mGrid->isIndexValid(pos+QPoint(gr_stamp, gr_stamp))) {
77 Werner 125
        // todo: in this case we should use another algorithm!!!
126
        return;
127
    }
399 werner 128
    grid_y = pos.y();
77 Werner 129
    for (y=0;y<gr_stamp; ++y) {
403 werner 130
 
399 werner 131
        grid_value_ptr = mGrid->ptr(pos.x(), grid_y);
132
        grid_x = pos.x();
77 Werner 133
        for (x=0;x<gr_stamp;++x) {
134
            // suppose there is no stamping outside
135
 
399 werner 136
            local_dom = mHeightGrid->valueAtIndex(grid_x/cPxPerHeight, grid_y/cPxPerHeight).height;
403 werner 137
            z = std::max(mHeight - (*mStamp).distanceToCenter(x,y), 0.f); // distance to center = height (45° line)
401 werner 138
            z_zstar = (z>=local_dom)?1.f:z/local_dom;
106 Werner 139
            value = (*mStamp)(x,y); // stampvalue
401 werner 140
            value = 1. - value*mOpacity * z_zstar; // calculated value
141
            // old: value = 1. - value*mOpacity / local_dom; // calculated value
77 Werner 142
            value = qMax(value, 0.02f); // limit value
143
 
399 werner 144
            *grid_value_ptr++ *= value;
403 werner 145
 
146
            grid_x++;
77 Werner 147
        }
403 werner 148
        grid_y++;
77 Werner 149
    }
150
 
151
    m_statPrint++; // count # of stamp applications...
152
}
153
 
155 werner 154
/// helper function for gluing the edges together
155
/// index: index at grid
156
/// count: number of pixels that are the simulation area (e.g. 100m and 2m pixel -> 50)
157
/// buffer: size of buffer around simulation area (in pixels)
295 werner 158
inline int torusIndex(int index, int count, int buffer, int ru_index)
155 werner 159
{
295 werner 160
    return buffer + ru_index + (index-buffer+count)%count;
155 werner 161
}
62 Werner 162
 
155 werner 163
 
164
/** Apply LIPs. This "Torus" functions wraps the influence at the edges of a 1ha simulation area.
165
  */
158 werner 166
void Tree::applyLIP_torus()
155 werner 167
{
168
    if (!mStamp)
169
        return;
170
    Q_ASSERT(mGrid!=0 && mStamp!=0 && mRU!=0);
295 werner 171
    int bufferOffset = mGrid->indexAt(QPointF(0.,0.)).x(); // offset of buffer
387 werner 172
    QPoint pos = QPoint((mPositionIndex.x()-bufferOffset)%cPxPerRU  + bufferOffset,
173
                        (mPositionIndex.y()-bufferOffset)%cPxPerRU + bufferOffset); // offset within the ha
295 werner 174
    QPoint ru_offset = QPoint(mPositionIndex.x() - pos.x(), mPositionIndex.y() - pos.y()); // offset of the corner of the resource index
155 werner 175
 
176
    int offset = mStamp->offset();
177
    pos-=QPoint(offset, offset);
178
 
179
    float local_dom; // height of Z* on the current position
180
    int x,y;
181
    float value;
182
    int gr_stamp = mStamp->size();
183
    int grid_x, grid_y;
184
    float *grid_value;
185
    if (!mGrid->isIndexValid(pos) || !mGrid->isIndexValid(pos+QPoint(gr_stamp, gr_stamp))) {
186
        // todo: in this case we should use another algorithm!!! necessary????
187
        return;
188
    }
295 werner 189
 
155 werner 190
    int xt, yt; // wraparound coordinates
191
    for (y=0;y<gr_stamp; ++y) {
192
        grid_y = pos.y() + y;
387 werner 193
        yt = torusIndex(grid_y, cPxPerRU,bufferOffset, ru_offset.y()); // 50 cells per 100m
155 werner 194
        for (x=0;x<gr_stamp;++x) {
195
            // suppose there is no stamping outside
196
            grid_x = pos.x() + x;
387 werner 197
            xt = torusIndex(grid_x,cPxPerRU,bufferOffset, ru_offset.x());
155 werner 198
 
387 werner 199
            local_dom = mHeightGrid->valueAtIndex(xt/cPxPerHeight,yt/cPxPerHeight).height;
155 werner 200
            value = (*mStamp)(x,y); // stampvalue
201
            value = 1. - value*mOpacity / local_dom; // calculated value
202
            value = qMax(value, 0.02f); // limit value
203
 
204
            grid_value = mGrid->ptr(xt, yt); // use wraparound coordinates
205
            *grid_value *= value;
206
        }
207
    }
208
 
209
    m_statPrint++; // count # of stamp applications...
210
}
211
 
74 Werner 212
/** heightGrid()
213
  This function calculates the "dominant height field". This grid is coarser as the fine-scaled light-grid.
214
*/
215
void Tree::heightGrid()
216
{
217
 
387 werner 218
    QPoint p = QPoint(mPositionIndex.x()/cPxPerHeight, mPositionIndex.y()/cPxPerHeight); // pos of tree on height grid
74 Werner 219
 
151 iland 220
    // count trees that are on height-grid cells (used for stockable area)
285 werner 221
    mHeightGrid->valueAtIndex(p).increaseCount();
401 werner 222
    if (mHeight > mHeightGrid->valueAtIndex(p).height)
223
        mHeightGrid->valueAtIndex(p).height=mHeight;
406 werner 224
 
225
    int r = mStamp->reader()->offset(); // distance between edge and the center pixel. e.g.: if r = 2 -> stamp=5x5
226
    int index_eastwest = mPositionIndex.x() % cPxPerHeight; // 4: very west, 0 east edge
227
    int index_northsouth = mPositionIndex.y() % cPxPerHeight; // 4: northern edge, 0: southern edge
228
    if (index_eastwest - r < 0) { // east
229
        mHeightGrid->valueAtIndex(p.x()-1, p.y()).height=qMax(mHeightGrid->valueAtIndex(p.x()-1, p.y()).height,mHeight-5.f);
230
    }
231
    if (index_eastwest + r >= cPxPerHeight) {  // west
232
        mHeightGrid->valueAtIndex(p.x()+1, p.y()).height=qMax(mHeightGrid->valueAtIndex(p.x()+1, p.y()).height,mHeight-5.f);
233
    }
234
    if (index_northsouth - r < 0) {  // south
235
        mHeightGrid->valueAtIndex(p.x(), p.y()-1).height=qMax(mHeightGrid->valueAtIndex(p.x(), p.y()-1).height,mHeight-5.f);
236
    }
237
    if (index_northsouth + r >= cPxPerHeight) {  // north
238
        mHeightGrid->valueAtIndex(p.x(), p.y()+1).height=qMax(mHeightGrid->valueAtIndex(p.x(), p.y()+1).height,mHeight-5.f);
239
    }
240
 
241
 
401 werner 242
    // without spread of the height grid
151 iland 243
 
401 werner 244
//    // height of Z*
245
//    const float cellsize = mHeightGrid->cellsize();
246
//
247
//    int index_eastwest = mPositionIndex.x() % cPxPerHeight; // 4: very west, 0 east edge
248
//    int index_northsouth = mPositionIndex.y() % cPxPerHeight; // 4: northern edge, 0: southern edge
249
//    int dist[9];
250
//    dist[3] = index_northsouth * 2 + 1; // south
251
//    dist[1] = index_eastwest * 2 + 1; // west
252
//    dist[5] = 10 - dist[3]; // north
253
//    dist[7] = 10 - dist[1]; // east
254
//    dist[8] = qMax(dist[5], dist[7]); // north-east
255
//    dist[6] = qMax(dist[3], dist[7]); // south-east
256
//    dist[0] = qMax(dist[3], dist[1]); // south-west
257
//    dist[2] = qMax(dist[5], dist[1]); // north-west
258
//    dist[4] = 0; // center cell
259
//    /* 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:
260
//       index = 4 + 3*sign(ix) + sign(iy) transforms combinations of directions to unique ids (0..8), which are used above.
261
//        e.g.: sign(ix) = -1, sign(iy) = 1 (=north-west) -> index = 4 + -3 + 1 = 2
262
//    */
263
//
264
//
265
//    int ringcount = int(floor(mHeight / cellsize)) + 1;
266
//    int ix, iy;
267
//    int ring;
268
//    float hdom;
269
//
270
//    for (ix=-ringcount;ix<=ringcount;ix++)
271
//        for (iy=-ringcount; iy<=+ringcount; iy++) {
272
//        ring = qMax(abs(ix), abs(iy));
273
//        QPoint pos(ix+p.x(), iy+p.y());
274
//        if (mHeightGrid->isIndexValid(pos)) {
275
//            float &rHGrid = mHeightGrid->valueAtIndex(pos).height;
276
//            if (rHGrid > mHeight) // skip calculation if grid is higher than tree
277
//                continue;
278
//            int direction = 4 + (ix?(ix<0?-3:3):0) + (iy?(iy<0?-1:1):0); // 4 + 3*sgn(x) + sgn(y)
279
//            hdom = mHeight - dist[direction];
280
//            if (ring>1)
281
//                hdom -= (ring-1)*10;
282
//
283
//            rHGrid = qMax(rHGrid, hdom); // write value
284
//        } // is valid
285
//    } // for (y)
39 Werner 286
}
40 Werner 287
 
155 werner 288
 
289
 
158 werner 290
void Tree::readLIF()
40 Werner 291
{
106 Werner 292
    if (!mStamp)
155 werner 293
        return;
294
    const Stamp *reader = mStamp->reader();
295
    if (!reader)
296
        return;
156 werner 297
    QPoint pos_reader = mPositionIndex;
155 werner 298
 
299
    int offset_reader = reader->offset();
300
    int offset_writer = mStamp->offset();
301
    int d_offset = offset_writer - offset_reader; // offset on the *stamp* to the crown-cells
302
 
303
    pos_reader-=QPoint(offset_reader, offset_reader);
40 Werner 304
 
155 werner 305
    float local_dom;
306
 
40 Werner 307
    int x,y;
308
    double sum=0.;
155 werner 309
    double value, own_value;
310
    float *grid_value;
403 werner 311
    float z, z_zstar;
155 werner 312
    int reader_size = reader->size();
313
    int rx = pos_reader.x();
314
    int ry = pos_reader.y();
315
    for (y=0;y<reader_size; ++y, ++ry) {
316
        grid_value = mGrid->ptr(rx, ry);
317
        for (x=0;x<reader_size;++x) {
318
 
387 werner 319
            local_dom = mHeightGrid->valueAtIndex((rx+x)/cPxPerHeight, ry/cPxPerHeight).height; // ry: gets ++ed in outer loop, rx not
403 werner 320
            z = std::max(mHeight - reader->distanceToCenter(x,y), 0.f); // distance to center = height (45° line)
321
            z_zstar = (z>=local_dom)?1.f:z/local_dom;
155 werner 322
 
403 werner 323
            own_value = 1. - mStamp->offsetValue(x,y,d_offset)*mOpacity * z_zstar;
324
            // old: own_value = 1. - mStamp->offsetValue(x,y,d_offset)*mOpacity / local_dom; // old: dom_height;
155 werner 325
            own_value = qMax(own_value, 0.02);
326
            value =  *grid_value++ / own_value; // remove impact of focal tree
403 werner 327
 
328
            //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 329
            //if (value>0.)
330
            sum += value * (*reader)(x,y);
331
 
40 Werner 332
        }
333
    }
155 werner 334
    mLRI = sum;
48 Werner 335
    // read dominance field...
155 werner 336
    // this applies only if some trees are potentially *higher* than the dominant height grid
337
    //if (dom_height < m_Height) {
48 Werner 338
        // if tree is higher than Z*, the dominance height
339
        // a part of the crown is in "full light".
155 werner 340
    //    m_statAboveZ++;
341
    //    mImpact = 1. - (1. - mImpact)*dom_height/m_Height;
342
    //}
403 werner 343
 
155 werner 344
    if (mLRI > 1.)
345
        mLRI = 1.;
206 werner 346
 
347
    // Finally, add LRI of this Tree to the ResourceUnit!
251 werner 348
    mRU->addWLA(mLeafArea, mLRI);
206 werner 349
 
212 werner 350
 
155 werner 351
    //qDebug() << "Tree #"<< id() << "value" << sum << "Impact" << mImpact;
206 werner 352
    //mRU->addWLA(mLRI*mLeafArea, mLeafArea);
40 Werner 353
}
354
 
158 werner 355
void Tree::heightGrid_torus()
155 werner 356
{
357
    // height of Z*
358
    const float cellsize = mHeightGrid->cellsize();
58 Werner 359
 
387 werner 360
    QPoint p = QPoint(mPositionIndex.x()/cPxPerHeight, mPositionIndex.y()/cPxPerHeight); // pos of tree on height grid
295 werner 361
    int bufferOffset = mHeightGrid->indexAt(QPointF(0.,0.)).x(); // offset of buffer
155 werner 362
 
363
    // count trees that are on height-grid cells (used for stockable area)
285 werner 364
    mHeightGrid->valueAtIndex(p).increaseCount();
155 werner 365
 
295 werner 366
    // torus coordinates
367
    p.setX((p.x()-bufferOffset)%10 + bufferOffset);
368
    p.setY((p.y()-bufferOffset)%10 + bufferOffset);
387 werner 369
    QPoint ru_offset =QPoint(mPositionIndex.x()/cPxPerHeight - p.x(), mPositionIndex.y()/cPxPerHeight - p.y());
295 werner 370
 
387 werner 371
    int index_eastwest = mPositionIndex.x() % cPxPerHeight; // 4: very west, 0 east edge
372
    int index_northsouth = mPositionIndex.y() % cPxPerHeight; // 4: northern edge, 0: southern edge
155 werner 373
    int dist[9];
374
    dist[3] = index_northsouth * 2 + 1; // south
375
    dist[1] = index_eastwest * 2 + 1; // west
376
    dist[5] = 10 - dist[3]; // north
377
    dist[7] = 10 - dist[1]; // east
378
    dist[8] = qMax(dist[5], dist[7]); // north-east
379
    dist[6] = qMax(dist[3], dist[7]); // south-east
380
    dist[0] = qMax(dist[3], dist[1]); // south-west
381
    dist[2] = qMax(dist[5], dist[1]); // north-west
382
    dist[4] = 0; // center cell
383
    /* 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:
384
       index = 4 + 3*sign(ix) + sign(iy) transforms combinations of directions to unique ids (0..8), which are used above.
385
        e.g.: sign(ix) = -1, sign(iy) = 1 (=north-west) -> index = 4 + -3 + 1 = 2
386
    */
387
 
388
 
389
    int ringcount = int(floor(mHeight / cellsize)) + 1;
390
    int ix, iy;
391
    int ring;
392
    float hdom;
393
    for (ix=-ringcount;ix<=ringcount;ix++)
394
        for (iy=-ringcount; iy<=+ringcount; iy++) {
395
        ring = qMax(abs(ix), abs(iy));
396
        QPoint pos(ix+p.x(), iy+p.y());
295 werner 397
        QPoint p_torus(torusIndex(pos.x(),10,bufferOffset,ru_offset.x()),
398
                       torusIndex(pos.y(),10,bufferOffset,ru_offset.y()));
399
        if (mHeightGrid->isIndexValid(p_torus)) {
400
            float &rHGrid = mHeightGrid->valueAtIndex(p_torus.x(),p_torus.y()).height;
155 werner 401
            if (rHGrid > mHeight) // skip calculation if grid is higher than tree
402
                continue;
403
            int direction = 4 + (ix?(ix<0?-3:3):0) + (iy?(iy<0?-1:1):0); // 4 + 3*sgn(x) + sgn(y)
404
            hdom = mHeight - dist[direction];
405
            if (ring>1)
406
                hdom -= (ring-1)*10;
407
 
408
            rHGrid = qMax(rHGrid, hdom); // write value
409
        } // is valid
410
    } // for (y)
411
}
412
 
413
/// Torus version of read stamp (glued edges)
158 werner 414
void Tree::readLIF_torus()
78 Werner 415
{
106 Werner 416
    if (!mStamp)
107 Werner 417
        return;
106 Werner 418
    const Stamp *reader = mStamp->reader();
78 Werner 419
    if (!reader)
107 Werner 420
        return;
295 werner 421
    int bufferOffset = mGrid->indexAt(QPointF(0.,0.)).x(); // offset of buffer
78 Werner 422
 
387 werner 423
    QPoint pos_reader = QPoint((mPositionIndex.x()-bufferOffset)%cPxPerRU + bufferOffset,
424
                               (mPositionIndex.y()-bufferOffset)%cPxPerRU + bufferOffset); // offset within the ha
295 werner 425
    QPoint ru_offset = QPoint(mPositionIndex.x() - pos_reader.x(), mPositionIndex.y() - pos_reader.y()); // offset of the corner of the resource index
426
 
78 Werner 427
    int offset_reader = reader->offset();
106 Werner 428
    int offset_writer = mStamp->offset();
78 Werner 429
    int d_offset = offset_writer - offset_reader; // offset on the *stamp* to the crown-cells
430
 
431
    pos_reader-=QPoint(offset_reader, offset_reader);
432
 
433
    float local_dom;
434
 
435
    int x,y;
436
    double sum=0.;
437
    double value, own_value;
438
    float *grid_value;
439
    int reader_size = reader->size();
440
    int rx = pos_reader.x();
441
    int ry = pos_reader.y();
155 werner 442
    int xt, yt; // wrapped coords
443
 
397 werner 444
    for (y=0;y<reader_size; ++y) {
445
        yt = torusIndex(ry+y,cPxPerRU, bufferOffset, ru_offset.y());
78 Werner 446
        for (x=0;x<reader_size;++x) {
387 werner 447
            xt = torusIndex(rx+x,cPxPerRU, bufferOffset, ru_offset.x());
155 werner 448
            grid_value = mGrid->ptr(xt,yt);
78 Werner 449
            //p = pos_reader + QPoint(x,y);
450
            //if (m_grid->isIndexValid(p)) {
387 werner 451
            local_dom = mHeightGrid->valueAtIndex(xt/cPxPerHeight, yt/cPxPerHeight).height; // ry: gets ++ed in outer loop, rx not
78 Werner 452
            //local_dom = m_dominanceGrid->valueAt( m_grid->cellCoordinates(p) );
125 Werner 453
 
149 werner 454
            own_value = 1. - mStamp->offsetValue(x,y,d_offset)*mOpacity / local_dom; // old: dom_height;
78 Werner 455
            own_value = qMax(own_value, 0.02);
155 werner 456
            value =  *grid_value / own_value; // remove impact of focal tree
397 werner 457
            // debug for one tree in HJA
458
            //if (id()==178020)
459
            //    qDebug() << x << y << xt << yt << *grid_value << local_dom << own_value << value << (*reader)(x,y);
321 werner 460
            //if (_isnan(value))
461
            //    qDebug() << "isnan" << id();
397 werner 462
            if (value * (*reader)(x,y)>1.)
463
                qDebug() << "LIFTorus: value>1.";
78 Werner 464
            sum += value * (*reader)(x,y);
465
 
466
            //} // isIndexValid
467
        }
468
    }
106 Werner 469
    mLRI = sum;
321 werner 470
    if (_isnan(mLRI)) {
471
        qDebug() << "LRI invalid (nan)!" << id();
472
        mLRI=0.;
473
        //qDebug() << reader->dump();
474
    }
148 iland 475
    if (mLRI > 1.)
476
        mLRI = 1.;
78 Werner 477
    //qDebug() << "Tree #"<< id() << "value" << sum << "Impact" << mImpact;
205 werner 478
 
479
    // Finally, add LRI of this Tree to the ResourceUnit!
251 werner 480
    mRU->addWLA(mLeafArea, mLRI);
58 Werner 481
}
482
 
155 werner 483
 
40 Werner 484
void Tree::resetStatistics()
485
{
486
    m_statPrint=0;
105 Werner 487
    m_statCreated=0;
48 Werner 488
    m_statAboveZ=0;
40 Werner 489
    m_nextId=1;
490
}
107 Werner 491
 
251 werner 492
void Tree::calcLightResponse()
493
{
494
    // calculate a light response from lri:
298 werner 495
    // http://iland.boku.ac.at/individual+tree+light+availability
251 werner 496
    double lri = limit(mLRI * mRU->LRImodifier(), 0., 1.);
274 werner 497
    mLightResponse = mSpecies->lightResponse(lri);
251 werner 498
    mRU->addLR(mLeafArea, mLightResponse);
499
 
500
}
501
 
110 Werner 502
//////////////////////////////////////////////////
503
////  Growth Functions
504
//////////////////////////////////////////////////
107 Werner 505
 
227 werner 506
/** grow() is the main function of the yearly tree growth.
507
  The main steps are:
298 werner 508
  - Production of GPP/NPP   @sa http://iland.boku.ac.at/primary+production http://iland.boku.ac.at/individual+tree+light+availability
509
  - Partitioning of NPP to biomass compartments of the tree @sa http://iland.boku.ac.at/allocation
227 werner 510
  - Growth of the stem http://iland.boku.ac.at/stem+growth (???)
387 werner 511
  Further activties: * the age of the tree is increased
512
                     * the mortality sub routine is executed
513
                     * seeds are produced */
107 Werner 514
void Tree::grow()
515
{
159 werner 516
    TreeGrowthData d;
169 werner 517
    mAge++; // increase age
230 werner 518
    // step 1: get "interception area" of the tree individual [m2]
519
    // the sum of all area of all trees of a unit equal the total stocked area * interception_factor(Beer-Lambert)
520
    double effective_area = mRU->interceptedArea(mLeafArea, mLightResponse);
107 Werner 521
 
230 werner 522
    // step 2: calculate GPP of the tree based
523
    // (1) get the amount of GPP for a "unit area" of the tree species
524
    double raw_gpp_per_area = mRU->resourceUnitSpecies(species()).prod3PG().GPPperArea();
525
    // (2) GPP (without aging-effect) in kg Biomass / year
526
    double raw_gpp = raw_gpp_per_area * effective_area;
161 werner 527
 
227 werner 528
    // apply aging according to the state of the individuum
388 werner 529
    const double aging_factor = mSpecies->aging(mHeight, mAge);
376 werner 530
    mRU->addTreeAging(mLeafArea, aging_factor);
227 werner 531
    double gpp = raw_gpp * aging_factor; //
532
    d.NPP = gpp * 0.47; // respiration loss, cf. Waring et al 1998.
113 Werner 533
 
279 werner 534
    //DBGMODE(
137 Werner 535
        if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreeNPP) && isDebugging()) {
133 Werner 536
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreeNPP);
537
            dumpList(out); // add tree headers
299 werner 538
            out << mLRI * mRU->LRImodifier() << mLightResponse << effective_area << raw_gpp << gpp << d.NPP << aging_factor;
133 Werner 539
        }
279 werner 540
    //); // DBGMODE()
217 werner 541
    if (d.NPP>0.)
542
        partitioning(d); // split npp to compartments and grow (diameter, height)
133 Werner 543
 
387 werner 544
    // mortality
200 werner 545
    if (Model::settings().mortalityEnabled)
546
        mortality(d);
110 Werner 547
 
159 werner 548
    mStressIndex = d.stress_index;
180 werner 549
 
550
    if (!isDead())
257 werner 551
        mRU->resourceUnitSpecies(species()).statistics().add(this, &d);
277 werner 552
 
387 werner 553
    // regeneration
554
    mSpecies->seedProduction(mHeight, mPositionIndex);
555
 
107 Werner 556
}
557
 
227 werner 558
/** partitioning of this years assimilates (NPP) to biomass compartments.
298 werner 559
  Conceptionally, the algorithm is based on Duursma, 2007.
560
  @sa http://iland.boku.ac.at/allocation */
159 werner 561
inline void Tree::partitioning(TreeGrowthData &d)
115 Werner 562
{
164 werner 563
    if (isDebugging())
564
        enableDebugging(true);
159 werner 565
    double npp = d.NPP;
115 Werner 566
    // add content of reserve pool
116 Werner 567
    npp += mNPPReserve;
159 werner 568
    const double foliage_mass_allo = species()->biomassFoliage(mDbh);
276 werner 569
    const double reserve_size = foliage_mass_allo * (1. + mSpecies->finerootFoliageRatio());
297 werner 570
    double refill_reserve = qMin(reserve_size, (1. + mSpecies->finerootFoliageRatio())*mFoliageMass); // not always try to refill reserve 100%
119 Werner 571
 
136 Werner 572
    double apct_wood, apct_root, apct_foliage; // allocation percentages (sum=1) (eta)
117 Werner 573
    // turnover rates
159 werner 574
    const double &to_fol = species()->turnoverLeaf();
575
    const double &to_root = species()->turnoverRoot();
136 Werner 576
    // the turnover rate of wood depends on the size of the reserve pool:
116 Werner 577
 
136 Werner 578
 
163 werner 579
    double to_wood = refill_reserve / (mWoodyMass + refill_reserve);
580
 
227 werner 581
    apct_root = mRU->resourceUnitSpecies(species()).prod3PG().rootFraction();
261 werner 582
    d.NPP_above = d.NPP * (1. - apct_root); // aboveground: total NPP - fraction to roots
298 werner 583
    double b_wf = species()->allometricRatio_wf(); // ratio of allometric exponents (b_woody / b_foliage)
117 Werner 584
 
585
    // Duursma 2007, Eq. (20)
167 werner 586
    apct_wood = (foliage_mass_allo*to_wood/npp + b_wf*(1.-apct_root) - b_wf*foliage_mass_allo*to_fol/npp) / ( foliage_mass_allo/mWoodyMass + b_wf );
163 werner 587
    if (apct_wood<0)
588
        apct_wood = 0.;
117 Werner 589
    apct_foliage = 1. - apct_root - apct_wood;
590
 
163 werner 591
 
592
    //DBGMODE(
593
            if (apct_foliage<0 || apct_wood<0)
594
                qDebug() << "transfer to foliage or wood < 0";
595
             if (npp<0)
596
                 qDebug() << "NPP < 0";
597
         //   );
598
 
136 Werner 599
    // Change of biomass compartments
276 werner 600
    double sen_root = mFineRootMass * to_root;
601
    double sen_foliage = mFoliageMass * to_fol;
298 werner 602
 
136 Werner 603
    // Roots
298 werner 604
    // http://iland.boku.ac.at/allocation#belowground_NPP
276 werner 605
    mFineRootMass -= sen_root; // reduce only fine root pool
606
    double delta_root = apct_root * npp;
607
    // 1st, refill the fine root pool
608
    double fineroot_miss = mFoliageMass * mSpecies->finerootFoliageRatio() - mFineRootMass;
609
    if (fineroot_miss>0.){
610
        double delta_fineroot = qMin(fineroot_miss, delta_root);
611
        mFineRootMass += delta_fineroot;
612
        delta_root -= delta_fineroot;
613
    }
614
    // 2nd, the rest of NPP allocated to roots go to coarse roots
615
    mCoarseRootMass += delta_root;
119 Werner 616
 
136 Werner 617
    // Foliage
159 werner 618
    double delta_foliage = apct_foliage * npp - sen_foliage;
137 Werner 619
    mFoliageMass += delta_foliage;
217 werner 620
    if (_isnan(mFoliageMass))
621
        qDebug() << "foliage mass invalid!";
163 werner 622
    if (mFoliageMass<0.) mFoliageMass=0.; // limit to zero
623
 
159 werner 624
    mLeafArea = mFoliageMass * species()->specificLeafArea(); // update leaf area
119 Werner 625
 
271 werner 626
    // stress index: different varaints at denominator: to_fol*foliage_mass = leafmass to rebuild,
198 werner 627
    // foliage_mass_allo: simply higher chance for stress
271 werner 628
    // note: npp = NPP + reserve (see above)
276 werner 629
    d.stress_index =qMax(1. - (npp) / ( to_fol*foliage_mass_allo + to_root*foliage_mass_allo*species()->finerootFoliageRatio() + reserve_size), 0.);
198 werner 630
 
136 Werner 631
    // Woody compartments
298 werner 632
    // see also: http://iland.boku.ac.at/allocation#reserve_and_allocation_to_stem_growth
136 Werner 633
    // (1) transfer to reserve pool
634
    double gross_woody = apct_wood * npp;
635
    double to_reserve = qMin(reserve_size, gross_woody);
636
    mNPPReserve = to_reserve;
637
    double net_woody = gross_woody - to_reserve;
137 Werner 638
    double net_stem = 0.;
164 werner 639
    mDbhDelta = 0.;
165 werner 640
 
641
 
136 Werner 642
    if (net_woody > 0.) {
643
        // (2) calculate part of increment that is dedicated to the stem (which is a function of diameter)
159 werner 644
        net_stem = net_woody * species()->allometricFractionStem(mDbh);
645
        d.NPP_stem = net_stem;
137 Werner 646
        mWoodyMass += net_woody;
136 Werner 647
        //  (3) growth of diameter and height baseed on net stem increment
159 werner 648
        grow_diameter(d);
136 Werner 649
    }
119 Werner 650
 
279 werner 651
    //DBGMODE(
137 Werner 652
     if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreePartition)
653
         && isDebugging() ) {
129 Werner 654
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreePartition);
655
            dumpList(out); // add tree headers
136 Werner 656
            out << npp << apct_foliage << apct_wood << apct_root
276 werner 657
                    << delta_foliage << net_woody << delta_root << mNPPReserve << net_stem << d.stress_index;
137 Werner 658
     }
144 Werner 659
 
279 werner 660
    //); // DBGMODE()
144 Werner 661
    //DBGMODE(
393 werner 662
      if (mWoodyMass<0. || mWoodyMass>40000 || mFoliageMass<0. || mFoliageMass>2000. || mCoarseRootMass<0. || mCoarseRootMass>30000
663
         || mNPPReserve>4000.) {
389 werner 664
         qDebug() << "Tree:partitioning: invalid or unlikely pools.";
144 Werner 665
         qDebug() << GlobalSettings::instance()->debugListCaptions(GlobalSettings::DebugOutputs(0));
666
         DebugList dbg; dumpList(dbg);
667
         qDebug() << dbg;
668
     } //);
669
 
136 Werner 670
    /*DBG_IF_X(mId == 1 , "Tree::partitioning", "dump", dump()
671
             + 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")
672
               .arg(npp).arg(senescence_foliage).arg(senescence_stem).arg(senescence_root)
673
               .arg(net_foliage).arg(net_stem).arg(net_root).arg(to_reserve).arg(mNPPReserve) );*/
129 Werner 674
 
115 Werner 675
}
676
 
125 Werner 677
 
134 Werner 678
/** Determination of diamter and height growth based on increment of the stem mass (@p net_stem_npp).
125 Werner 679
    Refer to XXX for equations and variables.
680
    This function updates the dbh and height of the tree.
227 werner 681
    The equations are based on dbh in meters! */
159 werner 682
inline void Tree::grow_diameter(TreeGrowthData &d)
119 Werner 683
{
684
    // determine dh-ratio of increment
685
    // height increment is a function of light competition:
125 Werner 686
    double hd_growth = relative_height_growth(); // hd of height growth
153 werner 687
    double d_m = mDbh / 100.; // current diameter in [m]
159 werner 688
    double net_stem_npp = d.NPP_stem;
689
 
153 werner 690
    const double d_delta_m = mDbhDelta / 100.; // increment of last year in [m]
115 Werner 691
 
159 werner 692
    const double mass_factor = species()->volumeFactor() * species()->density();
153 werner 693
    double stem_mass = mass_factor * d_m*d_m * mHeight; // result: kg, dbh[cm], h[meter]
123 Werner 694
 
153 werner 695
    // factor is in diameter increment per NPP [m/kg]
696
    double factor_diameter = 1. / (  mass_factor * (d_m + d_delta_m)*(d_m + d_delta_m) * ( 2. * mHeight/d_m + hd_growth) );
125 Werner 697
    double delta_d_estimate = factor_diameter * net_stem_npp; // estimated dbh-inc using last years increment
698
 
699
    // using that dbh-increment we estimate a stem-mass-increment and the residual (Eq. 9)
153 werner 700
    double stem_estimate = mass_factor * (d_m + delta_d_estimate)*(d_m + delta_d_estimate)*(mHeight + delta_d_estimate*hd_growth);
137 Werner 701
    double stem_residual = stem_estimate - (stem_mass + net_stem_npp);
125 Werner 702
 
703
    // the final increment is then:
704
    double d_increment = factor_diameter * (net_stem_npp - stem_residual); // Eq. (11)
144 Werner 705
    DBG_IF_X(d_increment<0. || d_increment>0.1, "Tree::grow_dimater", "increment out of range.", dump()
125 Werner 706
             + QString("\nhdz %1 factor_diameter %2 stem_residual %3 delta_d_estimate %4 d_increment %5 final residual(kg) %6")
707
               .arg(hd_growth).arg(factor_diameter).arg(stem_residual).arg(delta_d_estimate).arg(d_increment)
142 Werner 708
               .arg( mass_factor * (mDbh + d_increment)*(mDbh + d_increment)*(mHeight + d_increment*hd_growth)-((stem_mass + net_stem_npp)) ));
125 Werner 709
 
303 werner 710
    //DBGMODE(
326 werner 711
       // double res_final = mass_factor * (d_m + d_increment)*(d_m + d_increment)*(mHeight + d_increment*hd_growth)-((stem_mass + net_stem_npp));
712
        DBG_IF_X((mass_factor * (d_m + d_increment)*(d_m + d_increment)*(mHeight + d_increment*hd_growth)-((stem_mass + net_stem_npp))) > 1, "Tree::grow_diameter", "final residual stem estimate > 1kg", dump());
153 werner 713
        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 714
 
137 Werner 715
        if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreeGrowth) && isDebugging() ) {
126 Werner 716
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreeGrowth);
129 Werner 717
            dumpList(out); // add tree headers
143 Werner 718
            out << net_stem_npp << stem_mass << hd_growth << factor_diameter << delta_d_estimate*100 << d_increment*100;
126 Werner 719
        }
153 werner 720
 
303 werner 721
    //); // DBGMODE()
125 Werner 722
 
723
    d_increment = qMax(d_increment, 0.);
724
 
725
    // update state variables
153 werner 726
    mDbh += d_increment*100; // convert from [m] to [cm]
727
    mDbhDelta = d_increment*100; // save for next year's growth
728
    mHeight += d_increment * hd_growth;
158 werner 729
 
730
    // update state of LIP stamp and opacity
159 werner 731
    mStamp = species()->stamp(mDbh, mHeight); // get new stamp for updated dimensions
158 werner 732
    // calculate the CrownFactor which reflects the opacity of the crown
200 werner 733
    const double k=Model::settings().lightExtinctionCoefficientOpacity;
734
    mOpacity = 1. - exp(-k * mLeafArea / mStamp->crownArea());
158 werner 735
 
119 Werner 736
}
737
 
125 Werner 738
 
739
/// return the HD ratio of this year's increment based on the light status.
119 Werner 740
inline double Tree::relative_height_growth()
741
{
742
    double hd_low, hd_high;
743
    mSpecies->hdRange(mDbh, hd_low, hd_high);
744
 
125 Werner 745
    DBG_IF_X(hd_low>hd_high, "Tree::relative_height_growth", "hd low higher dann hd_high for ", dump());
746
    DBG_IF_X(hd_low < 20 || hd_high>250, "Tree::relative_height_growth", "hd out of range ", dump() + QString(" hd-low: %1 hd-high: %2").arg(hd_low).arg(hd_high));
747
 
748
    // scale according to LRI: if receiving much light (LRI=1), the result is hd_low (for open grown trees)
326 werner 749
    // use the corrected LRI (see tracker#11)
750
    double lri = limit(mLRI * mRU->LRImodifier(),0.,1.);
751
    double hd_ratio = hd_high - (hd_high-hd_low)*lri;
125 Werner 752
    return hd_ratio;
119 Werner 753
}
141 Werner 754
 
278 werner 755
/** This function is called if a tree dies.
756
  @sa ResourceUnit::cleanTreeList(), remove() */
277 werner 757
void Tree::die(TreeGrowthData *d)
758
{
759
    setFlag(Tree::TreeDead, true); // set flag that tree is dead
760
    mRU->resourceUnitSpecies(species()).statisticsDead().add(this, d); // add tree to statistics
761
}
762
 
278 werner 763
void Tree::remove()
764
{
765
    setFlag(Tree::TreeDead, true); // set flag that tree is dead
766
    mRU->resourceUnitSpecies(species()).statisticsMgmt().add(this, 0);
767
}
768
 
159 werner 769
void Tree::mortality(TreeGrowthData &d)
770
{
163 werner 771
    // death if leaf area is 0
772
    if (mFoliageMass<0.00001)
773
        die();
774
 
308 werner 775
    double p_death,  p_stress, p_intrinsic;
776
    p_intrinsic = species()->deathProb_intrinsic();
777
    p_stress = species()->deathProb_stress(d.stress_index);
778
    p_death =  p_intrinsic + p_stress;
190 werner 779
    double p = drandom(); //0..1
159 werner 780
    if (p<p_death) {
781
        // die...
782
        die();
783
    }
784
}
141 Werner 785
 
786
//////////////////////////////////////////////////
787
////  value functions
788
//////////////////////////////////////////////////
789
 
145 Werner 790
double Tree::volume() const
141 Werner 791
{
792
    /// @see Species::volumeFactor() for details
159 werner 793
    const double volume_factor = species()->volumeFactor();
157 werner 794
    const double volume =  volume_factor * mDbh*mDbh*mHeight * 0.0001; // dbh in cm: cm/100 * cm/100 = cm*cm * 0.0001 = m2
141 Werner 795
    return volume;
796
}
180 werner 797
 
798
double Tree::basalArea() const
799
{
800
    // A = r^2 * pi = d/2*pi; from cm->m: d/200
801
    const double b = (mDbh/200.)*(mDbh/200.)*M_PI;
802
    return b;
803
}