Subversion Repositories public iLand

Rev

Rev 909 | Rev 975 | Go to most recent revision | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 909 Rev 949
1
Redirecting to URL 'https://iland.boku.ac.at/svn/iland/tags/release_1.0/src/core/tree.cpp':
1
Redirecting to URL 'https://iland.boku.ac.at/svn/iland/tags/release_1.0/src/core/tree.cpp':
2
/********************************************************************************************
2
/********************************************************************************************
3
**    iLand - an individual based forest landscape and disturbance model
3
**    iLand - an individual based forest landscape and disturbance model
4
**    http://iland.boku.ac.at
4
**    http://iland.boku.ac.at
5
**    Copyright (C) 2009-  Werner Rammer, Rupert Seidl
5
**    Copyright (C) 2009-  Werner Rammer, Rupert Seidl
6
**
6
**
7
**    This program is free software: you can redistribute it and/or modify
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
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
9
**    the Free Software Foundation, either version 3 of the License, or
10
**    (at your option) any later version.
10
**    (at your option) any later version.
11
**
11
**
12
**    This program is distributed in the hope that it will be useful,
12
**    This program is distributed in the hope that it will be useful,
13
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
14
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
**    GNU General Public License for more details.
15
**    GNU General Public License for more details.
16
**
16
**
17
**    You should have received a copy of the GNU General Public License
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/>.
18
**    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
********************************************************************************************/
19
********************************************************************************************/
20
#include "global.h"
20
#include "global.h"
21
#include "tree.h"
21
#include "tree.h"
22
22
23
#include "grid.h"
23
#include "grid.h"
24
24
25
#include "stamp.h"
25
#include "stamp.h"
26
#include "species.h"
26
#include "species.h"
27
#include "resourceunit.h"
27
#include "resourceunit.h"
28
#include "model.h"
28
#include "model.h"
29
#include "snag.h"
29
#include "snag.h"
30
30
31
#include "forestmanagementengine.h"
31
#include "forestmanagementengine.h"
32
32
33
// static varaibles
33
// static varaibles
34
FloatGrid *Tree::mGrid = 0;
34
FloatGrid *Tree::mGrid = 0;
35
HeightGrid *Tree::mHeightGrid = 0;
35
HeightGrid *Tree::mHeightGrid = 0;
36
int Tree::m_statPrint=0;
36
int Tree::m_statPrint=0;
37
int Tree::m_statAboveZ=0;
37
int Tree::m_statAboveZ=0;
38
int Tree::m_statCreated=0;
38
int Tree::m_statCreated=0;
39
int Tree::m_nextId=0;
39
int Tree::m_nextId=0;
40
40
41
/** @class Tree
41
/** @class Tree
42
    @ingroup core
42
    @ingroup core
43
    A tree is the basic simulation entity of iLand and represents a single tree.
43
    A tree is the basic simulation entity of iLand and represents a single tree.
44
    Trees in iLand are designed to be lightweight, thus the list of stored properties is limited. Basic properties
44
    Trees in iLand are designed to be lightweight, thus the list of stored properties is limited. Basic properties
45
    are dimensions (dbh, height), biomass pools (stem, leaves, roots), the reserve NPP pool. Additionally, the location and species are stored.
45
    are dimensions (dbh, height), biomass pools (stem, leaves, roots), the reserve NPP pool. Additionally, the location and species are stored.
46
    A Tree has a height of at least 4m; trees below this threshold are covered by the regeneration layer (see Sapling).
46
    A Tree has a height of at least 4m; trees below this threshold are covered by the regeneration layer (see Sapling).
47
    Trees are stored in lists managed at the resource unit level.
47
    Trees are stored in lists managed at the resource unit level.
48

48

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