Subversion Repositories public iLand

Rev

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