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