Rev 444 | Rev 460 | 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 |
445 | werner | 610 | mSpecies->seedProduction(mAge, mPositionIndex); |
387 | werner | 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( |
428 | werner | 718 | if (mWoodyMass<0. || mWoodyMass>50000 || mFoliageMass<0. || mFoliageMass>2000. || mCoarseRootMass<0. || mCoarseRootMass>30000 |
393 | werner | 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; |
||
444 | werner | 835 | double p = drandom(ru()->randomGenerator()); //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 | } |